diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..be97a2033d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,892 @@ +[*] +charset = utf-8 +end_of_line = crlf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 100 +tab_width = 4 +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_wrap_on_typing = false + +[*.dart] +max_line_length = 80 + +[*.java] +ij_java_align_consecutive_assignments = true +ij_java_align_consecutive_variable_declarations = true +ij_java_align_group_field_declarations = true +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = false +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = true +ij_java_align_multiline_throws_list = true +ij_java_align_subsequent_simple_methods = true +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_count_to_use_import_on_demand = 99 +ij_java_class_names_in_javadoc = 1 +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = always +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = split_into_lines +ij_java_extends_keyword_wrap = split_into_lines +ij_java_extends_list_wrap = split_into_lines +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = always +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = false +ij_java_if_brace_force = if_multiline +ij_java_imports_layout = android.**, |, androidx.**, |, com.**, |, junit.**, |, net.**, |, org.**, |, java.**, |, javax.**, |, *, |, $*, | +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = true +ij_java_keep_simple_lambdas_in_one_line = true +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = true +ij_java_method_parameters_right_paren_on_new_line = true +ij_java_method_parameters_wrap = split_into_lines +ij_java_modifier_list_wrap = false +ij_java_names_count_to_use_import_on_demand = 99 +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = off +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = true +ij_java_resource_list_right_paren_on_new_line = true +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true +ij_java_space_inside_one_line_enum_braces = false +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = split_into_lines +ij_java_throws_list_wrap = normal +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = always +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = true +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + +[{*.ant, *.fxml, *.jhm, *.jnlp, *.jrxml, *.rng, *.tld, *.wsdl, *.xml, *.xsd, *.xsl, *.xslt, *.xul}] +ij_continuation_indent_size = 4 +ij_xml_align_attributes = false +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = false +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = true +ij_xml_text_wrap = normal +ij_xml_use_custom_settings = true + +[{*.bash, *.sh, *.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false + +[{*.c, *.c++, *.cc, *.cp, *.cpp, *.cu, *.cuh, *.cxx, *.h, *.h++, *.hh, *.hp, *.hpp, *.hxx, *.i, *.icc, *.ii, *.inl, *.ino, *.ipp, *.m, *.mm, *.pch, *.tcc, *.tpp}] +ij_c_add_brief_tag = false +ij_c_add_getter_prefix = true +ij_c_add_setter_prefix = true +ij_c_align_dictionary_pair_values = false +ij_c_align_group_field_declarations = false +ij_c_align_init_list_in_columns = true +ij_c_align_multiline_array_initializer_expression = true +ij_c_align_multiline_assignment = true +ij_c_align_multiline_binary_operation = true +ij_c_align_multiline_chained_methods = false +ij_c_align_multiline_for = true +ij_c_align_multiline_ternary_operation = true +ij_c_array_initializer_comma_on_next_line = false +ij_c_array_initializer_new_line_after_left_brace = false +ij_c_array_initializer_right_brace_on_new_line = false +ij_c_array_initializer_wrap = normal +ij_c_assignment_wrap = off +ij_c_binary_operation_sign_on_next_line = false +ij_c_binary_operation_wrap = normal +ij_c_blank_lines_after_class_header = 0 +ij_c_blank_lines_after_imports = 1 +ij_c_blank_lines_around_class = 1 +ij_c_blank_lines_around_field = 0 +ij_c_blank_lines_around_field_in_interface = 0 +ij_c_blank_lines_around_method = 1 +ij_c_blank_lines_around_method_in_interface = 1 +ij_c_blank_lines_around_namespace = 0 +ij_c_blank_lines_around_properties_in_declaration = 0 +ij_c_blank_lines_around_properties_in_interface = 0 +ij_c_blank_lines_before_imports = 1 +ij_c_blank_lines_before_method_body = 0 +ij_c_block_brace_placement = end_of_line +ij_c_block_brace_style = end_of_line +ij_c_block_comment_at_first_column = true +ij_c_catch_on_new_line = false +ij_c_class_brace_style = end_of_line +ij_c_class_constructor_init_list_align_multiline = true +ij_c_class_constructor_init_list_comma_on_next_line = false +ij_c_class_constructor_init_list_new_line_after_colon = never +ij_c_class_constructor_init_list_new_line_before_colon = if_long +ij_c_class_constructor_init_list_wrap = normal +ij_c_copy_is_deep = false +ij_c_create_interface_for_categories = true +ij_c_declare_generated_methods = true +ij_c_description_include_member_names = true +ij_c_discharged_short_ternary_operator = false +ij_c_do_not_add_breaks = false +ij_c_do_while_brace_force = never +ij_c_else_on_new_line = false +ij_c_enum_constants_comma_on_next_line = false +ij_c_enum_constants_wrap = on_every_item +ij_c_for_brace_force = never +ij_c_for_statement_new_line_after_left_paren = false +ij_c_for_statement_right_paren_on_new_line = false +ij_c_for_statement_wrap = off +ij_c_function_brace_placement = end_of_line +ij_c_function_call_arguments_align_multiline = true +ij_c_function_call_arguments_align_multiline_pars = false +ij_c_function_call_arguments_comma_on_next_line = false +ij_c_function_call_arguments_new_line_after_lpar = false +ij_c_function_call_arguments_new_line_before_rpar = false +ij_c_function_call_arguments_wrap = normal +ij_c_function_non_top_after_return_type_wrap = normal +ij_c_function_parameters_align_multiline = true +ij_c_function_parameters_align_multiline_pars = false +ij_c_function_parameters_comma_on_next_line = false +ij_c_function_parameters_new_line_after_lpar = false +ij_c_function_parameters_new_line_before_rpar = false +ij_c_function_parameters_wrap = normal +ij_c_function_top_after_return_type_wrap = normal +ij_c_generate_additional_eq_operators = true +ij_c_generate_additional_rel_operators = true +ij_c_generate_class_constructor = true +ij_c_generate_comparison_operators_use_std_tie = false +ij_c_generate_instance_variables_for_properties = ask +ij_c_generate_operators_as_members = true +ij_c_header_guard_style_pattern = ${PROJECT_NAME}_${FILE_NAME}_${EXT} +ij_c_if_brace_force = never +ij_c_in_line_short_ternary_operator = true +ij_c_indent_block_comment = true +ij_c_indent_c_struct_members = 4 +ij_c_indent_case_from_switch = true +ij_c_indent_class_members = 4 +ij_c_indent_directive_as_code = false +ij_c_indent_implementation_members = 0 +ij_c_indent_inside_code_block = 4 +ij_c_indent_interface_members = 0 +ij_c_indent_interface_members_except_ivars_block = false +ij_c_indent_namespace_members = 4 +ij_c_indent_preprocessor_directive = 0 +ij_c_indent_visibility_keywords = 0 +ij_c_insert_override = true +ij_c_insert_virtual_with_override = false +ij_c_introduce_auto_vars = false +ij_c_introduce_const_params = false +ij_c_introduce_const_vars = false +ij_c_introduce_generate_property = false +ij_c_introduce_generate_synthesize = true +ij_c_introduce_globals_to_header = true +ij_c_introduce_prop_to_private_category = false +ij_c_introduce_static_consts = true +ij_c_introduce_use_ns_types = false +ij_c_ivars_prefix = _ +ij_c_keep_blank_lines_before_end = 2 +ij_c_keep_blank_lines_before_right_brace = 2 +ij_c_keep_blank_lines_in_code = 2 +ij_c_keep_blank_lines_in_declarations = 2 +ij_c_keep_case_expressions_in_one_line = false +ij_c_keep_control_statement_in_one_line = true +ij_c_keep_directive_at_first_column = true +ij_c_keep_first_column_comment = true +ij_c_keep_line_breaks = true +ij_c_keep_nested_namespaces_in_one_line = false +ij_c_keep_simple_blocks_in_one_line = true +ij_c_keep_simple_methods_in_one_line = true +ij_c_keep_structures_in_one_line = false +ij_c_lambda_capture_list_align_multiline = false +ij_c_lambda_capture_list_align_multiline_bracket = false +ij_c_lambda_capture_list_comma_on_next_line = false +ij_c_lambda_capture_list_new_line_after_lbracket = false +ij_c_lambda_capture_list_new_line_before_rbracket = false +ij_c_lambda_capture_list_wrap = off +ij_c_line_comment_add_space = false +ij_c_line_comment_at_first_column = true +ij_c_method_brace_placement = end_of_line +ij_c_method_call_arguments_align_by_colons = true +ij_c_method_call_arguments_align_multiline = false +ij_c_method_call_arguments_special_dictionary_pairs_treatment = true +ij_c_method_call_arguments_wrap = off +ij_c_method_call_chain_wrap = off +ij_c_method_parameters_align_by_colons = true +ij_c_method_parameters_align_multiline = false +ij_c_method_parameters_wrap = off +ij_c_namespace_brace_placement = end_of_line +ij_c_parentheses_expression_new_line_after_left_paren = false +ij_c_parentheses_expression_right_paren_on_new_line = false +ij_c_place_assignment_sign_on_next_line = false +ij_c_property_nonatomic = true +ij_c_put_ivars_to_implementation = true +ij_c_refactor_compatibility_aliases_and_classes = true +ij_c_refactor_properties_and_ivars = true +ij_c_release_style = ivar +ij_c_retain_object_parameters_in_constructor = true +ij_c_semicolon_after_method_signature = false +ij_c_shift_operation_align_multiline = true +ij_c_shift_operation_wrap = normal +ij_c_show_non_virtual_functions = false +ij_c_space_after_colon = true +ij_c_space_after_colon_in_selector = false +ij_c_space_after_comma = true +ij_c_space_after_cup_in_blocks = false +ij_c_space_after_dictionary_literal_colon = true +ij_c_space_after_for_semicolon = true +ij_c_space_after_init_list_colon = true +ij_c_space_after_method_parameter_type_parentheses = false +ij_c_space_after_method_return_type_parentheses = false +ij_c_space_after_pointer_in_declaration = false +ij_c_space_after_quest = true +ij_c_space_after_reference_in_declaration = false +ij_c_space_after_reference_in_rvalue = false +ij_c_space_after_structures_rbrace = true +ij_c_space_after_superclass_colon = true +ij_c_space_after_type_cast = true +ij_c_space_after_visibility_sign_in_method_declaration = true +ij_c_space_before_autorelease_pool_lbrace = true +ij_c_space_before_catch_keyword = true +ij_c_space_before_catch_left_brace = true +ij_c_space_before_catch_parentheses = true +ij_c_space_before_category_parentheses = true +ij_c_space_before_chained_send_message = true +ij_c_space_before_class_left_brace = true +ij_c_space_before_colon = true +ij_c_space_before_comma = false +ij_c_space_before_dictionary_literal_colon = false +ij_c_space_before_do_left_brace = true +ij_c_space_before_else_keyword = true +ij_c_space_before_else_left_brace = true +ij_c_space_before_for_left_brace = true +ij_c_space_before_for_parentheses = true +ij_c_space_before_for_semicolon = false +ij_c_space_before_if_left_brace = true +ij_c_space_before_if_parentheses = true +ij_c_space_before_init_list = false +ij_c_space_before_init_list_colon = true +ij_c_space_before_method_call_parentheses = false +ij_c_space_before_method_left_brace = true +ij_c_space_before_method_parentheses = false +ij_c_space_before_namespace_lbrace = true +ij_c_space_before_pointer_in_declaration = true +ij_c_space_before_property_attributes_parentheses = false +ij_c_space_before_protocols_brackets = true +ij_c_space_before_quest = true +ij_c_space_before_reference_in_declaration = true +ij_c_space_before_superclass_colon = true +ij_c_space_before_switch_left_brace = true +ij_c_space_before_switch_parentheses = true +ij_c_space_before_template_call_lt = false +ij_c_space_before_template_declaration_lt = false +ij_c_space_before_try_left_brace = true +ij_c_space_before_while_keyword = true +ij_c_space_before_while_left_brace = true +ij_c_space_before_while_parentheses = true +ij_c_space_between_adjacent_brackets = false +ij_c_space_between_operator_and_punctuator = false +ij_c_space_within_empty_array_initializer_braces = false +ij_c_spaces_around_additive_operators = true +ij_c_spaces_around_assignment_operators = true +ij_c_spaces_around_bitwise_operators = true +ij_c_spaces_around_equality_operators = true +ij_c_spaces_around_lambda_arrow = true +ij_c_spaces_around_logical_operators = true +ij_c_spaces_around_multiplicative_operators = true +ij_c_spaces_around_pm_operators = false +ij_c_spaces_around_relational_operators = true +ij_c_spaces_around_shift_operators = true +ij_c_spaces_around_unary_operator = false +ij_c_spaces_within_array_initializer_braces = false +ij_c_spaces_within_braces = true +ij_c_spaces_within_brackets = false +ij_c_spaces_within_cast_parentheses = false +ij_c_spaces_within_catch_parentheses = false +ij_c_spaces_within_category_parentheses = false +ij_c_spaces_within_empty_braces = false +ij_c_spaces_within_empty_function_call_parentheses = false +ij_c_spaces_within_empty_function_declaration_parentheses = false +ij_c_spaces_within_empty_lambda_capture_list_bracket = false +ij_c_spaces_within_empty_template_call_ltgt = false +ij_c_spaces_within_empty_template_declaration_ltgt = false +ij_c_spaces_within_for_parentheses = false +ij_c_spaces_within_function_call_parentheses = false +ij_c_spaces_within_function_declaration_parentheses = false +ij_c_spaces_within_if_parentheses = false +ij_c_spaces_within_lambda_capture_list_bracket = false +ij_c_spaces_within_method_parameter_type_parentheses = false +ij_c_spaces_within_method_return_type_parentheses = false +ij_c_spaces_within_parentheses = false +ij_c_spaces_within_property_attributes_parentheses = false +ij_c_spaces_within_protocols_brackets = false +ij_c_spaces_within_send_message_brackets = false +ij_c_spaces_within_switch_parentheses = false +ij_c_spaces_within_template_call_ltgt = false +ij_c_spaces_within_template_declaration_ltgt = false +ij_c_spaces_within_template_double_gt = true +ij_c_spaces_within_while_parentheses = false +ij_c_special_else_if_treatment = true +ij_c_superclass_list_after_colon = never +ij_c_superclass_list_align_multiline = true +ij_c_superclass_list_before_colon = if_long +ij_c_superclass_list_comma_on_next_line = false +ij_c_superclass_list_wrap = on_every_item +ij_c_tag_prefix_of_block_comment = at +ij_c_tag_prefix_of_line_comment = back_slash +ij_c_template_call_arguments_align_multiline = false +ij_c_template_call_arguments_align_multiline_pars = false +ij_c_template_call_arguments_comma_on_next_line = false +ij_c_template_call_arguments_new_line_after_lt = false +ij_c_template_call_arguments_new_line_before_gt = false +ij_c_template_call_arguments_wrap = off +ij_c_template_declaration_function_body_indent = false +ij_c_template_declaration_function_wrap = split_into_lines +ij_c_template_declaration_struct_body_indent = false +ij_c_template_declaration_struct_wrap = split_into_lines +ij_c_template_parameters_align_multiline = false +ij_c_template_parameters_align_multiline_pars = false +ij_c_template_parameters_comma_on_next_line = false +ij_c_template_parameters_new_line_after_lt = false +ij_c_template_parameters_new_line_before_gt = false +ij_c_template_parameters_wrap = off +ij_c_ternary_operation_signs_on_next_line = true +ij_c_ternary_operation_wrap = normal +ij_c_type_qualifiers_placement = before +ij_c_use_modern_casts = true +ij_c_use_setters_in_constructor = true +ij_c_while_brace_force = never +ij_c_while_on_new_line = false +ij_c_wrap_property_declaration = off + +[{*.cmake, CMakeLists.txt}] +ij_cmake_align_multiline_parameters_in_calls = false +ij_cmake_force_commands_case = 2 +ij_cmake_keep_blank_lines_in_code = 2 +ij_cmake_space_before_for_parentheses = true +ij_cmake_space_before_if_parentheses = true +ij_cmake_space_before_method_call_parentheses = false +ij_cmake_space_before_method_parentheses = false +ij_cmake_space_before_while_parentheses = true +ij_cmake_spaces_within_for_parentheses = false +ij_cmake_spaces_within_if_parentheses = false +ij_cmake_spaces_within_method_call_parentheses = false +ij_cmake_spaces_within_method_parentheses = false +ij_cmake_spaces_within_while_parentheses = false + +[{*.gant, *.gradle, *.groovy, *.gy}] +ij_groovy_align_group_field_declarations = true +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = true +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = split_into_lines +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = always +ij_groovy_else_on_new_line = false +ij_groovy_enum_constants_wrap = split_into_lines +ij_groovy_extends_keyword_wrap = split_into_lines +ij_groovy_extends_list_wrap = split_into_lines +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = always +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_if_brace_force = if_multiline +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = false +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = true +ij_groovy_method_parameters_right_paren_on_new_line = true +ij_groovy_method_parameters_wrap = split_into_lines +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = true +ij_groovy_resource_list_right_paren_on_new_line = true +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = split_into_lines +ij_groovy_throws_list_wrap = normal +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = always +ij_groovy_while_on_new_line = false +ij_groovy_wrap_long_lines = false + +[{*.gradle.kts, *.kt, *.kts, *.main.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = split_into_lines +ij_kotlin_extends_list_wrap = split_into_lines +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = split_into_lines +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[{*.har, *.json}] +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.htm, *.html, *.sht, *.shtm, *.shtml}] +ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p +ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span, pre, textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal +ij_html_uniform_ident = false + +[{*.yaml, *.yml, .analysis_options}] +indent_size = 2 +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_space_before_colon = true +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true diff --git a/.gitignore b/.gitignore index 39fb081a42..41687a8067 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ *.iml .gradle -/local.properties -/.idea/workspace.xml -/.idea/libraries +local.properties +bintray.properties +sonatype.properties +.idea .DS_Store /build /captures .externalNativeBuild +/reports \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index 4987ecabe8..0000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml deleted file mode 100644 index 08663fae31..0000000000 --- a/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 13c46297a6..0000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - 1.8 - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1c811baddb..0000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index 7f68460d8b..0000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/DevLibUtils/.gitignore b/DevLibUtils/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/DevLibUtils/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/DevLibUtils/README.md b/DevLibUtils/README.md deleted file mode 100644 index ce976bd73d..0000000000 --- a/DevLibUtils/README.md +++ /dev/null @@ -1,1007 +0,0 @@ -## 使用 - -> ##### 只需要在 Application 中调用 `DevUtils.init()` 进行初始化就行 - -## 目录结构 - -``` -- dev.utils | 根目录 - - app | app相关工具类 - - anim | 动画相关 - - assist | 辅助类, 如 Camera,ScreenSensor - - cache | 缓存工具类 - - image | 图片相关处理 - - info | App信息, PackageInfo 等 - - logger | 日志库 DevLogger - - share | SharedPreferences 封装 - - toast | Toast、Toasty - - wifi | wifi、热点 - - common | java工具类, 不依赖android api - - assist | 各种快捷辅助类 - - cipher | 编/解码工具类 - - encrypt | 加密工具类 - - thread | 线程相关 - - validator | 数据校验工具类 -``` - -## 事项 - -- 内部存在两个日志工具类 -```java -// dev.utils.app - App 打印日志工具类 -LogPrintUtils -// dev.utils.common - Java Common 日志打印工具类 -JCLogUtils -``` - -- 需要开启日志, 单独调用 -```java -// 打开 lib 内部日志 -DevUtils.openLog(); -// 标示 debug 模式 -DevUtils.openDebug(); -``` - -- 部分api更新不及时或有遗漏等,`具体以对应的工具类为准` - -## API - -### `dev.utils.app.wifi` - -* **Wifi 热点工具类(兼容到Android 8.0) ->** [WifiHotUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiHotUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| createWifiConfigToAp | 创建Wifi配置信息(无其他操作,单独返回WifiConfig) | -| stratWifiAp | 开启Wifi热点 | -| closeWifiAp | 关闭Wifi热点 | -| getWifiApState | 获取Wifi热点状态 | -| getWifiApConfiguration | 获取Wifi热点配置信息 | -| setWifiApConfiguration | 设置Wifi热点配置信息 | -| isOpenWifiAp | 判断是否打开Wifi热点 | -| closeWifiApCheck | 关闭Wifi热点(判断当前状态) | -| isConnectHot | 是否有连接热点的设备 | -| getHotspotServiceIp | 获取热点主机ip地址 | -| getHotspotAllotIp | 获取连接上的子网关热点IP(一个) | -| getHotspotSplitIpMask | 获取热点拼接后的ip网关掩码 | -| intToString | 转换ip地址 | -| getApWifiSSID | 获取Wifi 热点名 | -| getApWifiPwd | 获取Wifi 热点密码 | -| setOnWifiAPListener | 设置Android Wifi监听(Android 8.0) | - - -* **wifi工具类 ->** [WifiHotUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getWifiManager | 获取wifi管理对象 | -| isOpenWifi | 判断是否打开wifi | -| openWifi | 打开WIFI | -| closeWifi | 关闭WIFI | -| toggleWifiEnabled | 自动切换wifi开关状态 | -| getWifiState | 获取当前WIFI连接状态 | -| startScan | 开始扫描wifi | -| getConfiguration | 获取已配置的网络 | -| getWifiList | 获取wifi网络列表 | -| getWifiInfo | 获取WifiInfo对象 | -| getMacAddress | 获取MAC地址 | -| getBSSID | 获取接入点的BSSID | -| getIPAddress | 获取IP地址 | -| getNetworkId | 获取连接的ID | -| getSSID | 获取SSID | -| formatSSID | 判断是否存在\"ssid\",存在则裁剪返回 | -| getPassword | 获取密码(经过处理) | -| getWifiType | 获取加密类型(int常量) - 判断String | -| getWifiTypeInt | 获取加密类型(int常量) - 判断int(String) | -| getWifiType | 获取加密类型(int常量) | -| getWifiTypeStr | 获取加密类型(String) | -| isConnNull | 判断是否连接为null - | -| isConnectAphot | 判断是否连接上Wifi(非连接中) | -| getSecurity | 获取Wifi配置,加密类型 | -| isExsitsPwd | 获知Wifi配置,是否属于密码加密类型 | -| isExsits | 查看以前是否也配置过这个网络 | -| delWifiConfig | 删除指定的 Wifi(SSID) 配置信息 | -| quickConnWifi | 快速连接Wifi(不使用静态ip方式) | -| createWifiConfig | 创建Wifi配置信息(无其他操作,单独返回WifiConfig) | -| removeWifiConfig | 移除某个Wifi配置信息 | -| disconnectWifi | 断开指定ID的网络 | -| setStaticWifiConfig | 设置静态Wifi信息 | -| setDNS | 设置DNS | -| setGateway | 设置网关 | -| setIpAddress | 设置Ip地址 | -| setStaticIpConfig | 设置Ip地址、网关、DNS(5.0之后) | - -### `dev.utils.app` - -* **无障碍功能工具类 ->** [AccessibilityUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/AccessibilityUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| checkAccessibility | 检查是否开启无障碍功能 | -| isAccessibilitySettingsOn | 判断是否开启无障碍功能 | -| printAccessibilityEvent | 打印Event 日志 | - - -* **Acitivty 工具类 ->** [ActivityUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ActivityUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isActivityExists | 判断是否存在指定的Activity | -| startHomeActivity | 回到桌面 -> 同点击Home键效果 | -| getLauncherActivity | 跳转到桌面 | -| getActivityIcon | 返回Activity 对应的图标 | -| getActivityLogo | 返回Activity 对应的Logo | - - -* **AlarmManager (全局定时器/闹钟)指定时长或以周期形式执行某项操作 ->** [AlarmUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/AlarmUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| startAlarmIntent | 开启定时器 | -| stopAlarmIntent | 关闭定时器 | -| startAlarmService | 开启轮询服务 | -| stopAlarmService | 停止启轮询服务 | - - -* **分析记录工具类 ->** [AnalysisRecordUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/AnalysisRecordUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| init | 初始化操作 | -| record | 日志记录 | -| isHandler | 是否处理日志记录 | -| setHandler | 设置是否处理日志记录 | -| getLogFolderName | 获取文件日志名 | -| setLogFolderName | 设置日志文件夹名 | -| getLogStoragePath | 获取日志存储路径 | -| setLogStoragePath | 设置日志存储路径 | -| getStoragePath | getStoragePath | -| getFileName | getFileName | -| getFileFunction | getFileFunction | -| getFileIntervalTime | getFileIntervalTime | -| getFolderName | getFolderName | -| obtain | 获取记录分析文件信息 | -| getLogPath | 获取日志地址 | -| getIntervalTimeFolder | 获取时间间隔 - 文件夹 | - - -* **App通用工具类 ->** [AppCommonUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/AppCommonUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getUUID | 获取设备唯一id | -| getRandomUUID | 获取随机数 唯一id | -| isFroyo | 是否在2.2版本及以上 | -| isGingerbread | 是否在2.3版本及以上 | -| isGingerbreadMR1 | 是否在2.3.3版本及以上 | -| isHoneycomb | 是否在3.0版本及以上 | -| isHoneycombMR1 | 是否在3.1版本及以上 | -| isIceCreamSandwich | 是否在4.0版本及以上 | -| isIceCreamSandwichMR1 | 是否在4.0.3版本及以上 | -| isJellyBean | 是否在4.1版本及以上 | -| isKitkat | 是否在4.4.2版本及以上 | -| isLollipop | 是否在5.0.1版本及以上 | -| isM | 是否在6.0版本及以上 | -| isN | 是否在7.0版本及以上 | -| isN_MR1 | 是否在7.1.1版本及以上 | -| isO | 是否在8.0版本及以上 | -| convertSDKVersion | 转换SDK版本 | - - -* **App(Android 工具类) ->** [AppUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/AppUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getWindowManager | 通过上下文获取 WindowManager | -| getMetaData | 获取 Manifest Meta Data | -| getView | 获取View | -| getResources | getResources | -| getString | getString | -| getTheme | getTheme | -| getAssets | getAssets | -| getDrawable | getDrawable | -| getColor | getColor | -| getColorStateList | getColorStateList | -| getSystemService | getSystemService | -| getPackageManager | getPackageManager | -| getConfiguration | getConfiguration | -| getDisplayMetrics | getDisplayMetrics | -| getContentResolver | getContentResolver | -| getAppIcon | 获取app的图标 | -| getAppPackageName | 获取app 包名 | -| getAppName | 获取app 名 | -| getAppVersionName | 获取app版本名 - 对外显示 | -| getAppVersionCode | 获取app版本号 - 内部判断 | -| setLanguage | 对内设置指定语言 (app 多语言,单独改变app语言) | -| installApp | 安装 App(支持 8.0)的意图 | -| installAppSilent | 静默安装app | -| uninstallApp | 卸载 App | -| uninstallAppSilent | 静默卸载 App | -| isAppInstalled | 判断是否安装了应用 | -| isInstalledApp | 判断是否安装指定包名的APP | -| isAppRoot | 判断是否存在root 权限 | -| isAppDebug | 判断是否app 是否debug模式 | -| isAppSystem | 判断app 是否系统app | -| isAppForeground | 判断app 是否在前台 | -| launchApp | 打开app | -| launchAppDetailsSettings | 跳转到 专门的APP 设置详情页面 | -| launchAppDetails | 跳转到 专门的APP 应用商城详情页面 | -| getAppPath | 获取app 路径 /data/data/包名/.apk | -| getAppSignature | 获取app 签名 | -| getAppSignatureSHA1 | 获取 app sha1值 | -| openPDFFile | 启动本地应用打开PDF | -| openWordFile | 启动本地应用打开PDF | -| openOfficeByWPS | 调用WPS打开office文档 | - - -* **状态栏相关工具类 ->** [BarUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/BarUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getStatusBarHeight | 获取状态栏高度 | -| setStatusBarVisibility | 设置状态栏是否显示 | -| isStatusBarVisible | 判断状态栏是否显示 | -| setStatusBarLightMode | 设置状态是否高亮模式 | -| addMarginTopEqualStatusBarHeight | 添加状态栏同等高度到View的顶部 | -| subtractMarginTopEqualStatusBarHeight | 减去状态栏同等高度 | -| setStatusBarColor | 设置状态栏颜色 | -| setStatusBarAlpha | 设置状态栏透明度 | -| setStatusBarColor4Drawer | 设置状态栏的颜色 | -| setStatusBarAlpha4Drawer | 设置状态栏透明度 | -| getActionBarHeight | 返回 ActionBase 高度 | -| setNotificationBarVisibility | 设置通知栏是否显示 | -| getNavBarHeight | 获取 NavigationView 高度 | -| setNavBarVisibility | 设置导航栏是否可见(图标显示) | -| setNavBarImmersive | 设置沉浸模式 | -| isNavBarVisible | 判断顶部状态栏(图标)是否显示 | -| setNavBar | 设置是否显示状态栏图标等 | - - -* **亮度相关工具类 ->** [BrightnessUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/BrightnessUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isAutoBrightnessEnabled | 判断是否开启自动调节亮度 | -| setAutoBrightnessEnabled | 设置是否开启自动调节亮度 | -| getBrightness | 获取屏幕亮度 | -| setBrightness | 设置屏幕亮度 | -| setWindowBrightness | 设置窗口亮度 | -| getWindowBrightness | 获取窗口亮度 | - - -* **摄像头相关工具类 ->** [CameraUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/CameraUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isSupportReverse | 判断是否支持反转摄像头(是否存在前置摄像头) | -| checkCameraFacing | 检查是否有摄像头 | -| isFrontCamera | 判断是否使用前置摄像头 | -| isBackCamera | 判断是否使用后置摄像头 | -| isUseCameraFacing | 判断使用的视像头 | -| freeCameraResource | 释放摄像头资源 | -| initCamera | 初始化摄像头 | -| open | 打开摄像头 | - - -* ** ->** [CleanUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/CleanUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| cleanInternalCache | 清除本应用内部缓存(/data/data/com.xxx.xxx/cache) | -| cleanDatabases | 清除本应用所有数据库(/data/data/com.xxx.xxx/databases) | -| cleanSharedPreference | 清除本应用SharedPreference(/data/data/com.xxx.xxx/shared_prefs) | -| cleanDatabaseByName | 按名字清除本应用数据库 | -| cleanFiles | 清除/data/data/com.xxx.xxx/files下的内容 | -| cleanExternalCache | 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache) | -| cleanCustomCache | 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 | -| cleanApplicationData | 清除本应用所有的数据 | -| getFolderSize | 获取文件夹大小 | -| getCacheSize | 获取缓存文件大小 | -| getFormatSize | 格式化单位 | - - -* **点击工具类 ->** [ClickUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ClickUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isFastDoubleClick | 判断两次点击的间隔 小于默认间隔时间(1秒), 则认为是多次无效点击 | -| initConfig | 初始化配置信息 | -| putConfig | 添加配置信息 | -| removeConfig | 移除配置信息 | - - -* **剪贴板相关工具类 ->** [ClipboardUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ClipboardUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| copyText | 复制文本到剪贴板 | -| getText | 获取剪贴板的文本 | -| copyUri | 复制uri到剪贴板 | -| getUri | 获取剪贴板的uri | -| copyIntent | 复制意图到剪贴板 | -| getIntent | 获取剪贴板的意图 | - - -* **ContentResolver 工具类 ->** [ContentResolverUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ContentResolverUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| notifyMediaStore | 通知刷新本地资源 | -| insertImageIntoMediaStore | 添加图片到系统相册(包含原图、相册图, 会存在两张) - 想要一张, 直接调用 notifyMediaStore() | -| insertVideoIntoMediaStore | 添加视频到系统相册 | -| insertIntoMediaStore | 保存到系统相册 | - - -* **获取CPU信息工具类 ->** [CPUUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/CPUUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getProcessorsCount | 获取处理器的Java虚拟机的数量 | -| getSysCPUSerialNum | 获取手机CPU序列号 | -| getCpuInfo | 获取CPU 信息 | -| getCpuModel | 获取CPU 型号 | -| getMaxCpuFreq | 获取 CPU 最大频率(单位KHZ) | -| getMinCpuFreq | 获取 CPU 最小频率(单位KHZ) | -| getCurCpuFreq | 实时获取 CPU 当前频率(单位KHZ) | -| getCoresNumbers | 获取 CPU 几核 | -| getCpuName | 获取CPU名字 | -| getCMDOutputString | 获取 CMD 指令回调数据 | - - -* **数据库工具类 (导入导出等) ->** [DBUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/DBUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| startExportDatabase | 导出数据库 | -| startImportDatabase | 导入数据库 | -| getDBPath | 获取数据库路径 | - - -* **设备相关工具类 ->** [DeviceUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/DeviceUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getSysLanguage | 获取当前操作系统的语言 | -| getDeviceInfo | 获取设备信息 | -| getDeviceInfo2 | 获取设备信息 | -| handleDeviceInfo | 处理设备信息 | -| getSDKVersionName | 获取设备系统版本号 | -| getSDKVersion | 获取当前SDK 版本号 | -| getAndroidId | 获取Android id | -| isDeviceRooted | 判断设备是否 root | -| getMacAddress | 获取设备 MAC 地址 | -| getManufacturer | 获取设备厂商 如 Xiaomi | -| getModel | 获取设备型号 如 RedmiNote4X | -| shutdown | 关机 需要 root 权限或者系统权限 | -| reboot | 重启 需要 root 权限或者系统权限 | -| reboot2Recovery | 重启到 recovery 需要 root 权限 | -| reboot2Bootloader | 重启到 bootloader 需要 root 权限 | -| getBaseband_Ver | BASEBAND-VER 基带版本 | -| getLinuxCore_Ver | CORE-VER 内核版本 | - - -* **Dialog 操作相关工具类 ->** [DialogUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/DialogUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| closeDialog | 关闭Dialog | -| closeDialogs | 关闭多个Dialog | -| closePopupWindow | 关闭PopupWindow | -| closePopupWindows | 关闭多个PopupWindow | -| creDialog | 创建加载 Dialog | -| creAutoCloseDialog | 创建自动关闭dialog | - - -* **EditText 工具类 ->** [EditTextUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/EditTextUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| setMaxLengthAnText | 设置长度限制,并且设置内容 | -| setMaxLength | 设置长度限制 | -| getSelectionStart | 获取光标位置 | -| getText | 获取输入的内容 | -| getTextLength | 获取输入的内容长度 | -| setText | 设置内容 | -| insert | 追加内容 - 当前光标位置追加 | -| setSelectTop | 设置光标在第一位 | -| setSelectBottom | 设置光标在最后一位 | -| setSelect | 设置光标位置 | -| setKeyListener | 设置输入限制 | -| getNumberAndEnglishKeyListener | 限制只能输入字母和数字,默认弹出英文输入法 | -| getNumberKeyListener | 限制只能输入数字,默认弹出数字列表 | -| getMarkId | getMarkId | -| isOperate | isOperate | -| setOperate | setOperate | -| getOperateState | getOperateState | -| setOperateState | setOperateState | -| beforeTextChanged | 在文本变化前调用 | -| onTextChanged | 在文本变化后调用 | -| afterTextChanged | 在文本变化后调用 | - - -* **编码工具类 ->** [EncodeUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/EncodeUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| urlEncode | url编码 - UTF-8 | -| urlDecode | url 解码 - UTF-8 | -| base64Encode | base64 编码 => 去除\n 替换 + 和 - 号 | -| base64Encode2String | base64 编码 | -| base64Decode | base64 解码 | -| base64DecodeToString | base64 解码 | -| htmlEncode | html编码 | -| htmlDecode | html解码 | - - -* **错误信息处理工具类 ->** [ErrorUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ErrorUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getErrorMsg | 获取错误信息 | -| getThrowableMsg | 将异常栈信息转为字符串 | -| getThrowableNewLinesMsg | 获取错误信息(有换行) | - - -* **App 文件记录工具类 ->** [FileRecordUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/FileRecordUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| appInit | App初始化调用方法 | -| saveErrorLog | 保存app错误日志 | -| saveLog | 保存app日志 | -| handleVariable | 处理可变参数 | - - -* **手电筒工具类 ->** [FlashlightUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/FlashlightUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getInstance | getInstance | -| register | 注册摄像头 | -| unregister | 注销摄像头 | -| setFlashlightOn | 打开闪光灯 | -| setFlashlightOff | 关闭闪光灯 | -| isFlashlightOn | 是否打开闪光灯 | -| isFlashlightEnable | 是否支持手机闪光灯 | - - -* **Handler 工具类, 默认开启一个 Handler,方便在各个地方随时执行主线程任务 ->** [HandlerUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/HandlerUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| init | 初始化操作 | -| getMainHandler | 获取主线程 Handler | -| postRunnable | 在主线程 Handler 中执行任务 | -| removeRunnable | 在主线程 Handler 中清除任务 | - - -* **Html 工具类 ->** [HtmlUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/HtmlUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| setHtmlText | 设置内容, 最终做处理 | -| addHtmlColor | 为给定的字符串添加HTML颜色标记 | -| addHtmlBold | 为给定的字符串添加HTML加粗标记 | -| addHtmlColorAndBlod | 为给定的字符串添加HTML颜色标记,以及加粗 | -| addHtmlUnderline | 为给定的字符串添加HTML下划线 | -| addHtmlIncline | 为给定的字符串添加HTML 字体倾斜 | -| keywordMadeRed | 将给定的字符串中所有给定的关键字标色 | -| keywordReplaceAll | 将给定的字符串中所有给定的关键字进行替换内容 | - - -* **Intent(意图) 相关工具类 ->** [IntentUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/IntentUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getInstallAppIntent | 获取安装 App(支持 8.0)的意图 | -| getUninstallAppIntent | 获取卸载 App 的意图 | -| getLaunchAppIntent | 获取打开 App 的意图 | -| getLaunchAppDetailsSettingsIntent | 获取 App 具体设置的意图 | -| getlaunchAppDetailIntent | 获取 到应用商店app详情界面的意图 | -| getShareTextIntent | 获取分享文本的意图 | -| getShareImageIntent | 获取分享图片的意图 | -| getComponentIntent | 获取其他应用组件的意图 | -| getShutdownIntent | 获取关机的意图 | -| getDialIntent | 获取跳至拨号界面意图 | -| getCallIntent | 获取拨打电话意图 | -| getSendSmsIntent | 获取发送短信界面的意图 | -| getCaptureIntent | 获取拍照的意图 | -| startSysSetting | 跳转到系统设置页面 | -| openWirelessSettings | 打开网络设置界面 - 3.0以下打开设置界面 | - - -* **软键盘相关辅助类 ->** [KeyBoardUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/KeyBoardUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| setDelayMillis | 设置延迟时间 | -| openKeyboard | 打开软键盘 | -| closeKeyboard | 关闭软键盘 | -| closeKeyBoardSpecial | 关闭软键盘 - 特殊处理 | -| toggleKeyboard | 自动切换键盘状态,如果键盘显示了则隐藏,隐藏着显示 | -| judgeView | 某个View里面的子View的View判断 | -| isSoftInputVisible | 判断软键盘是否可见 | -| registerSoftInputChangedListener | 注册软键盘改变监听器 | -| registerSoftInputChangedListener2 | 注册软键盘改变监听器 | -| fixSoftInputLeaks | 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 | -| onSoftInputChanged | onSoftInputChanged | - - -* **锁屏工具类 - 锁屏管理, 锁屏、禁用锁屏,判断是否锁屏 ->** [KeyguardUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/KeyguardUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getInstance | 获取 KeyguardUtils 实例 ,单例模式 | -| isKeyguardLocked | 是否锁屏 - android 4.1以上支持 | -| isKeyguardSecure | 是否有锁屏密码 - android 4.1以上支持 | -| inKeyguardRestrictedInputMode | 是否锁屏 - android 4.1 之前 | -| getKeyguardManager | getKeyguardManager | -| setKeyguardManager | setKeyguardManager | -| disableKeyguard | 屏蔽系统的屏保 | -| reenableKeyguard | 使能显示锁屏界面,如果你之前调用了disableKeyguard()方法取消锁屏界面,那么会马上显示锁屏界面。 | -| release | 释放资源 | -| newKeyguardLock | newKeyguardLock | -| getKeyguardLock | getKeyguardLock | -| setKeyguardLock | setKeyguardLock | - - -* **事件工具类 => AppReflectUtils(可以删除) ->** [ListenerUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ListenerUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getTouchListener | 获取 View 设置的 OnTouchListener | -| getListenerInfo | 获取 View ListenerInfo 对象(内部类) | -| getListenerInfoListener | 获取 View ListenerInfo 对象内部事件对象 | -| setOnClicks | 设置点击事件 | - - -* **定位相关工具类 ->** [LocationUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/LocationUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getLocation | 获取位置, 需要先判断是否开启了定位 | -| isGpsEnabled | 判断Gps是否可用 | -| isLocationEnabled | 判断定位是否可用 | -| openGpsSettings | 打开Gps设置界面 | -| register | 注册 | -| unregister | 注销 | -| getAddress | 根据经纬度获取地理位置 | -| getCountryName | 根据经纬度获取所在国家 | -| getLocality | 根据经纬度获取所在地 | -| getStreet | 根据经纬度获取所在街道 | -| isBetterLocation | 是否更好的位置 | -| isSameProvider | 是否相同的提供者 | -| getLastKnownLocation | 获取最后一次保留的坐标 | -| onLocationChanged | 当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发 | -| onStatusChanged | provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 | - - -* **Android Manifest工具类 ->** [ManifestUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ManifestUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getMetaData | 获取 Manifest Meta Data | -| getAppVersion | 获取app版本信息 | -| getAppVersionCode | 获取app版本号 | -| getAppVersionName | 获取app版本信息 | - - -* **获取内存信息 ->** [MemoryUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/MemoryUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getMemoryInfo | 获取内存信息 | -| printMemoryInfo | Print Memory info. | -| getAvailMemory | Get available memory info. | -| getTotalMemory | 获取总内存大小 | -| getMemoryAvailable | 获取可用内存大小 | -| getMemInfoIype | 获取 type info | - - -* **网络管理工具类 ->** [NetWorkUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/NetWorkUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getMobileDataEnabled | 获取移动网络打开状态(默认属于未打开) | -| setMobileDataEnabled | 设置移动网络开关(无判断是否已开启移动网络) - 实际无效果, 非系统应用无权限 | -| isConnect | 判断是否连接了网络 | -| getConnectType | 获取连接的网络类型 | -| isConnWifi | 判断是否连接Wifi(连接上、连接中) | -| isConnMobileData | 判断是否连接移动网络(连接上、连接中) | -| isAvailable | 判断网络是否可用 | -| is4G | 判断是否4G网络 | -| getWifiEnabled | 判断wifi是否打开 | -| isWifiAvailable | 判断wifi数据是否可用 | -| getNetworkOperatorName | 获取网络运营商名称 - 中国移动、如中国联通、中国电信 | -| getNetworkType | 获取当前网络类型 | -| getDomainAddress | 获取域名ip地址 | -| getIPAddress | 获取IP地址 | - - -* **通知栏管理类 ->** [NotificationUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/NotificationUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getNotificationManager | 获取通知栏管理类 | -| cancelAll | 移除通知 - 移除所有通知(只是针对当前Context下的Notification) | -| cancel | 移除通知 - 移除标记为id的通知 (只是针对当前Context下的所有Notification) | -| notify | 进行通知 | -| crePendingIntent | 获取跳转id | -| creNotification | 创建通知栏对象 | -| obtain | 获取 Led 配置参数 | -| isEmpty | 判断是否为null | - - -* **工具类: OS 系统相关 ->** [OSUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/OSUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getRomType | 获取 ROM 类型 | -| getBaseVersion | getBaseVersion | -| getVersion | getVersion | - - -* **路径相关工具类 ->** [PathUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/PathUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getRootPath | 获取 Android 系统根目录 path: /system | -| getDataPath | 获取 data 目录 path: /data | -| getIntDownloadCachePath | 获取缓存目录 path: data/cache | -| getAppIntCachePath | 获取此应用的缓存目录 path: /data/data/package/cache | -| getAppIntFilesPath | 获取此应用的文件目录 path: /data/data/package/files | -| getAppIntDbPath | 获取此应用的数据库文件目录 path: /data/data/package/databases/name | -| getExtStoragePath | 获取 Android 外置储存的根目录 path: /storage/emulated/0 | -| getExtAlarmsPath | 获取闹钟铃声目录 path: /storage/emulated/0/Alarms | -| getExtDcimPath | 获取相机拍摄的照片和视频的目录 path: /storage/emulated/0/DCIM | -| getExtDocumentsPath | 获取文档目录 path: /storage/emulated/0/Documents | -| getExtDownloadsPath | 获取下载目录 path: /storage/emulated/0/Download | -| getExtMoviesPath | 获取视频目录 path: /storage/emulated/0/Movies | -| getExtMusicPath | 获取音乐目录 path: /storage/emulated/0/Music | -| getExtNotificationsPath | 获取提示音目录 path: /storage/emulated/0/Notifications | -| getExtPicturesPath | 获取图片目录 path: /storage/emulated/0/Pictures | -| getExtPodcastsPath | 获取 Podcasts 目录 path: /storage/emulated/0/Podcasts | -| getExtRingtonesPath | 获取铃声目录 path: /storage/emulated/0/Ringtones | -| getAppExtCachePath | 获取此应用在外置储存中的缓存目录 path: /storage/emulated/0/Android/data/package/cache | -| getAppExtFilePath | 获取此应用在外置储存中的文件目录 path: /storage/emulated/0/Android/data/package/files | -| getAppExtAlarmsPath | 获取此应用在外置储存中的闹钟铃声目录 path: /storage/emulated/0/Android/data/package/files/Alarms | -| getAppExtDcimPath | 获取此应用在外置储存中的相机目录 path: /storage/emulated/0/Android/data/package/files/DCIM | -| getAppExtDocumentsPath | 获取此应用在外置储存中的文档目录 path: /storage/emulated/0/Android/data/package/files/Documents | -| getAppExtDownloadPath | 获取此应用在外置储存中的闹钟目录 path: /storage/emulated/0/Android/data/package/files/Download | -| getAppExtMoviesPath | 获取此应用在外置储存中的视频目录 path: /storage/emulated/0/Android/data/package/files/Movies | -| getAppExtMusicPath | 获取此应用在外置储存中的音乐目录 path: /storage/emulated/0/Android/data/package/files/Music | -| getAppExtNotificationsPath | 获取此应用在外置储存中的提示音目录 path: /storage/emulated/0/Android/data/package/files/Notifications | -| getAppExtPicturesPath | 获取此应用在外置储存中的图片目录 path: /storage/emulated/0/Android/data/package/files/Pictures | -| getAppExtPodcastsPath | 获取此应用在外置储存中的 Podcasts 目录 path: /storage/emulated/0/Android/data/package/files/Podcasts | -| getAppExtRingtonesPath | 获取此应用在外置储存中的铃声目录 path: /storage/emulated/0/Android/data/package/files/Ringtones | -| getObbPath | 获取此应用的 Obb 目录 path: /storage/emulated/0/Android/obb/package 一般用来存放游戏数据包 | -| getFilePathByUri | getFilePathByUri | - - -* **权限请求工具类 ->** [PermissionUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/PermissionUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isGranted | 判断是否授予了权限 | -| shouldShowRequestPermissionRationale | 是否拒绝了权限 - 拒绝过一次, 再次申请时, 弹出选择不再提醒并拒绝才会触发 true | -| permission | 申请权限初始化 | -| callBack | 设置回调方法 | -| request | 请求权限 | -| onRequestPermissionsResult | 请求权限回调 - 需要在 onRequestPermissionsResult 回调里面调用 | -| onGranted | 授权通过权限 | -| onDenied | 授权未通过权限 | -| start | start | -| onCreate | onCreate | - - -* **手机相关工具类 ->** [PhoneUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/PhoneUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isSimReady | 判断是否装载sim卡 | -| getUserCountry | 获取Sim卡所属地区,非国内地区暂不支持播放 | -| judgeArea | 判断地区,是否属于国内 | -| isPhone | 判断设备是否是手机 | -| getIMEI | 获取 IMEI 码 | -| getIMSI | 获取 IMSI 码 | -| getIMSIIDName | 获取IMSI处理过后的简称 | -| getPhoneType | 获取移动终端类型 | -| isSimCardReady | 判断 sim 卡是否准备好 | -| getSimOperatorName | 获取 Sim 卡运营商名称 => 中国移动、如中国联通、中国电信 | -| getSimOperatorByMnc | 获取 Sim 卡运营商名称 => 中国移动、如中国联通、中国电信 | -| getDeviceId | 获取设备id | -| getSerialNumber | 返回设备序列化 | -| getAndroidId | 获取Android id | -| getUUID | 获取设备唯一id | -| getPhoneStatus | 获取手机状态信息 | -| dial | 跳至拨号界面 | -| call | 拨打电话 | -| sendSms | 跳至发送短信界面 | -| sendSmsSilent | 发送短信 | -| getAllContactInfo | 获取手机联系人 | -| getAllContactInfo2 | 获取手机联系人 | -| getContactNum | 打开手机联系人界面点击联系人后便获取该号码 | -| getAllSMS | 获取手机短信并保存到 xml 中 | -| getMtkTeleInfo | MTK Phone. 获取 MTK 神机的双卡 IMSI、IMSI 信息 | -| getMtkTeleInfo2 | MTK Phone. 获取 MTK 神机的双卡 IMSI、IMSI 信息 | -| getQualcommTeleInfo | Qualcomm Phone. 获取 高通 神机的双卡 IMSI、IMSI 信息 | -| getSpreadtrumTeleInfo | Spreadtrum Phone. 获取 展讯 神机的双卡 IMSI、IMSI 信息 | -| toString | toString | - - -* **轮询工具类 ->** [PollingUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/PollingUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| startPolling | 开启轮询 | -| stopPolling | 停止轮询 | -| startPollingService | 开启轮询服务 | -| stopPollingService | 停止启轮询服务 | - - -* **电源管理工具类 ->** [PowerManagerUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/PowerManagerUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getInstance | 获取 PowerManagerUtils 实例 ,单例模式 | -| isScreenOn | 屏幕是否打开(亮屏) | -| turnScreenOn | 唤醒屏幕/点亮亮屏 | -| turnScreenOff | 释放屏幕锁, 允许休眠时间自动黑屏 | -| getWakeLock | getWakeLock | -| setWakeLock | setWakeLock | -| getPowerManager | getPowerManager | -| setPowerManager | setPowerManager | -| setBright | 设置屏幕常亮 | -| setWakeLockToBright | 设置WakeLock 常亮 | - - -* **进程相关工具类 ->** [ProcessUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ProcessUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getProcessName | 获取进程号对应的进程名 | -| getCurProcessName | 获得当前进程的名字 | -| getForegroundProcessName | 获取前台线程包名 | -| getAllBackgroundProcesses | 获取后台服务进程 | -| killAllBackgroundProcesses | 杀死所有的后台服务进程 | -| killBackgroundProcesses | 杀死后台服务进程 | - - -* **资源文件工具类 ->** [ResourceUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ResourceUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getString | 获得字符串 | -| getColor | 获得颜色 | -| getDrawable | 获得Drawable | -| getResourceId | 根据资源名获得资源id | -| getDrawableId2 | 获取资源id | -| getLayoutId | 获取 layout 布局文件 | -| getStringId | 获取 string 值 | -| getDrawableId | 获取 drawable | -| getMipmapId | 获取 mipmap | -| getStyleId | 获取 style | -| getStyleableId | 获取 styleable | -| getAnimId | 获取 anim | -| getId | 获取 id | -| getColorId | color | -| readBytesFromAssets | 获取 Assets 资源文件数据 | -| readStringFromAssets | 读取字符串 来自 Assets文件 | -| readBytesFromRaw | 从res/raw 中获取内容。 | -| readStringFromRaw | 读取字符串 来自Raw 文件 | -| geFileToListFromAssets | 获取 Assets 资源文件数据(返回ArrayList 一行的全部内容属于一个索引) | -| geFileToListFromRaw | 从res/raw 中获取内容。(返回ArrayList 一行的全部内容属于一个索引) | -| saveAssetsFormFile | 从Assets 资源中获取内容并保存到本地 | -| saveRawFormFile | 从res/raw 中获取内容并保存到本地 | - - -* **屏幕相关工具类 ->** [ScreenUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ScreenUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getScreenWidth | 获取屏幕的宽度(单位:px) | -| getScreenHeight | 获取屏幕的高度(单位:px) | -| getScreenWidthHeightToPoint | 通过上下文获取屏幕宽度高度 | -| getScreenWidthHeight | 通过上下文获取屏幕宽度高度 | -| getScreenSize | 获取屏幕分辨率 | -| getScreenSizeOfDevice | 获取屏幕英寸 例5.5英寸 | -| getDensity | 通过上下文获取屏幕密度 | -| getDensityDpi | 通过上下文获取屏幕密度Dpi | -| getScaledDensity | 通过上下文获取屏幕缩放密度 | -| getXDpi | 获取 X轴 dpi | -| getYDpi | 获取 Y轴 dpi | -| getWidthDpi | 获取 宽度比例 dpi 基准 | -| getHeightDpi | 获取 高度比例 dpi 基准 | -| getScreenInfo | 获取屏幕信息 | -| setFullScreen | 设置屏幕为全屏 | -| setLandscape | 设置屏幕为横屏 | -| setPortrait | 设置屏幕为竖屏 | -| isLandscape | 判断是否横屏 | -| isPortrait | 判断是否竖屏 | -| getScreenRotation | 获取屏幕旋转角度 | -| isScreenLock | 判断是否锁屏 | -| isTablet | 判断是否是平板 | -| getStatusHeight | 获得状态栏的高度(无关 android:theme 获取状态栏高度) | -| getStatusBarHeight | 获取应用区域 TitleBar 高度 (顶部灰色TitleBar高度,没有设置 android:theme 的 NoTitleBar 时会显示) | -| setSleepDuration | 设置进入休眠时长 - 需添加权限 | -| getSleepDuration | 获取进入休眠时长 | -| snapShotWithStatusBar | 获取当前屏幕截图,包含状态栏 (顶部灰色TitleBar高度,没有设置 android:theme 的 NoTitleBar 时会显示) | -| snapShotWithoutStatusBar | 获取当前屏幕截图,不包含状态栏 (如果 android:theme 全屏了,则截图无状态栏) | -| getNavigationBarHeight | 获取底部导航栏高度 | -| checkDeviceHasNavigationBar | 检测是否具有底部导航栏 | - - -* **SD卡相关辅助类 ->** [SDCardUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/SDCardUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isSDCardEnable | 判断SDCard是否正常挂载 | -| getSDCardFile | 获取SD卡路径(File对象) | -| getSDCardPath | 获取SD卡路径(无添加 -> / -> File.separator) | -| isSDCardEnablePath | 判断 SD 卡是否可用 | -| getSDCardPaths | 获取 SD 卡路径 | -| getAllBlockSize | 返回对应路径的空间总大小 | -| getAvailableBlocks | 返回对应路径的空闲空间(byte 字节大小) | -| getAlreadyBlock | 返回对应路径,已使用的空间大小 | -| getBlockSizeInfos | 返回对应路径的空间大小信息 | -| getSDTotalSize | 获得 SD 卡总大小 | -| getSDAvailableSize | 获得 SD 卡剩余容量,即可用大小 | -| getRomTotalSize | 获得机身内存总大小 | -| getRomAvailableSize | 获得机身可用内存 | -| getDiskCacheDir | 获取缓存地址 | -| getCacheFile | 获取缓存资源地址 | -| getCachePath | 获取缓存资源地址 | - - -* **服务相关工具类 ->** [ServiceUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ServiceUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| isServiceRunning | 判断服务是否运行 | -| getAllRunningService | 获取所有运行的服务 | -| startService | 启动服务 | -| stopService | 停止服务 | -| bindService | 绑定服务 | -| unbindService | 解绑服务 | - - -* **Shape 工具类 ->** [ShapeUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ShapeUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getDrawable | getDrawable | -| setDrawable | setDrawable | -| newBuilder | 创建新的 Shape Builder 对象 | -| newBuilderToLeft | 创建新的 Shape Builder 对象 | -| newBuilderToRight | 创建新的 Shape Builder 对象 | -| newBuilderToGradient | 创建渐变的 Shape Builder 对象 | -| build | 获取 Shape 工具类 | -| setRadius | 设置圆角 | -| setRadiusLeft | 设置圆角 | -| setRadiusRight | 设置圆角 | -| setCornerRadii | 内部处理方法 | -| setColor | 设置背景色(填充铺满) | -| setStroke | 设置边框颜色 | -| setSize | 设置大小 | - - -* **Shell 相关工具类 ->** [ShellUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ShellUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| execCmd | 是否是在 root 下执行命令 | - - -* **创建删除快捷图标工具类 ->** [ShortCutUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ShortCutUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| hasShortcut | 检测是否存在桌面快捷方式 | -| addShortcut | 为程序创建桌面快捷方式 | -| delShortcut | 删除程序的快捷方式 | - - -* **签名工具类(获取app,签名信息) ->** [SignaturesUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/SignaturesUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| toHexString | 进行转换 | -| signatureMD5 | 返回MD5 | -| signatureSHA1 | SHA1 | -| signatureSHA256 | SHA256 | -| isDebuggable | 判断签名是debug签名还是release签名 | -| getX509Certificate | 获取App 证书对象 | -| printSignatureName | 打印签名信息 | -| getSignaturesFromApk | 从APK中读取签名 | -| getCertificateFromApk | 从APK中读取签名 | - - -* **dp,px,sp转换、View获取宽高等 ->** [SizeUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/SizeUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| dipConvertPx | 根据手机的分辨率从 dp 的单位 转成为 px(像素) | -| pxConvertDip | 根据手机的分辨率从 px(像素) 的单位 转成为 dp | -| pxConvertSp | 根据手机的分辨率从 px(像素) 的单位 转成为 sp | -| spConvertPx | 根据手机的分辨率从 sp 的单位 转成为 px | -| dipConvertPx2 | 根据手机的分辨率从 dp 的单位 转成为 px(像素) 第二种 | -| spConvertPx2 | 根据手机的分辨率从 sp 的单位 转成为 px 第二种 | -| applyDimension | 各种单位转换 - 该方法存在于 TypedValue | -| forceGetViewSize | 获取视图的尺寸 | -| measureView | 测量视图尺寸 | -| getMeasuredWidth | 获取测量视图宽度 | -| getMeasuredHeight | 获取测量视图高度 | -| onGetSize | onGetSize | - - -* **颜色状态列表 工具类 ->** [StateListUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/StateListUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getColorStateList | 通过上下文获取 | -| createColorStateList | 创建 颜色状态列表 => createColorStateList("#ffffffff", "#ff44e6ff") | -| newSelector | 创建 Drawable选择切换 list => view.setBackground(Drawable) | - - -* **TextView 工具类 ->** [TextViewUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/TextViewUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getTextView | 获取TextView | -| getText | 获取文本 | -| setTextColor | 设置字体颜色 | -| setText | 设置内容 | -| setHtmlText | 设置 Html 内容 | -| setTVUnderLine | 给TextView设置下划线 | -| getTextHeight | 获取字体高度 | -| getTextTopOffsetHeight | 获取字体顶部偏移高度 | -| getTextWidth | 计算字体宽度 | -| getCenterRectY | 获取画布中间居中位置 | -| reckonTextSize | 通过需要的高度, 计算字体大小 | -| calcTextWidth | 计算第几位超过宽度 | - - -* **Uri 工具类 ->** [UriUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/UriUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getUriForFileToName | 返回处理后的Uri, 单独传递名字, 自动添加包名 ${applicationId} | -| getUriForFile | Return a content URI for a given file. | - - -* **震动相关工具类 ->** [VibrationUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/VibrationUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| vibrate | 震动 | -| cancel | 取消振动 | - - -* **View 操作相关工具类 ->** [ViewUtils.java](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/src/main/java/dev/utils/app/ViewUtils.java) - -| 方法 | 注释 | -| :-: | :-: | -| getContext | 获取上下文 | -| isEmpty | 判断View 是否为null | -| isVisibility | 判断View 是否显示 | -| isVisibilitys | 判断 View 是否都显示显示 | -| isVisibilityIN | 判断View 是否隐藏占位 | -| isVisibilityGone | 判断View 是否隐藏 | -| getVisibility | 获取显示的状态 (View.VISIBLE : View.GONE) | -| getVisibilityIN | 获取显示的状态 (View.VISIBLE : View.INVISIBLE) | -| setVisibility | 设置View显示状态 | -| setVisibilitys | 设置View 显示的状态 | -| toggleVisibilitys | 切换View 显示的状态 | -| toogleView | 切换View状态 | -| setViewImageRes | 设置View 图片资源 | -| findViewById | 初始化View | -| removeSelfFromParent | 把自身从父View中移除 | -| isTouchInView | 判断触点是否落在该View上 | -| requestLayoutParent | View 改变请求 | -| measureView | 测量 view | -| getViewWidth | 获取view的宽度 | -| getViewHeight | 获取view的高度 | -| getActivity | 获取view的上下文 | -| calcListViewItemHeight | 计算ListView Item 高度 | -| calcGridViewItemHeight | 计算GridView Item 高度 | -| getItemHeighet | 获取单独一个Item 高度 | \ No newline at end of file diff --git a/DevLibUtils/build.gradle b/DevLibUtils/build.gradle deleted file mode 100644 index 7084b5e1df..0000000000 --- a/DevLibUtils/build.gradle +++ /dev/null @@ -1,115 +0,0 @@ -apply plugin: 'com.android.library' - -def dev_utils_version = "1.0.0"; - -android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" - - defaultConfig { - minSdkVersion 19 - targetSdkVersion 27 - versionCode 1 - versionName dev_utils_version - - /** - - - */ - manifestPlaceholders = [ dev_utils_version : dev_utils_version ] - } - - - buildTypes { - - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - debug { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - lintOptions { - abortOnError false - } -} - -dependencies { - compile fileTree(include: ['*.jar'], dir: 'libs') - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support:support-v4:27.1.1' -} - -// == 打包, 包含注释 == - -// 指定编码 -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -// 打包源码 -task sourcesJar(type: Jar) { - from android.sourceSets.main.java.srcDirs - classifier = 'sources' -} - -task javadoc(type: Javadoc) { - failOnError false - source = android.sourceSets.main.java.sourceFiles - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - classpath += configurations.compile -} - -// 制作文档(Javadoc) -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir -} - -artifacts { - archives sourcesJar - archives javadocJar -} - -// https://www.jianshu.com/p/44f2d0ee32c8 - -// https://www.jianshu.com/p/1777a634db5e - -// https://www.cnblogs.com/xinaixia/p/7660173.html - -// ==== 项目打包 aar ===== - -// 将被嵌入的应用打包成 aar 包,这也是 Android Studio 的一种新特性,可以将应用所使用的资源性文件一起打包。编译即可打包生成 aar 包,而且也不用担心漏掉资源问题。 -// 直接使用aar, 内部包含 jar -// build\outputs\aar\DevLibUtils-debug.aar, DevLibUtils-release.aar - -// ==== 项目打包 jar ===== - -// Terminal 输入 gradlew makeJar - -// 方式 A :点击 Android Studio 右侧(一般在这个位置)的 Gradle 面板,在项目或者该类库的目录中找到 Tasks -> other -> makeJar 命令,双击这个makeJar之后等一会就会编译好jar包。 - -// DevLibUtils.jar -// DevLibUtils_v1.0.jar - -def _BASENAME = "DevLibUtils"; -def _VERSION = "_v" + android.defaultConfig.versionName; -def _DestinationPath = "build"; //生成jar包的位置 -def zipFile = file('build/intermediates/bundles/debug/classes.jar'); //待打包文件位置 - -task deleteBuild(type:Delete){ - delete _DestinationPath + _BASENAME + _VERSION + ".jar" -} - -task makeJar(type:Jar){ - from zipTree(zipFile) - from fileTree(dir:'src/main',includes:['assets/**']) //将assets目录打入jar包 - baseName = _BASENAME + _VERSION - destinationDir = file(_DestinationPath) -} - -makeJar.dependsOn(deleteBuild, build) \ No newline at end of file diff --git a/DevLibUtils/proguard-rules.pro b/DevLibUtils/proguard-rules.pro deleted file mode 100644 index f1b424510d..0000000000 --- a/DevLibUtils/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/DevLibUtils/src/main/AndroidManifest.xml b/DevLibUtils/src/main/AndroidManifest.xml deleted file mode 100644 index 50c5b4e4d4..0000000000 --- a/DevLibUtils/src/main/AndroidManifest.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - diff --git a/DevLibUtils/src/main/java/dev/DevUtils.java b/DevLibUtils/src/main/java/dev/DevUtils.java deleted file mode 100644 index b8fcb54e16..0000000000 --- a/DevLibUtils/src/main/java/dev/DevUtils.java +++ /dev/null @@ -1,298 +0,0 @@ -package dev; - -import android.app.Activity; -import android.app.Application; -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; - -import dev.utils.BuildConfig; -import dev.utils.JCLogUtils; -import dev.utils.LogPrintUtils; -import dev.utils.app.AnalysisRecordUtils; -import dev.utils.app.FileRecordUtils; -import dev.utils.app.HandlerUtils; -import dev.utils.app.cache.DevCache; -import dev.utils.app.logger.DevLoggerUtils; -import dev.utils.app.share.SharedUtils; - -/** - * detail: 开发工具类 - * Created by Ttt - */ -public final class DevUtils { - - /** 禁止构造对象,保证只有一个实例 */ - private DevUtils() { - } - -// // DevUtils 实例 -// private static DevUtils INSTANCE = new DevUtils(); -// -// /** 获取 DevUtils 实例 ,单例模式 */ -// public static DevUtils getInstance() { -// return INSTANCE; -// } - - // --- - - /** 全局 Application 对象 */ - private static Application sApplication; - /** 全局上下文 - getApplicationContext() */ - private static Context sContext; - /** 获取当前线程,主要判断是否属于主线程 */ - private static Thread sUiThread; - /** 全局Handler,便于子线程快捷操作等 */ - private static Handler sHandler; - /** 是否内部debug模式 */ - private static boolean debug = false; - - /** - * 默认初始化方法 - 必须调用 - Application.onCreate 中调用 - * @param context 上下文 - */ - public static void init(Context context) { - // 设置全局上下文 - initContext(context); - // 初始化全局 Application - initApplication(context); - // 初始化Shared 工具类 - SharedUtils.init(context); - // 初始化记录文件配置 - FileRecordUtils.appInit(); - // 初始化记录工具类 - AnalysisRecordUtils.init(context); - // 初始化 DevLogger 配置 - DevLoggerUtils.appInit(context); - // 初始化Handler工具类 - HandlerUtils.init(context); - // 初始化缓存类 - DevCache.get(context); - // 保存当前线程信息 - sUiThread = Thread.currentThread(); - // 初始化全局Handler - 主线程 - sHandler = new Handler(Looper.getMainLooper()); - // 注册 Activity 生命周期监听 - registerActivityLifecycleCallbacks(sApplication); - } - - /** - * 初始化全局上下文 - * @param context - */ - private static void initContext(Context context) { - // 如果为null, 才进行判断处理 - if (DevUtils.sContext == null){ - // 防止传进来的为null - if (context == null) { - return; - } - DevUtils.sContext = context.getApplicationContext(); - } - } - - /** - * 初始化全局 Application - * @param context - */ - private static void initApplication(Context context) { - // 如果为null, 才进行判断处理 - if (DevUtils.sApplication == null){ - if (context == null){ - return; - } - Application mApplication = null; - try { - mApplication = (Application) context.getApplicationContext(); - } catch (Exception e){ - } - // 防止传进来的为null - if (mApplication == null) { - return; - } - DevUtils.sApplication = mApplication; - } - } - - /** - * 获取全局上下文 - * @return - */ - public static Context getContext() { - return DevUtils.sContext; - } - - /** - * 获取上下文(判断null,视情况返回全局上下文) - * @param context - */ - public static Context getContext(Context context) { - // 进行判断 - if (context != null){ - return context; - } - return DevUtils.sContext; - } - - /** - * 获取全局 Application - * @return - */ - public static Application getApplication(){ - return DevUtils.sApplication; - } - - /** - * 获取Handler - * @return - */ - public static Handler getHandler(){ - if (sHandler == null){ - // 初始化全局Handler - 主线程 - sHandler = new Handler(Looper.getMainLooper()); //Looper.myLooper(); - } - return sHandler; - } - - /** - * 执行UI 线程任务 => Activity 的 runOnUiThread(Runnable) - * @param action 若当前非UI线程则切换到UI线程执行 - */ - public static void runOnUiThread(Runnable action) { - if (Thread.currentThread() != sUiThread) { - sHandler.post(action); - } else { - action.run(); - } - } - - /** - * 执行UI 线程任务 => 延时执行 - * @param action - * @param delayMillis - */ - public static void runOnUiThread(Runnable action, long delayMillis){ - sHandler.postDelayed(action, delayMillis); - } - - /** - * 打开日志 - */ - public static void openLog() { - // 专门打印 Android 日志信息 - LogPrintUtils.setPrintLog(true); - // 专门打印 Java 日志信息 - JCLogUtils.setPrintLog(true); - } - - /** - * 标记debug模式 - */ - public static void openDebug() { - DevUtils.debug = true; - } - - /** - * 判断是否Debug模式 - * @return - */ - public static boolean isDebug() { - return debug; - } - - // ================== - // ==== Activity ==== - // ================== - - /** - * 注册绑定Activity 生命周期事件处理 - * @param application - */ - private static void registerActivityLifecycleCallbacks(Application application){ - if (application != null){ - // 先移除旧的监听 - application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); - // 绑定新的监听 - application.registerActivityLifecycleCallbacks(lifecycleCallbacks); - } - } - - /** 保留当前(前台) Activity */ - private static Activity sCurActivity = null; - - /** - * 对Activity的生命周期事件进行集中处理。 - * http://blog.csdn.net/tongcpp/article/details/40344871 - */ - private static Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks() { - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - - } - - @Override - public void onActivityStarted(Activity activity) { - - } - - @Override - public void onActivityResumed(Activity activity) { - // 保存当前Activity - DevUtils.sCurActivity = activity; - } - - @Override - public void onActivityPaused(Activity activity) { - - } - - @Override - public void onActivityStopped(Activity activity) { - - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - - } - - @Override - public void onActivityDestroyed(Activity activity) { - - } - }; - - /** - * 获取当前Activity - * @return - */ - public static Activity getCurActivity(){ - return DevUtils.sCurActivity; - } - - /** - * 判断是否相同的 Activity - * @param activity - * @return - */ - public static boolean isSameActivity(Activity activity){ - if (activity != null && DevUtils.sCurActivity != null){ - try { - return DevUtils.sCurActivity.getClass().getName().equals(activity.getClass().getName()); - } catch (Exception e){ - } - } - return false; - } - - // == 工具类版本 == - - /** - * 获取工具类版本 - * @return - */ - public static String getUtilsVersion(){ - return BuildConfig.VERSION_NAME; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/JCLogUtils.java b/DevLibUtils/src/main/java/dev/utils/JCLogUtils.java deleted file mode 100644 index eb43dfb6d1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/JCLogUtils.java +++ /dev/null @@ -1,289 +0,0 @@ -package dev.utils; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.StringReader; -import java.io.StringWriter; - -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - -/** - * detail: Java Common 日志打印工具类(简化版) - 项目内部使用 - 主要打印非 Android 日志 - * Created by Ttt - */ -public final class JCLogUtils { - - private JCLogUtils(){ - } - - // 普通信息模式 - public static final int INFO = 0; - // DEBUG模式 - public static final int DEBUG = 1; - // ERROR模式 - public static final int ERROR = 2; - - // = - - /** JSON格式内容缩进 */ - private static final int JSON_INDENT = 4; - /** 是否打印日志 上线 = false,开发、debug = true*/ - private static boolean JUDGE_PRINT_LOG = false; - /** 默认DEFAULT_TAG */ - private static final String DEFAULT_DEFAULT_TAG = JCLogUtils.class.getSimpleName(); - - /** - * 判断是否打印日志 - * @return - */ - public static boolean isPrintLog() { - return JUDGE_PRINT_LOG; - } - - /** - * 设置是否打印日志 - * @param judgePrintLog - */ - public static void setPrintLog(boolean judgePrintLog) { - JUDGE_PRINT_LOG = judgePrintLog; - } - - /** - * 判断是否为null - * @param str - * @return - */ - private static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - // = - - /** - * 最终打印日志方法(全部调用此方法) - * @param logType 打印日志类型 - * @param tag 打印Tag - * @param msg 打印消息 - */ - private static void printLog(int logType, String tag, String msg) { - switch (logType) { - case INFO: - LogPrintUtils.iTag(tag, msg); - case ERROR: - LogPrintUtils.eTag(tag, msg); - break; - case DEBUG: - default: - LogPrintUtils.dTag(tag, msg); - break; - } - // 打印信息 - if (isEmpty(tag)){ - System.out.println(msg); - } else { - System.out.println(tag + " : " + msg); - } - } - - /** - * 处理信息 - * @param message 打印信息 - * @param args 占位符替换 - * @return - */ - private static String createMessage(String message, Object... args) { - String result = null; - try { - if(message != null){ - if(args == null){ - // 动态参数为null - result = "params is null"; - } else { - // 格式化字符串 - result = (args.length == 0 ? message : String.format(message, args)); - } - } else { - // 打印内容为null - result = "message is null"; - } - } catch (Exception e) { - // 出现异常 - result = e.toString(); - } - return result; - } - - /** - * 拼接错误信息 - * @param throwable 错误异常 - * @param message 需要打印的消息 - * @param args 动态参数 - * @return - */ - private static String splitErrorMessage(Throwable throwable, String message, Object... args) { - String result = null; - try { - if(throwable != null){ - if(message != null){ - result = createMessage(message, args) + " : " + throwable.toString(); - } else { - result = throwable.toString(); - } - } else { - result = createMessage(message, args); - } - } catch (Exception e) { - result = e.toString(); - } - return result; - } - - // =================== 对外公开方法 ========================= - - // ========= 使用默认TAG ========= - - public static void d(String message, Object... args) { - dTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void e(Throwable throwable) { - eTag(DEFAULT_DEFAULT_TAG, throwable); - } - - public void e(String message, Object... args) { - e(null, message, args); - } - - public static void e(Throwable throwable, String message, Object... args) { - eTag(DEFAULT_DEFAULT_TAG, throwable, message, args); - } - - public static void i(String message, Object... args) { - iTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void json(String json) { - jsonTag(DEFAULT_DEFAULT_TAG, json); - } - - public static void xml(String xml) { - xmlTag(DEFAULT_DEFAULT_TAG, xml); - } - - // -- 日志打印方法 -- - - public static void dTag(String tag, String message, Object... args) { - if (JUDGE_PRINT_LOG){ - printLog(DEBUG, tag, createMessage(message, args)); - } - } - - public static void eTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(ERROR, tag, createMessage(message, args)); - } - } - - public static void eTag(String tag, Throwable throwable) { - if(JUDGE_PRINT_LOG){ - printLog(ERROR, tag, splitErrorMessage(throwable, null)); - } - } - - public static void eTag(String tag, Throwable throwable, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(ERROR, tag, splitErrorMessage(throwable, message, args)); - } - } - - public static void iTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(INFO, tag, createMessage(message, args)); - } - } - - public static void jsonTag(String tag, String json) { - if (JUDGE_PRINT_LOG){ - // 判断传入JSON格式信息是否为null - if (isEmpty(json)) { - printLog(ERROR, tag, "Empty/Null json content"); - return; - } - try { - // 属于对象的JSON格式信息 - if (json.startsWith("{")) { - JSONObject jsonObject = new JSONObject(json); - // 进行缩进 - String message = jsonObject.toString(JSON_INDENT); - // 打印信息 - printLog(DEBUG, tag, message); - } else if (json.startsWith("[")) { - // 属于数据的JSON格式信息 - JSONArray jsonArray = new JSONArray(json); - // 进行缩进 - String message = jsonArray.toString(JSON_INDENT); - // 打印信息 - printLog(DEBUG, tag, message); - } - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - printLog(ERROR, tag, eHint + "\n" + json); - } - } - } - - public static void xmlTag(String tag, String xml) { - if (JUDGE_PRINT_LOG){ - // 判断传入XML格式信息是否为null - if (isEmpty(xml)) { - printLog(ERROR, tag, "Empty/Null xml content"); - return; - } - try { - Source xmlInput = new StreamSource(new StringReader(xml)); - StreamResult xmlOutput = new StreamResult(new StringWriter()); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - transformer.transform(xmlInput, xmlOutput); - // 获取打印消息 - String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); - // 打印信息 - printLog(DEBUG, tag, message); - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - printLog(ERROR, tag, eHint + "\n" + xml); - } - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/LogPrintUtils.java b/DevLibUtils/src/main/java/dev/utils/LogPrintUtils.java deleted file mode 100644 index f5daf7b262..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/LogPrintUtils.java +++ /dev/null @@ -1,310 +0,0 @@ -package dev.utils; - -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.StringReader; -import java.io.StringWriter; - -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - -/** - * detail: 日志打印工具类(简化版) - 项目内部使用 - * Created by Ttt - */ -public final class LogPrintUtils { - - private LogPrintUtils(){ - } - - /** JSON格式内容缩进 */ - private static final int JSON_INDENT = 4; - /** 是否打印日志 上线 = false,开发、debug = true*/ - private static boolean JUDGE_PRINT_LOG = false; - /** 默认DEFAULT_TAG */ - private static final String DEFAULT_DEFAULT_TAG = LogPrintUtils.class.getSimpleName(); - - /** - * 判断是否打印日志 - * @return - */ - public static boolean isPrintLog() { - return JUDGE_PRINT_LOG; - } - - /** - * 设置是否打印日志 - * @param judgePrintLog - */ - public static void setPrintLog(boolean judgePrintLog) { - JUDGE_PRINT_LOG = judgePrintLog; - } - - // = - - /** - * 最终打印日志方法(全部调用此方法) - * @param logType 打印日志类型 - * @param tag 打印Tag - * @param msg 打印消息 - */ - private static void printLog(int logType, String tag, String msg) { - switch (logType) { - case Log.ERROR: - Log.e(tag, msg); - break; - case Log.INFO: - Log.i(tag, msg); - break; - case Log.VERBOSE: - Log.v(tag, msg); - break; - case Log.WARN: - Log.w(tag, msg); - break; - case Log.ASSERT: - Log.wtf(tag, msg); - break; - case Log.DEBUG: - Log.d(tag, msg); - break; - default: - Log.d(tag, msg); - break; - } - } - - /** - * 处理信息 - * @param message 打印信息 - * @param args 占位符替换 - * @return - */ - private static String createMessage(String message, Object... args) { - String result = null; - try { - if(message != null){ - if(args == null){ - // 动态参数为null - result = "params is null"; - } else { - // 格式化字符串 - result = (args.length == 0 ? message : String.format(message, args)); - } - } else { - // 打印内容为null - result = "message is null"; - } - } catch (Exception e) { - // 出现异常 - result = e.toString(); - } - return result; - } - - /** - * 拼接错误信息 - * @param throwable 错误异常 - * @param message 需要打印的消息 - * @param args 动态参数 - * @return - */ - private static String splitErrorMessage(Throwable throwable, String message, Object... args) { - String result = null; - try { - if(throwable != null){ - if(message != null){ - result = createMessage(message, args) + " : " + throwable.toString(); - } else { - result = throwable.toString(); - } - } else { - result = createMessage(message, args); - } - } catch (Exception e) { - result = e.toString(); - } - return result; - } - - // =================== 对外公开方法 ========================= - - // ========= 使用默认TAG ========= - - public static void d(String message, Object... args) { - dTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void e(Throwable throwable) { - eTag(DEFAULT_DEFAULT_TAG, throwable, null); - } - - public void e(String message, Object... args) { - e(null, message, args); - } - - public static void e(Throwable throwable, String message, Object... args) { - eTag(DEFAULT_DEFAULT_TAG, throwable, message, args); - } - - public static void w(String message, Object... args) { - wTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void i(String message, Object... args) { - iTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void v(String message, Object... args) { - vTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void wtf(String message, Object... args) { - wtfTag(DEFAULT_DEFAULT_TAG, message, args); - } - - public static void json(String json) { - jsonTag(DEFAULT_DEFAULT_TAG, json); - } - - public static void xml(String xml) { - xmlTag(DEFAULT_DEFAULT_TAG, xml); - } - - // -- 日志打印方法 -- - - public static void dTag(String tag, String message, Object... args) { - if (JUDGE_PRINT_LOG){ - printLog(Log.DEBUG, tag, createMessage(message, args)); - } - } - - public static void eTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.ERROR, tag, createMessage(message, args)); - } - } - - public static void eTag(String tag, Throwable throwable) { - if(JUDGE_PRINT_LOG){ - printLog(Log.ERROR, tag, splitErrorMessage(throwable, null)); - } - } - - public static void eTag(String tag, Throwable throwable, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.ERROR, tag, splitErrorMessage(throwable, message, args)); - } - } - - public static void wTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.WARN, tag, createMessage(message, args)); - } - } - - public static void iTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.INFO, tag, createMessage(message, args)); - } - } - - public static void vTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.VERBOSE, tag, createMessage(message, args)); - } - } - - public static void wtfTag(String tag, String message, Object... args) { - if(JUDGE_PRINT_LOG){ - printLog(Log.ASSERT, tag, createMessage(message, args)); - } - } - - public static void jsonTag(String tag, String json) { - if (JUDGE_PRINT_LOG){ - // 判断传入JSON格式信息是否为null - if (TextUtils.isEmpty(json)) { - printLog(Log.ERROR, tag, "Empty/Null json content"); - return; - } - try { - // 属于对象的JSON格式信息 - if (json.startsWith("{")) { - JSONObject jsonObject = new JSONObject(json); - // 进行缩进 - String message = jsonObject.toString(JSON_INDENT); - // 打印信息 - printLog(Log.DEBUG, tag, message); - } else if (json.startsWith("[")) { - // 属于数据的JSON格式信息 - JSONArray jsonArray = new JSONArray(json); - // 进行缩进 - String message = jsonArray.toString(JSON_INDENT); - // 打印信息 - printLog(Log.DEBUG, tag, message); - } - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - printLog(Log.ERROR, tag, eHint + "\n" + json); - } - } - } - - public static void xmlTag(String tag, String xml) { - if (JUDGE_PRINT_LOG){ - // 判断传入XML格式信息是否为null - if (TextUtils.isEmpty(xml)) { - printLog(Log.ERROR, tag, "Empty/Null xml content"); - return; - } - try { - Source xmlInput = new StreamSource(new StringReader(xml)); - StreamResult xmlOutput = new StreamResult(new StringWriter()); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - transformer.transform(xmlInput, xmlOutput); - // 获取打印消息 - String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); - // 打印信息 - printLog(Log.DEBUG, tag, message); - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - printLog(Log.ERROR, tag, eHint + "\n" + xml); - } - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/AccessibilityUtils.java b/DevLibUtils/src/main/java/dev/utils/app/AccessibilityUtils.java deleted file mode 100644 index 5f81c2ae9a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/AccessibilityUtils.java +++ /dev/null @@ -1,246 +0,0 @@ -package dev.utils.app; - -import android.accessibilityservice.AccessibilityService; -import android.content.Intent; -import android.provider.Settings; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityNodeInfo; - -import java.util.ArrayList; -import java.util.List; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 无障碍功能工具类 - * Created by Ttt - * https://www.jianshu.com/p/981e7de2c7be - * https://www.jianshu.com/p/65afab3d1e2a - * https://www.jianshu.com/p/f67e950d84f7 - * https://blog.csdn.net/nishitouzhuma/article/details/51584606 - * https://blog.csdn.net/jw_66666/article/details/76571897 - * https://blog.csdn.net/dd864140130/article/details/51794318 - * ==== - * AccessibilityService 在 API < 18 的时候使用 AccessibilityService - * - */ -public final class AccessibilityUtils { - - private AccessibilityUtils() { - } - - // 日志TAG - private static final String TAG = AccessibilityUtils.class.getSimpleName(); - - /** - * 检查是否开启无障碍功能 - * @return - */ - public static boolean checkAccessibility(){ - return checkAccessibility(DevUtils.getContext().getPackageName()); - } - - /** - * 检查是否开启无障碍功能 - * @param pkgName - * @return - */ - public static boolean checkAccessibility(String pkgName) { - // 判断辅助功能是否开启 - if (!AccessibilityUtils.isAccessibilitySettingsOn(pkgName)) { - // 引导至辅助功能设置页面 - DevUtils.getContext().startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - return false; - } - return true; - } - - /** - * 判断是否开启无障碍功能 - * @param pkgName - * @return - */ - public static boolean isAccessibilitySettingsOn(String pkgName) { - int accessibilityEnabled = 0; - try { - accessibilityEnabled = Settings.Secure.getInt(DevUtils.getContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED); - } catch (Settings.SettingNotFoundException e) { - LogPrintUtils.eTag(TAG, e, "isAccessibilitySettingsOn - Settings.Secure.ACCESSIBILITY_ENABLED"); - } - if (accessibilityEnabled == 1) { - try { - String services = Settings.Secure.getString(DevUtils.getContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); - if (services != null) { - return services.toLowerCase().contains(pkgName.toLowerCase()); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isAccessibilitySettingsOn - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES"); - } - } - return false; - } - - // == 快捷方法 == - - /** - * 打印Event 日志 - * @param event - */ - public static void printAccessibilityEvent(AccessibilityEvent event){ - printAccessibilityEvent(event, TAG); - } - - /** - * 打印Event 日志 - * @param event - * @param tag - */ - public static void printAccessibilityEvent(AccessibilityEvent event, String tag){ - if (!LogPrintUtils.isPrintLog()){ - return; - } - LogPrintUtils.dTag(tag,"-------------------------------------------------------------"); - - int eventType = event.getEventType();//事件类型 - LogPrintUtils.dTag(tag, "packageName:" + event.getPackageName() + "");//响应事件的包名,也就是哪个应用才响应了这个事件 - LogPrintUtils.dTag(tag, "source:" + event.getSource() + "");//事件源信息 - LogPrintUtils.dTag(tag, "source class:" + event.getClassName() + "");//事件源的类名,比如android.widget.TextView - LogPrintUtils.dTag(tag, "event type(int):" + eventType + ""); - - switch (eventType) { - case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知栏事件 - LogPrintUtils.dTag(tag, "event type:TYPE_NOTIFICATION_STATE_CHANGED"); - break; - case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED://窗体状态改变 - LogPrintUtils.dTag(tag, "event type:TYPE_WINDOW_STATE_CHANGED"); - break; - case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED://View获取到焦点 - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED"); - break; - case AccessibilityEvent.TYPE_GESTURE_DETECTION_START: - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED"); - break; - case AccessibilityEvent.TYPE_GESTURE_DETECTION_END: - LogPrintUtils.dTag(tag, "event type:TYPE_GESTURE_DETECTION_END"); - break; - case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: - LogPrintUtils.dTag(tag, "event type:TYPE_WINDOW_CONTENT_CHANGED"); - break; - case AccessibilityEvent.TYPE_VIEW_CLICKED: - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_CLICKED"); - break; - case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_TEXT_CHANGED"); - break; - case AccessibilityEvent.TYPE_VIEW_SCROLLED: - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_SCROLLED"); - break; - case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: - LogPrintUtils.dTag(tag, "event type:TYPE_VIEW_TEXT_SELECTION_CHANGED"); - break; - } - - for (CharSequence txt : event.getText()) { - LogPrintUtils.dTag(tag, "text:" + txt);//输出当前事件包含的文本信息 - } - LogPrintUtils.dTag(tag, "-------------------------------------------------------------"); - } - - // === 其他处理 === - - /** - * 查找符合条件的节点 - * @param service - * @param text - * @return - */ - private List findAccessibilityNodeInfosByText(AccessibilityService service, String text) { - // 获取根节点 - AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow(); - // 取得当前激活窗体的根节点 - if (accessibilityNodeInfo == null) - return null; - // 通过文字找到当前的节点 - return accessibilityNodeInfo.findAccessibilityNodeInfosByText(text); - } - - /** - * 查找符合条件的节点 - * @param service - * @param id - * @return - */ - private List findAccessibilityNodeInfosByViewId(AccessibilityService service, String id) { - // 获取根节点 - AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow(); - // 取得当前激活窗体的根节点 - if (accessibilityNodeInfo == null) - return null; - // 通过文字找到当前的节点 - return accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id); - } - - /** - * 查找符合条件的节点 - * @param service - * @param text - * @param claName - * @return - */ - private ArrayList findAccessibilityNodeInfosByText(AccessibilityService service, String text, String claName) { - ArrayList lists = new ArrayList<>(); - // 获取根节点 - AccessibilityNodeInfo accessibilityNodeInfo = service.getRootInActiveWindow(); - // 取得当前激活窗体的根节点 - if (accessibilityNodeInfo == null) - return lists; - // 通过文字找到当前的节点 - List nodes = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text); - for (int i = 0; i < nodes.size(); i++) { - AccessibilityNodeInfo node = nodes.get(i); - // 判断是否符合的类型 - if (node.getClassName().equals(claName) && node.isEnabled()) { - // 保存符合条件 - lists.add(node); - } - } - return lists; - } - -// //获取根节点 -// AccessibilityNodeInfo rootNode = getRootInActiveWindow(); -// //匹配Text获取节点 -// List list1 = rootNode.findAccessibilityNodeInfosByText("match_text"); -// //匹配id获取节点 -// List list2 = rootNode.findAccessibilityNodeInfosByViewId("match_id"); -// //获取子节点 -// AccessibilityNodeInfo infoNode = rootNode.getChild(index); - -// //模拟点击事件 -// target.performAction(AccessibilityNodeInfo.ACTION_CLICK); -// //模拟输入内容 -// ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); -// ClipData clip = ClipData.newPlainText("label", ""); -// clipboard.setPrimaryClip(clip); -// target.performAction(AccessibilityNodeInfo.ACTION_PASTE); - -// //后退键 -// performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); -// //Home键 -// performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME); -// //模拟左滑 -// performGlobalAction(AccessibilityService.GESTURE_SWIPE_LEFT); - - -// disableSelf() 禁用当前服务,也就是在服务可以通过该方法停止运行 -// findFoucs(int falg) 查找拥有特定焦点类型的控件 -// getRootInActiveWindow() 如果配置能够获取窗口内容,则会返回当前活动窗口的根结点 -// getSeviceInfo() 获取当前服务的配置信息 -// onAccessibilityEvent(AccessibilityEvent event) 有关AccessibilityEvent事件的回调函数.系统通过sendAccessibiliyEvent()不断的发送AccessibilityEvent到此处 -// performGlobalAction(int action) 执行全局操作,比如返回,回到主页,打开最近等操作 -// setServiceInfo(AccessibilityServiceInfo info) 设置当前服务的配置信息 -// getSystemService(String name) 获取系统服务 -// onKeyEvent(KeyEvent event) 如果允许服务监听按键操作,该方法是按键事件的回调,需要注意,这个过程发生了系统处理按键事件之前 -// onServiceConnected() 系统成功绑定该服务时被触发,也就是当你在设置中开启相应的服务,系统成功的绑定了该服务时会触发,通常我们可以在这里做一些初始化操作 -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ActivityUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ActivityUtils.java deleted file mode 100644 index 45d88a48bd..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ActivityUtils.java +++ /dev/null @@ -1,178 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.v4.app.ActivityOptionsCompat; -import android.support.v4.util.Pair; -import android.view.View; - -import java.util.List; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Acitivty 工具类 - * Created by Ttt - */ -public final class ActivityUtils { - - private ActivityUtils() { - } - - // 日志TAG - private static final String TAG = ActivityUtils.class.getSimpleName(); - - /** - * 判断是否存在指定的Activity - * @param context 上下文 - * @param packageName 包名 - * @param className activity全路径类名 - * @return - */ - public static boolean isActivityExists(Context context, String packageName, String className) { - boolean result = true; - try { - Intent intent = new Intent(); - intent.setClassName(packageName, className); - if (context.getPackageManager().resolveActivity(intent, 0) == null) { - result = false; - } else if (intent.resolveActivity(context.getPackageManager()) == null) { - result = false; - } else { - List list = context.getPackageManager().queryIntentActivities(intent, 0); - if (list.size() == 0) { - result = false; - } - } - } catch (Exception e){ - result = false; - LogPrintUtils.eTag(TAG, e, "isActivityExists"); - } - return result; - } - - /** - * 回到桌面 -> 同点击Home键效果 - */ - public static void startHomeActivity() { - Intent homeIntent = new Intent(Intent.ACTION_MAIN); - homeIntent.addCategory(Intent.CATEGORY_HOME); - DevUtils.getContext().startActivity(homeIntent); - } - - /** - * 跳转到桌面 - * @return - */ - public static String getLauncherActivity() { - return getLauncherActivity(DevUtils.getContext().getPackageName()); - } - - /** - * 跳转到桌面 - * @param pkg - * @return - */ - public static String getLauncherActivity(@NonNull final String pkg) { - Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PackageManager pm = DevUtils.getContext().getPackageManager(); - List info = pm.queryIntentActivities(intent, 0); - for (ResolveInfo aInfo : info) { - if (aInfo.activityInfo.packageName.equals(pkg)) { - return aInfo.activityInfo.name; - } - } - return null; - } - - /** - * 返回Activity 对应的图标 - * @param clz - * @return - */ - public static Drawable getActivityIcon(final Class clz) { - return getActivityIcon(new ComponentName(DevUtils.getContext(), clz)); - } - - /** - * 返回Activity 对应的图标 - * @param activityName - * @return - */ - public static Drawable getActivityIcon(final ComponentName activityName) { - try { - return DevUtils.getContext().getPackageManager().getActivityIcon(activityName); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getActivityIcon"); - return null; - } - } - - /** - * 返回Activity 对应的Logo - * @param clz - * @return - */ - public static Drawable getActivityLogo(final Class clz) { - return getActivityLogo(new ComponentName(DevUtils.getContext(), clz)); - } - - /** - * 返回Activity 对应的Logo - * @param activityName - * @return - */ - public static Drawable getActivityLogo(final ComponentName activityName) { - try { - return DevUtils.getContext().getPackageManager().getActivityLogo(activityName); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getActivityLogo"); - return null; - } - } - - // == 以下方法使用介绍 == - // https://www.cnblogs.com/tianzhijiexian/p/4087917.html - // ActivityOptionsCompat.makeScaleUpAnimation(source, startX, startY, startWidth, startHeight) - - /** - * 设置跳转动画 - * @param context - * @param enterAnim - * @param exitAnim - * @return - */ - private static Bundle getOptionsBundle(final Context context, final int enterAnim, final int exitAnim) { - return ActivityOptionsCompat.makeCustomAnimation(context, enterAnim, exitAnim).toBundle(); - } - - /** - * 设置跳转动画 - * @param activity - * @param sharedElements - * @return - */ - private static Bundle getOptionsBundle(final Activity activity, final View[] sharedElements) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - int len = sharedElements.length; - @SuppressWarnings("unchecked") - Pair[] pairs = new Pair[len]; - for (int i = 0; i < len; i++) { - pairs[i] = Pair.create(sharedElements[i], sharedElements[i].getTransitionName()); - } - return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, pairs).toBundle(); - } - return ActivityOptionsCompat.makeSceneTransitionAnimation(activity, null, null).toBundle(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/AlarmUtils.java b/DevLibUtils/src/main/java/dev/utils/app/AlarmUtils.java deleted file mode 100644 index 672d675e5a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/AlarmUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.utils.app; - -import android.annotation.TargetApi; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -/** - * detail: AlarmManager (全局定时器/闹钟)指定时长或以周期形式执行某项操作 - * @author MaTianyu - * https://www.cnblogs.com/zyw-205520/p/4040923.html - */ -public final class AlarmUtils { - - private AlarmUtils(){ - } - - /** - * 开启定时器 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void startAlarmIntent(Context context, int triggerAtMillis, PendingIntent pendingIntent) { - AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - manager.set(AlarmManager.RTC_WAKEUP,triggerAtMillis, pendingIntent); - } - - /** - * 关闭定时器 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void stopAlarmIntent(Context context, PendingIntent pendingIntent) { - AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - manager.cancel(pendingIntent); - } - - /** - * 开启轮询服务 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void startAlarmService(Context context, int triggerAtMillis, Class cls, String action) { - Intent intent = new Intent(context, cls); - intent.setAction(action); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - startAlarmIntent(context, triggerAtMillis,pendingIntent); - } - - /** - * 停止启轮询服务 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void stopAlarmService(Context context, Class cls, String action) { - Intent intent = new Intent(context, cls); - intent.setAction(action); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - stopAlarmIntent(context, pendingIntent); - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/AnalysisRecordUtils.java b/DevLibUtils/src/main/java/dev/utils/app/AnalysisRecordUtils.java deleted file mode 100644 index 7488589a10..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/AnalysisRecordUtils.java +++ /dev/null @@ -1,706 +0,0 @@ -package dev.utils.app; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Environment; -import android.support.annotation.IntDef; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.Field; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import dev.utils.LogPrintUtils; - -/** - * detail: 分析记录工具类 - * Created by Ttt - */ -public final class AnalysisRecordUtils { - - private AnalysisRecordUtils() { - } - - // 日志TAG - private static final String TAG = AnalysisRecordUtils.class.getSimpleName(); - - // 上下文 - private static Context sContext; - // 日志文件夹名字(目录名) - private static String logFolderName = "LogRecord"; - // 日志存储路径 - private static String logStoragePath; - // 是否处理保存 - private static boolean isHandler = true; - - /** - * 初始化操作 - * @param context - */ - public static void init(Context context) { - if (context != null) { - sContext = context.getApplicationContext(); - } - // 初始化设备信息 - getDeviceInfo(); - // 初始化 App 信息 - getAppInfo(); - // 如果为null, 才设置 - if (TextUtils.isEmpty(logStoragePath)){ - // 获取根路径 - logStoragePath = FileInfo.getDiskCacheDir(sContext); - } - } - - // === 对外提供方法 === - - /** - * 日志记录 - * @param fileInfo - * @param args - * @return log 直接打印 - */ - public static String record(FileInfo fileInfo, String... args) { - // 如果不处理, 则直接跳过 - if (!isHandler){ - return "record not handler"; - } - if (fileInfo != null) { - if (args != null && args.length != 0) { - return saveLogRecord(fileInfo, args); - } - // 无数据记录 - return "no data record"; - } - // 信息为null - return "info is null"; - } - - /** - * 是否处理日志记录 - * @return - */ - public static boolean isHandler() { - return isHandler; - } - - /** - * 设置是否处理日志记录 - * @param isHandler - */ - public static void setHandler(boolean isHandler) { - AnalysisRecordUtils.isHandler = isHandler; - } - - /** - * 获取文件日志名 - * @return - */ - public static String getLogFolderName() { - return logFolderName; - } - - /** - * 设置日志文件夹名 - * @param logFolderName - */ - public static void setLogFolderName(String logFolderName) { - AnalysisRecordUtils.logFolderName = logFolderName; - } - - /** - * 获取日志存储路径 - * @return - */ - public static String getLogStoragePath() { - return logStoragePath; - } - - /** - * 设置日志存储路径 - * @param logStoragePath - */ - public static void setLogStoragePath(String logStoragePath) { - AnalysisRecordUtils.logStoragePath = logStoragePath; - } - - // ===== 内部方法 ===== - - /** - * 最终保存方法 - * @param fileInfo - * @param args - */ - private static String saveLogRecord(FileInfo fileInfo, String... args) { - // 如果不处理, 则直接跳过 - if (!isHandler){ - return "record not handler"; - } - // 文件信息为null, 则不处理 - if (fileInfo == null){ - return "info is null"; - } - // 如果文件地址为null, 则不处理 - if (TextUtils.isEmpty(fileInfo.getFileName())){ - // 文件名为null - return "fileName is null"; - } - // 获取文件名 - String fName = fileInfo.getFileName(); - // 获取文件提示 - String fHint = fileInfo.getFileFunction(); - try { - // 获取处理的日志 - String logContent = splitLog(args); - // 日志保存路径 - String logPath = fileInfo.getLogPath(); - // 获取日志地址 - String logFile = logPath + File.separator + fName; - // 返回地址 - File file = new File(logFile); - // 判断是否存在 - if (file.exists()) { - // 追加内容 - appendFile(logFile, logContent); - } else { - // ==== 首次则保存设备、App 信息 ==== - StringBuffer sBuffer = new StringBuffer(); - sBuffer.append("【设备信息】"); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - sBuffer.append(getDeviceInfo()); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - - sBuffer.append(NEW_LINE_STR); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("【版本信息】"); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - sBuffer.append(getAppInfo()); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - - sBuffer.append(NEW_LINE_STR); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("【文件信息】"); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - sBuffer.append(fHint); - sBuffer.append(NEW_LINE_STR); - sBuffer.append("==========================="); - sBuffer.append(NEW_LINE_STR); - // 创建文件夹,并且进行处理 - saveFile(sBuffer.toString(), logPath, fName); - // 追加内容 - appendFile(logFile, logContent); - } - // 返回打印日志 - return logContent; - } catch (Exception ignore) { - // 捕获异常 - return "catch error"; - } - } - - /** - * 拼接日志 - * @param args - * @return - */ - private static String splitLog(String... args) { - StringBuffer sBuffer = new StringBuffer(); - // 增加换行 - sBuffer.append(NEW_LINE_STR); - sBuffer.append(NEW_LINE_STR); - // 获取保存时间 - sBuffer.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - // 追加个边距 - sBuffer.append(" => "); - // 循环追加内容 - for (int i = 0, c = args.length; i < c; i++) { - // 追加保存内容 - sBuffer.append(args[i]); - } - return sBuffer.toString(); - } - - // === 设备信息统计 ==== - - // app 信息 - private static String APP_INFO_STR = null; - // 设备信息 - private static String DEVICE_INFO_STR = null; - // 用来存储设备信息 - private static HashMap DEVICE_INFO_MAPS = new HashMap<>(); - // 换行字符串 - private static final String NEW_LINE_STR = System.getProperty("line.separator"); - - /** - * 获取设备信息 - * @return - */ - private static String getDeviceInfo() { - if (DEVICE_INFO_STR != null) { - return DEVICE_INFO_STR; - } - // 获取设备信息 - getDeviceInfo(DEVICE_INFO_MAPS); - // 转换设备信息 - handleDeviceInfo("获取设备信息失败"); - // 返回设备信息 - return DEVICE_INFO_STR; - } - - /** - * 获取设备信息 - * @param dInfoMaps 传入设备信息传出HashMap - */ - private static void getDeviceInfo(HashMap dInfoMaps) { - // 获取设备信息类的所有申明的字段,即包括public、private和proteced, 但是不包括父类的申明字段。 - Field[] fields = Build.class.getDeclaredFields(); - // 遍历字段 - for (Field field : fields) { - try { - // 取消java的权限控制检查 - field.setAccessible(true); - // 获取类型对应字段的数据,并保存 - dInfoMaps.put(field.getName(), field.get(null).toString()); - } catch (Exception e) { - } - } - } - - /** - * 处理设备信息 - * @param eHint 错误提示,如获取设备信息失败 - */ - private static String handleDeviceInfo(String eHint) { - try { - // 如果不为null,则直接返回之前的信息 - if (!TextUtils.isEmpty(DEVICE_INFO_STR)) { - return DEVICE_INFO_STR; - } - // 初始化 StringBuffer,拼接字符串 - StringBuffer sBuffer = new StringBuffer(); - // 获取设备信息 - Iterator> mapIter = DEVICE_INFO_MAPS.entrySet().iterator(); - // 遍历设备信息 - while (mapIter.hasNext()) { - // 获取对应的key-value - Map.Entry rnEntry = mapIter.next(); - String rnKey = rnEntry.getKey(); // key - String rnValue = rnEntry.getValue(); // value - // 保存设备信息 - sBuffer.append(rnKey); - sBuffer.append(" = "); - sBuffer.append(rnValue); - sBuffer.append(NEW_LINE_STR); - } - // 保存设备信息 - DEVICE_INFO_STR = sBuffer.toString(); - // 返回设备信息 - return DEVICE_INFO_STR; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleDeviceInfo"); - } - return eHint; - } - - /** - * 获取app 信息 - * @return - */ - private static String getAppInfo() { - // 如果不为null,则直接返回之前的信息 - if (!TextUtils.isEmpty(APP_INFO_STR)) { - return APP_INFO_STR; - } - try { - StringBuffer sBuffer = new StringBuffer(); - // - - PackageManager pm = sContext.getPackageManager(); - PackageInfo pi = pm.getPackageInfo(sContext.getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - String versionName = pi.versionName == null ? "null" : pi.versionName; - String versionCode = pi.versionCode + ""; - // 保存版本信息 - sBuffer.append("versionName: " + versionName); - sBuffer.append("\nversionCode: " + versionCode); - // 保存其他信息 - sBuffer.append("\npackageName: " + pi.packageName); // 保存包名 - // 赋值版本信息 - APP_INFO_STR = sBuffer.toString(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppInfo"); - } - return APP_INFO_STR; - } - - // == 日志保存时间 == - // DEFAULT - 默认天,在根目录下 - public static final int DEFAULT = 0; - // 小时 - public static final int HH = 1; - // 分钟 - public static final int MM = 2; - // 秒 - public static final int SS = 3; - - //用 @IntDef "包住" 常量; - // @Retention 定义策略 - // 声明构造器 - @IntDef({ DEFAULT, HH, MM, SS}) - @Retention(RetentionPolicy.SOURCE) - public @interface TIME {} - - /** - * detail: 记录文件信息实体类 - * Created by Ttt - */ - public static class FileInfo { - - // 存储路径 - private String storagePath; - - // 文件夹名 - private String folderName; - - // 文件名 如: xxx.txt - private String fileName; - - // 文件记录的功能 - private String fileFunction; - - // 文件记录间隔时间 如: HH - private @TIME int fileIntervalTime = DEFAULT; - - // = 构造函数 = - - private FileInfo(String storagePath, String folderName, String fileName, String fileFunction, @TIME int fileIntervalTime) { - this.storagePath = storagePath; - this.folderName = folderName; - this.fileName = fileName; - this.fileFunction = fileFunction; - this.fileIntervalTime = fileIntervalTime; - } - - // = get/set 方法 = - - public String getStoragePath() { - if (TextUtils.isEmpty(storagePath)){ - return storagePath = getLogStoragePath(); - } - return storagePath; - } - - public String getFileName() { - return fileName; - } - - public String getFileFunction() { - return fileFunction; - } - - public int getFileIntervalTime() { - return fileIntervalTime; - } - - public String getFolderName(){ - if (TextUtils.isEmpty(folderName)){ - return folderName = getLogFolderName(); - } - return folderName; - } - - /** - * 获取记录分析文件信息 - * @param fileName - * @param fileFunction - * @return - */ - public static FileInfo obtain(String fileName, String fileFunction) { - return new FileInfo(null, null, fileName, fileFunction, DEFAULT); - } - - /** - * 获取记录分析文件信息 - * @param folderName - * @param fileName - * @param fileFunction - * @return - */ - public static FileInfo obtain(String folderName, String fileName, String fileFunction) { - return new FileInfo(null, folderName, fileName, fileFunction, DEFAULT); - } - - /** - * 获取记录分析文件信息 - * @param storagePath - * @param folderName - * @param fileName - * @param fileFunction - * @return - */ - public static FileInfo obtain(String storagePath, String folderName, String fileName, String fileFunction) { - return new FileInfo(storagePath, folderName, fileName, fileFunction, DEFAULT); - } - - // == - - /** - * 获取记录分析文件信息 - * @param fileName - * @param fileFunction - * @param fileIntervalTime - * @return - */ - public static FileInfo obtain(String fileName, String fileFunction, @TIME int fileIntervalTime) { - return new FileInfo(null, null, fileName, fileFunction, fileIntervalTime); - } - - /** - * 获取记录分析文件信息 - * @param folderName - * @param fileName - * @param fileFunction - * @param fileIntervalTime - * @return - */ - public static FileInfo obtain(String folderName, String fileName, String fileFunction, @TIME int fileIntervalTime) { - return new FileInfo(null, folderName, fileName, fileFunction, fileIntervalTime); - } - - /** - * 获取记录分析文件信息 - * @param storagePath - * @param folderName - * @param fileName - * @param fileFunction - * @param fileIntervalTime - * @return - */ - public static FileInfo obtain(String storagePath, String folderName, String fileName, String fileFunction, @TIME int fileIntervalTime) { - return new FileInfo(storagePath, folderName, fileName, fileFunction, fileIntervalTime); - } - - // = 内部处理方法 = - - /** - * 获取日志地址 - * @return - */ - public String getLogPath() { - // 返回拼接后的路径 - return getSavePath(getStoragePath(), logFolderName + File.separator + getDateNow("yyyy_MM_dd")) + getIntervalTimeFolder(); - } - - /** - * 获取时间间隔 - 文件夹 - * @return - */ - public String getIntervalTimeFolder() { - // 文件夹 - String folder = File.separator + getFolderName() + File.separator; - // 获取间隔时间 - int iTime = getFileIntervalTime(); - // 进行判断 - switch (iTime) { - case DEFAULT: - return folder; - case HH: - case MM: - case SS: - // 小时格式 - String hh_Foramt = getDateNow("HH"); - // 判断属于小时格式 - if (iTime == HH){ - // /folder/HH/HH_小时/ => /LogSpace/HH/HH_15/ - return folder + "HH/HH_" + hh_Foramt + File.separator; - } else { - // 分钟格式 - String mm_Foramt = getDateNow("mm"); - // 判断是否属于分钟 - if (iTime == MM){ - // /folder/HH/HH_小时/MM_分钟/ => /LogSpace/HH/HH_15/MM/MM_55/ - return folder + "HH/HH_" + hh_Foramt + "/MM/MM_" + mm_Foramt + File.separator; - } else { // 属于秒 - // 秒格式 - String ss_Foramt = getDateNow("ss"); - // /folder/HH/HH_小时/MM_分钟/ => /LogSpace/HH/HH_15/MM/MM_55/SS_12/ - return folder + "HH/HH_" + hh_Foramt + "/MM/MM_" + mm_Foramt + "/SS_" + ss_Foramt + File.separator; - } - } - } - // 放到未知目录下 - return "/Unknown/"; - } - - /** - * 获取当前日期的字符串 - * @param format 日期格式,譬如:HH, mm, ss - * @return 字符串 - */ - private String getDateNow(String format) { - try { - Calendar cld = Calendar.getInstance(); - DateFormat df = new SimpleDateFormat(format); - return df.format(cld.getTime()); - } catch (Exception e) { - } - return null; - } - - // - - - /** - * 获取保存地址 - * @param storagePath 存储路径 - * @param fPath 文件地址 - * @return - */ - private String getSavePath(String storagePath, String fPath) { - // 获取保存地址 - File file = new File(storagePath, fPath); - // 防止不存在目录文件,自动创建 - createFolder(file); - // 返回头像地址 - return file.getAbsolutePath(); - } - - /** - * 获取缓存地址 - * @return - */ - private static String getDiskCacheDir(Context context) { - String cachePath; - // 判断SDCard是否挂载 - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - cachePath = context.getExternalCacheDir().getPath(); - } else { - cachePath = context.getCacheDir().getPath(); - } - // 防止不存在目录文件,自动创建 - createFolder(new File(cachePath)); - // 返回文件存储地址 - return cachePath; - } - - /** - * 判断某个文件夹是否创建,未创建则创建(纯路径 - 无文件名) - * @param file 文件夹路径 (无文件名字.后缀) - */ - private static boolean createFolder(final File file) { - if (file != null) { - try { - // 当这个文件夹不存在的时候则创建文件夹 - if (!file.exists()) { - // 允许创建多级目录 - return file.mkdirs(); - // 这个无法创建多级目录 - // rootFile.mkdir(); - } - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createFolder"); - } - } - return false; - } - } - - // = 内部处理方法 = - - /** - * 追加文件:使用FileWriter - * @param fPath 文件地址 - * @param text 追加内容 - */ - private static void appendFile(String fPath, String text) { - if (fPath == null || text == null) { - return; - } - File file = new File(fPath); - if (!file.exists()) { // 如果文件不存在,则跳过 - return; - } - FileWriter writer = null; - try { - // 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件 - writer = new FileWriter(file, true); - writer.write(text); - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "appendFile"); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - } - } - } - } - - /** - * 保存文件 - * @param txt 保存内容 - * @param fPath 保存路径 - * @param fName 文件名.后缀 - * @return 是否保存成功 - */ - private static boolean saveFile(String txt, String fPath, String fName) { - try { - // 防止文件没创建 - createFile(fPath); - // 保存路径 - File sFile = new File(fPath, fName); - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(sFile); - fos.write(txt.getBytes()); - fos.close(); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveFile"); - } - return false; - } - - /** - * 判断某个文件夹是否创建,未创建则创建(纯路径 - 无文件名) - * @param fPath 文件夹路径 - */ - private static File createFile(String fPath) { - try { - File file = new File(fPath); - // 当这个文件夹不存在的时候则创建文件夹 - if(!file.exists()) { - // 允许创建多级目录 - file.mkdirs(); - // 这个无法创建多级目录 - // rootFile.mkdir(); - } - return file; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createFile"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/AppCommonUtils.java b/DevLibUtils/src/main/java/dev/utils/app/AppCommonUtils.java deleted file mode 100644 index 7b7231c51d..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/AppCommonUtils.java +++ /dev/null @@ -1,219 +0,0 @@ -package dev.utils.app; - -import android.os.Build; - -import java.util.Random; -import java.util.UUID; - -/** - * detail: App通用工具类 - * Created by Ttt - */ -public final class AppCommonUtils { - - private AppCommonUtils(){ - } - - /** - * 获取设备唯一id - * @return - */ - public String getUUID(){ - return PhoneUtils.getUUID(); - } - - /** - * 获取随机数 唯一id - * @return - */ - public String getRandomUUID(){ - // 获取随机数 - String random1 = (900000 + new Random().nextInt(10000)) + ""; - // 获取随机数 - String random2 = (900000 + new Random().nextInt(10000)) + ""; - // 获取当前时间 - String cTime = Long.toString(System.currentTimeMillis()) + random1 + random2; - // 生成唯一随机uuid cTime.hashCode(), random1.hashCode() | random2.hashCode() - UUID randomUUID = new UUID(cTime.hashCode(), ((long) random1.hashCode() << 32) | random2.hashCode()); - // 获取uid - return randomUUID.toString(); - } - - // == 版本判断处理 == - - /** - * 是否在2.2版本及以上 - * @return 是否在2.2版本及以上 - */ - public static boolean isFroyo() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; - } - - /** - * 是否在2.3版本及以上 - * @return 是否在2.3版本及以上 - */ - public static boolean isGingerbread() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; - } - - /** - * 是否在2.3.3版本及以上 - * @return 是否在2.3.3版本及以上 - */ - public static boolean isGingerbreadMR1() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1; - } - - /** - * 是否在3.0版本及以上 - * @return 是否在3.0版本及以上 - */ - public static boolean isHoneycomb() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; - } - - /** - * 是否在3.1版本及以上 - * @return 是否在3.1版本及以上 - */ - public static boolean isHoneycombMR1() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; - } - - /** - * 是否在4.0版本及以上 - * @return 是否在4.0版本及以上 - */ - public static boolean isIceCreamSandwich() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; - } - - /** - * 是否在4.0.3版本及以上 - * @return 是否在4.0.3版本及以上 - */ - public static boolean isIceCreamSandwichMR1() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; - } - - /** - * 是否在4.1版本及以上 - * @return 是否在4.1版本及以上 - */ - public static boolean isJellyBean() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; - } - - /** - * 是否在4.4.2版本及以上 - * @return 是否在4.4.2版本及以上 - */ - public static boolean isKitkat() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - } - - /** - * 是否在5.0.1版本及以上 - * @return 是否在5.0.1版本及以上 - */ - public static boolean isLollipop() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; - } - - /** - * 是否在6.0版本及以上 - * @return 是否在6.0版本及以上 - */ - public static boolean isM(){ - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; - } - - /** - * 是否在7.0版本及以上 - * @return 是否在7.0版本及以上 - */ - public static boolean isN(){ - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; - } - - /** - * 是否在7.1.1版本及以上 - * @return 是否在7.1.1版本及以上 - */ - public static boolean isN_MR1(){ - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1; - } - - /** - * 是否在8.0版本及以上 - * @return 是否在8.0版本及以上 - */ - public static boolean isO(){ - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - } - - /** - * 转换SDK版本 - * @param sdkVersion - * @return - */ - public static String convertSDKVersion(int sdkVersion){ - // https://www.cnblogs.com/maogefff/p/7819076.html - switch (sdkVersion){ - case 1: - return "Android 1.0"; - case 2: - return "Android 1.1"; - case 3: - return "Android 1.5"; - case 4: - return "Android 1.6"; - case 5: - return "Android 2.0"; - case 6: - return "Android 2.0.1"; - case 7: - return "Android 2.1.x"; - case 8: - return "Android 2.2.x"; - case 9: - return "Android 2.3.0-2"; - case 10: - return "Android 2.3.3-4"; - case 11: - return "Android 3.0.x"; - case 12: - return "Android 3.1.x"; - case 13: - return "Android 3.2"; - case 14: - return "Android 4.0.0-2"; - case 15: - return "Android 4.0.3-4"; - case 16: - return "Android 4.1.x"; - case 17: - return "Android 4.2.x"; - case 18: - return "Android 4.3"; - case 19: - return "Android 4.4"; - case 20: - return "Android 4.4W"; - case 21: - return "Android 5.0"; - case 22: - return "Android 5.1"; - case 23: - return "Android 6.0"; - case 24: - return "Android 7.0"; - case 25: - return "Android 7.1.1"; - case 26: - return "Android 8.0"; - } - return "unknown"; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/AppUtils.java b/DevLibUtils/src/main/java/dev/utils/app/AppUtils.java deleted file mode 100644 index ae1b009e28..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/AppUtils.java +++ /dev/null @@ -1,970 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.ActivityManager; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.content.pm.Signature; -import android.content.res.AssetManager; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.annotation.LayoutRes; -import android.support.annotation.NonNull; -import android.support.annotation.StringRes; -import android.support.v4.content.ContextCompat; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.View; -import android.view.WindowManager; - -import java.io.File; -import java.security.MessageDigest; -import java.util.List; -import java.util.Locale; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: App(Android 工具类) - * Created by Ttt - */ -public final class AppUtils { - - private AppUtils() { - } - - // 日志TAG - private static final String TAG = AppUtils.class.getSimpleName(); - - /** - * 通过上下文获取 WindowManager - * @return - */ - public static WindowManager getWindowManager() { - try { - return (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getWindowManager"); - } - return null; - } - -// /** -// * 通过上下文获取 DisplayMetrics (获取关于显示的通用信息,如显示大小,分辨率和字体) -// * @return -// */ -// public static DisplayMetrics getDisplayMetrics() { -// try { -// WindowManager wManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); -// if (wManager != null) { -// DisplayMetrics dMetrics = new DisplayMetrics(); -// wManager.getDefaultDisplay().getMetrics(dMetrics); -// return dMetrics; -// } -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getDisplayMetrics"); -// } -// return null; -// } - - /** - * 获取 Manifest Meta Data - * @param metaKey - * @return - */ - public static String getMetaData(String metaKey) { - try { - ApplicationInfo appInfo = DevUtils.getContext().getPackageManager().getApplicationInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_META_DATA); - String data = appInfo.metaData.getString(metaKey); - return data; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMetaData"); - } - return null; - } - - // == 快捷获取方法 == - - /** - * 获取View - * @param resource - * @return - */ - public static View getView(@LayoutRes int resource){ - return getView(DevUtils.getContext(), resource); - } - - /** - * 获取View - * @param context - * @param resource - * @return - */ - public static View getView(Context context, @LayoutRes int resource){ - try { - return ((LayoutInflater) DevUtils.getContext(context).getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(resource, null); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getView"); - } - return null; - } - - public static Resources getResources() { - try { - return DevUtils.getContext().getResources(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getResources"); - } - return null; - } - - public static String getString(@StringRes int id) { - try { - return DevUtils.getContext().getResources().getString(id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getString"); - } - return null; - } - - public static Resources.Theme getTheme() { - try { - return DevUtils.getContext().getTheme(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getTheme"); - } - return null; - } - - public static AssetManager getAssets() { - try { - return DevUtils.getContext().getAssets(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getAssets"); - } - return null; - } - - public static Drawable getDrawable(@DrawableRes int id) { - try { - return ContextCompat.getDrawable(DevUtils.getContext(), id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getDrawable"); - } - return null; - } - - public static int getColor( @ColorRes int id) { - try { - return ContextCompat.getColor(DevUtils.getContext(), id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getColor"); - } - return -1; - } - - public static ColorStateList getColorStateList(int id) { - try { - return ContextCompat.getColorStateList(DevUtils.getContext(), id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getColorStateList"); - } - return null; - } - - public static T getSystemService(String name){ - try { - return (T) DevUtils.getContext().getSystemService(name); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getSystemService"); - } - return null; - } - - public static PackageManager getPackageManager(){ - try { - return DevUtils.getContext().getPackageManager(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getPackageManager"); - } - return null; - } - - public static Configuration getConfiguration() { - try { - return DevUtils.getContext().getResources().getConfiguration(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getConfiguration"); - } - return null; - } - - public static DisplayMetrics getDisplayMetrics() { - try { - return DevUtils.getContext().getResources().getDisplayMetrics(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getDisplayMetrics"); - } - return null; - } - - public static ContentResolver getContentResolver() { - try { - return DevUtils.getContext().getContentResolver(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getContentResolver"); - } - return null; - } - - /** - * 获取app的图标 - * @return - */ - public static Drawable getAppIcon() { - return getAppIcon(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app的图标 - * @param packageName - * @return - */ - public static Drawable getAppIcon(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? null : pi.applicationInfo.loadIcon(pm); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppIcon"); - return null; - } - } - - /** - * 获取app 包名 - * @return - */ - public static String getAppPackageName() { - return DevUtils.getContext().getPackageName(); - } - - /** - * 获取app 名 - * @return - */ - public static String getAppName() { - return getAppName(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app 名 - * @param packageName - * @return - */ - public static String getAppName(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? null : pi.applicationInfo.loadLabel(pm).toString(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppName"); - return null; - } - } - - /** - * 获取app版本名 - 对外显示 - * @return - */ - public static String getAppVersionName() { - return getAppVersionName(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app版本名 - 对外显示 - * @param packageName The name of the package. - * @return - */ - public static String getAppVersionName(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageInfo pi = DevUtils.getContext().getPackageManager().getPackageInfo(packageName, 0); - return pi == null ? null : pi.versionName; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionName"); - return null; - } - } - - /** - * 获取app版本号 - 内部判断 - * @return - */ - public static int getAppVersionCode() { - return getAppVersionCode(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app版本号 - 内部判断 - * @param packageName The name of the package. - * @return - */ - public static int getAppVersionCode(final String packageName) { - if (isSpace(packageName)) return -1; - try { - PackageInfo pi = DevUtils.getContext().getPackageManager().getPackageInfo(packageName, 0); - return pi == null ? -1 : pi.versionCode; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionCode"); - return -1; - } - } - - // = - - /** - * 对内设置指定语言 (app 多语言,单独改变app语言) - * @param locale - */ - public static void setLanguage(Locale locale) { - try { - // 获得res资源对象 - Resources resources = DevUtils.getContext().getResources(); - // 获得设置对象 - Configuration config = resources.getConfiguration(); - // 获得屏幕参数:主要是分辨率,像素等。 - DisplayMetrics dm = resources.getDisplayMetrics(); - // 语言 - config.locale = locale; - // 更新语言 - resources.updateConfiguration(config, dm); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "setLanguage"); - } - } - - // = - - /** - * 安装 App(支持 8.0)的意图 - * @param filePath The path of file. - * @param authority 7.0 及以上安装需要传入清单文件中的}的 authorities 属性 - * @return 是否可以跳转 - */ - public static boolean installApp(final String filePath, final String authority) { - return installApp(getFileByPath(filePath), authority); - } - - /** - * 安装 App(支持 8.0)的意图 - * @param file The file. - * @param authority 7.0 及以上安装需要传入清单文件中的}的 authorities 属性 - * @return 是否可以跳转 - */ - public static boolean installApp(final File file, final String authority) { - if (!isFileExists(file)) return false; - try { - DevUtils.getContext().startActivity(IntentUtils.getInstallAppIntent(file, authority, true)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "installApp"); - return false; - } - return true; - } - - /** - * 安装 App(支持 8.0)的意图 - 回传 - * @param activity - * @param filePath - * @param authority - * @param requestCode - * @return - */ - public static boolean installApp(final Activity activity, final String filePath, final String authority, final int requestCode) { - return installApp(activity, getFileByPath(filePath), authority, requestCode); - } - - /** - * 安装 App(支持 8.0)的意图 - 回传 - * @param activity - * @param file - * @param authority - * @param requestCode - * @return - */ - public static boolean installApp(final Activity activity, final File file, final String authority, final int requestCode) { - if (!isFileExists(file)) return false; - try { - activity.startActivityForResult(IntentUtils.getInstallAppIntent(file, authority), requestCode); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "installApp"); - return false; - } - return true; - } - - /** - * 静默安装app - * @param filePath - * @return - */ - public static boolean installAppSilent(final String filePath) { - return installAppSilent(getFileByPath(filePath)); - } - - /** - * 静默安装app - * - * @param file The file. - * @return true : success, false : fail - */ - public static boolean installAppSilent(final File file) { - if (!isFileExists(file)) return false; - boolean isRoot = isDeviceRooted(); - String filePath = file.getAbsolutePath(); - String command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib pm install " + filePath; - ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isRoot); - if (commandResult.successMsg != null && commandResult.successMsg.toLowerCase().contains("success")) { - return true; - } else { - command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib64 pm install " + filePath; - commandResult = ShellUtils.execCmd(command, isRoot, true); - return commandResult.successMsg != null && commandResult.successMsg.toLowerCase().contains("success"); - } - } - - /** - * 卸载 App - * @param packageName - * @return 卸载 App 的意图 - * @param - */ - public static boolean uninstallApp(final String packageName) { - if (isSpace(packageName)) return false; - try { - DevUtils.getContext().startActivity(IntentUtils.getUninstallAppIntent(packageName, true)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "uninstallApp"); - return false; - } - return true; - } - - /** - * 卸载 App - * @param activity - * @param packageName - * @param requestCode - */ - public static boolean uninstallApp(final Activity activity, final String packageName, final int requestCode) { - if (isSpace(packageName)) return false; - try { - activity.startActivityForResult(IntentUtils.getUninstallAppIntent(packageName), requestCode); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "uninstallApp"); - return false; - } - return true; - } - - /** - * 静默卸载 App - * @param packageName - * @return - */ - public static boolean uninstallAppSilent(final String packageName) { - return uninstallAppSilent(packageName, false); - } - - /** - * 静默卸载 app - * @param packageName - * @param isKeepData - * @return - */ - public static boolean uninstallAppSilent(final String packageName, final boolean isKeepData) { - if (isSpace(packageName)) return false; - boolean isRoot = isDeviceRooted(); - String command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib pm uninstall " + (isKeepData ? "-k " : "") + packageName; - ShellUtils.CommandResult commandResult = ShellUtils.execCmd(command, isRoot, true); - if (commandResult.successMsg != null && commandResult.successMsg.toLowerCase().contains("success")) { - return true; - } else { - command = "LD_LIBRARY_PATH=/vendor/lib:/system/lib64 pm uninstall " + (isKeepData ? "-k " : "") + packageName; - commandResult = ShellUtils.execCmd(command, isRoot, true); - return commandResult.successMsg != null && commandResult.successMsg.toLowerCase().contains("success"); - } - } - - /** - * 判断是否安装了应用 - * @param action The Intent action, such as ACTION_VIEW. - * @param category The desired category. - * @return true : yes, false : no - */ - public static boolean isAppInstalled(@NonNull final String action, @NonNull final String category) { - try { - Intent intent = new Intent(action); - intent.addCategory(category); - PackageManager pm = DevUtils.getContext().getPackageManager(); - ResolveInfo info = pm.resolveActivity(intent, 0); - return info != null; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isAppInstalled"); - return false; - } - } - - /** - * 判断是否安装了应用 - * @param packageName - * @return true : yes, false : no - */ - public static boolean isAppInstalled(@NonNull final String packageName) { - return !isSpace(packageName) && IntentUtils.getLaunchAppIntent(packageName) != null; - } - - /** - * 判断是否安装指定包名的APP - * @param context 上下文 - * @param packageName 包路径 - * @return - */ - @SuppressWarnings("unused") - public static boolean isInstalledApp(Context context, String packageName) { - if (packageName == null || "".equals(packageName)) { - return false; - } - try { - ApplicationInfo info = context.getPackageManager().getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isInstalledApp"); - return false; - } - } - - /** - * 判断是否存在root 权限 - * @return true : yes, false : no - */ - public static boolean isAppRoot() { - ShellUtils.CommandResult result = ShellUtils.execCmd("echo root", true); - if (result.result == 0) return true; // result.errorMsg => 失败错误消息 - return false; - } - - /** - * 判断是否app 是否debug模式 - * @return true : yes, false : no - */ - public static boolean isAppDebug() { - return isAppDebug(DevUtils.getContext().getPackageName()); - } - - /** - * 判断是否app 是否debug模式 - * @param packageName - * @return true : yes, false : no - */ - public static boolean isAppDebug(final String packageName) { - if (isSpace(packageName)) return false; - try { - ApplicationInfo ai = DevUtils.getContext().getPackageManager().getApplicationInfo(packageName, 0); - return ai != null && (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isAppDebug"); - return false; - } - } - - /** - * 判断app 是否系统app - * @return true : yes, false : no - */ - public static boolean isAppSystem() { - return isAppSystem(DevUtils.getContext().getPackageName()); - } - - /** - * 判断app 是否系统app - * @param packageName - * @return true : yes, false : no - */ - public static boolean isAppSystem(final String packageName) { - if (isSpace(packageName)) return false; - try { - ApplicationInfo ai = DevUtils.getContext().getPackageManager().getApplicationInfo(packageName, 0); - return ai != null && (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isAppSystem"); - return false; - } - } - - /** - * 判断app 是否在前台 - * @return true : yes, false : no - */ - public static boolean isAppForeground() { - try { - ActivityManager manager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - List info = manager.getRunningAppProcesses(); - if (info == null || info.size() == 0) return false; - for (ActivityManager.RunningAppProcessInfo aInfo : info) { - if (aInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { - return aInfo.processName.equals(DevUtils.getContext().getPackageName()); - } - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isAppForeground"); - } - return false; - } - - /** - * 判断app 是否在前台 - * => 属于系统权限 - * @param packageName - * @return - */ - public static boolean isAppForeground(@NonNull final String packageName) { - return !isSpace(packageName) && packageName.equals(ProcessUtils.getForegroundProcessName()); - } - - /** - * 打开app - * @param packageName - */ - public static boolean launchApp(final String packageName) { - if (isSpace(packageName)) return false; - try { - DevUtils.getContext().startActivity(IntentUtils.getLaunchAppIntent(packageName, true)); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "launchApp"); - } - return false; - } - - /** - * 打开app, 并且回传 - * @param activity The activity. - * @param packageName - * @param requestCode - */ - public static boolean launchApp(final Activity activity, final String packageName, final int requestCode) { - if (isSpace(packageName)) return false; - try { - activity.startActivityForResult(IntentUtils.getLaunchAppIntent(packageName), requestCode); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "launchApp"); - } - return false; - } - - /** 跳转到 专门的APP 设置详情页面 */ - public static boolean launchAppDetailsSettings() { - return launchAppDetailsSettings(DevUtils.getContext().getPackageName()); - } - - /** - * 跳转到 专门的APP 设置详情页面 - * @param packageName - */ - public static boolean launchAppDetailsSettings(final String packageName) { - if (isSpace(packageName)) return false; - try { - DevUtils.getContext().startActivity(IntentUtils.getLaunchAppDetailsSettingsIntent(packageName, true)); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "launchAppDetailsSettings"); - } - return false; - } - - /** - * 跳转到 专门的APP 应用商城详情页面 - * @param marketPkg - * @return - */ - public static boolean launchAppDetails(final String marketPkg) { - return launchAppDetails(DevUtils.getContext().getPackageName(), marketPkg); - } - - /** - * 跳转到 专门的APP 应用商城详情页面 - * @param marketPkg - * @param marketPkg - * @return - */ - public static boolean launchAppDetails(final String packageName, final String marketPkg) { - if (isSpace(packageName)) return false; - try { - DevUtils.getContext().startActivity(IntentUtils.getlaunchAppDetailIntent(packageName, marketPkg, true)); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "launchAppDetails"); - } - return false; - } - - /** - * 获取app 路径 /data/data/包名/.apk - * @return - */ - public static String getAppPath() { - return getAppPath(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app 路径 /data/data/包名/.apk - * @param packageName - * @return - */ - public static String getAppPath(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? null : pi.applicationInfo.sourceDir; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppPath"); - return null; - } - } - - // == - - /** - * 获取app 签名 - * @return - */ - public static Signature[] getAppSignature() { - return getAppSignature(DevUtils.getContext().getPackageName()); - } - - /** - * 获取app 签名 - * @param packageName - * @return - */ - public static Signature[] getAppSignature(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - @SuppressLint("PackageManagerGetSignatures") - PackageInfo pi = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); - return pi == null ? null : pi.signatures; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppSignature"); - return null; - } - } - - /** - * 获取 app sha1值 - * @return - */ - public static String getAppSignatureSHA1() { - return getAppSignatureSHA1(DevUtils.getContext().getPackageName()); - } - - /** - * 获取 app sha1值 - * @param packageName - * @return - */ - public static String getAppSignatureSHA1(@NonNull final String packageName) { - Signature[] signature = getAppSignature(packageName); - if (signature == null || signature.length <= 0) return null; - return encryptSHA1ToString(signature[0].toByteArray()).replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0"); - } - - // == - - /** - * 检查是否存在某个文件 - * @param file 文件路径 - * @return 是否存在文件 - */ - private static boolean isFileExists(final File file){ - return file != null && file.exists(); - } - - /** - * 获取文件 - * @param filePath - * @return - */ - private static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } - - /** - * 判断设备是否 root - * @return - */ - private static boolean isDeviceRooted() { - String su = "su"; - String[] locations = { "/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/", - "/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/" }; - for (String location : locations) { - if (new File(location + su).exists()) { - return true; - } - } - return false; - } - - // 十六进制大写转换 - private static final char HEX_DIGITS[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - /** - * SHA1加密 - * @param data - * @return - */ - private static String encryptSHA1ToString(final byte[] data) { - return bytes2HexString(encryptSHA1(data)); - } - - /** - * SHA1加密 - * @param data - * @return - */ - private static byte[] encryptSHA1(final byte[] data) { - if (data == null || data.length <= 0) return null; - try { - MessageDigest md = MessageDigest.getInstance("SHA1"); - md.update(data); - return md.digest(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "encryptSHA1"); - return null; - } - } - - /** - * 数据加密转换16进制 - * @param bytes - * @return - */ - private static String bytes2HexString(final byte[] bytes) { - if (bytes == null) return null; - int len = bytes.length; - if (len <= 0) return null; - char[] ret = new char[len << 1]; - for (int i = 0, j = 0; i < len; i++) { - ret[j++] = HEX_DIGITS[bytes[i] >>> 4 & 0x0f]; - ret[j++] = HEX_DIGITS[bytes[i] & 0x0f]; - } - return new String(ret); - } - - // == 其他功能 == - - /** - * 启动本地应用打开PDF - * @param context 上下文 - * @param filePath 文件路径 - */ - public static boolean openPDFFile(Context context, String filePath) { - try { - File file = new File(filePath); - if (file.exists()) { - Uri path = Uri.fromFile(file); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(path, "application/pdf"); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - context.startActivity(intent); - return true; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "openPDFFile"); - } - return false; - } - - /** - * 启动本地应用打开PDF - * @param context 上下文 - * @param filePath 文件路径 - */ - public static boolean openWordFile(Context context, String filePath) { - try { - File file = new File(filePath); - if (file.exists()) { - Uri path = Uri.fromFile(file); - Intent intent = new Intent("android.intent.action.VIEW"); - intent.addCategory("android.intent.category.DEFAULT"); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setDataAndType(path, "application/msword"); - context.startActivity(intent); - return true; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "openWordFile"); - } - return false; - } - - /** - * 调用WPS打开office文档 - * @param context 上下文 - * @param filePath 文件路径 - */ - public static boolean openOfficeByWPS(Context context, String filePath) { - try { - // 检查是否安装WPS - String wpsPackageEng = "cn.wps.moffice_eng";// 普通版与英文版一样 - // String wpsActivity = "cn.wps.moffice.documentmanager.PreStartActivity"; - String wpsActivity2 = "cn.wps.moffice.documentmanager.PreStartActivity2";// 默认第三方程序启动 - - Intent intent = new Intent(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.setClassName(wpsPackageEng, wpsActivity2); - - Uri uri = Uri.fromFile(new File(filePath)); - intent.setData(uri); - context.startActivity(intent); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "openOfficeByWPS"); - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/BarUtils.java b/DevLibUtils/src/main/java/dev/utils/app/BarUtils.java deleted file mode 100644 index 53b50df982..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/BarUtils.java +++ /dev/null @@ -1,623 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Color; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.support.v4.widget.DrawerLayout; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewGroup.MarginLayoutParams; -import android.view.Window; -import android.view.WindowManager; -import android.widget.LinearLayout; - -import java.lang.reflect.Method; - -import dev.DevUtils; - -/** - * detail: 状态栏相关工具类 - * Created by Ttt - */ -public final class BarUtils { - - private BarUtils() { - } - - private static final int DEFAULT_ALPHA = 112; - private static final String TAG_COLOR = "TAG_COLOR"; - private static final String TAG_ALPHA = "TAG_ALPHA"; - private static final int TAG_OFFSET = -123; - - /** - * 获取状态栏高度 - * @return the status bar's height - */ - public static int getStatusBarHeight() { - Resources resources = DevUtils.getContext().getResources(); - int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android"); - return resources.getDimensionPixelSize(resourceId); - } - - /** - * 设置状态栏是否显示 - * @param activity The activity. - * @param isVisible True to set status bar visible, false otherwise. - */ - public static void setStatusBarVisibility(@NonNull final Activity activity, final boolean isVisible) { - setStatusBarVisibility(activity.getWindow(), isVisible); - } - - /** - * 设置状态栏是否显示 - * @param window The window. - * @param isVisible True to set status bar visible, false otherwise. - */ - public static void setStatusBarVisibility(@NonNull final Window window, final boolean isVisible) { - if (isVisible) { - window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } else { - window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - } - } - - /** - * 判断状态栏是否显示 - * @param activity The activity. - * @return true : yes, false : no - */ - public static boolean isStatusBarVisible(@NonNull final Activity activity) { - int flags = activity.getWindow().getAttributes().flags; - return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0; - } - - /** - * 设置状态是否高亮模式 - * @param activity The activity. - * @param isLightMode True to set status bar light mode, false otherwise. - */ - public static void setStatusBarLightMode(@NonNull final Activity activity, final boolean isLightMode) { - setStatusBarLightMode(activity.getWindow(), isLightMode); - } - - /** - * 设置状态是否高亮模式 - * @param window The window. - * @param isLightMode True to set status bar light mode, false otherwise. - */ - public static void setStatusBarLightMode(@NonNull final Window window, final boolean isLightMode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - View decorView = window.getDecorView(); - if (decorView != null) { - int vis = decorView.getSystemUiVisibility(); - if (isLightMode) { - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } - decorView.setSystemUiVisibility(vis); - } - } - } - - /** - * 添加状态栏同等高度到View的顶部 - * @param view The view. - */ - public static void addMarginTopEqualStatusBarHeight(@NonNull View view) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - Object haveSetOffset = view.getTag(TAG_OFFSET); - if (haveSetOffset != null && (Boolean) haveSetOffset) return; - MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams(); - layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin + getStatusBarHeight(), layoutParams.rightMargin, layoutParams.bottomMargin); - view.setTag(TAG_OFFSET, true); - } - - /** - * 减去状态栏同等高度 - * @param view The view. - */ - public static void subtractMarginTopEqualStatusBarHeight(@NonNull View view) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - Object haveSetOffset = view.getTag(TAG_OFFSET); - if (haveSetOffset == null || !(Boolean) haveSetOffset) return; - MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams(); - layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin - getStatusBarHeight(), layoutParams.rightMargin, layoutParams.bottomMargin); - view.setTag(TAG_OFFSET, false); - } - - /** - * 设置状态栏颜色 - * @param activity The activity. - * @param color The status bar's color. - */ - public static void setStatusBarColor(@NonNull final Activity activity, @ColorInt final int color) { - setStatusBarColor(activity, color, DEFAULT_ALPHA, false); - } - - /** - * 设置状态栏颜色 - * @param activity The activity. - * @param color The status bar's color. - * @param alpha The status bar's alpha which isn't the same as alpha in the color. - */ - public static void setStatusBarColor(@NonNull final Activity activity, @ColorInt final int color, @IntRange(from = 0, to = 255) final int alpha) { - setStatusBarColor(activity, color, alpha, false); - } - - /** - * 设置状态栏颜色 - * @param activity The activity. - * @param color The status bar's color. - * @param alpha The status bar's alpha which isn't the same as alpha in the color. - * @param isDecor True to add fake status bar in DecorView, - * false to add fake status bar in ContentView. - */ - public static void setStatusBarColor(@NonNull final Activity activity, @ColorInt final int color, @IntRange(from = 0, to = 255) final int alpha, final boolean isDecor) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - hideAlphaView(activity); - transparentStatusBar(activity); - addStatusBarColor(activity, color, alpha, isDecor); - } - - /** - * 设置状态栏颜色 - * @param fakeStatusBar The fake status bar view. - * @param color The status bar's color. - */ - public static void setStatusBarColor(@NonNull final View fakeStatusBar, @ColorInt final int color) { - setStatusBarColor(fakeStatusBar, color, DEFAULT_ALPHA); - } - - /** - * 设置状态栏颜色 - * @param fakeStatusBar The fake status bar view. - * @param color The status bar's color. - * @param alpha The status bar's alpha which isn't the same as alpha in the color. - */ - public static void setStatusBarColor(@NonNull final View fakeStatusBar, @ColorInt final int color, @IntRange(from = 0, to = 255) final int alpha) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - fakeStatusBar.setVisibility(View.VISIBLE); - transparentStatusBar((Activity) fakeStatusBar.getContext()); - ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams(); - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.height = BarUtils.getStatusBarHeight(); - fakeStatusBar.setBackgroundColor(getStatusBarColor(color, alpha)); - } - - /** - * 设置状态栏透明度 - * @param activity The activity. - */ - public static void setStatusBarAlpha(@NonNull final Activity activity) { - setStatusBarAlpha(activity, DEFAULT_ALPHA, false); - } - - /** - * 设置状态栏透明度 - * @param activity The activity. - * @param alpha The status bar's alpha. - */ - public static void setStatusBarAlpha(@NonNull final Activity activity, @IntRange(from = 0, to = 255) final int alpha) { - setStatusBarAlpha(activity, alpha, false); - } - - /** - * 设置状态栏透明度 - * @param activity The activity. - * @param alpha The status bar's alpha. - * @param isDecor True to add fake status bar in DecorView, - * false to add fake status bar in ContentView. - */ - public static void setStatusBarAlpha(@NonNull final Activity activity, @IntRange(from = 0, to = 255) final int alpha, final boolean isDecor) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - hideColorView(activity); - transparentStatusBar(activity); - addStatusBarAlpha(activity, alpha, isDecor); - } - - /** - * 设置状态栏透明度 - * @param fakeStatusBar The fake status bar view. - */ - public static void setStatusBarAlpha(@NonNull final View fakeStatusBar) { - setStatusBarAlpha(fakeStatusBar, DEFAULT_ALPHA); - } - - /** - * 设置状态栏透明度 - * @param fakeStatusBar The fake status bar view. - * @param alpha The status bar's alpha. - */ - public static void setStatusBarAlpha(@NonNull final View fakeStatusBar, @IntRange(from = 0, to = 255) final int alpha) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - fakeStatusBar.setVisibility(View.VISIBLE); - transparentStatusBar((Activity) fakeStatusBar.getContext()); - ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams(); - layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; - layoutParams.height = BarUtils.getStatusBarHeight(); - fakeStatusBar.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); - } - - /** - * 设置状态栏的颜色 - * DrawLayout must add android:fitsSystemWindows="true" - * @param activity The activity. - * @param drawer The DrawLayout. - * @param fakeStatusBar The fake status bar view. - * @param color The status bar's color. - * @param isTop True to set DrawerLayout at the top layer, false otherwise. - */ - public static void setStatusBarColor4Drawer(@NonNull final Activity activity, @NonNull final DrawerLayout drawer, @NonNull final View fakeStatusBar, @ColorInt final int color, final boolean isTop) { - setStatusBarColor4Drawer(activity, drawer, fakeStatusBar, color, DEFAULT_ALPHA, isTop); - } - - /** - * 设置状态栏的颜色 - * DrawLayout must add android:fitsSystemWindows="true" - * @param activity The activity. - * @param drawer The DrawLayout. - * @param fakeStatusBar The fake status bar view. - * @param color The status bar's color. - * @param alpha The status bar's alpha which isn't the same as alpha in the color. - * @param isTop True to set DrawerLayout at the top layer, false otherwise. - */ - public static void setStatusBarColor4Drawer(@NonNull final Activity activity, @NonNull final DrawerLayout drawer, @NonNull final View fakeStatusBar, @ColorInt final int color, - @IntRange(from = 0, to = 255) final int alpha, final boolean isTop) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - drawer.setFitsSystemWindows(false); - transparentStatusBar(activity); - setStatusBarColor(fakeStatusBar, color, isTop ? alpha : 0); - for (int i = 0, len = drawer.getChildCount(); i < len; i++) { - drawer.getChildAt(i).setFitsSystemWindows(false); - } - if (isTop) { - hideAlphaView(activity); - } else { - addStatusBarAlpha(activity, alpha, false); - } - } - - /** - * 设置状态栏透明度 - * DrawLayout must add android:fitsSystemWindows="true" - * @param activity The activity. - * @param drawer drawerLayout - * @param fakeStatusBar The fake status bar view. - * @param isTop True to set DrawerLayout at the top layer, false otherwise. - */ - public static void setStatusBarAlpha4Drawer(@NonNull final Activity activity, @NonNull final DrawerLayout drawer, @NonNull final View fakeStatusBar, final boolean isTop) { - setStatusBarAlpha4Drawer(activity, drawer, fakeStatusBar, DEFAULT_ALPHA, isTop); - } - - /** - * 设置状态栏透明度 - * DrawLayout must add android:fitsSystemWindows="true" - * @param activity The activity. - * @param drawer drawerLayout - * @param fakeStatusBar The fake status bar view. - * @param alpha The status bar's alpha. - * @param isTop True to set DrawerLayout at the top layer, false otherwise. - */ - public static void setStatusBarAlpha4Drawer(@NonNull final Activity activity, @NonNull final DrawerLayout drawer, @NonNull final View fakeStatusBar, - @IntRange(from = 0, to = 255) final int alpha, final boolean isTop) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - drawer.setFitsSystemWindows(false); - transparentStatusBar(activity); - setStatusBarAlpha(fakeStatusBar, isTop ? alpha : 0); - for (int i = 0, len = drawer.getChildCount(); i < len; i++) { - drawer.getChildAt(i).setFitsSystemWindows(false); - } - if (isTop) { - hideAlphaView(activity); - } else { - addStatusBarAlpha(activity, alpha, false); - } - } - - /** - * 设置状态栏颜色 - * @param activity - * @param color - * @param alpha - * @param isDecor - */ - private static void addStatusBarColor(final Activity activity, final int color, final int alpha, boolean isDecor) { - ViewGroup parent = isDecor ? (ViewGroup) activity.getWindow().getDecorView() : (ViewGroup) activity.findViewById(android.R.id.content); - View fakeStatusBarView = parent.findViewWithTag(TAG_COLOR); - if (fakeStatusBarView != null) { - if (fakeStatusBarView.getVisibility() == View.GONE) { - fakeStatusBarView.setVisibility(View.VISIBLE); - } - fakeStatusBarView.setBackgroundColor(getStatusBarColor(color, alpha)); - } else { - parent.addView(createColorStatusBarView(parent.getContext(), color, alpha)); - } - } - - /** - * 设置状态栏透明度 - * @param activity - * @param alpha - * @param isDecor - */ - private static void addStatusBarAlpha(final Activity activity, final int alpha, boolean isDecor) { - ViewGroup parent = isDecor ? (ViewGroup) activity.getWindow().getDecorView() : (ViewGroup) activity.findViewById(android.R.id.content); - View fakeStatusBarView = parent.findViewWithTag(TAG_ALPHA); - if (fakeStatusBarView != null) { - if (fakeStatusBarView.getVisibility() == View.GONE) { - fakeStatusBarView.setVisibility(View.VISIBLE); - } - fakeStatusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); - } else { - parent.addView(createAlphaStatusBarView(parent.getContext(), alpha)); - } - } - - /** - * 隐藏颜色View - * @param activity - */ - private static void hideColorView(final Activity activity) { - ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); - View fakeStatusBarView = decorView.findViewWithTag(TAG_COLOR); - if (fakeStatusBarView == null) return; - fakeStatusBarView.setVisibility(View.GONE); - } - - /** - * 隐藏透明度 - * @param activity - */ - private static void hideAlphaView(final Activity activity) { - ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); - View fakeStatusBarView = decorView.findViewWithTag(TAG_ALPHA); - if (fakeStatusBarView == null) return; - fakeStatusBarView.setVisibility(View.GONE); - } - - /** - * 获取状态栏颜色 = (传入颜色等,进行生成) - * @param color - * @param alpha - * @return - */ - private static int getStatusBarColor(final int color, final int alpha) { - if (alpha == 0) return color; - float a = 1 - alpha / 255f; - int red = (color >> 16) & 0xff; - int green = (color >> 8) & 0xff; - int blue = color & 0xff; - red = (int) (red * a + 0.5); - green = (int) (green * a + 0.5); - blue = (int) (blue * a + 0.5); - return Color.argb(255, red, green, blue); - } - - /** - * 创建对应颜色的状态栏View - * @param context - * @param color - * @param alpha - * @return - */ - private static View createColorStatusBarView(final Context context, final int color, final int alpha) { - View statusBarView = new View(context); - statusBarView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())); - statusBarView.setBackgroundColor(getStatusBarColor(color, alpha)); - statusBarView.setTag(TAG_COLOR); - return statusBarView; - } - - /** - * 创建对应透明度的状态栏View - * @param context - * @param alpha - * @return - */ - private static View createAlphaStatusBarView(final Context context, final int alpha) { - View statusBarView = new View(context); - statusBarView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight())); - statusBarView.setBackgroundColor(Color.argb(alpha, 0, 0, 0)); - statusBarView.setTag(TAG_ALPHA); - return statusBarView; - } - - /** - * 设置透明的状态栏 - * @param activity - */ - private static void transparentStatusBar(final Activity activity) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return; - Window window = activity.getWindow(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; - window.getDecorView().setSystemUiVisibility(option); - window.setStatusBarColor(Color.TRANSPARENT); - } else { - window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - } - } - - // ====================== - // ===== action bar ===== - // ====================== - - /** - * 返回 ActionBase 高度 - * @return the action bar's height - */ - public static int getActionBarHeight() { - TypedValue tv = new TypedValue(); - if (DevUtils.getContext().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { - return TypedValue.complexToDimensionPixelSize(tv.data, DevUtils.getContext().getResources().getDisplayMetrics()); - } - return 0; - } - - // ======================== - // === notification bar === - // ======================== - - /** - * 设置通知栏是否显示 - * - * @param isVisible True to set notification bar visible, false otherwise. - */ - public static void setNotificationBarVisibility(final boolean isVisible) { - String methodName; - if (isVisible) { - methodName = (Build.VERSION.SDK_INT <= 16) ? "expand" : "expandNotificationsPanel"; - } else { - methodName = (Build.VERSION.SDK_INT <= 16) ? "collapse" : "collapsePanels"; - } - invokePanels(methodName); - } - - /** - * 反射调用面板 - * @param methodName - */ - private static void invokePanels(final String methodName) { - try { - @SuppressLint("WrongConstant") - Object service = DevUtils.getContext().getSystemService("statusbar"); - @SuppressLint("PrivateApi") - Class statusBarManager = Class.forName("android.app.StatusBarManager"); - Method expand = statusBarManager.getMethod(methodName); - expand.invoke(service); - } catch (Exception ignore) { - } - } - - // ====================== - // === navigation bar === - // ====================== - - /** - * 获取 NavigationView 高度 - * @return the navigation bar's height - */ - public static int getNavBarHeight() { - Resources res = DevUtils.getContext().getResources(); - int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android"); - if (resourceId != 0) { - return res.getDimensionPixelSize(resourceId); - } else { - return 0; - } - } - - /** - * 设置导航栏是否可见(图标显示) - * @param activity The activity. - * @param isVisible True to set notification bar visible, false otherwise. - */ - public static void setNavBarVisibility(@NonNull final Activity activity, boolean isVisible) { - setNavBarVisibility(activity.getWindow(), isVisible); - } - - /** - * 设置导航栏是否可见(图标显示) - * @param window The window. - * @param isVisible True to set notification bar visible, false otherwise. - */ - public static void setNavBarVisibility(@NonNull final Window window, boolean isVisible) { - if (isVisible) { - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - } else { - window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - View decorView = window.getDecorView(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - int visibility = decorView.getSystemUiVisibility(); - decorView.setSystemUiVisibility(visibility & ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); - } - } - } - - /** - * 设置沉浸模式 - * @param activity The activity. - */ - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void setNavBarImmersive(@NonNull final Activity activity) { - setNavBarImmersive(activity.getWindow()); - } - - /** - * 设置顶部沉浸模式 - * @param window The window. - */ - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void setNavBarImmersive(@NonNull final Window window) { - View decorView = window.getDecorView(); - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - decorView.setSystemUiVisibility(uiOptions); - } - - /** - * 判断顶部状态栏(图标)是否显示 - * @param activity The activity. - * @return true : yes, false : no - */ - public static boolean isNavBarVisible(@NonNull final Activity activity) { - return isNavBarVisible(activity.getWindow()); - } - - /** - * 判断顶部状态栏(图标)是否显示 - * @param window The window. - * @return true : yes, false : no - */ - public static boolean isNavBarVisible(@NonNull final Window window) { - boolean isNoLimits = (window.getAttributes().flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0; - if (isNoLimits) return false; - View decorView = window.getDecorView(); - int visibility = decorView.getSystemUiVisibility(); - return (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; - } - - // == - - /** - * 设置是否显示状态栏图标等 - * @param activity - * @param isVisible - */ - public static void setNavBar(Activity activity, boolean isVisible){ - setNavBar(activity.getWindow(), isVisible); - } - - /** - * 设置是否显示状态栏图标等 - * @param window The window. - */ - @TargetApi(Build.VERSION_CODES.KITKAT) - public static void setNavBar(@NonNull final Window window, boolean isVisible) { - if (isVisible){ - // 显示状态栏 - WindowManager.LayoutParams params = window.getAttributes(); - params.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); - window.setAttributes(params); - window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - } else { - // 隐藏状态栏 - WindowManager.LayoutParams params = window.getAttributes(); - params.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; - window.setAttributes(params); - window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - } - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/BrightnessUtils.java b/DevLibUtils/src/main/java/dev/utils/app/BrightnessUtils.java deleted file mode 100644 index 052f7bfd5d..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/BrightnessUtils.java +++ /dev/null @@ -1,134 +0,0 @@ -package dev.utils.app; - -import android.content.ContentResolver; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.provider.Settings; -import android.support.annotation.IntRange; -import android.support.annotation.NonNull; -import android.view.Window; -import android.view.WindowManager; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 亮度相关工具类 - * Created by Ttt - */ -public final class BrightnessUtils { - - private BrightnessUtils() { - } - - // 日志TAG - private static final String TAG = BrightnessUtils.class.getSimpleName(); - - /** - * 判断是否开启自动调节亮度 - * @return true : 是, false : 否 - */ - public static boolean isAutoBrightnessEnabled() { - try { - int mode = Settings.System.getInt(DevUtils.getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE); - return mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isAutoBrightnessEnabled"); - return false; - } - } - - /** - * 设置是否开启自动调节亮度 - * - * @param enabled true : 打开, false : 关闭 - * @return true : 成功, false : 失败 - */ - public static boolean setAutoBrightnessEnabled(final boolean enabled) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(DevUtils.getContext())) { - try { - Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); - intent.setData(Uri.parse("package:" + DevUtils.getContext().getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - DevUtils.getContext().startActivity(intent); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setAutoBrightnessEnabled"); - } - return false; - } - try { - return Settings.System.putInt(DevUtils.getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, - enabled ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC : Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setAutoBrightnessEnabled"); - } - return false; - } - - /** - * 获取屏幕亮度 - * @return 屏幕亮度 0-255 - */ - public static int getBrightness() { - try { - return Settings.System.getInt(DevUtils.getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getBrightness"); - return 0; - } - } - - /** - * 设置屏幕亮度 - * - * @param brightness 亮度值 - */ - public static boolean setBrightness(@IntRange(from = 0, to = 255) final int brightness) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.System.canWrite(DevUtils.getContext())) { - try { - Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); - intent.setData(Uri.parse("package:" + DevUtils.getContext().getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - DevUtils.getContext().startActivity(intent); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setBrightness"); - } - return false; - } - try { - ContentResolver resolver = DevUtils.getContext().getContentResolver(); - boolean result = Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, brightness); - resolver.notifyChange(Settings.System.getUriFor("screen_brightness"), null); - return result; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setBrightness"); - } - return false; - } - - /** - * 设置窗口亮度 - * @param window 窗口 - * @param brightness 亮度值 - */ - public static void setWindowBrightness(@NonNull final Window window, @IntRange(from = 0, to = 255) final int brightness) { - if (window == null) return; - WindowManager.LayoutParams lp = window.getAttributes(); - lp.screenBrightness = brightness / 255f; - window.setAttributes(lp); - } - - /** - * 获取窗口亮度 - * @param window 窗口 - * @return 屏幕亮度 0-255 - */ - public static int getWindowBrightness(final Window window) { - if (window == null) return -1; - WindowManager.LayoutParams lp = window.getAttributes(); - float brightness = lp.screenBrightness; - if (brightness < 0) return getBrightness(); - return (int) (brightness * 255); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/CPUUtils.java b/DevLibUtils/src/main/java/dev/utils/app/CPUUtils.java deleted file mode 100644 index 30b6793a03..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/CPUUtils.java +++ /dev/null @@ -1,275 +0,0 @@ -package dev.utils.app; - -import android.text.format.Formatter; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.LineNumberReader; -import java.util.regex.Pattern; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 获取CPU信息工具类 - * Created by Ttt - */ -public final class CPUUtils { - - private CPUUtils(){ - } - - // 日志TAG - private static final String TAG = CPUUtils.class.getSimpleName(); - - /** - * 获取处理器的Java虚拟机的数量 - */ - public static int getProcessorsCount() { - return Runtime.getRuntime().availableProcessors(); - } - - /** - * 获取手机CPU序列号 - * @return String cpu序列号(16位) 读取失败为"0000000000000000" - */ - public static String getSysCPUSerialNum() { - String str = "", strCPU = "", cpuSerialNum = "0000000000000000"; - try { - //读取CPU信息 - Process pp = Runtime.getRuntime().exec("cat/proc/cpuinfo"); - InputStreamReader ir = new InputStreamReader(pp.getInputStream()); - LineNumberReader input = new LineNumberReader(ir); - //查找CPU序列号 - for (int i = 1; i < 100; i++) { - str = input.readLine(); - if (str != null) { - //查找到序列号所在行 - if (str.indexOf("Serial") > -1) { - //提取序列号 - strCPU = str.substring(str.indexOf(":") + 1, - str.length()); - //去空格 - cpuSerialNum = strCPU.trim(); - break; - } - } else { - break; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSysCPUSerialNum"); - } - return cpuSerialNum; - } - - /** - * 获取CPU 信息 - * @return - */ - public static String getCpuInfo() { - try { - FileReader fr = new FileReader("/proc/cpuinfo"); - BufferedReader br = new BufferedReader(fr); - return br.readLine(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCpuInfo"); - } - return ""; - } - - /** - * 获取CPU 型号 - * @return - */ - public static String getCpuModel() { - try { - FileReader fr = new FileReader("/proc/cpuinfo"); - BufferedReader br = new BufferedReader(fr); - String text = br.readLine(); - String[] array = text.split(":\\s+", 2); - return array[1]; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCpuModel"); - } - return ""; - } - - /** - * 获取 CPU 最大频率(单位KHZ) - * @return - */ - public static String getMaxCpuFreq() { - String result = ""; - ProcessBuilder cmd; - InputStream in = null; - try { - String[] args = {"/system/bin/cat", "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"}; - cmd = new ProcessBuilder(args); - Process process = cmd.start(); - in = process.getInputStream(); - byte[] re = new byte[24]; - while (in.read(re) != -1) { - result = result + new String(re); - } - in.close(); - result = Formatter.formatFileSize(DevUtils.getContext(), Long.parseLong(result.trim()) * 1024) + " Hz"; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMaxCpuFreq"); - result = "unknown"; - } finally { - if (in != null){ - try { - in.close(); - } catch (IOException e){ - } - } - } - return result; - } - - /** - * 获取 CPU 最小频率(单位KHZ) - * @return - */ - public static String getMinCpuFreq() { - String result = ""; - ProcessBuilder cmd; - InputStream in = null; - try { - String[] args = {"/system/bin/cat", "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq"}; - cmd = new ProcessBuilder(args); - Process process = cmd.start(); - in = process.getInputStream(); - byte[] re = new byte[24]; - while (in.read(re) != -1) { - result = result + new String(re); - } - in.close(); - result = Formatter.formatFileSize(DevUtils.getContext(), Long.parseLong(result.trim()) * 1024) + " Hz"; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMinCpuFreq"); - result = "unknown"; - } finally { - if (in != null){ - try { - in.close(); - } catch (IOException e){ - } - } - } - return result; - } - - /** - * 实时获取 CPU 当前频率(单位KHZ) - * @return - */ - public static String getCurCpuFreq() { - String result = ""; - try { - FileReader fr = new FileReader("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq"); - BufferedReader br = new BufferedReader(fr); - String text = br.readLine(); - result = Formatter.formatFileSize(DevUtils.getContext(), Long.parseLong(text.trim()) * 1024) + " Hz"; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCurCpuFreq"); - result = "unknown"; - } - return result; - } - - /** - * 获取 CPU 几核 - * @return - */ - public static int getCoresNumbers() { - // Private Class to display only CPU devices in the directory listing - class CpuFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - //Check if filename is "cpu", followed by a single digit number - if (Pattern.matches("cpu[0-9]+", pathname.getName())) { - return true; - } - return false; - } - } - // CPU 核心数 - int CPU_CORES = 0; - try { - //Get directory containing CPU info - File dir = new File("/sys/devices/system/cpu/"); - //Filter to only list the devices we care about - File[] files = dir.listFiles(new CpuFilter()); - //Return the number of cores (virtual CPU devices) - CPU_CORES = files.length; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCoresNumbers"); - } - if (CPU_CORES < 1) { - CPU_CORES = Runtime.getRuntime().availableProcessors(); - } - if (CPU_CORES < 1) { - CPU_CORES = 1; - } - return CPU_CORES; - } - - /** - * 获取CPU名字 - * @return - */ - public static String getCpuName() { - try { - BufferedReader bufferedReader = new BufferedReader(new FileReader("/proc/cpuinfo"), 8192); - String line = bufferedReader.readLine(); - bufferedReader.close(); - String[] array = line.split(":\\s+", 2); - if (array.length > 1) { - return array[1]; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCpuName"); - } - return null; - } - - /** - * 获取 CMD 指令回调数据 - * @param args - * @return - */ - public static String getCMDOutputString(String[] args) { - InputStream in = null; - try { - ProcessBuilder cmd = new ProcessBuilder(args); - Process process = cmd.start(); - in = process.getInputStream(); - StringBuilder sb = new StringBuilder(); - byte[] re = new byte[64]; - int len; - while ((len = in.read(re)) != -1) { - sb.append(new String(re, 0, len)); - } - in.close(); - process.destroy(); - return sb.toString(); - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "getCMDOutputString"); - } finally { - if (in != null){ - try { - in.close(); - } catch (IOException e){ - } - } - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/CameraUtils.java b/DevLibUtils/src/main/java/dev/utils/app/CameraUtils.java deleted file mode 100644 index 0dd1fe628e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/CameraUtils.java +++ /dev/null @@ -1,216 +0,0 @@ -package dev.utils.app; - -import android.hardware.Camera; - -import dev.utils.LogPrintUtils; - -/** - * detail: 摄像头相关工具类 - * Created by Ttt - */ -public final class CameraUtils { - - private CameraUtils(){ - } - - // 日志Tag - private static final String TAG = CameraUtils.class.getSimpleName(); - - // == 摄像头快速处理 == - - /** - * 判断是否支持反转摄像头(是否存在前置摄像头) - * @return - */ - public static boolean isSupportReverse(){ - try { - // 默认是不支持 - int isSupportReverse = 0; - // 判断是否支持前置,支持则使用前置 - if (checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)) { - isSupportReverse += 1; - // - - LogPrintUtils.dTag(TAG,"支持前置摄像头(手机屏幕)"); - } - // 判断是否支持后置,是则使用后置 - if (checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_BACK)) { - isSupportReverse += 1; - // - - LogPrintUtils.dTag(TAG,"支持后置摄像头(手机背面)"); - } - // 如果都支持才表示支持反转 - return isSupportReverse == 2; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isSupportReverse"); - } - // 默认不支持反转摄像头 - return false; - } - - /** - * 检查是否有摄像头 - * @param facing 前置还是后置 - * @return - */ - public static boolean checkCameraFacing(int facing) { - try { - int cameraCount = Camera.getNumberOfCameras(); - Camera.CameraInfo info = new Camera.CameraInfo(); - for (int i = 0; i < cameraCount; i++) { - Camera.getCameraInfo(i, info); - if (facing == info.facing) { - return true; - } - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "checkCameraFacing"); - } - return false; - } - - /** - * 判断是否使用前置摄像头 - * @param facing - * @return - */ - public static boolean isFrontCamera(int facing){ - return facing == Camera.CameraInfo.CAMERA_FACING_FRONT; - } - - /** - * 判断是否使用后置摄像头 - * @param facing - * @return - */ - public static boolean isBackCamera(int facing){ - return facing == Camera.CameraInfo.CAMERA_FACING_BACK; - } - - /** - * 判断使用的视像头 - * @param isFrontCamera 是否前置摄像头 - * @return - */ - public static int isUseCameraFacing(boolean isFrontCamera){ - // 默认使用后置摄像头 - int cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; - try { - // 支持的摄像头 - 前置, 后置 - boolean[] cFacingArys = new boolean[] { false, false}; - // 判断是否支持前置 - cFacingArys[0] = checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT); - // 判断是否支持后置 - cFacingArys[1] = checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_BACK); - // 进行判断想要使用的是前置,还是后置 - if (isFrontCamera && cFacingArys[0]){ // 使用前置, 必须也支持前置 - // 表示使用前置摄像头 - cameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT; - } else { - // 表示使用后置摄像头 - cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isUseCameraFacing"); - } - return cameraFacing; - } - - // == - - /** - * 释放摄像头资源 - * @param mCamera 摄像头对象 - */ - public static void freeCameraResource(android.hardware.Camera mCamera) { - try { - if (mCamera != null) { - mCamera.setPreviewCallback(null); - mCamera.stopPreview(); - mCamera.lock(); - mCamera.release(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "freeCameraResource"); - } finally { - mCamera = null; - } - } - - /** - * 初始化摄像头 - * @param mCamera - * @param isFrontCamera 是否前置摄像头 = true = 前置(屏幕面), false = 后置(手机背面) = 正常默认使用背面 - * @return 使用的摄像头 - */ - public static Camera initCamera(Camera mCamera, boolean isFrontCamera) { - // 如果之前存在摄像头数据, 则释放资源 - if (mCamera != null) { - freeCameraResource(mCamera); - } - try { - // 进行判断想要使用的是前置,还是后置 - if (isFrontCamera && checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)){ // 使用前置, 必须也支持前置 - // 初始化前置摄像头 - mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT); - } else { - // 初始化后置摄像头 - mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initCamera"); - // 释放资源 - freeCameraResource(mCamera); - } - return mCamera; - } - - /** - * 打开摄像头 - * @param cameraId {@link Camera.CameraInfo } CAMERA_FACING_FRONT(前置), CAMERA_FACING_BACK(后置) - * @return - */ - public static Camera open(int cameraId){ - // 判断支持的摄像头数量 - int numCameras = Camera.getNumberOfCameras(); - if (numCameras == 0) { - return null; - } - // 判断是否指定哪个摄像头 - boolean explicitRequest = cameraId >= 0; - - // 如果没指定, 则进行判断处理 - if (!explicitRequest) { // 默认使用 后置摄像头, 没有后置才用其他(前置) - int index = 0; - while (index < numCameras) { - Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); - Camera.getCameraInfo(index, cameraInfo); - if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { - break; - } - index++; - } - cameraId = index; - } - - Camera camera; - if (cameraId < numCameras) { - camera = Camera.open(cameraId); - } else { - if (explicitRequest) { - camera = null; - } else { - // 默认使用后置 - camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); - } - } - return camera; - } - - /** - * 打开摄像头(默认后置摄像头) - * @return - */ - public static Camera open() { - return open(-1); // Camera.CameraInfo.CAMERA_FACING_BACK - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/CleanUtils.java b/DevLibUtils/src/main/java/dev/utils/app/CleanUtils.java deleted file mode 100644 index d7e6e7c79b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/CleanUtils.java +++ /dev/null @@ -1,233 +0,0 @@ -package dev.utils.app; - -import android.os.Environment; - -import java.io.File; -import java.math.BigDecimal; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 本应用数据清除管理器 - * Created by Ttt - * 主要功能有清除内/外缓存,清除数据库,清除sharedPreference,清除files和清除自定义目录 - */ -public final class CleanUtils { - - private CleanUtils(){ - } - - // 日志TAG - private static final String TAG = CleanUtils.class.getSimpleName(); - - - /** - * 清除本应用内部缓存(/data/data/com.xxx.xxx/cache) - */ - public static void cleanInternalCache() { - try { - deleteFilesByDirectory(DevUtils.getContext().getCacheDir()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cleanInternalCache"); - } - } - - /** - * 清除本应用所有数据库(/data/data/com.xxx.xxx/databases) - */ - public static void cleanDatabases() { - try { - deleteFilesByDirectory(new File(DevUtils.getContext().getFilesDir().getPath() + DevUtils.getContext().getPackageName() + "/databases")); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cleanDatabases"); - } - } - - /** - * 清除本应用SharedPreference(/data/data/com.xxx.xxx/shared_prefs) - */ - public static void cleanSharedPreference() { - try { - deleteFilesByDirectory(new File(DevUtils.getContext().getFilesDir().getPath() + DevUtils.getContext().getPackageName() + "/shared_prefs")); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cleanSharedPreference"); - } - } - - /** - * 按名字清除本应用数据库 - * @param dbName 数据库名称 - */ - public static void cleanDatabaseByName(String dbName) { - try { - DevUtils.getContext().deleteDatabase(dbName); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cleanDatabaseByName"); - } - } - - /** - * 清除/data/data/com.xxx.xxx/files下的内容 - */ - public static void cleanFiles() { - try { - deleteFilesByDirectory(DevUtils.getContext().getFilesDir()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "cleanFiles"); - } - } - - /** - * 清除外部cache下的内容(/mnt/sdcard/android/data/com.xxx.xxx/cache) - */ - public static void cleanExternalCache() { - try { - if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - deleteFilesByDirectory(DevUtils.getContext().getExternalCacheDir()); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "cleanExternalCache"); - } - } - - /** - * 清除自定义路径下的文件,使用需小心,请不要误删。而且只支持目录下的文件删除 - * @param filePath 文件路径 - */ - public static void cleanCustomCache(String filePath) { - deleteFilesByDirectory(getFileByPath(filePath)); - } - - /** - * 清除本应用所有的数据 - * @param filePaths 文件路径 - */ - public static void cleanApplicationData(String... filePaths) { - cleanInternalCache(); - cleanExternalCache(); - cleanDatabases(); - cleanSharedPreference(); - cleanFiles(); - try { - for (String fPath : filePaths) { - cleanCustomCache(fPath); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cleanApplicationData"); - } - } - - /** - * 删除方法 这里只会删除某个文件夹下的文件,如果传入的directory是个文件,将不做处理 - * @param directory 文件夹File对象 - */ - private static void deleteFilesByDirectory(File directory) { - // 存在并且属于文件夹 - if (directory != null && directory.exists() && directory.isDirectory()) { - try { - // 获取文件列表 - File[] files = directory.listFiles(); - // 遍历删除文件 - for (File item : files) { - // 删除文件 - item.delete(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "deleteFilesByDirectory"); - } - } - } - - /** - * 获取文件夹大小 - * Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录,一般放一些长时间保存的数据 - * Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据 - * @param file - * @return - */ - public static long getFolderSize(File file) { - long size = 0; - try { - File[] files = file.listFiles(); - for (int i = 0, len = files.length; i < len; i++) { - // 如果下面还有文件 - if (files[i].isDirectory()) { - size = size + getFolderSize(files[i]); - } else { - size = size + files[i].length(); - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getFolderSize"); - } - return size; - } - - /** - * 获取缓存文件大小 - * @param file - * @return - */ - public static String getCacheSize(File file){ - return getFormatSize(getFolderSize(file)); - } - - /** - * 格式化单位 - * @param size - * @return - */ - public static String getFormatSize(double size) { - double kiloByte = size / 1024; - if (kiloByte < 1) { - return size + "Byte"; - } - - double megaByte = kiloByte / 1024; - if (megaByte < 1) { - BigDecimal result1 = new BigDecimal(Double.toString(kiloByte)); - return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"; - } - - double gigaByte = megaByte / 1024; - if (gigaByte < 1) { - BigDecimal result2 = new BigDecimal(Double.toString(megaByte)); - return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"; - } - - double teraBytes = gigaByte / 1024; - if (teraBytes < 1) { - BigDecimal result3 = new BigDecimal(Double.toString(gigaByte)); - return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"; - } - BigDecimal result4 = new BigDecimal(teraBytes); - return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"; - } - - // == - - /** - * 获取文件 - * @param filePath - * @return - */ - private static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/app/ClickUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ClickUtils.java deleted file mode 100644 index e014e55d2c..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ClickUtils.java +++ /dev/null @@ -1,185 +0,0 @@ -package dev.utils.app; - -import java.util.HashMap; - -import dev.utils.LogPrintUtils; - -/** - * detail: 点击工具类 - * Created by Ttt - */ -public final class ClickUtils { - - private ClickUtils(){ - } - - // 日志TAG - private static final String TAG = ClickUtils.class.getSimpleName(); - - // 上一次点击的标识id = viewId 等 - private static int lastTagId = -1; - /** 上次点击时间 */ - private static long lastClickTime = 0l; // 局限性是, 全局统一事件,如果上次点击后,立刻点击其他就无法点 - /** 默认间隔时间 */ - private static long DF_DIFF = 1000l; // 点击间隔1秒内 - /** 配置数据 */ - private static final HashMap mapConfig = new HashMap<>(); - /** 点击记录数据 */ - private static final HashMap mapRecords = new HashMap<>(); - - // === - - /** - * 判断两次点击的间隔 小于默认间隔时间(1秒), 则认为是多次无效点击 - * @return - */ - public static boolean isFastDoubleClick() { - return isFastDoubleClick(-1, DF_DIFF); - } - - /** - * 判断两次点击的间隔 小于默认间隔时间(1秒), 则认为是多次无效点击 - * @param tagId - * @return - */ - public static boolean isFastDoubleClick(int tagId) { - return isFastDoubleClick(tagId, DF_DIFF); - } - - /** - * 判断两次点击的间隔 小于间隔时间(diff), 则认为是多次无效点击 - * @param tagId - * @param diff - * @return - */ - public static boolean isFastDoubleClick(int tagId, long diff) { - long cTime = System.currentTimeMillis(); - long dTime = cTime - lastClickTime; - // 判断时间是否超过 - if (lastTagId == tagId && lastClickTime > 0 && dTime < diff) { - LogPrintUtils.dTag(TAG, "isFastDoubleClick 无效点击 => tagId: " + tagId + ", diff: " + diff); - return true; - } - LogPrintUtils.dTag(TAG, "isFastDoubleClick 有效点击 => tagId: " + tagId + ", diff: " + diff); - lastTagId = tagId; - lastClickTime = cTime; - return false; - } - - // === - - /** - * 判断两次点击的间隔(根据默认Tag判断) 小于指定间隔时间, 则认为是多次无效点击 - * @param tag - * @return - */ - public static boolean isFastDoubleClick(String tag){ - // 获取配置时间 - Long config_time = mapConfig.get(tag); - // 如果等于null, 则传入默认时间 - if (config_time == null){ - return isFastDoubleClick(tag, DF_DIFF); - } - return isFastDoubleClick(tag, config_time); - } - - /** - * 判断两次点击的间隔 小于间隔时间(diff), 则认为是多次无效点击 - * @param tag - * @param diff - * @return - */ - public static boolean isFastDoubleClick(String tag, long diff){ - // 获取上次点击的时间 - Long lastTime = mapRecords.get(tag); - if (lastTime == null){ - lastTime = 0l; - } - long cTime = System.currentTimeMillis(); - long dTime = cTime - lastTime; - // 判断时间是否超过 - if (lastTime > 0 && dTime < diff) { - LogPrintUtils.dTag(TAG, "isFastDoubleClick 无效点击 => tag: " + tag + ", diff: " + diff); - return true; - } - LogPrintUtils.dTag(TAG, "isFastDoubleClick 有效点击 => tag: " + tag + ", diff: " + diff); - // 保存上次点击时间 - mapRecords.put(tag, cTime); - return false; - } - - // === - - /** - * 判断两次点击的间隔(根据默认Tag判断) 小于指定间隔时间, 则认为是多次无效点击 - * @param obj - * @return - */ - public static boolean isFastDoubleClick(Object obj){ - // 获取TAG - String tag = ((obj != null) ? ("obj_" + obj.hashCode()) : "obj_null"); - // 获取配置时间 - Long config_time = mapConfig.get(tag); - // 如果等于null, 则传入默认时间 - if (config_time == null){ - return isFastDoubleClick(tag, DF_DIFF); - } - return isFastDoubleClick(tag, config_time); - } - - /** - * 判断两次点击的间隔 小于间隔时间(diff), 则认为是多次无效点击 - * @param obj - * @param diff - * @return - */ - public static boolean isFastDoubleClick(Object obj, long diff){ - // 获取TAG - String tag = ((obj != null) ? ("obj_" + obj.hashCode()) : "obj_null"); - // 获取上次点击的时间 - Long lastTime = mapRecords.get(tag); - if (lastTime == null){ - lastTime = 0l; - } - long cTime = System.currentTimeMillis(); - long dTime = cTime - lastTime; - // 判断时间是否超过 - if (lastTime > 0 && dTime < diff) { - LogPrintUtils.dTag(TAG, "isFastDoubleClick 无效点击 => obj: " + obj + ", diff: " + diff); - return true; - } - LogPrintUtils.dTag(TAG, "isFastDoubleClick 有效点击 => obj: " + obj + ", diff: " + diff); - // 保存上次点击时间 - mapRecords.put(tag, cTime); - return false; - } - - // === - - /** - * 初始化配置信息 - * @param mapConfig - */ - public static void initConfig(HashMap mapConfig){ - if (mapConfig != null){ - mapConfig.putAll(mapConfig); - } - } - - /** - * 添加配置信息 - * @param key - * @param val - */ - public static void putConfig(String key, Long val){ - mapConfig.put(key, val); - } - - /** - * 移除配置信息 - * @param key - */ - public static void removeConfig(String key){ - mapConfig.remove(key); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ClipboardUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ClipboardUtils.java deleted file mode 100644 index 9dc5d825e5..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ClipboardUtils.java +++ /dev/null @@ -1,122 +0,0 @@ -package dev.utils.app; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 剪贴板相关工具类 - * Created by Ttt - */ -public final class ClipboardUtils { - - private ClipboardUtils() { - } - - // 日志TAG - private static final String TAG = ClipboardUtils.class.getSimpleName(); - - /** - * 复制文本到剪贴板 - * @param text - */ - public static void copyText(final CharSequence text) { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - // 复制的数据 - ClipData clipData = ClipData.newPlainText("text", text); - // 设置复制的数据 - clipManager.setPrimaryClip(clipData); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "copyText"); - } - } - - /** - * 获取剪贴板的文本 - * @return 剪贴板的文本 - */ - public static CharSequence getText() { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clipData = clipManager.getPrimaryClip(); - if (clipData != null && clipData.getItemCount() > 0) { - return clipData.getItemAt(0).coerceToText(DevUtils.getContext()); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getText"); - } - return null; - } - - /** - * 复制uri到剪贴板 - * @param uri - */ - public static void copyUri(final Uri uri) { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - // 复制的数据 - ClipData clipData = ClipData.newUri(DevUtils.getContext().getContentResolver(), "", uri); - // 设置复制的数据 - clipManager.setPrimaryClip(clipData); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "copyUri"); - } - } - - /** - * 获取剪贴板的uri - * @return 剪贴板的uri - */ - public static Uri getUri() { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clipData = clipManager.getPrimaryClip(); - if (clipData != null && clipData.getItemCount() > 0) { - return clipData.getItemAt(0).getUri(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getUri"); - } - return null; - } - - /** - * 复制意图到剪贴板 - * @param intent 意图 - */ - public static void copyIntent(final Intent intent) { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - // 复制的数据 - ClipData clipData = ClipData.newIntent("intent", intent); - // 设置复制的数据 - clipManager.setPrimaryClip(clipData); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "copyIntent"); - } - } - - /** - * 获取剪贴板的意图 - * @return 剪贴板的意图 - */ - public static Intent getIntent() { - try { - ClipboardManager clipManager = (ClipboardManager) DevUtils.getContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clipData = clipManager.getPrimaryClip(); - if (clipData != null && clipData.getItemCount() > 0) { - return clipData.getItemAt(0).getIntent(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getIntent"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ContentResolverUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ContentResolverUtils.java deleted file mode 100644 index ca8fcb7d5b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ContentResolverUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -package dev.utils.app; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Intent; -import android.net.Uri; -import android.provider.MediaStore; -import android.text.TextUtils; - -import java.io.File; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: ContentResolver 工具类 - * Created by Ttt - */ -public final class ContentResolverUtils { - - // https://blog.csdn.net/lemon_blue/article/details/52353851 - // https://www.cnblogs.com/Sharley/p/7942142.html - - private ContentResolverUtils() { - } - - // 日志TAG - private static final String TAG = ContentResolverUtils.class.getSimpleName(); - - // MediaStore.MediaColumns.MIME_TYPE, isVideo ? "video/3gp" : "image/jpeg" - - /** - * 通知刷新本地资源 - * @param file - * @return - */ - public static boolean notifyMediaStore(File file){ - if (file != null){ - try { - // 最后通知图库扫描更新 - DevUtils.getContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); - // 通知成功 - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "notifyMediaStore"); - } - } - return false; - } - - /** - * 添加图片到系统相册(包含原图、相册图, 会存在两张) - 想要一张, 直接调用 notifyMediaStore() - * @param file - * @param fileName - * @param isNotify - * @return - */ - public static boolean insertImageIntoMediaStore(File file, String fileName, boolean isNotify){ - if (file != null){ - try { - // 添加到相册 - MediaStore.Images.Media.insertImage(DevUtils.getContext().getContentResolver(), file.getAbsolutePath(), TextUtils.isEmpty(fileName) ? file.getName() : fileName, null); - if (isNotify){ - notifyMediaStore(file); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "insertImageIntoMediaStore"); - } - } - return false; - } - - /** - * 添加视频到系统相册 - * @param file - * @return - */ - public static boolean insertVideoIntoMediaStore(File file){ - return insertIntoMediaStore(file, -1, true, "video/3gp"); - } - - /** - * 保存到系统相册 - * @param file - * @param createTime - * @param isVideo - * @param mimeType - */ - public static boolean insertIntoMediaStore(File file, long createTime, boolean isVideo, String mimeType) { - if (file != null && !TextUtils.isEmpty(mimeType)) { - try { - ContentResolver mContentResolver = DevUtils.getContext().getContentResolver(); - // 防止创建时间为null - if (createTime <= 0) - createTime = System.currentTimeMillis(); - - ContentValues values = new ContentValues(); - values.put(MediaStore.MediaColumns.TITLE, file.getName()); - values.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName()); - // 值一样,但是还是用常量区分对待 - values.put(isVideo ? MediaStore.Video.VideoColumns.DATE_TAKEN : MediaStore.Images.ImageColumns.DATE_TAKEN, createTime); - values.put(MediaStore.MediaColumns.DATE_MODIFIED, System.currentTimeMillis()); - values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis()); - if (!isVideo) - values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0); - // 文件路径 - values.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath()); - // 文件大小 - values.put(MediaStore.MediaColumns.SIZE, file.length()); - // 文件类型 - values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); - // 生成所属的URI资源 - Uri uri = mContentResolver.insert(isVideo ? MediaStore.Video.Media.EXTERNAL_CONTENT_URI : MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - // 最后通知图库更新 - DevUtils.getContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); - // 表示成功 - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "insertIntoMediaStore"); - } - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/DBUtils.java b/DevLibUtils/src/main/java/dev/utils/app/DBUtils.java deleted file mode 100644 index 8684b62fe6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/DBUtils.java +++ /dev/null @@ -1,111 +0,0 @@ -package dev.utils.app; - -import android.os.Environment; - -import java.io.InputStream; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.common.FileUtils; - -/** - * detail: 数据库工具类 (导入导出等) - * Created by Ttt - */ -public final class DBUtils { - - private DBUtils() { - } - - // 日志TAG - private static final String TAG = DBUtils.class.getSimpleName(); - - /** - * 导出数据库 - * @param targetFile 目标文件 - * @param dbName 数据库文件名 - * @return 是否倒出成功 - */ - public static boolean startExportDatabase(String targetFile, String dbName) { - // 判断 SDCard 是否挂载 - if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - return false; - } - try { - // Database 文件地址 - String sourceFilePath = getDBPath() + dbName; - // 获取结果 - boolean result = FileUtils.copyFile(sourceFilePath, targetFile, true); - // 返回结果 - return result; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "startExportDatabase"); - } - return false; - } - - // ==== - - /** - * 导入数据库 - * @param srcFilePath 待复制的文件地址 - * @param destFilePath 目标文件地址 - * @return 是否倒出成功 - */ - public static boolean startImportDatabase(String srcFilePath, String destFilePath){ - // 判断 SDCard 是否挂载 - if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - return false; - } - try { - // 获取结果 - boolean result = FileUtils.copyFile(srcFilePath, destFilePath, true); - // 返回结果 - return result; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "startImportDatabase"); - } - return false; - } - - /** - * 导入数据库 - * @param inputStream 文件流(被复制) - * @param destFilePath 目标文件地址 - * @return 是否倒出成功 - */ - public static boolean startImportDatabase(InputStream inputStream, String destFilePath){ - // 判断 SDCard 是否挂载 - if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { - return false; - } - try { - // 获取结果 - boolean result = FileUtils.copyFile(inputStream, destFilePath, true); - // 返回结果 - return result; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "startImportDatabase"); - } - return false; - } - - // == - - /** - * 获取数据库路径 - * @return - */ - public static String getDBPath(){ - try { - // Database 文件地址 - String dbPath = Environment.getDataDirectory() + "/data/" + DevUtils.getContext().getPackageName() + "/databases/"; - // 返回数据库路径 - return dbPath; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDBPath"); - } - return null; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/DeviceUtils.java b/DevLibUtils/src/main/java/dev/utils/app/DeviceUtils.java deleted file mode 100644 index dc3ed1e512..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/DeviceUtils.java +++ /dev/null @@ -1,504 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.os.PowerManager; -import android.provider.Settings; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Locale; -import java.util.Map; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 设备相关工具类 - * Created by Ttt - */ -public final class DeviceUtils { - - private DeviceUtils() { - } - - // 日志TAG - private static final String TAG = DBUtils.class.getSimpleName(); - -// // http://blog.csdn.net/zhangcanyan/article/details/52817866 -// android.os.Build.BOARD:获取设备基板名称 -// android.os.Build.BOOTLOADER:获取设备引导程序版本号 -// android.os.Build.BRAND:获取设备品牌 -// android.os.Build.CPU_ABI:获取设备指令集名称(CPU的类型) -// android.os.Build.CPU_ABI2:获取第二个指令集名称 -// android.os.Build.DEVICE:获取设备驱动名称 -// android.os.Build.DISPLAY:获取设备显示的版本包(在系统设置中显示为版本号)和ID一样 -// android.os.Build.FINGERPRINT:设备的唯一标识。由设备的多个信息拼接合成。 -// android.os.Build.HARDWARE:设备硬件名称,一般和基板名称一样(BOARD) -// android.os.Build.HOST:设备主机地址 -// android.os.Build.ID:设备版本号。 -// android.os.Build.MODEL :获取手机的型号 设备名称。 -// android.os.Build.MANUFACTURER:获取设备制造商 -// android:os.Build.PRODUCT:整个产品的名称 -// android:os.Build.RADIO:无线电固件版本号,通常是不可用的 显示unknown -// android.os.Build.TAGS:设备标签。如release-keys 或测试的 test-keys -// android.os.Build.TIME:时间 -// android.os.Build.TYPE:设备版本类型 主要为"user" 或"eng". -// android.os.Build.USER:设备用户名 基本上都为android-build -// android.os.Build.VERSION.RELEASE:获取系统版本字符串。如4.1.2 或2.2 或2.3等 -// android.os.Build.VERSION.CODENAME:设备当前的系统开发代号,一般使用REL代替 -// android.os.Build.VERSION.INCREMENTAL:系统源代码控制值,一个数字或者git hash值 -// android.os.Build.VERSION.SDK:系统的API级别 一般使用下面大的SDK_INT 来查看 -// android.os.Build.VERSION.SDK_INT:系统的API级别 数字表示 - - /** 换行字符串 */ - private static final String NEW_LINE_STR = System.getProperty("line.separator"); - - /** - * 获取当前操作系统的语言 - * @return String 系统语言 - */ - public static String getSysLanguage() { - String language = Locale.getDefault().getLanguage(); - return language; - } - - /** - * 获取设备信息 - * @param dInfoMaps 传入设备信息传出HashMap - */ - public static void getDeviceInfo(HashMap dInfoMaps) { - // 获取设备信息类的所有申明的字段,即包括public、private和proteced, 但是不包括父类的申明字段。 - Field[] fields = Build.class.getDeclaredFields(); - // 遍历字段 - for (Field field : fields) { - try { - // 取消java的权限控制检查 - field.setAccessible(true); - // 获取类型对应字段的数据,并保存 - dInfoMaps.put(field.getName(), field.get(null).toString()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDeviceInfo"); - } - } - } - - /** - * 获取设备信息 - * @param dInfoMaps 传入设备信息传出HashMap - */ - public static void getDeviceInfo2(HashMap dInfoMaps) { - // 获取设备信息类的所有申明的字段,即包括public、private和proteced, 但是不包括父类的申明字段。 - Field[] fields = Build.class.getDeclaredFields(); - // 遍历字段 - for (Field field : fields) { - try { - // 取消java的权限控制检查 - field.setAccessible(true); - - if (field.getName().toLowerCase().equals("SUPPORTED_ABIS".toLowerCase())) { - Object object = field.get(null); - // 判断是否数组 - if (object instanceof String[]) { - // 获取类型对应字段的数据,并保存 - 保存支持的指令集 [arm64-v8a, armeabi-v7a, armeabi] - dInfoMaps.put(field.getName().toLowerCase(), Arrays.toString((String[]) object)); - continue; - } - } - // 获取类型对应字段的数据,并保存 - dInfoMaps.put(field.getName().toLowerCase(), field.get(null).toString()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDeviceInfo2"); - } - } - } - - /** - * 处理设备信息 - * @param dInfoMaps 设备信息 - * @param eHint 错误提示,如获取设备信息失败 - */ - public static String handleDeviceInfo(HashMap dInfoMaps, String eHint) { - try { - // 初始化StringBuilder,拼接字符串 - StringBuilder sBuilder = new StringBuilder(); - // 获取设备信息 - Iterator> mapIter = dInfoMaps.entrySet().iterator(); - // 遍历设备信息 - while (mapIter.hasNext()) { - // 获取对应的key-value - Map.Entry rnEntry = (Map.Entry) mapIter.next(); - String rnKey = (String) rnEntry.getKey(); // key - String rnValue = (String) rnEntry.getValue(); // value - // 保存设备信息 - sBuilder.append(rnKey); - sBuilder.append(" = "); - sBuilder.append(rnValue); - sBuilder.append(NEW_LINE_STR); - } - return sBuilder.toString(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleDeviceInfo"); - } - return eHint; - } - - /** - * 获取设备系统版本号 - * @return 设备系统版本号 - */ - public static String getSDKVersionName() { - return Build.VERSION.RELEASE; - } - - /** - * 获取当前SDK 版本号 - * @return - */ - public static int getSDKVersion(){ - return Build.VERSION.SDK_INT; - } - - /** - * 获取Android id - * hint: - * 在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被wipe后该值会被重置。 - * @return - */ - public static String getAndroidId(){ - // Android id 默认为null - String androidId = null; - try { - androidId = Settings.Secure.getString(DevUtils.getContext().getContentResolver(), Settings.Secure.ANDROID_ID); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAndroidId"); - } - return androidId; - } - - /** - * 判断设备是否 root - * @return - */ - public static boolean isDeviceRooted() { - String su = "su"; - String[] locations = { "/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/", - "/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/" }; - for (String location : locations) { - if (new File(location + su).exists()) { - return true; - } - } - return false; - } - - // == - - /** 特殊mac地址用于判断是否获取失败 */ - private static final String CUSTOM_MAC = "02:00:00:00:00:00"; - - /** - * 获取设备 MAC 地址 - * hint: - * 需添加权限 - * 需添加权限 - * @return - */ - public static String getMacAddress() { - String macAddress = getMacAddressByWifiInfo(); - if (!CUSTOM_MAC.equals(macAddress)) { - return macAddress; - } - macAddress = getMacAddressByNetworkInterface(); - if (!CUSTOM_MAC.equals(macAddress)) { - return macAddress; - } - macAddress = getMacAddressByInetAddress(); - if (!CUSTOM_MAC.equals(macAddress)) { - return macAddress; - } - macAddress = getMacAddressByFile(); - if (!CUSTOM_MAC.equals(macAddress)) { - return macAddress; - } - // 没有打开wifi, 获取 WLAN MAC 地址失败 - return null; - } - - /** - * 获取设备 MAC 地址 - * hint: - * 需添加权限 - * @return - */ - @SuppressLint({"HardwareIds", "MissingPermission"}) - private static String getMacAddressByWifiInfo() { - try { - @SuppressLint("WifiManagerLeak") - WifiManager wifiManager = (WifiManager) DevUtils.getContext().getSystemService(Context.WIFI_SERVICE); - if (wifiManager != null) { - WifiInfo wifiInfo = wifiManager.getConnectionInfo(); - if (wifiInfo != null) return wifiInfo.getMacAddress(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMacAddressByWifiInfo"); - } - return CUSTOM_MAC; - } - - /** - * 获取设备 MAC 地址 - * hint: - * 需添加权限 - * @return - */ - private static String getMacAddressByNetworkInterface() { - try { - Enumeration nis = NetworkInterface.getNetworkInterfaces(); - while (nis.hasMoreElements()) { - NetworkInterface ni = nis.nextElement(); - if (ni == null || !ni.getName().equalsIgnoreCase("wlan0")) continue; - byte[] macBytes = ni.getHardwareAddress(); - if (macBytes != null && macBytes.length > 0) { - StringBuilder sb = new StringBuilder(); - for (byte b : macBytes) { - sb.append(String.format("%02x:", b)); - } - return sb.substring(0, sb.length() - 1); - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMacAddressByNetworkInterface"); - } - return CUSTOM_MAC; - } - - private static String getMacAddressByInetAddress() { - try { - InetAddress inetAddress = getInetAddress(); - if (inetAddress != null) { - NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress); - if (ni != null) { - byte[] macBytes = ni.getHardwareAddress(); - if (macBytes != null && macBytes.length > 0) { - StringBuilder sb = new StringBuilder(); - for (byte b : macBytes) { - sb.append(String.format("%02x:", b)); - } - return sb.substring(0, sb.length() - 1); - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMacAddressByInetAddress"); - } - return "02:00:00:00:00:00"; - } - - private static InetAddress getInetAddress() { - try { - Enumeration nis = NetworkInterface.getNetworkInterfaces(); - while (nis.hasMoreElements()) { - NetworkInterface ni = nis.nextElement(); - // To prevent phone of xiaomi return "10.0.2.15" - if (!ni.isUp()) continue; - Enumeration addresses = ni.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress inetAddress = addresses.nextElement(); - if (!inetAddress.isLoopbackAddress()) { - String hostAddress = inetAddress.getHostAddress(); - if (hostAddress.indexOf(':') < 0) return inetAddress; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getInetAddress"); - } - return null; - } - - /** - * 获取设备 MAC 地址 - * @return MAC 地址 - */ - private static String getMacAddressByFile() { - ShellUtils.CommandResult result = ShellUtils.execCmd("getprop wifi.interface", false); - if (result.result == 0) { - String name = result.successMsg; - if (name != null) { - result = ShellUtils.execCmd("cat /sys/class/net/" + name + "/address", false); - if (result.result == 0) { - String address = result.successMsg; - if (address != null && address.length() > 0) { - return address; - } - } - } - } - return CUSTOM_MAC; - } - - // == - - /** - * 获取设备厂商 如 Xiaomi - * @return 设备厂商 - */ - public static String getManufacturer() { - return Build.MANUFACTURER; - } - - /** - * 获取设备型号 如 RedmiNote4X - * @return 设备型号 - */ - public static String getModel() { - String model = Build.MODEL; - if (model != null) { - model = model.trim().replaceAll("\\s*", ""); - } else { - model = ""; - } - return model; - } - - /** - * 关机 需要 root 权限或者系统权限 - * @return - */ - public static boolean shutdown() { - try { - ShellUtils.execCmd("reboot -p", true); - Intent intent = new Intent("android.intent.action.ACTION_REQUEST_SHUTDOWN"); - intent.putExtra("android.intent.extra.KEY_CONFIRM", false); - DevUtils.getContext().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "shutdown"); - } - return false; - } - - /** - * 重启 需要 root 权限或者系统权限 - * @return - */ - public static boolean reboot() { - try { - ShellUtils.execCmd("reboot", true); - Intent intent = new Intent(Intent.ACTION_REBOOT); - intent.putExtra("nowait", 1); - intent.putExtra("interval", 1); - intent.putExtra("window", 0); - DevUtils.getContext().sendBroadcast(intent); - return true; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "reboot"); - } - return false; - } - - /** - * 重启 - * 需系统权限 - * @param reason 传递给内核来请求特殊的引导模式,如"recovery" - */ - public static void reboot(final String reason) { - try { - PowerManager mPowerManager = (PowerManager) DevUtils.getContext().getSystemService(Context.POWER_SERVICE); - if (mPowerManager == null) - return; - mPowerManager.reboot(reason); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "reboot"); - } - } - - /** 重启到 recovery 需要 root 权限 */ - public static void reboot2Recovery() { - ShellUtils.execCmd("reboot recovery", true); - } - - /** 重启到 bootloader 需要 root 权限 */ - public static void reboot2Bootloader() { - ShellUtils.execCmd("reboot bootloader", true); - } - - /** - * BASEBAND-VER - * 基带版本 - * return String - */ - public static String getBaseband_Ver() { - String Version = ""; - try { - Class cl = Class.forName("android.os.SystemProperties"); - Object invoker = cl.newInstance(); - Method m = cl.getMethod("get", new Class[]{String.class, String.class}); - Object result = m.invoke(invoker, new Object[]{"gsm.version.baseband", "no message"}); - Version = (String) result; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getBaseband_Ver"); - } - return Version; - } - - /** - * CORE-VER - * 内核版本 - * return String - */ - public static String getLinuxCore_Ver() { - String kernelVersion = ""; - try { - Process process = null; - try { - process = Runtime.getRuntime().exec("cat /proc/version"); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getLinuxCore_Ver - Process"); - } - InputStream outs = process.getInputStream(); - InputStreamReader isrout = new InputStreamReader(outs); - BufferedReader brout = new BufferedReader(isrout, 8 * 1024); - - String result = ""; - String line; - try { - while ((line = brout.readLine()) != null) { - result += line; - } - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "getLinuxCore_Ver - readLine"); - } - try { - if (result != "") { - String Keyword = "version "; - int index = result.indexOf(Keyword); - line = result.substring(index + Keyword.length()); - index = line.indexOf(" "); - kernelVersion = line.substring(0, index); - } - } catch (IndexOutOfBoundsException e) { - LogPrintUtils.eTag(TAG, e, "getLinuxCore_Ver - substring"); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getLinuxCore_Ver"); - } - return kernelVersion; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/DialogUtils.java b/DevLibUtils/src/main/java/dev/utils/app/DialogUtils.java deleted file mode 100644 index f67edc7a4b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/DialogUtils.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.utils.app; - -import android.app.Dialog; -import android.app.ProgressDialog; -import android.content.Context; -import android.widget.PopupWindow; - -/** - * detail: Dialog 操作相关工具类 - * Created by Ttt - */ -public final class DialogUtils { - - private DialogUtils(){ - } - - // ======== Dialog 相关 ======== - - /** - * 关闭Dialog - * @param dialog - */ - public static void closeDialog(Dialog dialog){ - if (dialog != null && dialog.isShowing()){ - dialog.dismiss(); - } - } - - /** - * 关闭多个Dialog - * @param dialogs - */ - public static void closeDialogs(Dialog... dialogs){ - if (dialogs != null && dialogs.length != 0){ - for (int i = 0, len = dialogs.length; i < len; i++){ - // 获取Dialog - Dialog dialog = dialogs[i]; - // 关闭Dialog - if (dialog != null && dialog.isShowing()){ - dialog.dismiss(); - } - } - } - } - - /** - * 关闭PopupWindow - * @param popupWindow - */ - public static void closePopupWindow(PopupWindow popupWindow){ - if (popupWindow != null && popupWindow.isShowing()){ - popupWindow.dismiss(); - } - } - - /** - * 关闭多个PopupWindow - * @param popupWindows - */ - public static void closePopupWindows(PopupWindow... popupWindows){ - if (popupWindows != null && popupWindows.length != 0){ - for (int i = 0, len = popupWindows.length; i < len; i++){ - // 获取Dialog - PopupWindow popupWindow = popupWindows[i]; - // 关闭Dialog - if (popupWindow != null && popupWindow.isShowing()){ - popupWindow.dismiss(); - } - } - } - } - - // == - - /** - * 创建加载 Dialog - * @param context - * @param title - * @param content - * @return - */ - public static ProgressDialog creDialog(Context context, String title, String content){ - return creDialog(context, title, content, false);// 不可以使用返回键 - } - - /** - * 创建加载 Dialog - * @param context - * @param title - * @param content - * @param isCancel - * @return - */ - public static ProgressDialog creDialog(Context context, String title, String content, boolean isCancel){ - ProgressDialog progressDialog = android.app.ProgressDialog.show(context, title, content); - progressDialog.setCancelable(isCancel); - return progressDialog; - } - - /** - * 创建自动关闭dialog - * @param context - * @param title - * @param content - * @return - */ - public static ProgressDialog creAutoCloseDialog(Context context, String title, String content){ - final ProgressDialog progressDialog = android.app.ProgressDialog.show(context, title, content); - progressDialog.setCancelable(false); - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - try { - // 显示10秒后,取消 ProgressDialog - Thread.sleep(10000); - } catch (InterruptedException e) { - } - try { - if (progressDialog != null) { - progressDialog.dismiss(); - } - } catch (Exception e) { - } - } - }); - thread.start(); - return progressDialog; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/EditTextUtils.java b/DevLibUtils/src/main/java/dev/utils/app/EditTextUtils.java deleted file mode 100644 index 7745d80fb1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/EditTextUtils.java +++ /dev/null @@ -1,374 +0,0 @@ -package dev.utils.app; - -import android.text.Editable; -import android.text.InputFilter; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.DigitsKeyListener; -import android.text.method.KeyListener; -import android.widget.EditText; - -import java.util.UUID; - -import dev.utils.LogPrintUtils; - -/** - * detail: EditText 工具类 - * Created by Ttt - */ -public final class EditTextUtils { - - private EditTextUtils(){ - } - - // 日志TAG - private static final String TAG = EditTextUtils.class.getSimpleName(); - - // https://blog.csdn.net/zhoujn90/article/details/44983905 - - // 设置是否显示光标 - // editText.setCursorVisible(true); - - /** - * 设置长度限制,并且设置内容 - * @param editText - * @param content - * @param maxLength - * @return - */ - public static EditText setMaxLengthAnText(EditText editText, String content, int maxLength){ - if (editText != null){ - // 设置长度限制, 并且设置内容 - setText(setMaxLength(editText, maxLength), content); - } - return editText; - } - - /** - * 设置长度限制 - * @param editText - * @param maxLength - * @return - */ - public static EditText setMaxLength(EditText editText, int maxLength){ - if (editText != null){ - if (maxLength > 0){ - // 设置最大长度限制 - InputFilter[] filters = { new InputFilter.LengthFilter(maxLength) }; - editText.setFilters(filters); - } - } - return editText; - } - - /** - * 获取光标位置 - * @param editText - * @return - */ - public static int getSelectionStart(EditText editText){ - if (editText != null){ - return editText.getSelectionStart(); - } - return 0; - } - - /** - * 获取输入的内容 - * @param editText - * @return - */ - public static String getText(EditText editText){ - if (editText != null){ - return editText.getText().toString(); - } - return ""; - } - - /** - * 获取输入的内容长度 - * @param editText - * @return - */ - public static int getTextLength(EditText editText){ - return getText(editText).length(); - } - - // = - - /** - * 设置内容 - * @param editText - * @param content - */ - public static EditText setText(EditText editText, String content){ - return setText(editText, content, true); - } - - /** - * 设置内容 - * @param editText - * @param content - * @param isSelect 是否设置光标 - */ - public static EditText setText(EditText editText, String content, boolean isSelect){ - if (editText != null){ - if (content != null){ - // 设置文本 - editText.setText(content); - if (isSelect){ - // 设置光标 - editText.setSelection(editText.getText().toString().length()); - } - } - } - return editText; - } - - /** - * 追加内容 - 当前光标位置追加 - * @param editText - * @param content - * @param isSelect - * @return - */ - public static EditText insert(EditText editText, String content, boolean isSelect){ - if (editText != null){ - return insert(editText, content, editText.getSelectionStart(), isSelect); - } - return editText; - } - - /** - * 追加内容 - * @param editText - * @param content - * @param start 开始添加的位置 - * @param isSelect - * @return - */ - public static EditText insert(EditText editText, String content, int start, boolean isSelect){ - if (editText != null){ - if (!TextUtils.isEmpty(content)){ - try { - Editable editable = editText.getText(); - // 在指定位置 追加内容 - editable.insert(start, content); - // 判断是否选中 - if (isSelect){ - // 设置光标 - editText.setSelection(editText.getText().toString().length()); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "insert"); - } - } - } - return editText; - } - - // == - - /** - * 设置光标在第一位 - * @param editText - * @return - */ - public static EditText setSelectTop(EditText editText){ - return setSelect(editText, 0); - } - - /** - * 设置光标在最后一位 - * @param editText - * @return - */ - public static EditText setSelectBottom(EditText editText){ - if (editText != null){ - // 设置光标 - editText.setSelection(editText.getText().toString().length()); - } - return editText; - } - - /** - * 设置光标位置 - * @param editText - * @param select - * @return - */ - public static EditText setSelect(EditText editText, int select){ - if (editText != null){ - if (select >= 0){ - // 判断是否超过限制 - int length = editText.getText().toString().length(); - // 如果超过长度, 则设置最后 - if (select > length){ - // 设置光标 - editText.setSelection(length); - } else { - // 设置光标 - editText.setSelection(select); - } - } - } - return editText; - } - - // = - - /** - * 设置输入限制 - * @param editText - * @param keyListener - * @return - * setKeyListener(editText, getNumberAndEnglishKeyListener()); - */ - public static EditText setKeyListener(EditText editText, KeyListener keyListener){ - if (editText != null){ - editText.setKeyListener(keyListener); - } - return editText; - } - - /** - * 设置输入限制 - * @param editText - * @param digits 只能输入的内容 -> 1234567890 - * @return - * setKeyListener(editText, "1234567890"); - */ - public static EditText setKeyListener(EditText editText, String digits){ - if (editText != null){ - if (TextUtils.isEmpty(digits)){ - editText.setKeyListener(null); - } else { - editText.setKeyListener(DigitsKeyListener.getInstance(digits)); - } - } - return editText; - } - - - // =============== 输入法Key Listener 快捷处理 ========================== - - /** - * 限制只能输入字母和数字,默认弹出英文输入法 - * @return - */ - public static DigitsKeyListener getNumberAndEnglishKeyListener() { - /** 限制只能输入字母和数字,默认弹出英文输入法 */ - DigitsKeyListener digitsKeyListener = new DigitsKeyListener() { - @Override - public int getInputType() { - return InputType.TYPE_TEXT_VARIATION_PASSWORD; - } - - @Override - protected char[] getAcceptedChars() { - char[] data = "qwertyuioplkjhgfdsazxcvbnmQWERTYUIOPLKJHGFDSAZXCVBNM1234567890".toCharArray(); - return data; - } - }; - return digitsKeyListener; - } - - /** - * 限制只能输入数字,默认弹出数字列表 - * @return - */ - public static DigitsKeyListener getNumberKeyListener() { - /** 限制只能输入数字,默认弹出数字列表 */ - DigitsKeyListener digitsKeyListener = new DigitsKeyListener() { - @Override - public int getInputType() { - return InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL; - } - - @Override - protected char[] getAcceptedChars() { - char[] data = "1234567890".toCharArray(); - return data; - } - }; - return digitsKeyListener; - } - - // == - - // https://blog.csdn.net/zhuwentao2150/article/details/51546773 - - // editText.addTextChangedListener(DevTextWatcher); - - /** - * detail: 开发输入监听抽象类 - * Created by Ttt - */ - public static abstract class DevTextWatcher implements TextWatcher { - - // 标记id - 一定程度上唯一 - private final int markId; - // 判断是否操作中 - 操作状态 -> 如果是否使用搜索数据等 - private boolean isOperate = false; - // 标记状态, 特殊需求处理 - private int operateState = -1; - - public DevTextWatcher(){ - // 初始化id - this.markId = UUID.randomUUID().hashCode(); - } - - public final int getMarkId() { - return markId; - } - - public final boolean isOperate() { - return isOperate; - } - - public final void setOperate(boolean operate) { - isOperate = operate; - } - - public final int getOperateState() { - return operateState; - } - - public final void setOperateState(int operateState) { - this.operateState = operateState; - } - - // == 回调接口 == - - /** - * 在文本变化前调用 - * @param s - * @param start - * @param count - * @param after - */ - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } - - /** - * 在文本变化后调用 - * @param s - * @param start - * @param before - * @param count - */ - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - /** - * 在文本变化后调用 - * @param s - */ - @Override - public void afterTextChanged(Editable s) { - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/EncodeUtils.java b/DevLibUtils/src/main/java/dev/utils/app/EncodeUtils.java deleted file mode 100644 index 403426b8e8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/EncodeUtils.java +++ /dev/null @@ -1,192 +0,0 @@ -package dev.utils.app; - -import android.os.Build; -import android.text.Html; -import android.util.Base64; - -import java.net.URLDecoder; -import java.net.URLEncoder; - -import dev.utils.LogPrintUtils; - -/** - * detail: 编码工具类 - * Created by Ttt - */ -public final class EncodeUtils { - - private EncodeUtils() { - } - - // 日志TAG - private static final String TAG = EncodeUtils.class.getSimpleName(); - - /** - * url编码 - UTF-8 - * @param input The input. - * @return the urlencoded string - */ - public static String urlEncode(final String input) { - return urlEncode(input, "UTF-8"); - } - - /** - * url编码 - * @param input The input. - * @param charsetName The name of charset. - * @return the urlencoded string - */ - public static String urlEncode(final String input, final String charsetName) { - try { - return URLEncoder.encode(input, charsetName); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "urlEncode"); - return null; - } - } - - /** - * url 解码 - UTF-8 - * @param input The input. - * @return the string of decode urlencoded string - */ - public static String urlDecode(final String input) { - return urlDecode(input, "UTF-8"); - } - - /** - * url 解码 - * @param input The input. - * @param charsetName The name of charset. - * @return the string of decode urlencoded string - */ - public static String urlDecode(final String input, final String charsetName) { - try { - return URLDecoder.decode(input, charsetName); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "urlDecode"); - return null; - } - } - -// CRLF 这个参数看起来比较眼熟,它就是Win风格的换行符,意思就是使用CR LF这一对作为一行的结尾而不是Unix风格的LF -// -// DEFAULT 这个参数是默认,使用默认的方法来加密 -// -// NO_PADDING 这个参数是略去加密字符串最后的”=” -// -// NO_WRAP 这个参数意思是略去所有的换行符(设置后CRLF就没用了) -// -// URL_SAFE 这个参数意思是加密时不使用对URL和文件名有特殊意义的字符来作为加密字符,具体就是以-和_取代+和/ - - /** - * base64 编码 => 去除\n 替换 + 和 - 号 - * @param input The input. - * @return Base64-encode bytes - */ - public static byte[] base64Encode(final String input) { - return base64Encode(input.getBytes()); - } - - /** - * base64 编码 - * @param input The input. - * @return Base64-encode bytes - */ - public static byte[] base64Encode(final byte[] input) { - return Base64.encode(input, Base64.NO_WRAP); - } - - /** - * base64 编码 - * @param input The input. - * @return Base64-encode string - */ - public static String base64Encode2String(final byte[] input) { - return Base64.encodeToString(input, Base64.NO_WRAP); - } - - /** - * base64 解码 - * @param input The input. - * @return the string of decode Base64-encode string - */ - public static byte[] base64Decode(final String input) { - return Base64.decode(input, Base64.NO_WRAP); - } - - /** - * base64 解码 - * @param input The input. - * @return the bytes of decode Base64-encode bytes - */ - public static byte[] base64Decode(final byte[] input) { - return Base64.decode(input, Base64.NO_WRAP); - } - - /** - * base64 解码 - * @param input - * @return - */ - public static String base64DecodeToString(final byte[] input) { - try { - return new String(base64Decode(input)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "base64DecodeToString"); - } - return null; - } - - /** - * html编码 - * @param input The input. - * @return html-encode string - */ - public static String htmlEncode(final CharSequence input) { - StringBuilder sb = new StringBuilder(); - char c; - for (int i = 0, len = input.length(); i < len; i++) { - c = input.charAt(i); - switch (c) { - case '<': - sb.append("<"); //$NON-NLS-1$ - break; - case '>': - sb.append(">"); //$NON-NLS-1$ - break; - case '&': - sb.append("&"); //$NON-NLS-1$ - break; - case '\'': - //http://www.w3.org/TR/xhtml1 - // The named character reference ' (the apostrophe, U+0027) was - // introduced in XML 1.0 but does not appear in HTML. Authors should - // therefore use ' instead of ' to work as expected in HTML 4 - // user agents. - sb.append("'"); //$NON-NLS-1$ - break; - case '"': - sb.append("""); //$NON-NLS-1$ - break; - default: - sb.append(c); - } - } - return sb.toString(); - } - - /** - * html解码 - * @param input The input. - * @return the string of decode html-encode string - */ - @SuppressWarnings("deprecation") - public static CharSequence htmlDecode(final String input) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return Html.fromHtml(input, Html.FROM_HTML_MODE_LEGACY); - } else { - return Html.fromHtml(input); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ErrorUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ErrorUtils.java deleted file mode 100644 index ff8ca24f5a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ErrorUtils.java +++ /dev/null @@ -1,133 +0,0 @@ -package dev.utils.app; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; - -/** - * detail: 错误信息处理工具类 - * Created by Ttt - */ -public final class ErrorUtils { - - private ErrorUtils(){ - } - - /** 换行字符串 */ - private static final String NEW_LINE_STR = System.getProperty("line.separator"); - /** 换行字符串 - 两行 */ - private static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - /** - * 获取错误信息 - * @param e - * @return - */ - public static String getErrorMsg(Exception e){ - try { - if (e != null){ - return e.getMessage(); - } - } catch (Exception ex){ - return ex.getMessage(); - } - return "e(null)"; - } - - /** - * 将异常栈信息转为字符串 - * @param e 字符串 - * @return 异常栈 - */ - public static String getThrowableMsg(Throwable e) { - try { - if (e != null) { - StringWriter writer = new StringWriter(); - e.printStackTrace(new PrintWriter(writer)); - return writer.toString(); - } - } catch (Exception ex){ - return ex.getMessage(); - } - return "e(null)"; - } - - // ================== 异常信息处理 =================== - - /** - * 获取错误信息(无换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - public static String getThrowableMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 写入错误栈信息 - ex.printStackTrace(printWriter); - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - } finally { - if(printWriter != null) { - printWriter.close(); - } - } - return eHint; - } - - /** - * 获取错误信息(有换行) - * @param ex 错误信息 - * @return 默认返回 "" - */ - public static String getThrowableNewLinesMsg(Throwable ex) { - return getThrowableNewLinesMsg("", ex); - } - - /** - * 获取错误信息(有换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - public static String getThrowableNewLinesMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 获取错误栈信息 - StackTraceElement[] stElement = ex.getStackTrace(); - // 标题,提示属于什么异常 - printWriter.append(ex.toString()); - printWriter.append(NEW_LINE_STR); - // 遍历错误栈信息,并且进行换行,缩进 - for(StackTraceElement st : stElement) { - printWriter.append("\tat "); - printWriter.append(st.toString()); - printWriter.append(NEW_LINE_STR); - } - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - } finally { - if(printWriter != null) { - try { - printWriter.close(); - } catch (Exception e) { - } - } - } - return eHint; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/FileRecordUtils.java b/DevLibUtils/src/main/java/dev/utils/app/FileRecordUtils.java deleted file mode 100644 index 9679193688..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/FileRecordUtils.java +++ /dev/null @@ -1,499 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.Field; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: App 文件记录工具类 - * Created by Ttt - */ -public final class FileRecordUtils { - - private FileRecordUtils(){ - } - - // 日志TAG - private static final String TAG = FileRecordUtils.class.getSimpleName(); - - // =================== 配置信息 ======================= - - /** App版本(如1.0.01) 显示给用户看的 */ - static String APP_VERSION_NAME = ""; - - /** android:versionCode——整数值,代表应用程序代码的相对版本,也就是版本更新过多少次。(不显示给用户看) */ - static String APP_VERSION_CODE = ""; - - /** 设备信息 */ - static String DEVICE_INFO_STR = null; - - /** 用来存储设备信息 */ - static HashMap DEVICE_INFO_MAPS = new HashMap(); - - /** 换行字符串 */ - static final String NEW_LINE_STR = System.getProperty("line.separator"); - - /** 换行字符串 - 两行 */ - static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - // ================== APP、设备信息处理 =================== - - /** - * 获取app版本信息 - */ - static String[] getAppVersion() { - String[] aVersion = null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - String versionName = pi.versionName == null ? "null" : pi.versionName; - String versionCode = pi.versionCode + ""; - // -- - aVersion = new String[]{versionName,versionCode}; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersion"); - } - return aVersion; - } - - /** - * 获取设备信息 - * @param dInfoMaps 传入设备信息传出HashMap - */ - static void getDeviceInfo(HashMap dInfoMaps) { - // 获取设备信息类的所有申明的字段,即包括public、private和proteced, 但是不包括父类的申明字段。 - Field[] fields = Build.class.getDeclaredFields(); - // 遍历字段 - for (Field field : fields) { - try { - // 取消java的权限控制检查 - field.setAccessible(true); - // 获取类型对应字段的数据,并保存 - dInfoMaps.put(field.getName(), field.get(null).toString()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDeviceInfo"); - } - } - } - - /** - * 处理设备信息 - * @param eHint 错误提示,如获取设备信息失败 - */ - static String handleDeviceInfo(String eHint) { - try { - // 如果不为null,则直接返回之前的信息 - if(!TextUtils.isEmpty(DEVICE_INFO_STR)) { - return DEVICE_INFO_STR; - } - // 初始化StringBuilder,拼接字符串 - StringBuilder sBuilder = new StringBuilder(); - // 获取设备信息 - Iterator> mapIter = DEVICE_INFO_MAPS.entrySet().iterator(); - // 遍历设备信息 - while (mapIter.hasNext()) { - // 获取对应的key-value - Map.Entry rnEntry = (Map.Entry) mapIter.next(); - String rnKey = (String) rnEntry.getKey(); // key - String rnValue = (String) rnEntry.getValue(); // value - // 保存设备信息 - sBuilder.append(rnKey); - sBuilder.append(" = "); - sBuilder.append(rnValue); - sBuilder.append(NEW_LINE_STR); - } - // 保存设备信息 - DEVICE_INFO_STR = sBuilder.toString(); - // 返回设备信息 - return DEVICE_INFO_STR; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleDeviceInfo"); - } - return eHint; - } - - // ================== 时间格式化 =================== - - /** 日期格式类型 */ - static final String yyyyMMddHHmmss = "yyyy-MM-dd HH:mm:ss"; - - /** - * 获取当前日期的字符串 - * @return 字符串 - */ - @SuppressLint("SimpleDateFormat") - static String getDateNow() { - try { - Calendar cld = Calendar.getInstance(); - DateFormat df = new SimpleDateFormat(yyyyMMddHHmmss); - return df.format(cld.getTime()); - } catch (Exception e) { - } - return null; - } - - - // ================== 文件操作 =================== - - /** - * 判断某个文件夹是否创建,未创建则创建(不能加入文件名) - * @param fPath 文件夹路径 - */ - static File createFile(String fPath) { - try { - File file = new File(fPath); - // 当这个文件夹不存在的时候则创建文件夹 - if(!file.exists()) { - // 允许创建多级目录 - file.mkdirs(); - } - return file; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createFile"); - } - return null; - } - - /** - * 保存文件 - * @param txt 保存内容 - * @param fUrl 保存路径(包含文件名.后缀) - * @return 是否保存成功 - */ - static boolean saveFile(String txt, String fUrl) { - try { - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(fUrl); - fos.write(txt.getBytes()); - fos.close(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveFile"); - return false; - } - return true; - } - - - // ================== 异常信息处理 =================== - - /** - * 获取错误信息(无换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - static String getThrowableMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 写入错误栈信息 - ex.printStackTrace(printWriter); - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getThrowableMsg"); - } finally { - if(printWriter != null) { - printWriter.close(); - } - } - return eHint; - } - - /** - * 获取错误信息(有换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - static String getThrowableNewLinesMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 获取错误栈信息 - StackTraceElement[] stElement = ex.getStackTrace(); - // 标题,提示属于什么异常 - printWriter.append(ex.toString()); - printWriter.append(NEW_LINE_STR); - // 遍历错误栈信息,并且进行换行,缩进 - for(StackTraceElement st : stElement) { - printWriter.append("\tat "); - printWriter.append(st.toString()); - printWriter.append(NEW_LINE_STR); - } - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getThrowableNewLinesMsg"); - } finally { - if(printWriter != null) { - try { - printWriter.close(); - } catch (Exception e) { - } - } - } - return eHint; - } - - // ================== 对外公开方法 =================== - - /** - * App初始化调用方法 - */ - public static void appInit() { - // 如果版本信息为null,才进行处理 - if (TextUtils.isEmpty(APP_VERSION_CODE) || TextUtils.isEmpty(APP_VERSION_NAME)) { - // 获取app版本信息 - String[] aVersion = getAppVersion(); - // 保存app版本信息 - APP_VERSION_NAME = aVersion[0]; - APP_VERSION_CODE = aVersion[1]; - } - // 判断是否存在设备信息 - if (DEVICE_INFO_MAPS.size() == 0) { - // 获取设备信息 - getDeviceInfo(DEVICE_INFO_MAPS); - // 转换字符串 - handleDeviceInfo(""); - } - } - - // ========= 保存错误日志信息 ========== - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param isNewLines 是否换行 - * @param printDevice 是否打印设备信息 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveErrorLog(Throwable ex, String fPath, String fName, boolean isNewLines, boolean printDevice, String... eHint) { - return saveErrorLog(ex, null, null, fPath, fName, isNewLines, printDevice, eHint); - } - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param isNewLines 是否换行 - * @param printDevice 是否打印设备信息 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveErrorLog(Throwable ex, String head, String bottom, String fPath, String fName, boolean isNewLines, boolean printDevice, String... eHint) { - // 处理可变参数(错误提示) - eHint = handleVariable(2, eHint); - // 日志拼接 - StringBuilder sBuilder = new StringBuilder(); - // 防止文件夹不存在 - createFile(fPath); - // 设备信息 - String dInfo = handleDeviceInfo(eHint[0]); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(head)) { - sBuilder.append(head); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 保存App信息 - sBuilder.append("date: " + getDateNow()); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionName: " + APP_VERSION_NAME); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionCode: " + APP_VERSION_CODE); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - // 如果需要打印设备信息 - if (printDevice) { - // 保存设备信息 - sBuilder.append(dInfo); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 错误信息 - String eMsg = null; - // 是否换行 - if(isNewLines) { - eMsg = getThrowableNewLinesMsg(eHint[1], ex); - } else { - eMsg = getThrowableMsg(eHint[1], ex); - } - // 保存异常信息 - sBuilder.append(eMsg); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(bottom)) { - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(bottom); - } - // 保存日志到文件 - return saveFile(sBuilder.toString(), fPath + File.separator + fName); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param printDevice 是否打印设备信息 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String fPath, String fName, boolean printDevice, String... eHint){ - return saveLog(log, null, null, fPath, fName, printDevice, eHint); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param printDevice 是否打印设备信息 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String head, String bottom, String fPath, String fName, boolean printDevice, String... eHint){ - // 处理可变参数(错误提示) - eHint = handleVariable(2, eHint); - // 日志拼接 - StringBuilder sBuilder = new StringBuilder(); - // 防止文件夹不存在 - createFile(fPath); - // 设备信息 - String dInfo = handleDeviceInfo(eHint[0]); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(head)) { - sBuilder.append(head); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 保存App信息 - sBuilder.append("date: " + getDateNow()); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionName: " + APP_VERSION_NAME); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionCode: " + APP_VERSION_CODE); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - // 如果需要打印设备信息 - if (printDevice) { - // 保存设备信息 - sBuilder.append(dInfo); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 保存日志信息 - sBuilder.append(log); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(bottom)) { - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(bottom); - } - // 保存日志到文件 - return saveFile(sBuilder.toString(), fPath + File.separator + fName); - } - - // == - - /** - * 处理可变参数 - * @param length 保留长度 - * @param vArrays 可变参数数组 - * @return - */ - public static String[] handleVariable(int length, String[] vArrays) { - // 处理后的数据, - String[] hArrays = new String[length]; - // 是否统一处理 - boolean isUnifiedHandler = true; - try { - if(vArrays != null) { - // 获取可变参数数组长度 - int vLength = vArrays.length; - // 如果长度超出预留长度 - if(vLength >= length) { - for(int i = 0;i < length;i++) { - if(vArrays[i] == null) { - hArrays[i] = ""; - } else { - hArrays[i] = vArrays[i]; - } - } - // 但可变参数长度,超过预留长度时,已经处理完毕,不需要再次处理,节省遍历资源 - isUnifiedHandler = false; - } else { - for(int i = 0;i < vLength;i++) { - if(vArrays[i] == null) { - hArrays[i] = ""; - } else { - hArrays[i] = vArrays[i]; - } - } - } - } - if(isUnifiedHandler) { - // 统一处理,如果数据未null,则设置为“”,防止拼接出现 "null" - for(int i = 0;i < length;i++) { - if(hArrays[i] == null) { - hArrays[i] = ""; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleVariable"); - } - return hArrays; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/FlashlightUtils.java b/DevLibUtils/src/main/java/dev/utils/app/FlashlightUtils.java deleted file mode 100644 index f759e88006..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/FlashlightUtils.java +++ /dev/null @@ -1,170 +0,0 @@ -package dev.utils.app; - -import android.content.pm.PackageManager; -import android.graphics.SurfaceTexture; -import android.hardware.Camera; - -import java.io.IOException; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -import static android.hardware.Camera.Parameters.FLASH_MODE_OFF; -import static android.hardware.Camera.Parameters.FLASH_MODE_TORCH; - -/** - * detail: 手电筒工具类 - * Created by Ttt - */ -public final class FlashlightUtils { - - private FlashlightUtils() { - } - - // 日志TAG - private final String TAG = FileRecordUtils.class.getSimpleName(); - - public static FlashlightUtils getInstance() { - return LazyHolder.INSTANCE; - } - - private static final class LazyHolder { - private static final FlashlightUtils INSTANCE = new FlashlightUtils(); - } - - // = - - private Camera mCamera; - - /** - * 注册摄像头 - * @return - */ - public boolean register() { - try { - mCamera = Camera.open(0); - } catch (Throwable t) { - return false; - } - if (mCamera == null) { - return false; - } - try { - mCamera.setPreviewTexture(new SurfaceTexture(0)); - mCamera.startPreview(); - return true; - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "register"); - return false; - } - } - - /** - * 注销摄像头 - */ - public void unregister() { - if (mCamera == null) return; - mCamera.stopPreview(); - mCamera.release(); - } - - /** - * 打开闪光灯 - */ - public void setFlashlightOn() { - if (mCamera == null) { - return; - } - Camera.Parameters parameters = mCamera.getParameters(); - if (!FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { - parameters.setFlashMode(FLASH_MODE_TORCH); - mCamera.setParameters(parameters); - } - } - - /** - * 关闭闪光灯 - */ - public void setFlashlightOff() { - if (mCamera == null) { - return; - } - Camera.Parameters parameters = mCamera.getParameters(); - if (FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { - parameters.setFlashMode(FLASH_MODE_OFF); - mCamera.setParameters(parameters); - } - } - - /** - * 是否打开闪光灯 - * @return - */ - public boolean isFlashlightOn() { - if (mCamera == null) { - return false; - } - Camera.Parameters parameters = mCamera.getParameters(); - return FLASH_MODE_TORCH.equals(parameters.getFlashMode()); - } - - /** - * 是否支持手机闪光灯 - * @return - */ - public static boolean isFlashlightEnable() { - return DevUtils.getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); - } - - // = - - /** - * 打开闪光灯 - * @param camera - */ - public void setFlashlightOn(Camera camera) { - if (camera != null) { - try { - Camera.Parameters parameter = camera.getParameters(); - parameter.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); - camera.setParameters(parameter); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setFlashlightOn"); - } - } - } - - /** - * 关闭闪光灯 - * @param camera - */ - public void setFlashlightOff(Camera camera) { - if (camera != null) { - try { - Camera.Parameters parameter = camera.getParameters(); - parameter.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); - camera.setParameters(parameter); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setFlashlightOff"); - } - } - } - - /** - * 是否打开了摄像头 - * @param camera - * @return - */ - public boolean isFlashlightOn(Camera camera) { - if (camera == null) { - return false; - } - try { - Camera.Parameters parameters = camera.getParameters(); - return FLASH_MODE_TORCH.equals(parameters.getFlashMode()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isFlashlightOn"); - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/HandlerUtils.java b/DevLibUtils/src/main/java/dev/utils/app/HandlerUtils.java deleted file mode 100644 index fed98ae7c5..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/HandlerUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -package dev.utils.app; - -import android.content.Context; -import android.os.Handler; - -/** - * detail: Handler 工具类, 默认开启一个 Handler,方便在各个地方随时执行主线程任务 - * Created by Ttt - */ -public final class HandlerUtils { - - private HandlerUtils() { - } - - /** 主线程 Handler */ - private static Handler mainHandler; - - /** - * 初始化操作 - * @param context - */ - public static void init(Context context) { - if (mainHandler == null) mainHandler = new Handler(context.getMainLooper()); - } - - /** - * 获取主线程 Handler - * @return 主线程 Handler - */ - public static Handler getMainHandler() { - return mainHandler; - } - - /** - * 在主线程 Handler 中执行任务 - * @param runnable 可执行的任务 - */ - public static void postRunnable(Runnable runnable) { - getMainHandler().post(runnable); - } - - /** - * 在主线程 Handler 中执行延迟任务 - * @param runnable 可执行的任务 - * @param delay 延迟时间 - */ - public static void postRunnable(Runnable runnable, long delay) { - getMainHandler().postDelayed(runnable, delay); - } - - /** - * 在主线程 Handler 中执行延迟任务 - * @param runnable 可执行的任务 - * @param delay 延迟时间 - * @param times 轮询次数 - * @param interval 轮询时间 - */ - public static void postRunnable(final Runnable runnable, long delay, final int times, final int interval) { - Runnable loop = new Runnable() { - private int mTimes; - @Override - public void run() { - if (mTimes < times) { - runnable.run(); - getMainHandler().postDelayed(this, interval); - } - mTimes++; - } - }; - getMainHandler().postDelayed(loop, delay); - } - - /** - * 在主线程 Handler 中清除任务 - * @param runnable 需要清除的任务 - */ - public static void removeRunnable(Runnable runnable) { - getMainHandler().removeCallbacks(runnable); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/HtmlUtils.java b/DevLibUtils/src/main/java/dev/utils/app/HtmlUtils.java deleted file mode 100644 index 24c67fb056..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/HtmlUtils.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.utils.app; - -import android.text.Html; -import android.widget.TextView; - -/** - * detail: Html 工具类 - * Created by Ttt - */ -public final class HtmlUtils { - - private HtmlUtils() { - } - - /** - * 设置内容, 最终做处理 - * @param textView - * @param content - */ - public static void setHtmlText(TextView textView, String content){ - if (textView != null && content != null){ - textView.setText(Html.fromHtml(content)); - } - } - - /** - * 为给定的字符串添加HTML颜色标记 - * @param content 给定的字符串 - * @return 最后放在 Html.fromHtml();内 - */ - public static String addHtmlColor(String content, String color) { - return "" + content + ""; - } - - /** - * 为给定的字符串添加HTML颜色标记 - * @param content 给定的字符串 - * @param fString 格式化的内容 - * @return 最后放在 Html.fromHtml();内 - */ - public static String addHtmlColor(String content, String fString, String color) { - return String.format(content, (""+ fString +"")); - } - - /** - * 为给定的字符串添加HTML加粗标记 - * @param content 给定的字符串 - * @return 最后放在 Html.fromHtml();内 - * ====================== - * 这种方式也可以加粗,但是是所有字体加粗(非部分字体加粗) - * android.text.TextPaint tp = textView.getPaint(); - * tp.setFakeBoldText(true); - */ - public static String addHtmlBold(String content) { - return "" + content + ""; - } - - /** - * 为给定的字符串添加HTML颜色标记,以及加粗 - * @param content 给定的字符串 - * @return 最后放在 Html.fromHtml();内 - */ - public static String addHtmlColorAndBlod(String content, String color) { - return "" + content + ""; - } - - /** - * 为给定的字符串添加HTML下划线 - * @param content 给定的字符串 - * @return 最后放在 Html.fromHtml();内 - */ - public static String addHtmlUnderline(String content) { - return "" + content + ""; - } - - /** - * 为给定的字符串添加HTML 字体倾斜 - * @param content 给定的字符串 - * @return 最后放在 Html.fromHtml();内 - * ================================= - * 如果需要倾斜自定义角度,需要自定义TextView,在onDraw里面加上 - * - 倾斜度,上下左右居中 - * canvas.rotate(倾斜角度, getMeasuredWidth() / 3, getMeasuredHeight() / 3); - */ - public static String addHtmlIncline(String content) { - return "" + content + ""; - } - - /** - * 将给定的字符串中所有给定的关键字标色 - * @param source 给定的字符串 - * @param keyword 给定的关键字 - * @param color 需要标记的颜色 - * @return - */ - public static String keywordMadeRed(String source, String keyword, String color) { - return keywordReplaceAll(source, keyword, ("" + keyword + "")); - } - - /** - * 将给定的字符串中所有给定的关键字进行替换内容 - * @param source 给定的字符串 - * @param keyword 给定的关键字 - * @param replacement 替换的内容 - * @return 返回的是带Html标签的字符串,在使用时要通过Html.fromHtml() 转换为Spanned对象再传递给TextView对象 - */ - public static String keywordReplaceAll(String source, String keyword, String replacement) { - try { - if(source != null && source.trim().length() != 0) { - if (keyword != null && keyword.trim().length() != 0) { - if(replacement != null && replacement.trim().length() != 0) { - return source.replaceAll(keyword , replacement); - } - } - } - } catch (Exception e) { - } - return source; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/IntentUtils.java b/DevLibUtils/src/main/java/dev/utils/app/IntentUtils.java deleted file mode 100644 index 0002476e65..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/IntentUtils.java +++ /dev/null @@ -1,533 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.MediaStore; -import android.provider.Settings; -import android.support.v4.content.FileProvider; -import android.text.TextUtils; - -import java.io.File; - -import dev.DevUtils; -import dev.utils.common.FileUtils; - -/** - * detail: Intent(意图) 相关工具类 - * Created by Ttt - */ -public final class IntentUtils { - - private IntentUtils() { - } - - /** - * 获取安装 App(支持 8.0)的意图 - * - * @param filePath The path of file. - * @param authority 7.0 及以上安装需要传入清单文件中的}的 authorities 属性 - *
参看 https://developer.android.com/reference/android/support/v4/content/FileProvider.html - * @return 安装 App(支持 8.0)的意图 - */ - public static Intent getInstallAppIntent(final String filePath, final String authority) { - return getInstallAppIntent(FileUtils.getFileByPath(filePath), authority); - } - - /** - * 获取安装 App(支持 8.0)的意图 - * - * @param file The file. - * @param authority 7.0 及以上安装需要传入清单文件中的}的 authorities 属性 - *
参看 https://developer.android.com/reference/android/support/v4/content/FileProvider.html - * @return 安装 App(支持 8.0)的意图 - */ - public static Intent getInstallAppIntent(final File file, final String authority) { - return getInstallAppIntent(file, authority, false); - } - - /** - * 获取安装 App(支持 8.0)的意图 - * - * @param file The file. - * @param authority 7.0 及以上安装需要传入清单文件中的}的 authorities 属性 - *
参看 https://developer.android.com/reference/android/support/v4/content/FileProvider.html - * @param isNewTask 是否开启新的任务栈 - * @return 安装 App(支持 8.0)的意图 - */ - public static Intent getInstallAppIntent(final File file, final String authority, final boolean isNewTask) { - if (file == null) return null; - Intent intent = new Intent(Intent.ACTION_VIEW); - Uri data; - String type = "application/vnd.android.package-archive"; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - data = Uri.fromFile(file); - } else { - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - data = FileProvider.getUriForFile(DevUtils.getContext(), authority, file); - } - intent.setDataAndType(data, type); - return getIntent(intent, isNewTask); - } - - /** - * 获取卸载 App 的意图 - * @param packageName The name of the package. - * @return 卸载 App 的意图 - */ - public static Intent getUninstallAppIntent(final String packageName) { - return getUninstallAppIntent(packageName, false); - } - - /** - * 获取卸载 App 的意图 - * @param packageName The name of the package. - * @param isNewTask 是否开启新的任务栈 - * @return 卸载 App 的意图 - */ - public static Intent getUninstallAppIntent(final String packageName, final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_DELETE); - intent.setData(Uri.parse("package:" + packageName)); - return getIntent(intent, isNewTask); - } - - /** - * 获取打开 App 的意图 - * @param packageName The name of the package. - * @return 打开 App 的意图 - */ - public static Intent getLaunchAppIntent(final String packageName) { - return getLaunchAppIntent(packageName, false); - } - - /** - * 获取打开 App 的意图 - * @param packageName The name of the package. - * @param isNewTask 是否开启新的任务栈 - * @return 打开 App 的意图 - */ - public static Intent getLaunchAppIntent(final String packageName, final boolean isNewTask) { - Intent intent = DevUtils.getContext().getPackageManager().getLaunchIntentForPackage(packageName); - if (intent == null) return null; - return getIntent(intent, isNewTask); - } - - /** - * 获取 App 具体设置的意图 - * @param packageName The name of the package. - * @return App 具体设置的意图 - */ - public static Intent getLaunchAppDetailsSettingsIntent(final String packageName) { - return getLaunchAppDetailsSettingsIntent(packageName, false); - } - - /** - * 获取 App 具体设置的意图 - * @param packageName The name of the package. - * @param isNewTask 是否开启新的任务栈 - * @return App 具体设置的意图 - */ - public static Intent getLaunchAppDetailsSettingsIntent(final String packageName, final boolean isNewTask) { - Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS"); - intent.setData(Uri.parse("package:" + packageName)); - return getIntent(intent, isNewTask); - } - - /** - * 获取 到应用商店app详情界面的意图 - * @param packageName 目标App的包名 - * @param marketPkg 应用商店包名 ,如果为""则由系统弹出应用商店列表供用户选择,否则调转到目标市场的应用详情界面,某些应用商店可能会失败 - */ - public static Intent getlaunchAppDetailIntent(final String packageName, final String marketPkg) { - return getlaunchAppDetailIntent(packageName, marketPkg, false); - } - - /** - * 获取 到应用商店app详情界面的意图 - * @param packageName 目标App的包名 - * @param marketPkg 应用商店包名 ,如果为""则由系统弹出应用商店列表供用户选择,否则调转到目标市场的应用详情界面,某些应用商店可能会失败 - * @param isNewTask 是否开启新的任务栈 - */ - public static Intent getlaunchAppDetailIntent(final String packageName, final String marketPkg, final boolean isNewTask) { - try { - if (TextUtils.isEmpty(packageName)) return null; - - Uri uri = Uri.parse("market://details?id=" + packageName); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if (!TextUtils.isEmpty(marketPkg)) { - intent.setPackage(marketPkg); - } - return getIntent(intent, isNewTask); - } catch (Exception e) { - } - return null; - } - - /** - * 获取分享文本的意图 - * @param content 分享文本 - * @return 分享文本的意图 - */ - public static Intent getShareTextIntent(final String content) { - return getShareTextIntent(content, false); - } - - /** - * 获取分享文本的意图 - * @param content 分享文本 - * @param isNewTask 是否开启新的任务栈 - * @return 分享文本的意图 - */ - - public static Intent getShareTextIntent(final String content, final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, content); - return getIntent(intent, isNewTask); - } - - /** - * 获取分享图片的意图 - * @param content 文本 - * @param imagePath 图片文件路径 - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final String imagePath) { - return getShareImageIntent(content, imagePath, false); - } - - /** - * 获取分享图片的意图 - * @param content 文本 - * @param imagePath 图片文件路径 - * @param isNewTask 是否开启新的任务栈 - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final String imagePath, final boolean isNewTask) { - if (imagePath == null || imagePath.length() == 0) return null; - return getShareImageIntent(content, new File(imagePath), isNewTask); - } - - /** - * 获取分享图片的意图 - * @param content 文本 - * @param image 图片文件 - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final File image) { - return getShareImageIntent(content, image, false); - } - - /** - * 获取分享图片的意图 - * @param content 文本 - * @param image 图片文件 - * @param isNewTask 是否开启新的任务栈 - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final File image, final boolean isNewTask) { - if (image != null && image.isFile()) return null; - return getShareImageIntent(content, Uri.fromFile(image), isNewTask); - } - - /** - * 获取分享图片的意图 - * @param content 分享文本 - * @param uri 图片 uri - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final Uri uri) { - return getShareImageIntent(content, uri, false); - } - - /** - * 获取分享图片的意图 - * @param content 分享文本 - * @param uri 图片 uri - * @param isNewTask 是否开启新的任务栈 - * @return 分享图片的意图 - */ - public static Intent getShareImageIntent(final String content, final Uri uri, final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, content); - intent.putExtra(Intent.EXTRA_STREAM, uri); - intent.setType("image/*"); - return getIntent(intent, isNewTask); - } - - /** - * 获取其他应用组件的意图 - * @param packageName The name of the package. - * @param className 全类名 - * @return 其他应用组件的意图 - */ - public static Intent getComponentIntent(final String packageName, final String className) { - return getComponentIntent(packageName, className, null, false); - } - - /** - * 获取其他应用组件的意图 - * @param packageName The name of the package. - * @param className 全类名 - * @param isNewTask 是否开启新的任务栈 - * @return 其他应用组件的意图 - */ - public static Intent getComponentIntent(final String packageName, final String className, final boolean isNewTask) { - return getComponentIntent(packageName, className, null, isNewTask); - } - - /** - * 获取其他应用组件的意图 - * @param packageName The name of the package. - * @param className 全类名 - * @param bundle bundle - * @return 其他应用组件的意图 - */ - public static Intent getComponentIntent(final String packageName, final String className, final Bundle bundle) { - return getComponentIntent(packageName, className, bundle, false); - } - - /** - * 获取其他应用组件的意图 - * @param packageName The name of the package. - * @param className 全类名 - * @param bundle bundle - * @param isNewTask 是否开启新的任务栈 - * @return 其他应用组件的意图 - */ - public static Intent getComponentIntent(final String packageName, final String className, final Bundle bundle, final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_VIEW); - if (bundle != null) intent.putExtras(bundle); - ComponentName cn = new ComponentName(packageName, className); - intent.setComponent(cn); - return getIntent(intent, isNewTask); - } - - /** - * 获取关机的意图 - * - * @return 关机的意图 - */ - public static Intent getShutdownIntent() { - return getShutdownIntent(false); - } - - /** - * 获取关机的意图 - * - * @param isNewTask 是否开启新的任务栈 - * @return 关机的意图 - */ - public static Intent getShutdownIntent(final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_SHUTDOWN); - return getIntent(intent, isNewTask); - } - - /** - * 获取跳至拨号界面意图 - * @param phoneNumber 电话号码 - * @return 跳至拨号界面意图 - */ - public static Intent getDialIntent(final String phoneNumber) { - return getDialIntent(phoneNumber, false); - } - - /** - * 获取跳至拨号界面意图 - * @param phoneNumber 电话号码 - * @param isNewTask 是否开启新的任务栈 - * @return 跳至拨号界面意图 - */ - public static Intent getDialIntent(final String phoneNumber, final boolean isNewTask) { - Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)); - return getIntent(intent, isNewTask); - } - - /** - * 获取拨打电话意图 - * - * @param phoneNumber 电话号码 - * @return 拨打电话意图 - */ - public static Intent getCallIntent(final String phoneNumber) { - return getCallIntent(phoneNumber, false); - } - - /** - * 获取拨打电话意图 - * - * @param phoneNumber 电话号码 - * @param isNewTask 是否开启新的任务栈 - * @return 拨打电话意图 - */ - public static Intent getCallIntent(final String phoneNumber, final boolean isNewTask) { - Intent intent = new Intent("android.intent.action.CALL", Uri.parse("tel:" + phoneNumber)); - return getIntent(intent, isNewTask); - } - - /** - * 获取发送短信界面的意图 - * @param phoneNumber 接收号码 - * @param content 短信内容 - * @return 发送短信界面的意图 - */ - public static Intent getSendSmsIntent(final String phoneNumber, final String content) { - return getSendSmsIntent(phoneNumber, content, false); - } - - /** - * 获取跳至发送短信界面的意图 - * @param phoneNumber 接收号码 - * @param content 短信内容 - * @param isNewTask 是否开启新的任务栈 - * @return 发送短信界面的意图 - */ - public static Intent getSendSmsIntent(final String phoneNumber, final String content, final boolean isNewTask) { - Uri uri = Uri.parse("smsto:" + phoneNumber); - Intent intent = new Intent(Intent.ACTION_SENDTO, uri); - intent.putExtra("sms_body", content); - return getIntent(intent, isNewTask); - } - - /** - * 获取拍照的意图 - * @param outUri 输出的 uri - * @return 拍照的意图 - */ - public static Intent getCaptureIntent(final Uri outUri) { - return getCaptureIntent(outUri, false); - } - - /** - * 获取拍照的意图 - * @param outUri 输出的 uri - * @param isNewTask 是否开启新的任务栈 - * @return 拍照的意图 - */ - public static Intent getCaptureIntent(final Uri outUri, final boolean isNewTask) { - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - return getIntent(intent, isNewTask); - } - - /** - * 跳转到系统设置页面 - * @param activity - */ - public static void startSysSetting(Activity activity) { -// if(android.os.Build.VERSION.SDK_INT > 10 ) { -// activity.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS)); -// }else { -// activity.startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS)); -// } -// // 跳转到 无线和网络 设置页面(可以设置移动网络,sim卡1,2的移动网络) -// Intent intent = new Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS); -// Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); - - Intent intent = new Intent(Settings.ACTION_SETTINGS); - activity.startActivity(intent); - } - - /** - * 跳转到系统设置页面 - * @param activity - * @param requestCode 回传请求code - */ - public static void startSysSetting(Activity activity, int requestCode) { - Intent intent = new Intent(Settings.ACTION_SETTINGS); - activity.startActivityForResult(intent, requestCode); - } - - /** - * 打开网络设置界面 - 3.0以下打开设置界面 - */ - public static void openWirelessSettings(Activity activity) { - if (android.os.Build.VERSION.SDK_INT > 10) { - activity.startActivity(new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } else { - activity.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } - } - - // == - - private static Intent getIntent(final Intent intent, final boolean isNewTask) { - return isNewTask ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) : intent; - } - -// /** -// * 获取选择照片的 Intent -// * -// * @return -// */ -// public static Intent getPickIntentWithGallery() { -// Intent intent = new Intent(Intent.ACTION_PICK); -// return intent.setType("image*//*"); -// } -// -// /** -// * 获取从文件中选择照片的 Intent -// * -// * @return -// */ -// public static Intent getPickIntentWithDocuments() { -// Intent intent = new Intent(Intent.ACTION_GET_CONTENT); -// return intent.setType("image*//*"); -// } -// -// -// public static Intent buildImageGetIntent(final Uri saveTo, final int outputX, final int outputY, final boolean returnData) { -// return buildImageGetIntent(saveTo, 1, 1, outputX, outputY, returnData); -// } -// -// public static Intent buildImageGetIntent(Uri saveTo, int aspectX, int aspectY, -// int outputX, int outputY, boolean returnData) { -// Intent intent = new Intent(); -// if (Build.VERSION.SDK_INT < 19) { -// intent.setAction(Intent.ACTION_GET_CONTENT); -// } else { -// intent.setAction(Intent.ACTION_OPEN_DOCUMENT); -// intent.addCategory(Intent.CATEGORY_OPENABLE); -// } -// intent.setType("image*//*"); -// intent.putExtra("output", saveTo); -// intent.putExtra("aspectX", aspectX); -// intent.putExtra("aspectY", aspectY); -// intent.putExtra("outputX", outputX); -// intent.putExtra("outputY", outputY); -// intent.putExtra("scale", true); -// intent.putExtra("return-data", returnData); -// intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString()); -// return intent; -// } -// -// public static Intent buildImageCropIntent(final Uri uriFrom, final Uri uriTo, final int outputX, final int outputY, final boolean returnData) { -// return buildImageCropIntent(uriFrom, uriTo, 1, 1, outputX, outputY, returnData); -// } -// -// public static Intent buildImageCropIntent(Uri uriFrom, Uri uriTo, int aspectX, int aspectY, -// int outputX, int outputY, boolean returnData) { -// Intent intent = new Intent("com.android.camera.action.CROP"); -// intent.setDataAndType(uriFrom, "image*//*"); -// intent.putExtra("crop", "true"); -// intent.putExtra("output", uriTo); -// intent.putExtra("aspectX", aspectX); -// intent.putExtra("aspectY", aspectY); -// intent.putExtra("outputX", outputX); -// intent.putExtra("outputY", outputY); -// intent.putExtra("scale", true); -// intent.putExtra("return-data", returnData); -// intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString()); -// return intent; -// } -// -// public static Intent buildImageCaptureIntent(final Uri uri) { -// Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); -// intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); -// return intent; -// } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/KeyBoardUtils.java b/DevLibUtils/src/main/java/dev/utils/app/KeyBoardUtils.java deleted file mode 100644 index 64f032209c..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/KeyBoardUtils.java +++ /dev/null @@ -1,522 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.graphics.Rect; -import android.os.Handler; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; - -import java.lang.reflect.Field; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 软键盘相关辅助类 - * Created by Ttt - */ -public final class KeyBoardUtils { - - private KeyBoardUtils() { - } - - // 日志TAG - private static final String TAG = KeyBoardUtils.class.getSimpleName(); - - /** 默认延迟时间 */ - private static int DELAY_MILLIS = 300; - /** 键盘显示 */ - public static final int KEYBOARD_DISPLAY = 930; - /** 键盘隐藏 */ - public static final int KEYBOARD_HIDE = 931; - - - /** - * 避免输入法面板遮挡 manifest.xml 中 activity 中设置 - * android:windowSoftInputMode="adjustPan" - * android:windowSoftInputMode="adjustUnspecified|stateHidden" - */ - - // == ----------------------------------------- == - - /** - * 设置延迟时间 - * @param delayMillis - */ - public static void setDelayMillis(int delayMillis) { - DELAY_MILLIS = delayMillis; - } - - // ================ - // == 打开软键盘 == - // ================ - - /** - * 打开软键盘 - * @param mEditText 输入框 - */ - public static void openKeyboard(EditText mEditText) { - if (mEditText != null) { - try { - InputMethodManager imm = (InputMethodManager) mEditText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED); - imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "openKeyboard"); - } - } - } - - /** - * 打开软键盘 - * @param mEditText - * @param vHandler - */ - public static void openKeyboard(final EditText mEditText, Handler vHandler){ - openKeyboard(mEditText, vHandler, DELAY_MILLIS); - } - - /** - * 打开软键盘 - * @param mEditText - * @param vHandler - * @param delayMillis - */ - public static void openKeyboard(final EditText mEditText, Handler vHandler, int delayMillis){ - if (vHandler != null && mEditText != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - try { - mEditText.requestFocus(); - mEditText.setSelection(mEditText.getText().toString().length()); - } catch (Exception e){ - } - openKeyboard(mEditText); - } - }, delayMillis); - } - } - - // - - - /** - * 打开软键盘 - */ - public static void openKeyboard() { - try { - InputMethodManager imm = (InputMethodManager) DevUtils.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "openKeyboard"); - } - } - - /** - * 打开软键盘 - * @param vHandler - */ - public static void openKeyboard(Handler vHandler){ - openKeyboard(vHandler, DELAY_MILLIS); - } - - /** - * 打开软键盘 - * @param vHandler - * @param delayMillis - */ - public static void openKeyboard(Handler vHandler, int delayMillis){ - if (vHandler != null && DevUtils.getContext() != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - openKeyboard(); - } - }, delayMillis); - } - } - - // ================ - // == 关闭软键盘 == - // ================ - - /** - * 关闭软键盘 - * @param mEditText 输入框 - */ - public static void closeKeyboard(EditText mEditText) { - if (mEditText != null) { - try { - InputMethodManager imm = (InputMethodManager) mEditText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeKeyboard"); - } - } - } - - /** - * 关闭软键盘 - */ - public static void closeKeyboard() { - if (DevUtils.getContext() != null) { - try { - InputMethodManager imm = (InputMethodManager) DevUtils.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeKeyboard"); - } - } - } - - /** - * 关闭软键盘 - * @param mActivity 当前页面 - */ - public static void closeKeyboard(Activity mActivity) { - if (mActivity != null) { - try { - InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mActivity.getWindow().peekDecorView().getWindowToken(), 0); - //imm.hideSoftInputFromWindow(mActivity.getCurrentFocus().getWindowToken(), 0); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeKeyboard"); - } - } - } - - /** - * 关闭dialog中打开的键盘 - * @param mDialog - */ - public static void closeKeyboard(Dialog mDialog) { - if (mDialog != null) { - try { - InputMethodManager imm = (InputMethodManager) mDialog.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mDialog.getWindow().peekDecorView().getWindowToken(), 0); - //imm.hideSoftInputFromWindow(mDialog.getCurrentFocus().getWindowToken(), 0); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeKeyboard"); - } - } - } - - // == - - /** - * 关闭软键盘 - 特殊处理 - * @param mEditText - * @param mDialog - */ - public static void closeKeyBoardSpecial(EditText mEditText, Dialog mDialog){ - try { - // 关闭输入法 - closeKeyboard(); - // 关闭输入法 - closeKeyboard(mEditText); - // 关闭输入法 - closeKeyboard(mDialog); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "closeKeyBoardSpecial"); - } - } - - public static void closeKeyBoardSpecial(final EditText mEditText, final Dialog mDialog, Handler vHandler){ - closeKeyBoardSpecial(mEditText, mDialog, vHandler, DELAY_MILLIS); - } - - /** - * 关闭软键盘 - 特殊处理(两个都关闭) - * @param mEditText - * @param mDialog - * @param vHandler - * @param delayMillis - */ - public static void closeKeyBoardSpecial(final EditText mEditText, final Dialog mDialog, Handler vHandler, int delayMillis){ - if (vHandler != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - closeKeyBoardSpecial(mEditText, mDialog); - } - }, delayMillis); - } - } - - // - - - public static void closeKeyboard(final EditText mEditText, Handler vHandler){ - closeKeyboard(mEditText, vHandler, DELAY_MILLIS); - } - - /** - * 关闭软键盘 - * @param mEditText - * @param vHandler - * @param delayMillis - */ - public static void closeKeyboard(final EditText mEditText, Handler vHandler, int delayMillis){ - if (vHandler != null && mEditText != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - closeKeyboard(mEditText); - } - }, delayMillis); - } - } - - public static void closeKeyboard(Handler vHandler){ - closeKeyboard(vHandler, DELAY_MILLIS); - } - - /** - * 关闭软键盘 - * @param vHandler - * @param delayMillis - */ - public static void closeKeyboard(Handler vHandler, int delayMillis){ - if (vHandler != null && DevUtils.getContext() != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - closeKeyboard(); - } - }, delayMillis); - } - } - - public static void closeKeyboard(final Activity mActivity, Handler vHandler){ - closeKeyboard(mActivity, vHandler, DELAY_MILLIS); - } - - /** - * 关闭软键盘 - * @param mActivity - * @param vHandler - * @param delayMillis - */ - public static void closeKeyboard(final Activity mActivity, Handler vHandler, int delayMillis){ - if (vHandler != null && mActivity != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - closeKeyboard(mActivity); - } - }, delayMillis); - } - } - - public static void closeKeyboard(final Dialog mDialog, Handler vHandler){ - closeKeyboard(mDialog, vHandler, DELAY_MILLIS); - } - - /** - * 关闭软键盘 - * @param mDialog - * @param vHandler - * @param delayMillis - */ - public static void closeKeyboard(final Dialog mDialog, Handler vHandler, int delayMillis){ - if (vHandler != null && mDialog != null){ - // 延迟打开 - vHandler.postDelayed(new Runnable() { - @Override - public void run() { - closeKeyboard(mDialog); - } - }, delayMillis); - } - } - - // == ----------------------------------------- == - - // 下面暂时无法使用,缺少判断键盘是否显示,否则和自动切换无区别 - // InputMethodManager.isActive() (无法获取) - // Activity.getWindow().getAttributes().softInputMode (有些版本可以,不适用) - // ==----== - - /** - * 自动切换键盘状态,如果键盘显示了则隐藏,隐藏着显示 - */ - public static void toggleKeyboard() { - // 程序启动后,自动弹出软键盘,可以通过设置一个时间函数来实现,不能再onCreate里写 - try { - InputMethodManager imm = (InputMethodManager) DevUtils.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "toggleKeyboard"); - } - } - - // ========== 点击非EditText 则隐藏输入法 =============== - - /** - * 某个View里面的子View的View判断 - * @param view - */ - public static void judgeView(View view, final Activity activity) { - if (!(view instanceof EditText)) { - view.setOnTouchListener(new View.OnTouchListener() { - public boolean onTouch(View v, MotionEvent event) { - closeKeyboard(activity); - return false; - } - }); - } - // -- - if (view instanceof ViewGroup) { - for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { - View innerView = ((ViewGroup) view).getChildAt(i); - judgeView(innerView, activity); - } - } - } - - // ========== 输入法隐藏显示 =============== - - /** - * 判断软键盘是否可见 - * @param activity - * @return true : 可见, false : 不可见 - */ - public static boolean isSoftInputVisible(final Activity activity) { - return isSoftInputVisible(activity, 200); - } - - /** - * 判断软键盘是否可见 - * @param activity - * @param minHeightOfSoftInput 软键盘最小高度 - * @return true : 可见, false : 不可见 - */ - public static boolean isSoftInputVisible(final Activity activity, final int minHeightOfSoftInput) { - return getContentViewInvisibleHeight(activity) >= minHeightOfSoftInput; - } - - /** - * 计算View的宽度高度 - * @param activity - * @return - */ - private static int getContentViewInvisibleHeight(final Activity activity) { - try { - final View contentView = activity.findViewById(android.R.id.content); - Rect rect = new Rect(); - contentView.getWindowVisibleDisplayFrame(rect); - return contentView.getRootView().getHeight() - rect.height(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getContentViewInvisibleHeight"); - return 0; - } - } - - /** - * 注册软键盘改变监听器 - * @param activity - * @param listener listener - */ - public static void registerSoftInputChangedListener(final Activity activity, final OnSoftInputChangedListener listener) { - try { - // 获取根View - final View contentView = activity.findViewById(android.R.id.content); - // 添加事件 - contentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (listener != null) { - // 获取高度 - int height = getContentViewInvisibleHeight(activity); - // 判断是否相同 - listener.onSoftInputChanged(height >= 200, height); - } - } - }); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "registerSoftInputChangedListener"); - } - } - - /** - * 注册软键盘改变监听器 - * @param activity - * @param listener listener - */ - public static void registerSoftInputChangedListener2(final Activity activity, final OnSoftInputChangedListener listener) { - final View decorView = activity.getWindow().getDecorView(); - decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if (listener != null){ - try { - Rect rect = new Rect(); - decorView.getWindowVisibleDisplayFrame(rect); - // 计算出可见屏幕的高度 - int displayHight = rect.bottom - rect.top; - // 获得屏幕整体的高度 - int hight = decorView.getHeight(); - // 获得键盘高度 - int keyboardHeight = hight - displayHight; - // 计算一定比例 - boolean visible = (double) displayHight / hight < 0.8; - // 判断是否显示 - listener.onSoftInputChanged(visible, keyboardHeight); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "registerSoftInputChangedListener2"); - } - } - } - }); - } - - /** 输入法弹出、隐藏改变事件 */ - public interface OnSoftInputChangedListener { - - void onSoftInputChanged(boolean visible, int height); - } - - // == - - /** - * 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 - * @param context - */ - public static void fixSoftInputLeaks(final Context context) { - if (context == null) return; - try { - InputMethodManager imm = (InputMethodManager) DevUtils.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - String[] strArr = new String[]{"mCurRootView", "mServedView", "mNextServedView"}; - for (int i = 0; i < 3; i++) { - try { - Field declaredField = imm.getClass().getDeclaredField(strArr[i]); - if (declaredField == null) continue; - if (!declaredField.isAccessible()) { - declaredField.setAccessible(true); - } - Object obj = declaredField.get(imm); - if (obj == null || !(obj instanceof View)) continue; - View view = (View) obj; - if (view.getContext() == context) { - declaredField.set(imm, null); - } else { - return; - } - } catch (Throwable th) { - } - } - } catch (Exception e){ - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/KeyguardUtils.java b/DevLibUtils/src/main/java/dev/utils/app/KeyguardUtils.java deleted file mode 100644 index 4caab7a7e0..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/KeyguardUtils.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.utils.app; - -import android.app.KeyguardManager; -import android.content.Context; -import android.os.Build; -import android.support.annotation.RequiresApi; - -import dev.DevUtils; - -/** - * detail: 锁屏工具类 - 锁屏管理, 锁屏、禁用锁屏,判断是否锁屏 - * Created by Ttt - * - */ -@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) -public final class KeyguardUtils { - - // KeyguardUtils 实例 - private static KeyguardUtils INSTANCE; - - /** 获取 KeyguardUtils 实例 ,单例模式 */ - public static KeyguardUtils getInstance() { - if (INSTANCE == null){ - INSTANCE = new KeyguardUtils(); - } - return INSTANCE; - } - - // 锁屏管理类 - KeyguardManager keyguardManager; - // android-26 开始过时 - KeyguardManager.KeyguardLock keyguardLock; - - /** 构造函数 */ - private KeyguardUtils() { - // 获取系统服务 - keyguardManager = (KeyguardManager) DevUtils.getContext().getSystemService(Context.KEYGUARD_SERVICE); - // 初始化锁 - keyguardLock = keyguardManager.newKeyguardLock("KeyguardUtils"); - } - - /** - * 是否锁屏 - android 4.1以上支持 - * @return - */ - public boolean isKeyguardLocked() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - return false; - } else { - return keyguardManager.isKeyguardLocked(); - } - } - - /** - * 是否有锁屏密码 - android 4.1以上支持 - * @return - */ - public boolean isKeyguardSecure() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - return false; - } else { - return keyguardManager.isKeyguardSecure(); - } - } - - /** - * 是否锁屏 - android 4.1 之前 - * @return - */ - public boolean inKeyguardRestrictedInputMode() { - return keyguardManager.inKeyguardRestrictedInputMode(); - } - - public KeyguardManager getKeyguardManager() { - return keyguardManager; - } - - public void setKeyguardManager(KeyguardManager keyguardManager) { - this.keyguardManager = keyguardManager; - } - - // == - - /** - * 屏蔽系统的屏保 - * 利用 disableKeyguard 解锁, 解锁并不是真正的解锁, 只是把锁屏的界面隐藏掉而已 - */ - public void disableKeyguard() { - keyguardLock.disableKeyguard(); - } - - /** - * 使能显示锁屏界面,如果你之前调用了disableKeyguard()方法取消锁屏界面,那么会马上显示锁屏界面。 - */ - public void reenableKeyguard() { - keyguardLock.reenableKeyguard(); - } - - /** - * 释放资源 - */ - public void release() { - if (keyguardLock != null) { - // 禁用显示键盘锁定 - keyguardLock.reenableKeyguard(); - } - } - - public void newKeyguardLock (String tag){ - keyguardLock = keyguardManager.newKeyguardLock(tag); - } - - public KeyguardManager.KeyguardLock getKeyguardLock() { - return keyguardLock; - } - - public void setKeyguardLock(KeyguardManager.KeyguardLock keyguardLock) { - this.keyguardLock = keyguardLock; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ListenerUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ListenerUtils.java deleted file mode 100644 index 18c514f20a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ListenerUtils.java +++ /dev/null @@ -1,152 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.view.View; -import android.view.Window; - -import java.lang.reflect.Field; - -import dev.utils.LogPrintUtils; - -/** - * detail: 事件工具类 => AppReflectUtils(可以删除) - * Created by Ttt - */ -public final class ListenerUtils { - - private ListenerUtils() { - } - - // 日志TAG - private static final String TAG = ListenerUtils.class.getSimpleName(); - - /** - * 获取 View 设置的 OnTouchListener - * @param view - * @return - */ - public static View.OnTouchListener getTouchListener(View view){ - return (View.OnTouchListener) getListenerInfoListener(view, "mOnTouchListener"); - } - - /** - * 获取 View ListenerInfo 对象(内部类) - * @param view - * @return - */ - public static Object getListenerInfo(View view){ - try { - // 获取 ListenerInfo 对象 - Field infoField = View.class.getDeclaredField("mListenerInfo"); - infoField.setAccessible(true); - return infoField.get(view); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getListenerInfo"); - } - return null; - } - - /** - * 获取 View ListenerInfo 对象内部事件对象 - * @param view - * @param listener - * @return - */ - public static Object getListenerInfoListener(View view, String listener){ - try { - // 获取 ListenerInfo 对象 - Object listenerInfo = getListenerInfo(view); - // 获取 ListenerInfo 对象中的 mOnTouchListener 属性 - Class infoClazz = Class.forName("android.view.View$ListenerInfo"); - Field listenerField = infoClazz.getDeclaredField(listener); - listenerField.setAccessible(true); - // 进行获取返回 - return listenerField.get(listenerInfo); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getListenerInfoListener"); - } - return null; - } - - // = - - /** - * 设置点击事件 - * @param view - * @param onClickListener - * @param viewIds - */ - public static void setOnClicks(final View view, final View.OnClickListener onClickListener, final int... viewIds){ - if (view != null && onClickListener != null && viewIds != null){ - for (int i = 0, len = viewIds.length; i < len; i++){ - View findView = findViewById(view, viewIds[i]); - findView.setOnClickListener(onClickListener); - } - } - } - - /** - * 设置点击事件 - * @param activity - * @param onClickListener - * @param viewIds - */ - public static void setOnClicks(final Activity activity, final View.OnClickListener onClickListener, final int... viewIds){ - if (activity != null && onClickListener != null && viewIds != null){ - for (int i = 0, len = viewIds.length; i < len; i++){ - View findView = findViewById(activity, viewIds[i]); - findView.setOnClickListener(onClickListener); - } - } - } - - /** - * 设置点击事件 - * @param onClickListener - * @param views - */ - public static void setOnClicks(final View.OnClickListener onClickListener, final View... views){ - if (onClickListener != null && views != null){ - for (int i = 0, len = views.length; i < len; i++){ - if (views[i] != null) { - views[i].setOnClickListener(onClickListener); - } - } - } - } - - // == 初始化View操作等 == - - /** - * 初始化View - * @param view - * @param id - * @param - * @return - */ - private static T findViewById(View view, int id) { - return view.findViewById(id); - } - - /** - * 初始化View - * @param window - * @param id - * @param - * @return - */ - private static T findViewById(Window window, int id){ - return window.findViewById(id); - } - - /** - * 初始化View - * @param activity - * @param id - * @param - * @return - */ - private static T findViewById(Activity activity, int id) { - return activity.findViewById(id); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/LocationUtils.java b/DevLibUtils/src/main/java/dev/utils/app/LocationUtils.java deleted file mode 100644 index b7e869e7fa..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/LocationUtils.java +++ /dev/null @@ -1,363 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.location.Address; -import android.location.Criteria; -import android.location.Geocoder; -import android.location.Location; -import android.location.LocationListener; -import android.location.LocationManager; -import android.location.LocationProvider; -import android.os.Bundle; -import android.provider.Settings; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -import static android.content.Context.LOCATION_SERVICE; - -/** - * detail: 定位相关工具类 - * Created by Ttt - */ -public final class LocationUtils { - - private LocationUtils() { - } - - // 日志TAG - private static final String TAG = LocationUtils.class.getSimpleName(); - - /** 时间常量 = 2分钟 */ - private static final int MINUTES_TWO = 1000 * 60 * 2; - /** 定位改变通知事件 */ - private static OnLocationChangeListener mListener; - /** 自定义定位事件 */ - private static CustomLocationListener myLocationListener; - /** 定位管理对象 */ - private static LocationManager mLocationManager; - - /** - * 获取位置, 需要先判断是否开启了定位 - * - * @param context - * @param listener - * @param time 间隔时间 - * @param distance 间隔距离 - * @return {@code Location} - */ - @SuppressLint("MissingPermission") - public static Location getLocation(Context context, LocationListener listener, long time, float distance) { - Location location = null; - try { - mLocationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE); - if (isLocationEnabled()) { - mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, time, distance, listener); - if (mLocationManager != null) { - location = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - if (location != null) { - mLocationManager.removeUpdates(listener); - return location; - } - } - } - //when GPS is enabled. - if (isGpsEnabled()) { - if (location == null) { - mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, time, distance, listener); - if (mLocationManager != null) { - location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (location != null) { - mLocationManager.removeUpdates(listener); - return location; - } - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e,"getLocation"); - } - return location; - } - - /** - * 判断Gps是否可用 - * @return true : 是, false : 否 - */ - public static boolean isGpsEnabled() { - LocationManager lm = (LocationManager) DevUtils.getContext().getSystemService(LOCATION_SERVICE); - return lm != null && lm.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - /** - * 判断定位是否可用 - * @return true : 是, false : 否 - */ - public static boolean isLocationEnabled() { - LocationManager lm = (LocationManager) DevUtils.getContext().getSystemService(LOCATION_SERVICE); - return lm != null && (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER) || lm.isProviderEnabled(LocationManager.GPS_PROVIDER)); - } - - /** - * 打开Gps设置界面 - */ - public static void openGpsSettings() { - Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); - DevUtils.getContext().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); - } - - /** - * 注册 - 使用完记得调用{@link #unregister()} - * 需添加权限 {@code } - * 需添加权限 {@code } - * 需添加权限 {@code } - * 如果{@code minDistance}为0,则通过{@code minTime}来定时更新; - * {@code minDistance}不为0,则以{@code minDistance}为准; - * 两者都为0,则随时刷新。 - * @param minTime 位置信息更新周期(单位:毫秒) - * @param minDistance 位置变化最小距离:当位置距离变化超过此值时,将更新位置信息(单位:米) - * @param listener 位置刷新的回调接口 - * @return true : 初始化成功, false : 初始化失败 - */ - @SuppressLint("MissingPermission") - public static boolean register(long minTime, long minDistance, OnLocationChangeListener listener) { - if (listener == null) return false; - mLocationManager = (LocationManager) DevUtils.getContext().getSystemService(LOCATION_SERVICE); - if (mLocationManager == null || (!mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER) - && !mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))) { - return false; - } - mListener = listener; - String provider = mLocationManager.getBestProvider(getCriteria(), true); - Location location = mLocationManager.getLastKnownLocation(provider); - if (location != null) listener.getLastKnownLocation(location); - if (myLocationListener == null) myLocationListener = new CustomLocationListener(); - mLocationManager.requestLocationUpdates(provider, minTime, minDistance, myLocationListener); - return true; - } - - /** - * 注销 - */ - @SuppressLint("MissingPermission") - public static void unregister() { - if (mLocationManager != null) { - if (myLocationListener != null) { - mLocationManager.removeUpdates(myLocationListener); - myLocationListener = null; - } - mLocationManager = null; - } - if (mListener != null) { - mListener = null; - } - } - - /** - * 设置定位参数 - * @return {@link Criteria} - */ - private static Criteria getCriteria() { - Criteria criteria = new Criteria(); - // 设置定位精确度 Criteria.ACCURACY_COARSE比较粗略,Criteria.ACCURACY_FINE则比较精细 - criteria.setAccuracy(Criteria.ACCURACY_FINE); - // 设置是否要求速度 - criteria.setSpeedRequired(false); - // 设置是否允许运营商收费 - criteria.setCostAllowed(false); - // 设置是否需要方位信息 - criteria.setBearingRequired(false); - // 设置是否需要海拔信息 - criteria.setAltitudeRequired(false); - // 设置对电源的需求 - criteria.setPowerRequirement(Criteria.POWER_LOW); - return criteria; - } - - /** - * 根据经纬度获取地理位置 - * @param latitude 纬度 - * @param longitude 经度 - * @return {@link Address} - */ - public static Address getAddress(double latitude, double longitude) { - Geocoder geocoder = new Geocoder(DevUtils.getContext(), Locale.getDefault()); - try { - List
addresses = geocoder.getFromLocation(latitude, longitude, 1); - if (addresses.size() > 0) return addresses.get(0); - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e,"getAddress"); - } - return null; - } - - /** - * 根据经纬度获取所在国家 - * @param latitude 纬度 - * @param longitude 经度 - * @return 所在国家 - */ - public static String getCountryName(double latitude, double longitude) { - Address address = getAddress(latitude, longitude); - return address == null ? "unknown" : address.getCountryName(); - } - - /** - * 根据经纬度获取所在地 - * @param latitude 纬度 - * @param longitude 经度 - * @return 所在地 - */ - public static String getLocality(double latitude, double longitude) { - Address address = getAddress(latitude, longitude); - return address == null ? "unknown" : address.getLocality(); - } - - /** - * 根据经纬度获取所在街道 - * @param latitude 纬度 - * @param longitude 经度 - * @return 所在街道 - */ - public static String getStreet(double latitude, double longitude) { - Address address = getAddress(latitude, longitude); - return address == null ? "unknown" : address.getAddressLine(0); - } - - /** - * 是否更好的位置 - * @param newLocation The new Location that you want to evaluate - * @param currentBestLocation The current Location fix, to which you want to compare the new one - * @return true : 是, false : 否 - */ - public static boolean isBetterLocation(Location newLocation, Location currentBestLocation) { - if (currentBestLocation == null) { - // A new location is always better than no location - return true; - } - // 检查位置信息的时间间隔 - long timeDelta = newLocation.getTime() - currentBestLocation.getTime(); - boolean isSignificantlyNewer = timeDelta > MINUTES_TWO; - boolean isSignificantlyOlder = timeDelta < -MINUTES_TWO; - boolean isNewer = timeDelta > 0; - - // 如果时间超过2分钟, 则使用新的位置 - if (isSignificantlyNewer) { - return true; - } else if (isSignificantlyOlder) { // 时间超过两分钟 - return false; - } - - // 检查新的位置时间 - int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy()); - boolean isLessAccurate = accuracyDelta > 0; - boolean isMoreAccurate = accuracyDelta < 0; - boolean isSignificantlyLessAccurate = accuracyDelta > 200; - - // 检查旧位置和新位置是否来自同一提供者。 - boolean isFromSameProvider = isSameProvider(newLocation.getProvider(), currentBestLocation.getProvider()); - - // 判断最新的位置 - if (isMoreAccurate) { - return true; - } else if (isNewer && !isLessAccurate) { - return true; - } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) { - return true; - } - return false; - } - - /** - * 是否相同的提供者 - * @param provider0 提供者1 - * @param provider1 提供者2 - * @return true : 是, false : 否 - */ - public static boolean isSameProvider(String provider0, String provider1) { - if (provider0 == null) { - return provider1 == null; - } - return provider0.equals(provider1); - } - - /** - * 自定义定位监听事件 - */ - private static class CustomLocationListener implements LocationListener { - /** - * 当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发 - * @param location 坐标 - */ - @Override - public void onLocationChanged(Location location) { - if (mListener != null) { - mListener.onLocationChanged(location); - } - } - - /** - * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 - * @param provider 提供者 - * @param status 状态 - * @param extras provider可选包 - */ - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - if (mListener != null) { - mListener.onStatusChanged(provider, status, extras); - } - switch (status) { - case LocationProvider.AVAILABLE: - LogPrintUtils.dTag(TAG, "当前GPS状态为可见状态"); - break; - case LocationProvider.OUT_OF_SERVICE: - LogPrintUtils.dTag(TAG, "当前GPS状态为服务区外状态"); - break; - case LocationProvider.TEMPORARILY_UNAVAILABLE: - LogPrintUtils.dTag(TAG, "当前GPS状态为暂停服务状态"); - break; - } - } - - /** provider被enable时触发此函数,比如GPS被打开 */ - @Override - public void onProviderEnabled(String provider) { - } - - /** provider被disable时触发此函数,比如GPS被关闭 */ - @Override - public void onProviderDisabled(String provider) { - } - } - - /** 定位改变事件 */ - public interface OnLocationChangeListener { - - /** - * 获取最后一次保留的坐标 - * @param location 坐标 - */ - void getLastKnownLocation(Location location); - - /** - * 当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发 - * @param location 坐标 - */ - void onLocationChanged(Location location); - - /** - * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 - * @param provider 提供者 - * @param status 状态 - * @param extras provider可选包 - */ - void onStatusChanged(String provider, int status, Bundle extras);//位置状态发生改变 - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ManifestUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ManifestUtils.java deleted file mode 100644 index 58b5e495aa..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ManifestUtils.java +++ /dev/null @@ -1,147 +0,0 @@ -package dev.utils.app; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Android Manifest工具类 - * Created by Ttt - */ -public final class ManifestUtils { - - private ManifestUtils(){ - } - - // 日志TAG - private static final String TAG = ManifestUtils.class.getSimpleName(); - - /** - * 获取 Manifest Meta Data - * @param metaKey - * @return - */ - public static String getMetaData(String metaKey) { - try { - ApplicationInfo appInfo = DevUtils.getContext().getPackageManager().getApplicationInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_META_DATA); - String data = appInfo.metaData.getString(metaKey); - return data; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMetaData"); - } - return null; - } - - // == - - /** - * 获取app版本信息 - * @return 0 = versionName , 1 = versionCode - */ - public static String[] getAppVersion() { - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - String versionName = pi.versionName == null ? "null" : pi.versionName; - String versionCode = pi.versionCode + ""; - - return new String[]{versionName,versionCode}; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersion"); - } - return null; - } - - /** - * 获取app版本号 - * @return 当前版本Code - */ - public static int getAppVersionCode() { - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - return pi.versionCode; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionCode"); - } - return -1; - } - - /** - * 获取app版本信息 - * @return 当前版本信息 - */ - public static String getAppVersionName() { - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - return pi.versionName; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionName"); - } - return null; - } - - // = - - /** - * 获取app版本号 - 内部判断 - * @param packageName - * @return - */ - public static int getAppVersionCode(final String packageName) { - if (isSpace(packageName)) return -1; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? -1 : pi.versionCode; - } catch (PackageManager.NameNotFoundException e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionCode"); - return -1; - } - } - - - /** - * 获取app版本名 - 对外显示 - * @param packageName - * @return - */ - public static String getAppVersionName(final String packageName) { - if (isSpace(packageName)) return null; - try { - PackageManager pm = DevUtils.getContext().getPackageManager(); - PackageInfo pi = pm.getPackageInfo(packageName, 0); - return pi == null ? null : pi.versionName; - } catch (PackageManager.NameNotFoundException e) { - LogPrintUtils.eTag(TAG, e, "getAppVersionName"); - return null; - } - } - - // = - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/MemoryUtils.java b/DevLibUtils/src/main/java/dev/utils/app/MemoryUtils.java deleted file mode 100644 index cd40be9c65..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/MemoryUtils.java +++ /dev/null @@ -1,179 +0,0 @@ -package dev.utils.app; - -import android.annotation.TargetApi; -import android.app.ActivityManager; -import android.content.Context; -import android.os.Build; -import android.text.format.Formatter; - -import java.io.BufferedReader; -import java.io.FileReader; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 获取内存信息 - * Created by Ttt - */ -public final class MemoryUtils { - - private MemoryUtils(){ - } - - // 日志TAG - private static final String TAG = MemoryUtils.class.getSimpleName(); - - /** - * Print memory info. such as: - * MemTotal: 1864292 kB - * MemFree: 779064 kB - * Buffers: 4540 kB - * Cached: 185656 kB - * SwapCached: 13160 kB - * Active: 435588 kB - * Inactive: 269312 kB - * Active(anon): 386188 kB - * Inactive(anon): 132576 kB - * Active(file): 49400 kB - * Inactive(file): 136736 kB - * Unevictable: 2420 kB - * Mlocked: 0 kB - * HighTotal: 1437692 kB - * HighFree: 520212 kB - * LowTotal: 426600 kB - * LowFree: 258852 kB - * SwapTotal: 511996 kB - * SwapFree: 171876 kB - * Dirty: 412 kB - * Writeback: 0 kB - * AnonPages: 511924 kB - * Mapped: 152368 kB - * Shmem: 1636 kB - * Slab: 109224 kB - * SReclaimable: 75932 kB - * SUnreclaim: 33292 kB - * KernelStack: 13056 kB - * PageTables: 28032 kB - * NFS_Unstable: 0 kB - * Bounce: 0 kB - * WritebackTmp: 0 kB - * CommitLimit: 1444140 kB - * Committed_AS: 25977748 kB - * VmallocTotal: 458752 kB - * VmallocUsed: 123448 kB - * VmallocChunk: 205828 kB - */ - public static String printMemInfo() { - try { - FileReader fileReader = new FileReader(MEM_INFO_PATH); - BufferedReader bufferedReader = new BufferedReader(fileReader, 4 * 1024); - StringBuffer stringBuffer = new StringBuffer(); - String str; - while ((str = bufferedReader.readLine()) != null) { - // 追加保存内容 - stringBuffer.append(str); - } - bufferedReader.close(); - return stringBuffer.toString(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "printMemInfo"); - } - return ""; - } - - /** - * 获取内存信息 - * @return - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static ActivityManager.MemoryInfo getMemoryInfo() { - ActivityManager am = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); - am.getMemoryInfo(mi); - return mi; - } - - /** - * Print Memory info. - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static ActivityManager.MemoryInfo printMemoryInfo(Context context) { - ActivityManager.MemoryInfo mi = getMemoryInfo(); - StringBuilder sBuilder = new StringBuilder(); - sBuilder.append("_______ Memory : "); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - sBuilder.append("\ntotalMem :").append(mi.totalMem); - } - sBuilder.append("\navailMem :").append(mi.availMem); - sBuilder.append("\nlowMemory :").append(mi.lowMemory); - sBuilder.append("\nthreshold :").append(mi.threshold); - return mi; - } - - /** - * Get available memory info. - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static String getAvailMemory() {// 获取android 当前可用内存大小 - ActivityManager am = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); - am.getMemoryInfo(mi); // mi.availMem; 当前系统的可用内存 - // 将获取的内存大小规格化 - return Formatter.formatFileSize(DevUtils.getContext(), mi.availMem); - } - - - // 内存信息文件地址 - private static final String MEM_INFO_PATH = "/proc/meminfo"; - // 获取内存总大小 - public static final String MEMTOTAL = "MemTotal"; - // 获取可用内存 - public static final String MEMAVAILABLE = "MemAvailable"; - - - /** - * 获取总内存大小 - * @return - */ - public static String getTotalMemory() { - return getMemInfoIype(MEMTOTAL); - } - - /** - * 获取可用内存大小 - * @return - */ - public static String getMemoryAvailable() { - return getMemInfoIype(MEMAVAILABLE); - } - - /** - * 获取 type info - * @param type - * @return - */ - public static String getMemInfoIype(String type) { - try { - FileReader fileReader = new FileReader(MEM_INFO_PATH); - BufferedReader bufferedReader = new BufferedReader(fileReader, 4 * 1024); - String str = null; - while ((str = bufferedReader.readLine()) != null) { - if (str.contains(type)) { - break; - } - } - bufferedReader.close(); - /* \\s表示 空格,回车,换行等空白符, - +号表示一个或多个的意思 */ - String[] array = str.split("\\s+"); - // 获得系统总内存,单位是KB,乘以1024转换为Byte - long length = Long.valueOf(array[1]).longValue() * 1024; - return android.text.format.Formatter.formatFileSize(DevUtils.getContext(), length); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMemInfoIype"); - } - return "unknown"; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/NetWorkUtils.java b/DevLibUtils/src/main/java/dev/utils/app/NetWorkUtils.java deleted file mode 100644 index ffc4636fa2..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/NetWorkUtils.java +++ /dev/null @@ -1,398 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.NetworkInfo.State; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.support.annotation.RequiresPermission; -import android.telephony.TelephonyManager; - -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Enumeration; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 网络管理工具类 - * Created by Ttt - */ -public final class NetWorkUtils { - - private NetWorkUtils() { - } - - // 日志TAG - private static final String TAG = NetWorkUtils.class.getSimpleName(); - - /** - * 获取移动网络打开状态(默认属于未打开) - * @return - */ - public static boolean getMobileDataEnabled() { - try { - // 移动网络开关状态 - boolean mState; - // 属于5.0以下的使用 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // 获取网络连接状态 - ConnectivityManager cManager = (ConnectivityManager) DevUtils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); - // 反射获取方法 - Method method = cManager.getClass().getMethod("getMobileDataEnabled"); - // 调用方法,获取状态 - mState = (Boolean) method.invoke(cManager); - } else { - TelephonyManager tm = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - // 反射获取方法 - Method method = tm.getClass().getDeclaredMethod("getDataEnabled"); - // 调用方法,获取状态 - mState = (Boolean) method.invoke(tm); - } - // 返回移动网络开关状态 - return mState; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMobileDataEnabled"); - } - return false; - } - - /** - * 设置移动网络开关(无判断是否已开启移动网络) - 实际无效果, 非系统应用无权限 - * @param isOpen 是否打开移动网络 - * @return 是否执行正常 - * hint: - * 需系统应用、添加权限 - * - */ - public static boolean setMobileDataEnabled(boolean isOpen) { - try { - // 属于5.0以下的使用 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // 获取网络连接状态 - ConnectivityManager cManager = (ConnectivityManager) DevUtils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); - // 通过反射设置移动网络 - Method mMethod = ConnectivityManager.class.getDeclaredMethod("setMobileDataEnabled", Boolean.TYPE); - // 设置移动网络 - mMethod.invoke(cManager, isOpen); - } else { // 需要 android.Manifest.permission.MODIFY_PHONE_STATE 权限, 普通APP无法获取 - TelephonyManager tm = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - // 通过反射设置移动网络 - Method mMethod = tm.getClass().getDeclaredMethod("setDataEnabled", boolean.class); - // 设置移动网络 - mMethod.invoke(tm, isOpen); - } - } catch (Exception e) { // 开启移动网络失败 - LogPrintUtils.eTag(TAG, e, "setMobileDataEnabled"); - return false; - } - return true; - } - - /** - * 判断是否连接了网络 - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean isConnect() { - // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理) - try { - ConnectivityManager cManager = (ConnectivityManager) DevUtils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); - if (cManager != null) { - // 获取网络连接管理的对象 - NetworkInfo nInfo = cManager.getActiveNetworkInfo(); - if (nInfo != null && nInfo.isConnected()) { - // 判断当前网络是否已经连接 - if (nInfo.getState() == State.CONNECTED) { - return true; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isConnect"); - } - return false; - } - - /** - * 获取连接的网络类型 - * @return -1 = 等于未知 , 1 = Wifi, 2 = 移动网络 - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static int getConnectType() { - // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理) - try { - // 获取网络连接状态 - ConnectivityManager cManager = (ConnectivityManager) DevUtils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE); - // 判断连接的是否wifi - State wifi = cManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState(); - // 判断是否连接上 - if (wifi == State.CONNECTED || wifi == State.CONNECTING) { - return 1; - } else { - // 判断连接的是否移动网络 - State mobile = cManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState(); - // 判断移动网络是否连接上 - if (mobile == State.CONNECTED || mobile == State.CONNECTING) { - return 2; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getConnectType"); - } - return -1; - } - - /** - * 判断是否连接Wifi(连接上、连接中) - * @return - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean isConnWifi() { - return (getConnectType() == 1); - } - - /** - * 判断是否连接移动网络(连接上、连接中) - * @return - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean isConnMobileData() { - return (getConnectType() == 2); - } - - // === - - /** 网络连接类型 */ - public enum NetworkType { - NETWORK_WIFI, - NETWORK_4G, - NETWORK_3G, - NETWORK_2G, - NETWORK_UNKNOWN, - NETWORK_NO - } - - /** - * 判断网络是否可用 - * @return true: 可用 false: 不可用 - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean isAvailable() { - NetworkInfo info = getActiveNetworkInfo(); - return info != null && info.isAvailable(); - } - - /** - * 获取活动网络信息 - * @return NetworkInfo - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - private static NetworkInfo getActiveNetworkInfo() { - if (DevUtils.getContext() != null){ - try { - return ((ConnectivityManager) DevUtils.getContext().getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getActiveNetworkInfo"); - } - } - return null; - } - - // == - - /** - * 判断是否4G网络 - * @return - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean is4G(){ - return getNetworkType() == NetworkType.NETWORK_4G; - } - - /** - * 判断wifi是否打开 - * @return - */ - public static boolean getWifiEnabled() { - try { - @SuppressLint("WifiManagerLeak") - WifiManager wifiManager = (WifiManager) DevUtils.getContext().getSystemService(Context.WIFI_SERVICE); - return wifiManager.isWifiEnabled(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getWifiEnabled"); - } - return false; - } - - /** - * 判断wifi数据是否可用 - * @return - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static boolean isWifiAvailable() { - return getWifiEnabled() && isAvailable(); - } - - /** - * 获取网络运营商名称 - 中国移动、如中国联通、中国电信 - * @return 运营商名称 - */ - public static String getNetworkOperatorName() { - try { - TelephonyManager tm = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return tm != null ? tm.getNetworkOperatorName() : null; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getNetworkOperatorName"); - } - return null; - } - - // == - - /** - * 获取当前网络类型 - * @return DevUtils.getContext() - * @return 网络类型 - */ - @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - public static NetworkType getNetworkType() { - // 默认网络类型 - NetworkType netType = NetworkType.NETWORK_NO; - // 获取网络信息 - NetworkInfo networkInfo = getActiveNetworkInfo(); - // 判断是否可用 - if (networkInfo != null && networkInfo.isAvailable()) { // 同 getNetworkClass 方法 - // 属于可用则修改为未知 - netType = NetworkType.NETWORK_UNKNOWN; - // 获取类型 - switch (networkInfo.getType()){ - case ConnectivityManager.TYPE_WIFI: // 属于Wifi - netType = NetworkType.NETWORK_WIFI; - break; - case ConnectivityManager.TYPE_MOBILE: // 属于手机网络 - switch (networkInfo.getSubtype()) { - // == 2G网络 == - case TelephonyManager.NETWORK_TYPE_GSM: - case TelephonyManager.NETWORK_TYPE_GPRS: - case TelephonyManager.NETWORK_TYPE_CDMA: - case TelephonyManager.NETWORK_TYPE_EDGE: - case TelephonyManager.NETWORK_TYPE_1xRTT: - case TelephonyManager.NETWORK_TYPE_IDEN: - netType = NetworkType.NETWORK_2G; - break; - // == 3G网络 == - case TelephonyManager.NETWORK_TYPE_TD_SCDMA: - case TelephonyManager.NETWORK_TYPE_EVDO_A: - case TelephonyManager.NETWORK_TYPE_UMTS: - case TelephonyManager.NETWORK_TYPE_EVDO_0: - case TelephonyManager.NETWORK_TYPE_HSDPA: - case TelephonyManager.NETWORK_TYPE_HSUPA: - case TelephonyManager.NETWORK_TYPE_HSPA: - case TelephonyManager.NETWORK_TYPE_EVDO_B: - case TelephonyManager.NETWORK_TYPE_EHRPD: - case TelephonyManager.NETWORK_TYPE_HSPAP: - netType = NetworkType.NETWORK_3G; - break; - // == 4G网络 == - case TelephonyManager.NETWORK_TYPE_LTE: - case TelephonyManager.NETWORK_TYPE_IWLAN: - //case TelephonyManager.NETWORK_TYPE_LTE_CA: // 19 - case 19: - netType = NetworkType.NETWORK_4G; - break; - default: // 其他判断 - try { - // 判断子类名字 - String subtypeName = networkInfo.getSubtypeName(); - // == 3G 网络 == - if (subtypeName.equalsIgnoreCase("TD-SCDMA") - || subtypeName.equalsIgnoreCase("WCDMA") - || subtypeName.equalsIgnoreCase("CDMA2000")) { - netType = NetworkType.NETWORK_3G; - } else { - netType = NetworkType.NETWORK_UNKNOWN; - } - } catch (Exception e) { - // 保存未知 - netType = NetworkType.NETWORK_UNKNOWN; - } - break; - } - break; - } - } - return netType; - } - - /** - * 获取域名ip地址 - * @param domain 域名 www.baidu.com 不需要加上http - * @return ip地址 - */ - public static String getDomainAddress(final String domain) { - try { - ExecutorService exec = Executors.newCachedThreadPool(); - Future fs = exec.submit(new Callable() { - @Override - public String call() throws Exception { - InetAddress inetAddress; - try { - inetAddress = InetAddress.getByName(domain); - return inetAddress.getHostAddress(); - } catch (UnknownHostException e) { - LogPrintUtils.eTag(TAG, e, "getDomainAddress"); - } - return null; - } - }); - return fs.get(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDomainAddress"); - } - return null; - } - - /** - * 获取IP地址 - * @param useIPv4 是否用IPv4 - * @return IP地址 - */ - public static String getIPAddress(boolean useIPv4) { - try { - Enumeration nis = NetworkInterface.getNetworkInterfaces(); - while (nis.hasMoreElements()) { - NetworkInterface ni = nis.nextElement(); - // 防止小米手机返回10.0.2.15 - if (!ni.isUp()) continue; - for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements(); ) { - InetAddress inetAddress = addresses.nextElement(); - if (!inetAddress.isLoopbackAddress()) { - String hostAddress = inetAddress.getHostAddress(); - boolean isIPv4 = hostAddress.indexOf(':') < 0; - if (useIPv4) { - if (isIPv4) return hostAddress; - } else { - if (!isIPv4) { - int index = hostAddress.indexOf('%'); - return index < 0 ? hostAddress.toUpperCase() : hostAddress.substring(0, index).toUpperCase(); - } - } - } - } - } - } catch (SocketException e) { - LogPrintUtils.eTag(TAG, e, "getIPAddress"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/NotificationUtils.java b/DevLibUtils/src/main/java/dev/utils/app/NotificationUtils.java deleted file mode 100644 index 43b22ed0fa..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/NotificationUtils.java +++ /dev/null @@ -1,300 +0,0 @@ -package dev.utils.app; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.os.Build; - -import dev.DevUtils; - -/** - * detail: 通知栏管理类 - * Created by Ttt - * https://blog.csdn.net/hss01248/article/details/55096553 - * https://www.jianshu.com/p/cf5f6c30019d - */ -public final class NotificationUtils { - - private NotificationUtils(){ - } - - // 通知栏管理类 - private static NotificationManager mNotificationManager = null; - - /** - * 获取通知栏管理类 - * @return - */ - public static NotificationManager getNotificationManager() { - if (mNotificationManager == null){ - mNotificationManager = (NotificationManager) DevUtils.getContext().getSystemService(Context.NOTIFICATION_SERVICE); - } - return mNotificationManager; - } - - /** - * 移除通知 - 移除所有通知(只是针对当前Context下的Notification) - */ - public static void cancelAll(){ - if (getNotificationManager() != null){ - mNotificationManager.cancelAll(); - } - } - - /** - * 移除通知 - 移除标记为id的通知 (只是针对当前Context下的所有Notification) - * @param args - */ - public static void cancel(final int... args){ - if (getNotificationManager() != null && args != null){ - for (int id : args){ - mNotificationManager.cancel(id); - } - } - } - - /** - * 移除通知 - 移除标记为id的通知 (只是针对当前Context下的所有Notification) - * @param tag - * @param id - */ - public static void cancel(final String tag, final int id){ - if (getNotificationManager() != null && tag != null){ - mNotificationManager.cancel(tag, id); - } - } - - /** - * 进行通知 - * @param id - * @param notification - * @return - */ - public static boolean notify(final int id, final Notification notification){ - if (getNotificationManager() != null && notification != null){ - mNotificationManager.notify(id, notification); - return true; - } - return false; - } - - /** - * 进行通知 - * @param tag - * @param id - * @param notification - * @return - */ - public static boolean notify(final String tag, final int id, final Notification notification){ - if (getNotificationManager() != null && tag != null && notification != null){ - mNotificationManager.notify(tag, id, notification); - return true; - } - return false; - } - - // == 封装外部调用 == - -// // 使用自定义View -// RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.xxx); -// // 设置View -// Notification.builder.setContent(remoteViews); - - /** - * 获取跳转id - * @param intent - * @param id - * @return - */ - public static PendingIntent crePendingIntent(Intent intent, int id){ - /* 跳转Intent */ - PendingIntent pIntent = PendingIntent.getActivity(DevUtils.getContext(), id, intent, PendingIntent.FLAG_UPDATE_CURRENT); - return pIntent; - } - - /** - * 创建通知栏对象 - * @param icon - * @param title - * @param msg - * @return - */ - public static Notification creNotification(int icon, String title, String msg) { - return creNotification(null, icon, title, title, msg, true, VibratePattern.obtain(0, 100, 300), LightPattern.obtain(Color.WHITE, 1000, 1000)); - } - - /** - * 创建通知栏对象 - * @param icon - * @param title - * @param msg - * @param vibratePattern - * @param lightPattern - * @return - */ - public static Notification creNotification(int icon, String title, String msg, VibratePattern vibratePattern, LightPattern lightPattern) { - return creNotification(null, icon, title, title, msg, true, vibratePattern, lightPattern); - } - - /** - * 创建通知栏对象 - * @param pendingIntent - * @param icon - * @param ticker - * @param title - * @param msg - * @param isAutoCancel - * @param vibratePattern - * @param lightPattern - * @return - */ - public static Notification creNotification(PendingIntent pendingIntent, int icon, String ticker, String title, String msg, boolean isAutoCancel, VibratePattern vibratePattern, LightPattern lightPattern) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - Notification.Builder builder = new Notification.Builder(DevUtils.getContext()); - // 点击通知执行intent - builder.setContentIntent(pendingIntent); - // 设置图标 - builder.setSmallIcon(icon); - // 设置图标 - builder.setLargeIcon(BitmapFactory.decodeResource(DevUtils.getContext().getResources(), icon)); - // 指定通知的ticker内容,通知被创建的时候,在状态栏一闪而过,属于瞬时提示信息。 - builder.setTicker(ticker); - // 设置标题 - builder.setContentTitle(title); - // 设置内容 - builder.setContentText(msg); - // 设置消息提醒,震动 | 声音 - builder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND); - // 将AutoCancel设为true后,当你点击通知栏的notification后,它会自动被取消消失 - builder.setAutoCancel(isAutoCancel); - // 设置时间 - builder.setWhen(System.currentTimeMillis()); - // 设置震动参数 - if (vibratePattern != null && !vibratePattern.isEmpty()){ - builder.setVibrate(vibratePattern.vibrates); - } - // 设置 led 灯参数 - if (lightPattern != null){ - builder.setLights(lightPattern.argb, lightPattern.durationMS, lightPattern.startOffMS); - } - // = 初始化 Notification 对象 = - Notification baseNF; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { - baseNF = builder.getNotification(); - } else { - baseNF = builder.build(); - } - return baseNF; - } else { -// // https://www.cnblogs.com/Arture/p/5523695.html -// // -- 初始化通知信息实体类 -- -// Notification notification = new Notification(); -// // 设置图标 -// notification.icon = icon; -// // 设置图标 -// notification.largeIcon = BitmapFactory.decodeResource(DevUtils.getContext().getResources(), icon); -// // 指定通知的ticker内容,通知被创建的时候,在状态栏一闪而过,属于瞬时提示信息。 -// notification.tickerText = title; -// // 设置时间 -// notification.when = System.currentTimeMillis(); -// // 设置消息提醒,震动 | 声音 -// notification.defaults = Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND; -// // 点击了此通知则取消该通知栏 -// if (isAutoCancel) { -// notification.flags |= Notification.FLAG_AUTO_CANCEL; -// } -// // 设置震动参数 -// if (vibratePattern != null && !vibratePattern.isEmpty()){ -// notification.vibrate = vibratePattern.vibrates; -// } -// // 设置 led 灯参数 -// if (lightPattern != null){ -// try { -// notification.ledARGB = lightPattern.argb; // 控制 LED 灯的颜色,一般有红绿蓝三种颜色可选 -// notification.ledOffMS = lightPattern.startOffMS; // 指定 LED 灯暗去的时长,也是以毫秒为单位 -// notification.ledOnMS = lightPattern.durationMS; // 指定 LED 灯亮起的时长,以毫秒为单位 -// notification.flags = Notification.FLAG_SHOW_LIGHTS; -// } catch (Exception e){ -// } -// } -// // 设置标题内容等 - 已经移除, 现在都是支持 4.0以上, 不需要兼容处理 -// notification.setLatestEventInfo(DevUtils.getContext(), title, msg, pendingIntent); -// return notification; - } - return null; - } - - /** - * detail: 设置通知栏 Led 灯参数实体类 - * Created by Ttt - */ - public static class LightPattern { - /** - * 手机处于锁屏状态时, LED灯就会不停地闪烁, 提醒用户去查看手机,下面是绿色的灯光一 闪一闪的效果 - */ - private int argb = 0; // 控制 LED 灯的颜色,一般有红绿蓝三种颜色可选 - private int startOffMS = 0; // 指定 LED 灯暗去的时长,也是以毫秒为单位 - private int durationMS = 0; // 指定 LED 灯亮起的时长,以毫秒为单位 - - private LightPattern(int argb, int startOffMS, int durationMS) { - this.argb = argb; - this.startOffMS = startOffMS; - this.durationMS = durationMS; - } - - /** - * 获取 Led 配置参数 - * @param argb 颜色 - * @param startOffMS 开始时间 - * @param durationMS 持续时间 - * @return - */ - public static LightPattern obtain(int argb, int startOffMS, int durationMS) { - return new LightPattern(argb, startOffMS, durationMS); - } - } - - /** - * detail: 设置通知栏 震动参数实体类 - * Created by Ttt - */ - public static class VibratePattern { - /** - * vibrate 属性是一个长整型的数组,用于设置手机静止和振动的时长,以毫秒为单位。 - * 参数中下标为0的值表示手机静止的时长,下标为1的值表示手机振动的时长, 下标为2的值又表示手机静止的时长,以此类推。 - */ - // long[] vibrates = { 0, 1000, 1000, 1000 }; - private long[] vibrates = null; - - private VibratePattern(long[] vibrates) { - this.vibrates = vibrates; - } - - /** - * 判断是否为null - * @return - */ - public boolean isEmpty(){ - if (vibrates != null){ - if (vibrates.length != 0){ - return false; - } - } - return true; - } - - /** - * 获取 震动时间 配置参数 - * @param args - * @return - */ - public static VibratePattern obtain(long... args) { - return new VibratePattern(args); - } - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/OSUtils.java b/DevLibUtils/src/main/java/dev/utils/app/OSUtils.java deleted file mode 100644 index c37c46371e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/OSUtils.java +++ /dev/null @@ -1,361 +0,0 @@ -package dev.utils.app; - -import android.os.Environment; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import dev.utils.LogPrintUtils; - -/** - * detail: 工具类: OS 系统相关 - * Created by Ttt - * 简介 : 由于国内定制系统的泛滥, 不同定制系统的一些功能或实现方法会有所不同, 如果需要做到足够好的适配工作, 需要 对不同的定制系统做一些专门的适配. - * API : 获取 ROM 类型等 - */ -public final class OSUtils { - - private OSUtils() { - } - - // 日志TAG - private static final String TAG = OSUtils.class.getSimpleName(); - - /** ROM 类型 */ - private static final ROM ROM_TYPE = initRomType(); - - private static final String KEY_DISPLAY_ID = "ro.build.display.id"; - private static final String KEY_BASE_OS_VERSION = "ro.build.version.base_os"; - private static final String KEY_CLIENT_ID_BASE = "ro.com.google.clientidbase"; - - // 小米 : MIUI - private static final String KEY_MIUI_VERSION = "ro.build.version.incremental"; // "7.6.15" - private static final String KEY_MIUI_VERSION_NANE = "ro.miui.ui.version.name"; // "V8" - private static final String KEY_MIUI_VERSION_CODE = "ro.miui.ui.version.code"; // "6" - private static final String VALUE_MIUI_CLIENT_ID_BASE = "android-xiaomi"; - - // 华为 : EMUI - private static final String KEY_EMUI_VERSION = "ro.build.version.emui"; // "EmotionUI_3.0" - private static final String KEY_EMUI_API_LEVEL = "ro.build.hw_emui_api_level"; // - private static final String KEY_EMUI_SYSTEM_VERSION = "ro.confg.hw_systemversion"; // "T1-A21wV100R001C233B008_SYSIMG" - - // 魅族 : Flyme - private static final String KEY_FLYME_PUBLISHED = "ro.flyme.published"; // "true" - private static final String KEY_FLYME_SETUP = "ro.meizu.setupwizard.flyme"; // "true" - private static final String VALUE_FLYME_DISPLAY_ID_CONTAIN = "Flyme"; // "Flyme OS 4.5.4.2U" - - // OPPO : ColorOS - private static final String KEY_COLOROS_VERSION = "ro.oppo.theme.version"; // "703" - private static final String KEY_COLOROS_THEME_VERSION = "ro.oppo.version"; // "" - private static final String KEY_COLOROS_ROM_VERSION = "ro.rom.different.version"; // "ColorOS2.1" - private static final String VALUE_COLOROS_BASE_OS_VERSION_CONTAIN = "OPPO"; // "OPPO/R7sm/R7sm:5.1.1/LMY47V/1440928800:user/release-keys" - private static final String VALUE_COLOROS_CLIENT_ID_BASE = "android-oppo"; - - // vivo : FuntouchOS - private static final String KEY_FUNTOUCHOS_BOARD_VERSION = "ro.vivo.board.version"; // "MD" - private static final String KEY_FUNTOUCHOS_OS_NAME = "ro.vivo.os.name"; // "Funtouch" - private static final String KEY_FUNTOUCHOS_OS_VERSION = "ro.vivo.os.version"; // "3.0" - private static final String KEY_FUNTOUCHOS_DISPLAY_ID = "ro.vivo.os.build.display.id"; // "FuntouchOS_3.0" - private static final String KEY_FUNTOUCHOS_ROM_VERSION = "ro.vivo.rom.version"; // "rom_3.1" - private static final String VALUE_FUNTOUCHOS_CLIENT_ID_BASE = "android-vivo"; - - // Samsung - private static final String VALUE_SAMSUNG_BASE_OS_VERSION_CONTAIN = "samsung"; // "samsung/zeroltezc/zeroltechn:6.0.1/MMB29K/G9250ZCU2DQD1:user/release-keys" - private static final String VALUE_SAMSUNG_CLIENT_ID_BASE = "android-samsung"; - - // Sony - private static final String KEY_SONY_PROTOCOL_TYPE = "ro.sony.irremote.protocol_type"; // "2" - private static final String KEY_SONY_ENCRYPTED_DATA = "ro.sony.fota.encrypteddata"; // "supported" - private static final String VALUE_SONY_CLIENT_ID_BASE = "android-sonyericsson"; - - // 乐视 : eui - private static final String KEY_EUI_VERSION = "ro.letv.release.version"; // "5.9.023S" - private static final String KEY_EUI_VERSION_DATE = "ro.letv.release.version_date"; // "5.9.023S_03111" - private static final String KEY_EUI_NAME = "ro.product.letv_name"; // "乐1s" - private static final String KEY_EUI_MODEL = "ro.product.letv_model"; // "Letv X500" - - // 金立 : amigo - private static final String KEY_AMIGO_ROM_VERSION = "ro.gn.gnromvernumber"; // "GIONEE ROM5.0.16" - private static final String KEY_AMIGO_SYSTEM_UI_SUPPORT = "ro.gn.amigo.systemui.support"; // "yes" - private static final String VALUE_AMIGO_DISPLAY_ID_CONTAIN = "amigo"; // "amigo3.5.1" - private static final String VALUE_AMIGO_CLIENT_ID_BASE = "android-gionee"; - - // 酷派 : yulong - private static final String KEY_YULONG_VERSION_RELEASE = "ro.yulong.version.release"; // "5.1.046.P1.150921.8676_M01" - private static final String KEY_YULONG_VERSION_TAG = "ro.yulong.version.tag"; // "LC" - private static final String VALUE_YULONG_CLIENT_ID_BASE = "android-coolpad"; - - // HTC : Sense - private static final String KEY_SENSE_BUILD_STAGE = "htc.build.stage"; // "2" - private static final String KEY_SENSE_BLUETOOTH_SAP = "ro.htc.bluetooth.sap"; // "true" - private static final String VALUE_SENSE_CLIENT_ID_BASE = "android-htc-rev"; - - // LG : LG - private static final String KEY_LG_SW_VERSION = "ro.lge.swversion"; // "D85720b" - private static final String KEY_LG_SW_VERSION_SHORT = "ro.lge.swversion_short"; // "V20b" - private static final String KEY_LG_FACTORY_VERSION = "ro.lge.factoryversion"; // "LGD857AT-00-V20b-CUO-CN-FEB-17-2015+0" - - // 联想 - private static final String KEY_LENOVO_DEVICE = "ro.lenovo.device"; // "phone" - private static final String KEY_LENOVO_PLATFORM = "ro.lenovo.platform"; // "qualcomm" - private static final String KEY_LENOVO_ADB = "ro.lenovo.adb"; // "apkctl,speedup" - private static final String VALUE_LENOVO_CLIENT_ID_BASE = "android-lenovo"; - - /** - * 获取 ROM 类型 - * @return ROM - */ - public static ROM getRomType() { - return ROM_TYPE; - } - - /** - * 初始化 ROM 类型 - */ - private static ROM initRomType() { - ROM rom = ROM.Other; - FileInputStream is = null; - try { - Properties buildProperties = new Properties(); - is = new FileInputStream(new File(Environment.getRootDirectory(), "build.prop")); - buildProperties.load(is); - - if (buildProperties.containsKey(KEY_MIUI_VERSION_NANE) || buildProperties.containsKey(KEY_MIUI_VERSION_CODE)) { - // MIUI - rom = ROM.MIUI; - if (buildProperties.containsKey(KEY_MIUI_VERSION_NANE)) { - String versionName = buildProperties.getProperty(KEY_MIUI_VERSION_NANE); - if (!TextUtils.isEmpty(versionName) && versionName.matches("[Vv]\\d+")) { // V8 - try { - rom.setBaseVersion(Integer.parseInt(versionName.split("[Vv]")[1])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - if (buildProperties.containsKey(KEY_MIUI_VERSION)) { - String versionStr = buildProperties.getProperty(KEY_MIUI_VERSION); - if (!TextUtils.isEmpty(versionStr) && versionStr.matches("[\\d.]+")) { - rom.setVersion(versionStr); - } - } - } else if (buildProperties.containsKey(KEY_EMUI_VERSION) || buildProperties.containsKey(KEY_EMUI_API_LEVEL) - || buildProperties.containsKey(KEY_EMUI_SYSTEM_VERSION)) { - // EMUI - rom = ROM.EMUI; - if (buildProperties.containsKey(KEY_EMUI_VERSION)) { - String versionStr = buildProperties.getProperty(KEY_EMUI_VERSION); - Matcher matcher = Pattern.compile("EmotionUI_([\\d.]+)").matcher(versionStr); // EmotionUI_3.0 - if (!TextUtils.isEmpty(versionStr) && matcher.find()) { - try { - String version = matcher.group(1); - rom.setVersion(version); - rom.setBaseVersion(Integer.parseInt(version.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_FLYME_SETUP) || buildProperties.containsKey(KEY_FLYME_PUBLISHED)) { - // Flyme - rom = ROM.Flyme; - if (buildProperties.containsKey(KEY_DISPLAY_ID)) { - String versionStr = buildProperties.getProperty(KEY_DISPLAY_ID); - Matcher matcher = Pattern.compile("Flyme[^\\d]*([\\d.]+)[^\\d]*").matcher(versionStr); // Flyme OS 4.5.4.2U - if (!TextUtils.isEmpty(versionStr) && matcher.find()) { - try { - String version = matcher.group(1); - rom.setVersion(version); - rom.setBaseVersion(Integer.parseInt(version.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_COLOROS_VERSION) || buildProperties.containsKey(KEY_COLOROS_THEME_VERSION) - || buildProperties.containsKey(KEY_COLOROS_ROM_VERSION)) { - // ColorOS - rom = ROM.ColorOS; - if (buildProperties.containsKey(KEY_COLOROS_ROM_VERSION)) { - String versionStr = buildProperties.getProperty(KEY_COLOROS_ROM_VERSION); - Matcher matcher = Pattern.compile("ColorOS([\\d.]+)").matcher(versionStr); // ColorOS2.1 - if (!TextUtils.isEmpty(versionStr) && matcher.find()) { - try { - String version = matcher.group(1); - rom.setVersion(version); - rom.setBaseVersion(Integer.parseInt(version.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_FUNTOUCHOS_OS_NAME) || buildProperties.containsKey(KEY_FUNTOUCHOS_OS_VERSION) - || buildProperties.containsKey(KEY_FUNTOUCHOS_DISPLAY_ID)) { - // FuntouchOS - rom = ROM.FuntouchOS; - if (buildProperties.containsKey(KEY_FUNTOUCHOS_OS_VERSION)) { - String versionStr = buildProperties.getProperty(KEY_FUNTOUCHOS_OS_VERSION); - if (!TextUtils.isEmpty(versionStr) && versionStr.matches("[\\d.]+")) { // 3.0 - try { - rom.setVersion(versionStr); - rom.setBaseVersion(Integer.parseInt(versionStr.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_EUI_VERSION) || buildProperties.containsKey(KEY_EUI_NAME) - || buildProperties.containsKey(KEY_EUI_MODEL)) { - // EUI - rom = ROM.EUI; - if (buildProperties.containsKey(KEY_EUI_VERSION)) { - String versionStr = buildProperties.getProperty(KEY_EUI_VERSION); - Matcher matcher = Pattern.compile("([\\d.]+)[^\\d]*").matcher(versionStr); // 5.9.023S - if (!TextUtils.isEmpty(versionStr) && matcher.find()) { - try { - String version = matcher.group(1); - rom.setVersion(version); - rom.setBaseVersion(Integer.parseInt(version.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_AMIGO_ROM_VERSION) || buildProperties.containsKey(KEY_AMIGO_SYSTEM_UI_SUPPORT)) { - // amigo - rom = ROM.AmigoOS; - if (buildProperties.containsKey(KEY_DISPLAY_ID)) { - String versionStr = buildProperties.getProperty(KEY_DISPLAY_ID); - Matcher matcher = Pattern.compile("amigo([\\d.]+)[a-zA-Z]*").matcher(versionStr); // "amigo3.5.1" - if (!TextUtils.isEmpty(versionStr) && matcher.find()) { - try { - String version = matcher.group(1); - rom.setVersion(version); - rom.setBaseVersion(Integer.parseInt(version.split("\\.")[0])); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } - } - } - } else if (buildProperties.containsKey(KEY_SONY_PROTOCOL_TYPE) || buildProperties.containsKey(KEY_SONY_ENCRYPTED_DATA)) { - // Sony - rom = ROM.Sony; - } else if (buildProperties.containsKey(KEY_YULONG_VERSION_RELEASE) || buildProperties.containsKey(KEY_YULONG_VERSION_TAG)) { - // YuLong - rom = ROM.YuLong; - } else if (buildProperties.containsKey(KEY_SENSE_BUILD_STAGE) || buildProperties.containsKey(KEY_SENSE_BLUETOOTH_SAP)) { - // Sense - rom = ROM.Sense; - } else if (buildProperties.containsKey(KEY_LG_SW_VERSION) || buildProperties.containsKey(KEY_LG_SW_VERSION_SHORT) - || buildProperties.containsKey(KEY_LG_FACTORY_VERSION)) { - // LG - rom = ROM.LG; - } else if (buildProperties.containsKey(KEY_LENOVO_DEVICE) || buildProperties.containsKey(KEY_LENOVO_PLATFORM) - || buildProperties.containsKey(KEY_LENOVO_ADB)) { - // Lenovo - rom = ROM.Lenovo; - } else if (buildProperties.containsKey(KEY_DISPLAY_ID)) { - String displayId = buildProperties.getProperty(KEY_DISPLAY_ID); - if (!TextUtils.isEmpty(displayId)) { - if (displayId.contains(VALUE_FLYME_DISPLAY_ID_CONTAIN)) { - return ROM.Flyme; - } else if (displayId.contains(VALUE_AMIGO_DISPLAY_ID_CONTAIN)) { - return ROM.AmigoOS; - } - } - } else if (buildProperties.containsKey(KEY_BASE_OS_VERSION)) { - String baseOsVersion = buildProperties.getProperty(KEY_BASE_OS_VERSION); - if (!TextUtils.isEmpty(baseOsVersion)) { - if (baseOsVersion.contains(VALUE_COLOROS_BASE_OS_VERSION_CONTAIN)) { - return ROM.ColorOS; - } else if (baseOsVersion.contains(VALUE_SAMSUNG_BASE_OS_VERSION_CONTAIN)) { - return ROM.SamSung; - } - } - } else if (buildProperties.containsKey(KEY_CLIENT_ID_BASE)) { - String clientIdBase = buildProperties.getProperty(KEY_CLIENT_ID_BASE); - switch (clientIdBase) { - case VALUE_MIUI_CLIENT_ID_BASE: - return ROM.MIUI; - case VALUE_COLOROS_CLIENT_ID_BASE: - return ROM.ColorOS; - case VALUE_FUNTOUCHOS_CLIENT_ID_BASE: - return ROM.FuntouchOS; - case VALUE_SAMSUNG_CLIENT_ID_BASE: - return ROM.SamSung; - case VALUE_SONY_CLIENT_ID_BASE: - return ROM.Sony; - case VALUE_YULONG_CLIENT_ID_BASE: - return ROM.YuLong; - case VALUE_SENSE_CLIENT_ID_BASE: - return ROM.Sense; - case VALUE_LENOVO_CLIENT_ID_BASE: - return ROM.Lenovo; - case VALUE_AMIGO_CLIENT_ID_BASE: - return ROM.AmigoOS; - default: - break; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "initRomType"); - } finally { - if (is != null) { - try { - is.close(); - } catch (Exception e) { - } - } - } - return rom; - } - - public enum ROM { - MIUI, // 小米 - Flyme, // 魅族 - EMUI, // 华为 - ColorOS, // OPPO - FuntouchOS, // vivo - SmartisanOS, // 锤子 - EUI, // 乐视 - Sense, // HTC - AmigoOS, // 金立 - _360OS, // 奇酷360 - NubiaUI, // 努比亚 - H2OS, // 一加 - YunOS, // 阿里巴巴 - YuLong, // 酷派 - - SamSung, // 三星 - Sony, // 索尼 - Lenovo, // 联想 - LG, // LG - - Google, // 原生 - - Other; // CyanogenMod, Lewa OS, 百度云OS, Tencent OS, 深度OS, IUNI OS, Tapas OS, Mokee - - private int baseVersion = -1; - private String version; - - void setVersion(String version) { - this.version = version; - } - - void setBaseVersion(int baseVersion) { - this.baseVersion = baseVersion; - } - - public int getBaseVersion() { - return baseVersion; - } - - public String getVersion() { - return version; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/PathUtils.java b/DevLibUtils/src/main/java/dev/utils/app/PathUtils.java deleted file mode 100644 index 4e7c2182c1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/PathUtils.java +++ /dev/null @@ -1,360 +0,0 @@ -package dev.utils.app; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.support.annotation.RequiresApi; - -import dev.DevUtils; - -/** - * detail: 路径相关工具类 - * @author: Blankj - */ -public final class PathUtils { - - private PathUtils() { - } - - /** - * 获取 Android 系统根目录 - path: /system - * @return 系统根目录 - */ - public static String getRootPath() { - return Environment.getRootDirectory().getAbsolutePath(); - } - - /** - * 获取 data 目录 - path: /data - * @return data 目录 - */ - public static String getDataPath() { - return Environment.getDataDirectory().getAbsolutePath(); - } - - /** - * 获取缓存目录 - path: data/cache - * @return 缓存目录 - */ - public static String getIntDownloadCachePath() { - return Environment.getDownloadCacheDirectory().getAbsolutePath(); - } - - /** - * 获取此应用的缓存目录 - path: /data/data/package/cache - * @return 此应用的缓存目录 - */ - public static String getAppIntCachePath() { - return DevUtils.getContext().getCacheDir().getAbsolutePath(); - } - - /** - * 获取此应用的文件目录 - path: /data/data/package/files - * @return 此应用的文件目录 - */ - public static String getAppIntFilesPath() { - return DevUtils.getContext().getFilesDir().getAbsolutePath(); - } - - /** - * 获取此应用的数据库文件目录 - path: /data/data/package/databases/name - * @param name 数据库文件名 - * @return 数据库文件目录 - */ - public static String getAppIntDbPath(String name) { - return DevUtils.getContext().getDatabasePath(name).getAbsolutePath(); - } - - /** - * 获取 Android 外置储存的根目录 - path: /storage/emulated/0 - * @return 外置储存根目录 - */ - public static String getExtStoragePath() { - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - /** - * 获取闹钟铃声目录 - path: /storage/emulated/0/Alarms - * @return 闹钟铃声目录 - */ - public static String getExtAlarmsPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS).getAbsolutePath(); - } - - /** - * 获取相机拍摄的照片和视频的目录 - path: /storage/emulated/0/DCIM - * @return 照片和视频目录 - */ - public static String getExtDcimPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(); - } - - /** - * 获取文档目录 - path: /storage/emulated/0/Documents - * @return 文档目录 - */ - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public static String getExtDocumentsPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); - } - - /** - * 获取下载目录 - path: /storage/emulated/0/Download - * @return 下载目录 - */ - public static String getExtDownloadsPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - } - - /** - * 获取视频目录 - path: /storage/emulated/0/Movies - * @return 视频目录 - */ - public static String getExtMoviesPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath(); - } - - /** - * 获取音乐目录 - path: /storage/emulated/0/Music - * @return 音乐目录 - */ - public static String getExtMusicPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath(); - } - - /** - * 获取提示音目录 - path: /storage/emulated/0/Notifications - * @return 提示音目录 - */ - public static String getExtNotificationsPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS).getAbsolutePath(); - } - - /** - * 获取图片目录 - path: /storage/emulated/0/Pictures - * @return 图片目录 - */ - public static String getExtPicturesPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath(); - } - - /** - * 获取 Podcasts 目录 - path: /storage/emulated/0/Podcasts - * @return Podcasts 目录 - */ - public static String getExtPodcastsPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS).getAbsolutePath(); - } - - /** - * 获取铃声目录 - path: /storage/emulated/0/Ringtones - * @return 下载目录 - */ - public static String getExtRingtonesPath() { - return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的缓存目录 - path: /storage/emulated/0/Android/data/package/cache - * @return 此应用在外置储存中的缓存目录 - */ - public static String getAppExtCachePath() { - return DevUtils.getContext().getExternalCacheDir().getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的文件目录 - path: /storage/emulated/0/Android/data/package/files - * @return 此应用在外置储存中的文件目录 - */ - public static String getAppExtFilePath() { - return DevUtils.getContext().getExternalFilesDir(null).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的闹钟铃声目录 - path: /storage/emulated/0/Android/data/package/files/Alarms - * @return 此应用在外置储存中的闹钟铃声目录 - */ - public static String getAppExtAlarmsPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_ALARMS).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的相机目录 - path: /storage/emulated/0/Android/data/package/files/DCIM - * @return 此应用在外置储存中的相机目录 - */ - public static String getAppExtDcimPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_DCIM).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的文档目录 - path: /storage/emulated/0/Android/data/package/files/Documents - * @return 此应用在外置储存中的文档目录 - */ - @RequiresApi(api = Build.VERSION_CODES.KITKAT) - public static String getAppExtDocumentsPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的闹钟目录 - path: /storage/emulated/0/Android/data/package/files/Download - * @return 此应用在外置储存中的闹钟目录 - */ - public static String getAppExtDownloadPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的视频目录 - path: /storage/emulated/0/Android/data/package/files/Movies - * @return 此应用在外置储存中的视频目录 - */ - public static String getAppExtMoviesPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_MOVIES).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的音乐目录 - path: /storage/emulated/0/Android/data/package/files/Music - * @return 此应用在外置储存中的音乐目录 - */ - public static String getAppExtMusicPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的提示音目录 - path: /storage/emulated/0/Android/data/package/files/Notifications - * @return 此应用在外置储存中的提示音目录 - */ - public static String getAppExtNotificationsPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的图片目录 - path: /storage/emulated/0/Android/data/package/files/Pictures - * @return 此应用在外置储存中的图片目录 - */ - public static String getAppExtPicturesPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的 Podcasts 目录 - path: /storage/emulated/0/Android/data/package/files/Podcasts - * @return 此应用在外置储存中的 Podcasts 目录 - */ - public static String getAppExtPodcastsPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_PODCASTS).getAbsolutePath(); - } - - /** - * 获取此应用在外置储存中的铃声目录 - path: /storage/emulated/0/Android/data/package/files/Ringtones - * @return 此应用在外置储存中的铃声目录 - */ - public static String getAppExtRingtonesPath() { - return DevUtils.getContext().getExternalFilesDir(Environment.DIRECTORY_RINGTONES).getAbsolutePath(); - } - - /** - * 获取此应用的 Obb 目录 - path: /storage/emulated/0/Android/obb/package - * 一般用来存放游戏数据包 - * @return 此应用的 Obb 目录 - */ - public static String getObbPath() { - return DevUtils.getContext().getObbDir().getAbsolutePath(); - } - - public static String getFilePathByUri(Context context, Uri uri) { - String path = null; - // 以 file:// 开头的 - if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { - path = uri.getPath(); - return path; - } - // 以 content:// 开头的,比如 content://media/extenral/images/media/17766 - if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { - Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}, null, null, null); - if (cursor != null) { - if (cursor.moveToFirst()) { - int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - if (columnIndex > -1) { - path = cursor.getString(columnIndex); - } - } - cursor.close(); - } - return path; - } - // 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700 - if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - if (DocumentsContract.isDocumentUri(context, uri)) { - if (isExternalStorageDocument(uri)) { - // ExternalStorageProvider - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - if ("primary".equalsIgnoreCase(type)) { - path = Environment.getExternalStorageDirectory() + "/" + split[1]; - return path; - } - } else if (isDownloadsDocument(uri)) { - // DownloadsProvider - final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), - Long.valueOf(id)); - path = getDataColumn(context, contentUri, null, null); - return path; - } else if (isMediaDocument(uri)) { - // MediaProvider - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - final String selection = "_id=?"; - final String[] selectionArgs = new String[]{split[1]}; - path = getDataColumn(context, contentUri, selection, selectionArgs); - return path; - } - } - } - return null; - } - - private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { - Cursor cursor = null; - final String column = "_data"; - final String[] projection = {column}; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - if (cursor != null && cursor.moveToFirst()) { - final int column_index = cursor.getColumnIndexOrThrow(column); - return cursor.getString(column_index); - } - } finally { - if (cursor != null) - cursor.close(); - } - return null; - } - - private static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - private static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - private static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/PermissionUtils.java b/DevLibUtils/src/main/java/dev/utils/app/PermissionUtils.java deleted file mode 100644 index 5950762201..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/PermissionUtils.java +++ /dev/null @@ -1,383 +0,0 @@ -package dev.utils.app; - -import android.Manifest; -import android.app.Activity; -import android.app.Fragment; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresApi; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import dev.DevUtils; - -/** - * detail: 权限请求工具类 - * Created by Ttt - * hint: - * - - * 参考: - * https://github.com/anthonycr/Grant - * compile 'com.anthonycr.grant:permissions:1.0' - * - - * 权限介绍 - * https://www.cnblogs.com/mengdd/p/4892856.html - * - - * 第三方库: - * PermissionsDispatcher: https://github.com/hotchemi/PermissionsDispatcher - * RxPermissions: https://github.com/tbruyelle/RxPermissions - * Grant: https://github.com/anthonycr/Grant - * - - * 使用方法 - * // 第一种请求方式 - * PermissionUtils.permission("").callBack(null).request(); - * // 第二种请求方式 - 需要在 onRequestPermissionsResult 中通知调用 - * PermissionUtils.permission("").callBack(null).request(Activity); - * ====== - * 注意事项: 需要注意在onResume 中调用 - * 不管是第一种方式, 跳自定义的Activity, 还是第二种 系统内部跳转授权页面, 都会多次触发onResume - * https://www.aliyun.com/jiaocheng/8030.html - * 尽量避免在 onResume中调用 - * com.anthonycr.grant:permissions:1.0 也是会触发onResume 只是 通过 Set mPendingRequests 来控制请求过的权限 - * 拒绝后在onResume 方法内再次请求, 直接触发授权成功, 如果需要清空通过调用 notifyPermissionsChange 通知改变, 否则一直调用获取权限,拒绝过后,都会认为是请求通过 - */ -public final class PermissionUtils { - - /** Permission 请求Code */ - public static final int P_REQUEST_CODE = 100; - /** 全部权限 */ - private final static Set mAllPermissions = new HashSet<>(1); - // 判断是否请求过 - private boolean isRequest = false; - /** 申请的权限 */ - private List mPermissions = new ArrayList<>(); - /** 准备请求的权限 */ - private List mPermissionsRequest = new ArrayList<>(); - /** 申请通过的权限 */ - private List mPermissionsGranted = new ArrayList<>(); - /** 申请未通过的权限 */ - private List mPermissionsDenied = new ArrayList<>(); - /** 申请未通过的权限 - 永久拒绝 */ - private List mPermissionsDeniedForever = new ArrayList<>(); - /** 查询不到的权限 */ - private List mPermissionsNotFound = new ArrayList<>(); - /** 操作回调 */ - private PermissionCallBack mCallBack; - /** 回调方法 */ - private Looper mLooper = Looper.getMainLooper(); - - static { - // 初始化权限数据 - initializePermissionsMap(); - } - - /** 初始化遍历保存全部权限 */ - private static synchronized void initializePermissionsMap() { - Field[] fields = Manifest.permission.class.getFields(); - for (Field field : fields) { - String name = null; - try { - name = (String) field.get(""); - } catch (IllegalAccessException e) { - } - mAllPermissions.add(name); - } - } - - // = - - /** - * 构造函数 - * @param permissions - */ - private PermissionUtils(final String... permissions){ - mPermissions.clear(); - // 防止数据为null - if (permissions != null && permissions.length != 0){ - // 遍历全部需要申请的权限 - for (String permission : permissions){ - mPermissions.add(permission); - } - } - } - - // == - - /** - * 判断是否授予了权限 - * @param permissions - * @return - */ - public static boolean isGranted(final String... permissions) { - // 防止数据为null - if (permissions != null && permissions.length != 0) { - // 遍历全部需要申请的权限 - for (String permission : permissions) { - if (!isGranted(DevUtils.getContext(), permission)) { - return false; - } - } - } - return true; - } - - /** - * 判断是否授予了权限 - * @param context - * @param permission - * @return - */ - private static boolean isGranted(final Context context, final String permission) { - // SDK 版本小于 23 则表示直接通过 || 检查是否通过权限 - return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission); - } - - /** - * 是否拒绝了权限 - 拒绝过一次, 再次申请时, 弹出选择不再提醒并拒绝才会触发 true - * @param activity - * @param permission - * @return - */ - public static boolean shouldShowRequestPermissionRationale(Activity activity, final String permission){ - return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission); - } - - // == 使用方法 == - - /** - * 申请权限初始化 - * @param permissions - * @return - */ - public static PermissionUtils permission(final String... permissions){ - return new PermissionUtils(permissions); - } - - /** - * 设置回调方法 - * @param callBack - */ - public PermissionUtils callBack(PermissionCallBack callBack){ - if (isRequest){ - return this; - } - this.mCallBack = callBack; - return this; - } - - /** - * 权限判断处理 - * @return -1 已经请求过, 0 = 不处理, 1 = 需要请求 - */ - private int checkPermissions(){ - if (isRequest){ - return -1; // 已经申请过 - } - isRequest = true; - // 如果 SDK 版本小于 23 则直接通过 - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - // 表示全部权限都通过 - mPermissionsGranted.addAll(mPermissions); - // 处理请求回调 - requestCallback(); - } else { - for (String permission : mPermissions){ - // 首先判断是否存在 - if (mAllPermissions.contains(permission)){ - // 判断是否通过请求 - if (isGranted(DevUtils.getContext(), permission)){ - mPermissionsGranted.add(permission); // 权限允许通过 - } else { - mPermissionsRequest.add(permission); // 准备请求权限 - } - } else { - // 保存到没找到的权限集合 - mPermissionsNotFound.add(permission); - } - } - // 判断是否存在等待请求的权限 - if (mPermissionsRequest.isEmpty()){ - // 处理请求回调 - requestCallback(); - } else { // 表示需要申请 - return 1; - } - } - // 表示不需要申请 - return 0; - } - - /** - * 请求权限 - * -- - * 内部自动调用 PermissionUtils.isGranted, 并且进行判断处理 - * 无需调用以下代码判断 - * boolean isGranted = PermissionUtils.isGranted(Manifest.permission.xx); - */ - public void request(){ - if (checkPermissions() == 1){ - // 如果 SDK 版本大于 23 才请求 - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { - sInstance = this; - // 自定义Activity - PermissionUtils.PermissionActivity.start(DevUtils.getContext()); - } - } - } - - /** - * 请求权限 - * @param activity {@link Fragment#getActivity()} - */ - public void request(Activity activity){ - request(activity, P_REQUEST_CODE); - } - - /** - * 请求权限 - 需要在Activity 的 onRequestPermissionsResult 回调中 调用 PermissionUtils.onRequestPermissionsResult(this); - * @param activity {@link Fragment#getActivity()} - * @param requestCode - */ - public void request(Activity activity, int requestCode){ - if (checkPermissions() == 1 && activity != null){ - // 如果 SDK 版本大于 23 才请求 - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { - sInstance = this; - // 请求权限 - String[] permissions = mPermissionsRequest.toArray(new String[mPermissionsRequest.size()]); - // 请求权限 - ActivityCompat.requestPermissions(activity, permissions, requestCode); - } - } - } - - // == 请求权限回调 == - - public interface PermissionCallBack { - /** - * 授权通过权限 - * @param permissionUtils - */ - void onGranted(PermissionUtils permissionUtils); - - /** - * 授权未通过权限 - * @param permissionUtils - */ - void onDenied(PermissionUtils permissionUtils); - } - - // == 内部Activity == - - // 内部持有对象 - private static PermissionUtils sInstance; - - // 实现Activity的透明效果 - // https://blog.csdn.net/u014434080/article/details/52260407 - @RequiresApi(api = Build.VERSION_CODES.M) - public static class PermissionActivity extends Activity { - - public static void start(final Context context) { - Intent starter = new Intent(context, PermissionActivity.class); - starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(starter); - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // 请求权限 - int size = sInstance.mPermissionsRequest.size(); - requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1); - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - sInstance.onRequestPermissionsResultCommon(this); // 处理回调 - finish(); // 关闭当前页面 - } - } - - // == 内部处理方法 == - - /** 内部请求回调, 统一处理方法 */ - private void requestCallback() { - if (mCallBack != null){ - // 判断是否允许全部权限 - boolean isGrantedAll = (mPermissions.size() == mPermissionsGranted.size()); - // 允许则触发回调 - if (isGrantedAll){ - new Handler(mLooper).post(new Runnable() { - @Override - public void run() { - mCallBack.onGranted(PermissionUtils.this); - } - }); - } else { - new Handler(mLooper).post(new Runnable() { - @Override - public void run() { - mCallBack.onDenied(PermissionUtils.this); - } - }); - } - } - } - - /** - * 请求回调权限回调处理 - 通用 - * @param activity - */ - private void onRequestPermissionsResultCommon(final Activity activity) { - // 获取权限状态 - getPermissionsStatus(activity); - // 判断请求结果 - requestCallback(); - } - - /** - * 获取权限状态 - * @param activity - */ - private void getPermissionsStatus(final Activity activity) { - for (String permission : mPermissionsRequest) { - // 判断是否通过请求 - if (isGranted(activity, permission)){ - mPermissionsGranted.add(permission); - } else { - // 未授权 - mPermissionsDenied.add(permission); - // 拒绝权限 - if (!shouldShowRequestPermissionRationale(activity, permission)) { - mPermissionsDeniedForever.add(permission); - } - } - } - } - - // == 通过传入Activity 方式 == - - /** - * 请求权限回调 - 需要在 onRequestPermissionsResult 回调里面调用 - * @param activity - */ - public static void onRequestPermissionsResult(Activity activity){ - if (activity != null && sInstance != null){ - // 触发回调 - sInstance.onRequestPermissionsResultCommon(activity); - } - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/app/PhoneUtils.java b/DevLibUtils/src/main/java/dev/utils/app/PhoneUtils.java deleted file mode 100644 index ca38436017..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/PhoneUtils.java +++ /dev/null @@ -1,808 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.PendingIntent; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.SystemClock; -import android.provider.ContactsContract; -import android.provider.Settings; -import android.telephony.SmsManager; -import android.telephony.TelephonyManager; -import android.text.TextUtils; -import android.util.Xml; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.File; -import java.io.FileOutputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.UUID; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 手机相关工具类 - * Created by Ttt - */ -public final class PhoneUtils { - - private PhoneUtils() { - } - - // 日志TAG - private static final String TAG = PhoneUtils.class.getSimpleName(); - - /** - * 判断是否装载sim卡 - * @return - */ - public static boolean isSimReady() { - try { - // 获取电话管理类 - TelephonyManager tpManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - // 是否准备完毕 - if(tpManager != null && tpManager.getSimState() == TelephonyManager.SIM_STATE_READY) { - return true; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isSimReady"); - } - return false; - } - - /** - * 获取Sim卡所属地区,非国内地区暂不支持播放 - * @return 返回SIM的地区码 - */ - public static String getUserCountry() { - try { - TelephonyManager tpManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - String simCountry = tpManager.getSimCountryIso(); - if (simCountry != null && simCountry.length() == 2) { - // SIM country code is available - return simCountry.toLowerCase(Locale.CHINA); - } else if (tpManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { - // device is not 3G (would be unreliable) - String networkCountry = tpManager.getNetworkCountryIso(); - if (networkCountry != null && networkCountry.length() == 2) { - // network country code is available - return networkCountry.toLowerCase(Locale.CHINESE); - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getUserCountry"); - } - return null; - } - - /** - * 判断地区,是否属于国内 - * @return 状态码 1 属于国内(中国),2 属于 国外,3 属于无SIM卡 - */ - public static int judgeArea() { - // 默认属于无sim卡 - int state = 3; - try { - String countryCode = getUserCountry(); - // 不等于null,表示属于存在SIM卡 - if (countryCode != null) { - // zh_CN Locale.SIMPLIFIED_CHINESE - // 截取前面两位属于zh表示属于中国 - String country = countryCode.substring(0, 2); - // 如果属于ch开头表示属于中国 - if (country.toLowerCase().equals("cn")) { - state = 1; - } else { - state = 2; - } - } else { // 不存在sim卡 - String localCountry = Locale.getDefault().getCountry(); - // 如果属于ch开头表示属于中国 - if (localCountry.toLowerCase().equals("cn")) { - return 1; - } else { - return 2; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "judgeArea"); - } - return state; - } - - /** - * 判断设备是否是手机 - * @return true : 是, false : 否 - */ - public static boolean isPhone() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isPhone"); - } - return false; - } - - /** - * 获取 IMEI 码 - * - * @return IMEI 码 - * ========== - * IMEI是International Mobile Equipment Identity (国际移动设备标识)的简称 - * IMEI由15位数字组成的”电子串号”,它与每台手机一一对应,而且该码是全世界唯一的 - * 其组成为: - * 1. 前6位数(TAC)是”型号核准号码”,一般代表机型 - * 2. 接着的2位数(FAC)是”最后装配号”,一般代表产地 - * 3. 之后的6位数(SNR)是”串号”,一般代表生产顺序号 - * 4. 最后1位数(SP)通常是”0″,为检验码,目前暂备用 - */ - @SuppressLint({"HardwareIds", "MissingPermission"}) - public static String getIMEI() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null ? telephonyManager.getDeviceId() : null; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getIMEI"); - } - return null; - } - - /** - * 获取 IMSI 码 - * - * @return IMSI 码 - * ========= - * IMSI是国际移动用户识别码的简称(International Mobile Subscriber Identity) - * IMSI共有15位,其结构如下: - * MCC+MNC+MIN - * MCC:Mobile Country Code,移动国家码,共3位,中国为460; - * MNC:Mobile NetworkCode,移动网络码,共2位 - * 在中国,移动的代码为电00和02,联通的代码为01,电信的代码为03 - * 合起来就是(也是Android手机中APN配置文件中的代码): - * 中国移动:46000 46002 - * 中国联通:46001 - * 中国电信:46003 - * 举例,一个典型的IMSI号码为460030912121001 - */ - @SuppressLint({"HardwareIds", "MissingPermission"}) - public static String getIMSI() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null ? telephonyManager.getSubscriberId() : null; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getIMSI"); - } - return null; - } - - /** - * 获取IMSI处理过后的简称 - * @param IMSI - * @return - */ - public static String getIMSIIDName(String IMSI){ - if (IMSI != null) { - if (IMSI.startsWith("46000") || IMSI.startsWith("46002")) { - return "中国移动"; - } else if (IMSI.startsWith("46001")) { - return "中国联通"; - } else if (IMSI.startsWith("46003")) { - return "中国电信"; - } - } - return null; - } - - /** - * 获取移动终端类型 - * @return 手机制式 - * {@link TelephonyManager#PHONE_TYPE_NONE } : 0 手机制式未知 - * {@link TelephonyManager#PHONE_TYPE_GSM } : 1 手机制式为 GSM,移动和联通 - * {@link TelephonyManager#PHONE_TYPE_CDMA } : 2 手机制式为 CDMA,电信 - * {@link TelephonyManager#PHONE_TYPE_SIP } : 3 - */ - public static int getPhoneType() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null ? telephonyManager.getPhoneType() : -1; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getPhoneType"); - } - return -1; - } - - /** - * 判断 sim 卡是否准备好 - * @return true : 是, false : 否 - */ - public static boolean isSimCardReady() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null && telephonyManager.getSimState() == TelephonyManager.SIM_STATE_READY; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isSimCardReady"); - } - return false; - } - - /** - * 获取 Sim 卡运营商名称 => 中国移动、如中国联通、中国电信 - * @return sim 卡运营商名称 - */ - public static String getSimOperatorName() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - return telephonyManager != null ? telephonyManager.getSimOperatorName() : null; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getSimOperatorName"); - } - return null; - } - - /** - * 获取 Sim 卡运营商名称 => 中国移动、如中国联通、中国电信 - * @return 移动网络运营商名称 - */ - public static String getSimOperatorByMnc() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - String operator = telephonyManager != null ? telephonyManager.getSimOperator() : null; - if (operator == null) return null; - switch (operator) { - case "46000": - case "46002": - case "46007": - return "中国移动"; - case "46001": - return "中国联通"; - case "46003": - return "中国电信"; - default: - return operator; - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getSimOperatorByMnc"); - } - return null; - } - - /** - * 获取设备id - * - * @return - */ - @SuppressLint({"HardwareIds", "MissingPermission"}) - public static String getDeviceId(){ - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager == null) return ""; - return telephonyManager.getDeviceId(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getDeviceId"); - } - return null; - } - - /** - * 返回设备序列化 - * @return - */ - public static String getSerialNumber(){ - return android.os.Build.SERIAL; - } - - /** - * 获取Android id - * @return - */ - public static String getAndroidId() { - try { - // 在设备首次启动时,系统会随机生成一个64位的数字,并把这个数字以16进制字符串的形式保存下来,这个16进制的字符串就是ANDROID_ID,当设备被wipe后该值会被重置。 - String androidId = Settings.Secure.getString(DevUtils.getContext().getContentResolver(), Settings.Secure.ANDROID_ID); - return androidId; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAndroidId"); - } - return null; - } - - /** - * 获取设备唯一id - * @return - */ - public static String getUUID(){ - String androidId = getAndroidId() + ""; - String deviceId = getDeviceId() + ""; - String serialNumber = getSerialNumber() + ""; - // 生成唯一关联uuid androidId.hashCode(), deviceId.hashCode() | serialNumber.hashCode() - UUID deviceUuid = new UUID(androidId.hashCode(), ((long) deviceId.hashCode() << 32) | serialNumber.hashCode()); - // 获取uid - return deviceUuid.toString(); - } - - /** - * 获取手机状态信息 - * - * @return DeviceId(IMEI) = 99000311726612
- * DeviceSoftwareVersion = 00
- * Line1Number =
- * NetworkCountryIso = cn
- * NetworkOperator = 46003
- * NetworkOperatorName = 中国电信
- * NetworkType = 6
- * PhoneType = 2
- * SimCountryIso = cn
- * SimOperator = 46003
- * SimOperatorName = 中国电信
- * SimSerialNumber = 89860315045710604022
- * SimState = 5
- * SubscriberId(IMSI) = 460030419724900
- * VoiceMailNumber = *86
- */ - @SuppressLint({"HardwareIds", "MissingPermission"}) - public static String getPhoneStatus() { - try { - TelephonyManager telephonyManager = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager == null) return ""; - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append("DeviceId(IMEI) = " + telephonyManager.getDeviceId() + "\n"); - stringBuffer.append("DeviceSoftwareVersion = " + telephonyManager.getDeviceSoftwareVersion() + "\n"); - stringBuffer.append("Line1Number = " + telephonyManager.getLine1Number() + "\n"); - stringBuffer.append("NetworkCountryIso = " + telephonyManager.getNetworkCountryIso() + "\n"); - stringBuffer.append("NetworkOperator = " + telephonyManager.getNetworkOperator() + "\n"); - stringBuffer.append("NetworkOperatorName = " + telephonyManager.getNetworkOperatorName() + "\n"); - stringBuffer.append("NetworkType = " + telephonyManager.getNetworkType() + "\n"); - stringBuffer.append("PhoneType = " + telephonyManager.getPhoneType() + "\n"); - stringBuffer.append("SimCountryIso = " + telephonyManager.getSimCountryIso() + "\n"); - stringBuffer.append("SimOperator = " + telephonyManager.getSimOperator() + "\n"); - stringBuffer.append("SimOperatorName = " + telephonyManager.getSimOperatorName() + "\n"); - stringBuffer.append("SimSerialNumber = " + telephonyManager.getSimSerialNumber() + "\n"); - stringBuffer.append("SimState = " + telephonyManager.getSimState() + "\n"); - stringBuffer.append("SubscriberId(IMSI) = " + telephonyManager.getSubscriberId() + "(" + getIMSIIDName(telephonyManager.getSubscriberId()) + ")"+ "\n"); - stringBuffer.append("VoiceMailNumber = " + telephonyManager.getVoiceMailNumber() + "\n"); - return stringBuffer.toString(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getPhoneStatus"); - } - return ""; - } - - /** - * 跳至拨号界面 - * @param phoneNumber 电话号码 - */ - public static void dial(final String phoneNumber) { - DevUtils.getContext().startActivity(IntentUtils.getDialIntent(phoneNumber, true)); - } - - /** - * 拨打电话 - * - * @param phoneNumber 电话号码 - */ - public static void call(final String phoneNumber) { - DevUtils.getContext().startActivity(IntentUtils.getCallIntent(phoneNumber, true)); - } - - /** - * 跳至发送短信界面 - * @param phoneNumber 接收号码 - * @param content 短信内容 - */ - public static void sendSms(final String phoneNumber, final String content) { - DevUtils.getContext().startActivity(IntentUtils.getSendSmsIntent(phoneNumber, content, true)); - } - - /** - * 发送短信 - * - * @param phoneNumber 接收号码 - * @param content 短信内容 - */ - public static void sendSmsSilent(final String phoneNumber, final String content) { - if (TextUtils.isEmpty(content)) return; - PendingIntent sentIntent = PendingIntent.getBroadcast(DevUtils.getContext(), 0, new Intent(), 0); - SmsManager smsManager = SmsManager.getDefault(); - if (content.length() >= 70) { - List ms = smsManager.divideMessage(content); - for (String str : ms) { - smsManager.sendTextMessage(phoneNumber, null, str, sentIntent, null); - } - } else { - smsManager.sendTextMessage(phoneNumber, null, content, sentIntent, null); - } - } - - /** - * 获取手机联系人 - * - * - * return - */ - public static ArrayList> getAllContactInfo() { - ArrayList> list = new ArrayList<>(); - // 1.获取内容解析者 - ContentResolver resolver = DevUtils.getContext().getContentResolver(); - // 2.获取内容提供者的地址:com.android.contacts - // raw_contacts 表的地址 :raw_contacts - // view_data 表的地址 : data - // 3.生成查询地址 - Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts"); - Uri date_uri = Uri.parse("content://com.android.contacts/data"); - // 4.查询操作,先查询 raw_contacts,查询 contact_id - // projection : 查询的字段 - Cursor cursor = resolver.query(raw_uri, new String[]{"contact_id"}, null, null, null); - try { - // 5.解析 cursor - if (cursor != null) { - while (cursor.moveToNext()) { - // 6.获取查询的数据 - String contact_id = cursor.getString(0); - // cursor.getString(cursor.getColumnIndex("contact_id"));//getColumnIndex - // : 查询字段在 cursor 中索引值,一般都是用在查询字段比较多的时候 - // 判断 contact_id 是否为空 - if (!TextUtils.isEmpty(contact_id)) {//null "" - // 7.根据 contact_id 查询 view_data 表中的数据 - // selection : 查询条件 - // selectionArgs :查询条件的参数 - // sortOrder : 排序 - // 空指针: 1.null.方法 2.参数为 null - Cursor c = resolver.query(date_uri, new String[]{"data1", "mimetype"}, "raw_contact_id=?", new String[]{contact_id}, null); - HashMap map = new HashMap(); - // 8.解析 c - if (c != null) { - while (c.moveToNext()) { - // 9.获取数据 - String data1 = c.getString(0); - String mimetype = c.getString(1); - // 10.根据类型去判断获取的 data1 数据并保存 - if (mimetype.equals("vnd.android.cursor.item/phone_v2")) { - map.put("phone", data1); // 电话 - } else if (mimetype.equals("vnd.android.cursor.item/name")) { - map.put("name", data1); // 姓名 - } - } - } - // 11.添加到集合中数据 - list.add(map); - // 12.关闭 cursor - if (c != null) { - c.close(); - } - } - } - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getAllContactInfo"); - } finally { - // 12.关闭 cursor - if (cursor != null) { - cursor.close(); - } - } - return list; - } - - /** - * 获取手机联系人 - * @return - */ - public static ArrayList> getAllContactInfo2() { - ArrayList> list = new ArrayList<>(); - try { - Cursor cursor = DevUtils.getContext().getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); - while (cursor.moveToNext()) { - HashMap map = new HashMap<>(); - // 电话号码 - String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)).trim().replaceAll(" ", ""); - // 手机联系人名字 - String phoneName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)).trim(); - // 保存手机号 - map.put("phone", phoneNumber); - // 保存名字 - map.put("name", phoneName); - // == - list.add(map); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAllContactInfo2"); - } - // 返回数据 - return list; - } - - /** - * 打开手机联系人界面点击联系人后便获取该号码 - */ - public static void getContactNum() { - Intent intent = new Intent(); - intent.setAction("android.intent.action.PICK"); - intent.setType("vnd.android.cursor.dir/phone_v2"); - ((Activity) DevUtils.getContext()).startActivityForResult(intent, 0); - -// @Override -// protected void onActivityResult ( int requestCode, int resultCode, Intent data){ -// super.onActivityResult(requestCode, resultCode, data); -// if (data != null) { -// Uri uri = data.getData(); -// String num = null; -// // 创建内容解析者 -// ContentResolver contentResolver = getContentResolver(); -// Cursor cursor = contentResolver.query(uri, -// null, null, null, null); -// while (cursor.moveToNext()) { -// num = cursor.getString(cursor.getColumnIndex("data1")); -// } -// cursor.close(); -// num = num.replaceAll("-", "");//替换的操作,555-6 -> 5556 -// } -// } - } - - /** - * 获取手机短信并保存到 xml 中 - * - * - */ - public static void getAllSMS() { - // 1.获取短信 - // 1.1获取内容解析者 - ContentResolver resolver = DevUtils.getContext().getContentResolver(); - // 1.2获取内容提供者地址 sms,sms表的地址:null 不写 - // 1.3获取查询路径 - Uri uri = Uri.parse("content://sms"); - // 1.4.查询操作 - // projection : 查询的字段 - // selection : 查询的条件 - // selectionArgs : 查询条件的参数 - // sortOrder : 排序 - Cursor cursor = resolver.query(uri, - new String[]{"address", "date", "type", "body"}, - null, - null, - null - ); - // 设置最大进度 - int count = cursor.getCount();//获取短信的个数 - // 2.备份短信 - // 2.1获取xml序列器 - XmlSerializer xmlSerializer = Xml.newSerializer(); - try { - // 2.2设置xml文件保存的路径 - // os : 保存的位置 - // encoding : 编码格式 - xmlSerializer.setOutput(new FileOutputStream(new File("/mnt/sdcard/backupsms.xml")), "utf-8"); - // 2.3设置头信息 - // standalone : 是否独立保存 - xmlSerializer.startDocument("utf-8", true); - // 2.4设置根标签 - xmlSerializer.startTag(null, "smss"); - // 1.5.解析cursor - while (cursor.moveToNext()) { - SystemClock.sleep(1000); - // 2.5设置短信的标签 - xmlSerializer.startTag(null, "sms"); - // 2.6设置文本内容的标签 - xmlSerializer.startTag(null, "address"); - String address = cursor.getString(0); - // 2.7设置文本内容 - xmlSerializer.text(address); - xmlSerializer.endTag(null, "address"); - xmlSerializer.startTag(null, "date"); - String date = cursor.getString(1); - xmlSerializer.text(date); - xmlSerializer.endTag(null, "date"); - xmlSerializer.startTag(null, "type"); - String type = cursor.getString(2); - xmlSerializer.text(type); - xmlSerializer.endTag(null, "type"); - xmlSerializer.startTag(null, "body"); - String body = cursor.getString(3); - xmlSerializer.text(body); - xmlSerializer.endTag(null, "body"); - xmlSerializer.endTag(null, "sms"); - } - xmlSerializer.endTag(null, "smss"); - xmlSerializer.endDocument(); - // 2.8将数据刷新到文件中 - xmlSerializer.flush(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAllSMS"); - } - } - - // ================ - // === 双卡模块 === - // ================ - - // 双卡双待系统IMEI和IMSI方案 - // http://benson37.iteye.com/blog/1923946 - - /** - * 双卡双待神机IMSI、IMSI、PhoneType信息 - * - */ - public static class TeleInfo { - public String imsi_1; - public String imsi_2; - public String imei_1; - public String imei_2; - public int phoneType_1; - public int phoneType_2; - - @Override - public String toString() { - return "TeleInfo{" + - "imsi_1='" + imsi_1 + '\'' + - ", imsi_2='" + imsi_2 + '\'' + - ", imei_1='" + imei_1 + '\'' + - ", imei_2='" + imei_2 + '\'' + - ", phoneType_1=" + phoneType_1 + - ", phoneType_2=" + phoneType_2 + - '}'; - } - } - - /** - * MTK Phone. - * 获取 MTK 神机的双卡 IMSI、IMSI 信息 - */ - public static TeleInfo getMtkTeleInfo(Context context) { - TeleInfo teleInfo = new TeleInfo(); - try { - Class phone = Class.forName("com.android.internal.telephony.Phone"); - - Field fields1 = phone.getField("GEMINI_SIM_1"); - fields1.setAccessible(true); - int simId_1 = (Integer) fields1.get(null); - - Field fields2 = phone.getField("GEMINI_SIM_2"); - fields2.setAccessible(true); - int simId_2 = (Integer) fields2.get(null); - - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - Method getSubscriberIdGemini = TelephonyManager.class.getDeclaredMethod("getSubscriberIdGemini", int.class); - String imsi_1 = (String) getSubscriberIdGemini.invoke(tm, simId_1); - String imsi_2 = (String) getSubscriberIdGemini.invoke(tm, simId_2); - teleInfo.imsi_1 = imsi_1; - teleInfo.imsi_2 = imsi_2; - - Method getDeviceIdGemini = TelephonyManager.class.getDeclaredMethod("getDeviceIdGemini", int.class); - String imei_1 = (String) getDeviceIdGemini.invoke(tm, simId_1); - String imei_2 = (String) getDeviceIdGemini.invoke(tm, simId_2); - - teleInfo.imei_1 = imei_1; - teleInfo.imei_2 = imei_2; - - Method getPhoneTypeGemini = TelephonyManager.class.getDeclaredMethod("getPhoneTypeGemini", int.class); - int phoneType_1 = (Integer) getPhoneTypeGemini.invoke(tm, simId_1); - int phoneType_2 = (Integer) getPhoneTypeGemini.invoke(tm, simId_2); - teleInfo.phoneType_1 = phoneType_1; - teleInfo.phoneType_2 = phoneType_2; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMtkTeleInfo"); - } - return teleInfo; - } - - /** - * MTK Phone. - * 获取 MTK 神机的双卡 IMSI、IMSI 信息 - */ - public static TeleInfo getMtkTeleInfo2(Context context) { - TeleInfo teleInfo = new TeleInfo(); - try { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - Class phone = Class.forName("com.android.internal.telephony.Phone"); - Field fields1 = phone.getField("GEMINI_SIM_1"); - fields1.setAccessible(true); - int simId_1 = (Integer) fields1.get(null); - Field fields2 = phone.getField("GEMINI_SIM_2"); - fields2.setAccessible(true); - int simId_2 = (Integer) fields2.get(null); - - Method getDefault = TelephonyManager.class.getMethod("getDefault", int.class); - TelephonyManager tm1 = (TelephonyManager) getDefault.invoke(tm, simId_1); - TelephonyManager tm2 = (TelephonyManager) getDefault.invoke(tm, simId_2); - - String imsi_1 = tm1.getSubscriberId(); - String imsi_2 = tm2.getSubscriberId(); - teleInfo.imsi_1 = imsi_1; - teleInfo.imsi_2 = imsi_2; - - String imei_1 = tm1.getDeviceId(); - String imei_2 = tm2.getDeviceId(); - teleInfo.imei_1 = imei_1; - teleInfo.imei_2 = imei_2; - - int phoneType_1 = tm1.getPhoneType(); - int phoneType_2 = tm2.getPhoneType(); - teleInfo.phoneType_1 = phoneType_1; - teleInfo.phoneType_2 = phoneType_2; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getMtkTeleInfo2"); - } - return teleInfo; - } - - /** - * Qualcomm Phone. - * 获取 高通 神机的双卡 IMSI、IMSI 信息 - */ - public static TeleInfo getQualcommTeleInfo(Context context) { - TeleInfo teleInfo = new TeleInfo(); - try { - TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - Class simTMclass = Class.forName("android.telephony.MSimTelephonyManager"); - Object sim = context.getSystemService("phone_msim"); - int simId_1 = 0; - int simId_2 = 1; - - Method getSubscriberId = simTMclass.getMethod("getSubscriberId", int.class); - String imsi_1 = (String) getSubscriberId.invoke(sim, simId_1); - String imsi_2 = (String) getSubscriberId.invoke(sim, simId_2); - teleInfo.imsi_1 = imsi_1; - teleInfo.imsi_2 = imsi_2; - - Method getDeviceId = simTMclass.getMethod("getDeviceId", int.class); - String imei_1 = (String) getDeviceId.invoke(sim, simId_1); - String imei_2 = (String) getDeviceId.invoke(sim, simId_2); - teleInfo.imei_1 = imei_1; - teleInfo.imei_2 = imei_2; - - Method getDataState = simTMclass.getMethod("getDataState"); - int phoneType_1 = tm.getDataState(); - int phoneType_2 = (Integer) getDataState.invoke(sim); - teleInfo.phoneType_1 = phoneType_1; - teleInfo.phoneType_2 = phoneType_2; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getQualcommTeleInfo"); - } - return teleInfo; - } - - /** - * Spreadtrum Phone. - * 获取 展讯 神机的双卡 IMSI、IMSI 信息 - */ - public static TeleInfo getSpreadtrumTeleInfo() { - TeleInfo teleInfo = new TeleInfo(); - try { - TelephonyManager tm1 = (TelephonyManager) DevUtils.getContext().getSystemService(Context.TELEPHONY_SERVICE); - String imsi_1 = tm1.getSubscriberId(); - String imei_1 = tm1.getDeviceId(); - int phoneType_1 = tm1.getPhoneType(); - teleInfo.imsi_1 = imsi_1; - teleInfo.imei_1 = imei_1; - teleInfo.phoneType_1 = phoneType_1; - - Class phoneFactory = Class.forName("com.android.internal.telephony.PhoneFactory"); - Method getServiceName = phoneFactory.getMethod("getServiceName", String.class, int.class); - getServiceName.setAccessible(true); - String spreadTmService = (String) getServiceName.invoke(phoneFactory, Context.TELEPHONY_SERVICE, 1); - - TelephonyManager tm2 = (TelephonyManager) DevUtils.getContext().getSystemService(spreadTmService); - String imsi_2 = tm2.getSubscriberId(); - String imei_2 = tm2.getDeviceId(); - int phoneType_2 = tm2.getPhoneType(); - teleInfo.imsi_2 = imsi_2; - teleInfo.imei_2 = imei_2; - teleInfo.phoneType_2 = phoneType_2; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSpreadtrumTeleInfo"); - } - return teleInfo; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/PollingUtils.java b/DevLibUtils/src/main/java/dev/utils/app/PollingUtils.java deleted file mode 100644 index 66fc5cf046..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/PollingUtils.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.utils.app; - -import android.annotation.TargetApi; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.SystemClock; - -/** - * detail: 轮询工具类 - * @author MaTianyu - */ -public final class PollingUtils { - - private PollingUtils(){ - } - - /** - * 开启轮询 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void startPolling(Context context, int mills, PendingIntent pendingIntent) { - AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - manager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), mills, pendingIntent); - } - - /** - * 停止轮询 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void stopPolling(Context context, PendingIntent pendingIntent) { - AlarmManager manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - manager.cancel(pendingIntent); - } - - /** - * 开启轮询服务 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void startPollingService(Context context, int mills, Class cls, String action) { - Intent intent = new Intent(context, cls); - intent.setAction(action); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - startPolling(context, mills, pendingIntent); - } - - /** - * 停止启轮询服务 - */ - @TargetApi(Build.VERSION_CODES.CUPCAKE) - public static void stopPollingService(Context context, Class cls, String action) { - Intent intent = new Intent(context, cls); - intent.setAction(action); - PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - stopPolling(context, pendingIntent); - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/PowerManagerUtils.java b/DevLibUtils/src/main/java/dev/utils/app/PowerManagerUtils.java deleted file mode 100644 index 6fc0249400..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/PowerManagerUtils.java +++ /dev/null @@ -1,128 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.content.Context; -import android.os.Build; -import android.os.PowerManager; -import android.view.Window; -import android.view.WindowManager; - -import dev.DevUtils; - -/** - * detail: 电源管理工具类 - * Created by Ttt - * - */ -public final class PowerManagerUtils { - - // PowerManagerUtils 实例 - private static PowerManagerUtils INSTANCE; - - /** 获取 PowerManagerUtils 实例 ,单例模式 */ - public static PowerManagerUtils getInstance() { - if (INSTANCE == null){ - INSTANCE = new PowerManagerUtils(); - } - return INSTANCE; - } - - /** 电源管理类 */ - PowerManager powerManager; - // 电源管理锁 - PowerManager.WakeLock wakeLock; - - /** 构造函数 */ - private PowerManagerUtils() { - // 获取系统服务 - powerManager = (PowerManager) DevUtils.getContext().getSystemService(Context.POWER_SERVICE); - // 电源管理锁 - wakeLock = powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.FULL_WAKE_LOCK, "PowerManagerUtils"); - } - - /** - * 屏幕是否打开(亮屏) - * @return - */ - public boolean isScreenOn() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR_MR1) { - return false; - } else { - return powerManager.isScreenOn(); - } - } - - /** - * 唤醒屏幕/点亮亮屏 - */ - public void turnScreenOn() { - if (wakeLock != null && !wakeLock.isHeld()) { - wakeLock.acquire(); - } - } - - /** - * 释放屏幕锁, 允许休眠时间自动黑屏 - */ - public void turnScreenOff() { - if (wakeLock != null && wakeLock.isHeld()) { - try { - wakeLock.release(); - } catch (Exception e) { - } - } - } - - public PowerManager.WakeLock getWakeLock() { - return wakeLock; - } - - public void setWakeLock(PowerManager.WakeLock wakeLock) { - this.wakeLock = wakeLock; - } - - public PowerManager getPowerManager() { - return powerManager; - } - - public void setPowerManager(PowerManager powerManager) { - this.powerManager = powerManager; - } - - /** - * 设置屏幕常亮 - * @param activity - */ - public static void setBright(Activity activity){ - if (activity != null){ - setBright(activity.getWindow()); - } - } - - /** - * 设置屏幕常亮 - * @param window {@link Activity#getWindow()} - */ - public static void setBright(Window window){ - if (window != null) { - window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - } - - /** - * 设置WakeLock 常亮 - * @return - * run: {@link Activity#onResume()} - */ - public static PowerManager.WakeLock setWakeLockToBright(){ - // onResume() - PowerManager.WakeLock mWakeLock = PowerManagerUtils.getInstance().getPowerManager().newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "setWakeLockToBright"); - mWakeLock.acquire(); // 常量, 持有不黑屏 - -// // onPause() -// if (mWakeLock != null){ -// mWakeLock.release(); // 释放资源, 到休眠时间自动黑屏 -// } - return mWakeLock; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ProcessUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ProcessUtils.java deleted file mode 100644 index d41f53bf66..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ProcessUtils.java +++ /dev/null @@ -1,209 +0,0 @@ -package dev.utils.app; - -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.usage.UsageStats; -import android.app.usage.UsageStatsManager; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.provider.Settings; -import android.support.annotation.NonNull; -import android.text.TextUtils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 进程相关工具类 - * Created by Ttt - */ -public final class ProcessUtils { - - private ProcessUtils() { - } - - // 日志TAG - private static final String TAG = ProcessUtils.class.getSimpleName(); - - /** - * 获取进程号对应的进程名 - * @param pid 进程号 => android.os.Process.myPid() - * @return 进程名 - */ - public static String getProcessName(int pid) { - BufferedReader reader = null; - try { - reader = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline")); - String processName = reader.readLine(); - if (!TextUtils.isEmpty(processName)) { - processName = processName.trim(); - } - return processName; - } catch (Throwable throwable) { - LogPrintUtils.eTag(TAG, throwable, "getProcessName"); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (IOException e) { - } - } - } - return null; - } - - /** - * 获得当前进程的名字 - * hit: 获取当前进程 DevUtils.getContext().getApplicationInfo().packageName - * @return 进程号 - */ - public static String getCurProcessName() { - int pid = android.os.Process.myPid(); - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - for (ActivityManager.RunningAppProcessInfo appProcess : activityManager.getRunningAppProcesses()) { - if (appProcess.pid == pid) { - return appProcess.processName; - } - } - return null; - } - - /** - * 获取前台线程包名 - * => 属于系统权限 - * @return 前台应用包名 - */ - public static String getForegroundProcessName() { - if (DevUtils.getContext() == null){ - return null; - } - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return null; - List listInfos = activityManager.getRunningAppProcesses(); - if (listInfos != null && listInfos.size() > 0) { - for (ActivityManager.RunningAppProcessInfo apInfo : listInfos) { - if (apInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { - return apInfo.processName; - } - } - } - // SDK 大于 21 时 - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) { - PackageManager packageManager = DevUtils.getContext().getPackageManager(); - Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); - List listResolves = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - // 无权限 - if (listResolves.size() <= 0) { - return null; - } - try { - UsageStatsManager usageStatsManager = (UsageStatsManager) DevUtils.getContext().getSystemService(Context.USAGE_STATS_SERVICE); - List listUsageStats = null; - if (usageStatsManager != null) { - long endTime = System.currentTimeMillis(); - long beginTime = endTime - 86400000 * 7; - listUsageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, beginTime, endTime); - } - if (listUsageStats == null || listUsageStats.isEmpty()) return null; - UsageStats recentStats = null; - for (UsageStats usageStats : listUsageStats) { - if (recentStats == null || usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) { - recentStats = usageStats; - } - } - return recentStats == null ? null : recentStats.getPackageName(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getForegroundProcessName"); - } - } - return null; - } - - /** - * 获取后台服务进程 - * - * @return 后台服务进程 - */ - public static Set getAllBackgroundProcesses() { - if (DevUtils.getContext() == null){ - return Collections.emptySet(); - } - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return Collections.emptySet(); - List listInfos = activityManager.getRunningAppProcesses(); - Set set = new HashSet<>(); - if (listInfos != null) { - for (ActivityManager.RunningAppProcessInfo apInfo : listInfos) { - Collections.addAll(set, apInfo.pkgList); - } - } - return set; - } - - /** - * 杀死所有的后台服务进程 - * - * @return 被暂时杀死的服务集合 - */ - @SuppressLint("MissingPermission") - public static Set killAllBackgroundProcesses() { - if (DevUtils.getContext() == null){ - return null; - } - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return Collections.emptySet(); - List listInfos = activityManager.getRunningAppProcesses(); - Set set = new HashSet<>(); - for (ActivityManager.RunningAppProcessInfo apInfo : listInfos) { - for (String pkg : apInfo.pkgList) { - activityManager.killBackgroundProcesses(pkg); - set.add(pkg); - } - } - listInfos = activityManager.getRunningAppProcesses(); - for (ActivityManager.RunningAppProcessInfo aInfo : listInfos) { - for (String pkg : aInfo.pkgList) { - set.remove(pkg); - } - } - return set; - } - - /** - * 杀死后台服务进程 - * - * @param packageName The name of the package. - * @return true : 杀死成功, false : 杀死失败 - */ - @SuppressLint("MissingPermission") - public static boolean killBackgroundProcesses(@NonNull final String packageName) { - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return false; - List listInfos = activityManager.getRunningAppProcesses(); - if (listInfos == null || listInfos.size() == 0) return true; - for (ActivityManager.RunningAppProcessInfo apInfo : listInfos) { - if (Arrays.asList(apInfo.pkgList).contains(packageName)) { - activityManager.killBackgroundProcesses(packageName); - } - } - listInfos = activityManager.getRunningAppProcesses(); - if (listInfos == null || listInfos.size() == 0) return true; - for (ActivityManager.RunningAppProcessInfo aInfo : listInfos) { - if (Arrays.asList(aInfo.pkgList).contains(packageName)) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ResourceUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ResourceUtils.java deleted file mode 100644 index 7a2ba5ca79..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ResourceUtils.java +++ /dev/null @@ -1,498 +0,0 @@ -package dev.utils.app; - -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.text.TextUtils; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.R; - -/** - * detail: 资源文件工具类 - * Created by Ttt - */ -public final class ResourceUtils { - - private ResourceUtils() { - } - - // 日志TAG - private static final String TAG = ResourceUtils.class.getSimpleName(); - - public static final String LAYTOUT = "layout"; - public static final String DRAWABLE = "drawable"; - public static final String MIPMAP = "mipmap"; - public static final String MENU = "menu"; - public static final String RAW = "raw"; - public static final String ANIM = "anim"; - public static final String STRING = "string"; - public static final String STYLE = "style"; - public static final String STYLEABLE = "styleable"; - public static final String INTEGER = "integer"; - public static final String ID = "id"; - public static final String DIMEN = "dimen"; - public static final String COLOR = "color"; - public static final String BOOL = "bool"; - public static final String ATTR = "attr"; - - /** - * 获得字符串 - * @param strId 字符串id - * @return 字符串 - */ - public static String getString(int strId) { - try { - return DevUtils.getContext().getResources().getString(strId); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getString"); - } - return ""; - } - - /** - * 获得颜色 - * @param colorId 颜色id - * @return 颜色 - */ - public static int getColor(int colorId) { - try { - return DevUtils.getContext().getResources().getColor(colorId); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getColor"); - } - return -1; - } - - /** - * 获得Drawable - * @param drawableId Drawable的id - * @return Drawable - */ - public static Drawable getDrawable(int drawableId) { - try { - return DevUtils.getContext().getResources().getDrawable(drawableId); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDrawable"); - } - return null; - } - - /** - * 根据资源名获得资源id - * @param name 资源名 - * @param type 资源类型 - * @return 资源id,找不到返回0 - */ - public static int getResourceId(String name, String type) { - try { - //PackageManager pm = DevUtils.getContext().getPackageManager(); - Resources resources = DevUtils.getContext().getResources(); - return resources.getIdentifier(name, type, DevUtils.getContext().getPackageName()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getResourceId"); - } - return 0; - } - - /** - * 获取资源id - * @param imageName - * @return - */ - public static int getDrawableId2(String imageName){ - Class mipmap = R.drawable.class; - try { - Field field = mipmap.getField(imageName); - int resId = field.getInt(imageName); - return resId; - } catch (NoSuchFieldException e) { // 如果没有在"drawable"下找到imageName,将会返回0 - LogPrintUtils.eTag(TAG, e, "getDrawableId2"); - return 0; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDrawableId2"); - return 0; - } - } - - // == - - /** - * 获取 layout 布局文件 - * @param resName layout xml 的文件名 - * @return layout - */ - public static int getLayoutId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "layout", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getLayoutId"); - } - return 0; - } - - /** - * 获取 string 值 - * @param resName string name的名称 - * @return string - */ - public static int getStringId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "string", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getStringId"); - } - return 0; - } - - /** - * 获取 drawable - * @param resName drawable 的名称 - * @return drawable - */ - public static int getDrawableId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "drawable", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getDrawableId"); - } - return 0; - } - - /** - * 获取 mipmap - * @param resName - * @return - */ - public static int getMipmapId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "mipmap", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getMipmapId"); - } - return 0; - } - - - /** - * 获取 style - * @param resName style的名称 - * @return style - */ - public static int getStyleId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "style", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getStyleId"); - } - return 0; - } - - /** - * 获取 styleable - * @param resName styleable 的名称 - * @return styleable - */ - public static Object getStyleableId(String resName){ - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "styleable", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getStyleableId"); - } - return 0; - } - - - /** - * 获取 anim - * @param resName anim xml 文件名称 - * @return anim - */ - public static int getAnimId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "anim", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getAnimId"); - } - return 0; - } - - /** - * 获取 id - * @param resName id 的名称 - * @return - */ - public static int getId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "id", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getId"); - } - return 0; - } - - /** - * color - * @param resName color 名称 - * @return - */ - public static int getColorId(String resName) { - try { - return DevUtils.getContext().getResources().getIdentifier(resName, "color", DevUtils.getContext().getPackageName()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getColorId"); - } - return 0; - } - - // == ----------------------------------------- == - - /** - * 获取 Assets 资源文件数据 - * @param fileName 资源文件名,可分成,如根目录,a.txt 或者子目录 /www/a.html - * @return byte[] , - > new String(byte[],encode) - */ - public static byte[] readBytesFromAssets(String fileName) { - if (DevUtils.getContext() != null && !TextUtils.isEmpty(fileName)) { - InputStream iStream = null; - try { - iStream = DevUtils.getContext().getResources().getAssets().open(fileName); - int length = iStream.available(); - byte[] buffer = new byte[length]; - iStream.read(buffer); - iStream.close(); - iStream = null; - return buffer; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "readBytesFromAssets"); - } finally { - if(iStream != null) { - try { - iStream.close(); - } catch (Exception e) { - } - } - } - } - return null; - } - - /** - * 读取字符串 来自 Assets文件 - * @param fileName - * @return - */ - public static String readStringFromAssets(String fileName) { - try { - return new String(readBytesFromAssets(fileName), "UTF-8"); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "readStringFromAssets"); - } - return null; - } - - /** - * 从res/raw 中获取内容。 - * @param resId 资源id - * @return byte[] , - > new String(byte[],encode) - */ - public static byte[] readBytesFromRaw(int resId) { - if (DevUtils.getContext() != null) { - InputStream iStream = null; - try { - iStream = DevUtils.getContext().getResources().openRawResource(resId); - int length = iStream.available(); - byte[] buffer = new byte[length]; - iStream.read(buffer); - iStream.close(); - iStream = null; - return buffer; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "readBytesFromRaw"); - } finally { - if(iStream != null) { - try { - iStream.close(); - } catch (Exception e) { - } - } - } - } - return null; - } - - /** - * 读取字符串 来自Raw 文件 - * @param resId - * @return - */ - public static String readStringFromRaw(int resId) { - try { - return new String(readBytesFromRaw(resId), "UTF-8"); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "readStringFromRaw"); - } - return null; - } - - // == - - /** - * 获取 Assets 资源文件数据(返回ArrayList 一行的全部内容属于一个索引) - * @param fileName 资源文件名,可分成,如根目录,a.txt 或者子目录 /www/a.html - * @return - */ - public static ArrayList geFileToListFromAssets(String fileName) { - if (DevUtils.getContext() != null && !TextUtils.isEmpty(fileName)) { - InputStream iStream = null; - InputStreamReader inReader = null; - BufferedReader bufReader = null; - try { - iStream = DevUtils.getContext().getResources().getAssets().open(fileName); - inReader = new InputStreamReader(iStream); - bufReader = new BufferedReader(inReader); - ArrayList fileContent = new ArrayList(); - String line; - while ((line = bufReader.readLine()) != null) { - fileContent.add(line); - } - return fileContent; - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "geFileToListFromAssets"); - } finally { - try { - bufReader.close(); - inReader.close(); - iStream.close(); - } catch (Exception e) { - } - } - } - return null; - } - - /** - * 从res/raw 中获取内容。(返回ArrayList 一行的全部内容属于一个索引) - * @param resId 资源id - * @return - */ - public static List geFileToListFromRaw(int resId) { - if (DevUtils.getContext() != null) { - InputStream iStream = null; - InputStreamReader inReader = null; - BufferedReader bufReader = null; - try { - iStream = DevUtils.getContext().getResources().openRawResource(resId); - inReader = new InputStreamReader(iStream); - bufReader = new BufferedReader(inReader); - List fileContent = new ArrayList(); - String line = null; - while ((line = bufReader.readLine()) != null) { - fileContent.add(line); - } - return fileContent; - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "geFileToListFromRaw"); - } finally { - try { - bufReader.close(); - inReader.close(); - iStream.close(); - } catch (Exception e) { - } - } - } - return null; - } - - - // ======== - /** - * 从Assets 资源中获取内容并保存到本地 - * @param fileName 资源文件名,可分成,如根目录,a.txt 或者子目录 /www/a.html - * @param file 保存地址 - * @return 是否保存成功 - */ - public static boolean saveAssetsFormFile(String fileName, File file) { - if (DevUtils.getContext() != null) { - try { - // 获取 Assets 文件 - InputStream iStream = DevUtils.getContext().getResources().getAssets().open(fileName); - // 存入SDCard - FileOutputStream fileOutputStream = new FileOutputStream(file); - // 设置数据缓冲 - byte[] buffer = new byte[1024]; - // 创建输入输出流 - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - int len = 0; - while ((len = iStream.read(buffer)) != -1) { - outStream.write(buffer, 0, len); - } - // 保存数据 - byte[] bytes = outStream.toByteArray(); - // 写入保存的文件 - fileOutputStream.write(bytes); - // 关闭流 - outStream.close(); - iStream.close(); - // -- - fileOutputStream.flush(); - fileOutputStream.close(); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveAssetsFormFile"); - } - } - return false; - } - - /** - * 从res/raw 中获取内容并保存到本地 - * @param resId 资源id - * @param file 保存地址 - * @return 是否保存成功 - */ - public static boolean saveRawFormFile(int resId, File file) { - if (DevUtils.getContext() != null) { - try { - // 获取raw 文件 - InputStream iStream = DevUtils.getContext().getResources().openRawResource(resId); - // 存入SDCard - FileOutputStream fileOutputStream = new FileOutputStream(file); - // 设置数据缓冲 - byte[] buffer = new byte[1024]; - // 创建输入输出流 - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - int len = 0; - while ((len = iStream.read(buffer)) != -1) { - outStream.write(buffer, 0, len); - } - // 保存数据 - byte[] bytes = outStream.toByteArray(); - // 写入保存的文件 - fileOutputStream.write(bytes); - // 关闭流 - outStream.close(); - iStream.close(); - // -- - fileOutputStream.flush(); - fileOutputStream.close(); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveRawFormFile"); - } - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/SDCardUtils.java b/DevLibUtils/src/main/java/dev/utils/app/SDCardUtils.java deleted file mode 100644 index ff1f9bddfb..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/SDCardUtils.java +++ /dev/null @@ -1,323 +0,0 @@ -package dev.utils.app; - -import android.content.Context; -import android.os.Environment; -import android.os.StatFs; -import android.os.storage.StorageManager; -import android.text.format.Formatter; - -import java.io.File; -import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.common.FileUtils; - -/** - * detail: SD卡相关辅助类 - * Created by Ttt - */ -public final class SDCardUtils { - - private SDCardUtils() { - } - - // 日志TAG - private static final String TAG = SDCardUtils.class.getSimpleName(); - - // == ----------------------------------------- == - - /** - * 判断SDCard是否正常挂载 - * @return - */ - public static boolean isSDCardEnable() { - // android.os.Environment - return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); - } - - /** - * 获取SD卡路径(File对象) - * @return - */ - public static File getSDCardFile() { - return Environment.getExternalStorageDirectory(); - } - - /** - * 获取SD卡路径(无添加 -> / -> File.separator) - * @return - */ - public static String getSDCardPath() { - return Environment.getExternalStorageDirectory().getAbsolutePath(); - } - - // == - - /** - * 判断 SD 卡是否可用 - * @return true : 可用, false : 不可用 - */ - public static boolean isSDCardEnablePath() { - return !getSDCardPaths().isEmpty(); - } - - /** - * 获取 SD 卡路径 - * @param removable true : 外置 SD 卡, false : 内置 SD 卡 - * @return SD 卡路径 - */ - @SuppressWarnings("TryWithIdenticalCatches") - public static List getSDCardPaths(final boolean removable) { - List listPaths = new ArrayList<>(); - try { - StorageManager storageManager = (StorageManager) DevUtils.getContext().getSystemService(Context.STORAGE_SERVICE); - Class storageVolumeClazz = Class.forName("android.os.storage.StorageVolume"); - Method getVolumeList = StorageManager.class.getMethod("getVolumeList"); - Method getPath = storageVolumeClazz.getMethod("getPath"); - Method isRemovable = storageVolumeClazz.getMethod("isRemovable"); - Object result = getVolumeList.invoke(storageManager); - final int length = Array.getLength(result); - for (int i = 0; i < length; i++) { - Object storageVolumeElement = Array.get(result, i); - String path = (String) getPath.invoke(storageVolumeElement); - boolean res = (Boolean) isRemovable.invoke(storageVolumeElement); - if (removable == res) { - listPaths.add(path); - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSDCardPaths"); - } - return listPaths; - } - - /** - * 获取 SD 卡路径 - * @return SD 卡路径 - */ - @SuppressWarnings("TryWithIdenticalCatches") - public static List getSDCardPaths() { - List listPaths = new ArrayList<>(); - try { - StorageManager storageManager = (StorageManager) DevUtils.getContext().getSystemService(Context.STORAGE_SERVICE); - Method getVolumePathsMethod = StorageManager.class.getMethod("getVolumePaths"); - getVolumePathsMethod.setAccessible(true); - Object invoke = getVolumePathsMethod.invoke(storageManager); - listPaths = Arrays.asList((String[]) invoke); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSDCardPaths"); - } - return listPaths; - } - - // == - - /** - * 返回对应路径的空间总大小 - * @param path - * @return - */ - public static long getAllBlockSize(String path) { - try { - // 获取路径的存储空间信息 - StatFs statFs = new StatFs(path); - // 获取单个数据块的大小(Byte) - long blockSize = statFs.getBlockSize(); - // 获取数据块的数量 - long blockCount = statFs.getBlockCount(); - // 返回空间总大小 - return (blockCount * blockSize); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAllBlockSize"); - } - return -1l; - } - - /** - * 返回对应路径的空闲空间(byte 字节大小) - * @param path - * @return - */ - public static long getAvailableBlocks(String path) { - try { - // 获取路径的存储空间信息 - StatFs statFs = new StatFs(path); - // 获取单个数据块的大小(Byte) - long blockSize = statFs.getBlockSize(); - // 空闲的数据块的数量 - long freeBlocks = statFs.getAvailableBlocks(); - // 返回空闲空间 - return (freeBlocks * blockSize); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAvailableBlocks"); - } - return -1l; - } - - - /** - * 返回对应路径,已使用的空间大小 - * @param path - * @return - */ - public static long getAlreadyBlock(String path) { - try { - // 获取路径的存储空间信息 - StatFs statFs = new StatFs(path); - // 获取单个数据块的大小(Byte) - long blockSize = statFs.getBlockSize(); - // 获取数据块的数量 - long blockCount = statFs.getBlockCount(); - // 空闲的数据块的数量 - long freeBlocks = statFs.getAvailableBlocks(); - // 返回空间总大小 - return ((blockCount - freeBlocks)* blockSize); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAlreadyBlock"); - } - return -1l; - } - - /** - * 返回对应路径的空间大小信息 - * @return 返回数据,0 = 总空间大小,1 = 空闲控件大小 , 2 = 已使用空间大小 - */ - public static long[] getBlockSizeInfos(String path) { - try { - // 获取路径的存储空间信息 - StatFs statFs = new StatFs(path); - // 获取单个数据块的大小(Byte) - long blockSize = statFs.getBlockSize(); - // 获取数据块的数量 - long blockCount = statFs.getBlockCount(); - // 空闲的数据块的数量 - long freeBlocks = statFs.getAvailableBlocks(); - // 计算空间大小信息 - long[] blocks = new long[3]; - blocks[0] = blockSize * blockCount; - blocks[1] = blockSize * freeBlocks; - blocks[2] = ((blockCount - freeBlocks)* blockSize); - // 返回空间大小信息 - return blocks; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getBlockSizeInfos"); - } - return null; - } - - /** - * 获得 SD 卡总大小 - * @return - */ - public static String getSDTotalSize() { - try { - File path = Environment.getExternalStorageDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSizeLong(); - long totalBlocks = stat.getBlockCountLong(); - return Formatter.formatFileSize(DevUtils.getContext(), blockSize * totalBlocks); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getSDTotalSize"); - } - return "unknown"; - } - - /** - * 获得 SD 卡剩余容量,即可用大小 - * @return - */ - public static String getSDAvailableSize() { - try { - File path = Environment.getExternalStorageDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSizeLong(); - long availableBlocks = stat.getAvailableBlocksLong(); - return Formatter.formatFileSize(DevUtils.getContext(), blockSize * availableBlocks); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getSDAvailableSize"); - } - return "unknown"; - } - - /** - * 获得机身内存总大小 - * @return - */ - public static String getRomTotalSize() { - try { - File path = Environment.getDataDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSizeLong(); - long totalBlocks = stat.getBlockCountLong(); - return Formatter.formatFileSize(DevUtils.getContext(), blockSize * totalBlocks); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getRomTotalSize"); - } - return "unknown"; - } - - /** - * 获得机身可用内存 - * @return - */ - public static String getRomAvailableSize() { - try { - File path = Environment.getDataDirectory(); - StatFs stat = new StatFs(path.getPath()); - long blockSize = stat.getBlockSizeLong(); - long availableBlocks = stat.getAvailableBlocksLong(); - return Formatter.formatFileSize(DevUtils.getContext(), blockSize * availableBlocks); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getRomAvailableSize"); - } - return "unknown"; - } - - // - - - /** - * 获取缓存地址 - * @return - */ - public static String getDiskCacheDir() { - String cachePath; - if (isSDCardEnable()) { // 判断SDCard是否挂载 - cachePath = DevUtils.getContext().getExternalCacheDir().getPath(); - } else { - cachePath = DevUtils.getContext().getCacheDir().getPath(); - } - // 防止不存在目录文件,自动创建 - FileUtils.createFolder(cachePath); - // 返回文件存储地址 - return cachePath; - } - - /** - * 获取缓存资源地址 - * @param fPath 文件地址 - * @return - */ - public static File getCacheFile(String fPath) { - return new File(getCachePath(fPath)); - } - - /** - * 获取缓存资源地址 - * @param fPath 文件地址 - * @return - */ - public static String getCachePath(String fPath){ - // 获取缓存地址 - String cachePath = new File(getDiskCacheDir(), fPath).getAbsolutePath(); - // 防止不存在目录文件,自动创建 - FileUtils.createFolder(cachePath); - // 返回头像地址 - return cachePath; - } -} - diff --git a/DevLibUtils/src/main/java/dev/utils/app/ScreenUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ScreenUtils.java deleted file mode 100644 index d711d6e1ae..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ScreenUtils.java +++ /dev/null @@ -1,729 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Build; -import android.provider.Settings; -import android.support.annotation.NonNull; -import android.util.DisplayMetrics; -import android.view.Surface; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; - -import java.lang.reflect.Method; -import java.text.DecimalFormat; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 屏幕相关工具类 - * Created by Ttt - */ -public final class ScreenUtils { - - private ScreenUtils() { - } - - // 日志TAG - private static final String TAG = ScreenUtils.class.getSimpleName(); - - /** - * 通过上下文获取 DisplayMetrics (获取关于显示的通用信息,如显示大小,分辨率和字体) - * @return - */ - private static DisplayMetrics getDisplayMetrics() { - try { - WindowManager wManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - if (wManager != null) { - DisplayMetrics dMetrics = new DisplayMetrics(); - wManager.getDefaultDisplay().getMetrics(dMetrics); - return dMetrics; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDisplayMetrics"); - } - return null; - } - - // == ----------------------------------------- == - -// /** -// * 通过上下文获取屏幕宽度 -// * @param mContext -// * @return -// */ -// @SuppressWarnings("deprecation") -// public static int getScreenWidth() { -// try { -// // 获取屏幕信息 -// DisplayMetrics dMetrics = getDisplayMetrics(); -// if (dMetrics != null) { -// return dMetrics.widthPixels; -// } -// // 这种也可以获取,不过已经提问过时(下面这段可以注释掉) -// WindowManager wManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); -// if (wManager != null) { -// return wManager.getDefaultDisplay().getWidth(); -// } -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getScreenWidth"); -// } -// return -1; -// } -// -// /** -// * 通过上下文获取屏幕高度 -// * @param mContext -// * @return -// */ -// @SuppressWarnings("deprecation") -// public static int getScreenHeight() { -// try { -// // 获取屏幕信息 -// DisplayMetrics dMetrics = getDisplayMetrics(); -// if (dMetrics != null) { -// return dMetrics.heightPixels; -// } -// // 这种也可以获取,不过已经提示过时(下面这段可以注释掉) -// WindowManager wManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); -// if (wManager != null) { -// return wManager.getDefaultDisplay().getHeight(); -// } -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getScreenHeight"); -// } -// return -1; -// } -// -// /** -// * 通过上下文获取屏幕宽度高度 -// * @param mContext -// * @return int[] 0 = 宽度,1 = 高度 -// */ -// @SuppressWarnings("deprecation") -// public static int[] getScreenWidthHeight() { -// try { -// // 获取屏幕信息 -// DisplayMetrics dMetrics = getDisplayMetrics(); -// if (dMetrics != null) { -// return new int[] { dMetrics.widthPixels, dMetrics.heightPixels }; -// } -// // 这种也可以获取,不过已经提示过时(下面这段可以注释掉) -// WindowManager wManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); -// if (wManager != null) { -// int width = wManager.getDefaultDisplay().getWidth(); -// int height = wManager.getDefaultDisplay().getHeight(); -// return new int[] { width, height }; -// } -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getScreenWidthHeight"); -// } -// return null; -// } - - /** - * 获取屏幕的宽度(单位:px) - * @return 屏幕宽 - */ - public static int getScreenWidth() { - try { - WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - if (windowManager == null) { - return DevUtils.getContext().getResources().getDisplayMetrics().widthPixels; - } - Point point = new Point(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - windowManager.getDefaultDisplay().getRealSize(point); - } else { - windowManager.getDefaultDisplay().getSize(point); - } - return point.x; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getScreenWidth"); - } - return -1; - } - - /** - * 获取屏幕的高度(单位:px) - * @return 屏幕高 - */ - public static int getScreenHeight() { - try { - WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - if (windowManager == null) { - return DevUtils.getContext().getResources().getDisplayMetrics().heightPixels; - } - Point point = new Point(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - windowManager.getDefaultDisplay().getRealSize(point); - } else { - windowManager.getDefaultDisplay().getSize(point); - } - return point.y; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getScreenHeight"); - } - return -1; - } - - /** - * 通过上下文获取屏幕宽度高度 - * @return point.x 宽, point.y 高 - */ - public static Point getScreenWidthHeightToPoint() { - try { - WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - if (windowManager == null) { - DisplayMetrics dMetrics = DevUtils.getContext().getResources().getDisplayMetrics(); - return new Point(dMetrics.widthPixels, dMetrics.heightPixels); - } - Point point = new Point(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - windowManager.getDefaultDisplay().getRealSize(point); - } else { - windowManager.getDefaultDisplay().getSize(point); - } - return point; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getScreenWidthHeightToPoint"); - } - return null; - } - - /** - * 通过上下文获取屏幕宽度高度 - * @return int[] 0 = 宽度,1 = 高度 - */ - public static int[] getScreenWidthHeight() { - try { - WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - if (windowManager == null) { - DisplayMetrics dMetrics = DevUtils.getContext().getResources().getDisplayMetrics(); - return new int[] { dMetrics.widthPixels, dMetrics.heightPixels }; - } - Point point = new Point(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - windowManager.getDefaultDisplay().getRealSize(point); - } else { - windowManager.getDefaultDisplay().getSize(point); - } - return new int[] { point.x, point.y }; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getScreenWidthHeight"); - } - return new int[] { 0, 0 }; - } - - /** - * 获取屏幕分辨率 - * @return - */ - public static String getScreenSize() { - try { - // 获取分辨率 - int[] whArys = getScreenWidthHeight(); - // 返回分辨率信息 - return whArys[1] + "x" + whArys[0]; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getScreenSize"); - } - return "unknown"; - } - - /** - * 获取屏幕英寸 例5.5英寸 - * @return - */ - public static String getScreenSizeOfDevice() { - // https://blog.csdn.net/lincyang/article/details/42679589 - try { - Point point = new Point(); - WindowManager windowManager = (WindowManager) DevUtils.getContext().getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getRealSize(point); - DisplayMetrics dm = DevUtils.getContext().getResources().getDisplayMetrics(); - double x = Math.pow(point.x/ dm.xdpi, 2); - double y = Math.pow(point.y / dm.ydpi, 2); - double screenInches = Math.sqrt(x + y); - // 转换大小 - DecimalFormat df = new DecimalFormat("#.0"); - return df.format(screenInches); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getScreenSizeOfDevice"); - } - return "unknown"; - } - - // == - - /** - * 通过上下文获取屏幕密度 - * @return - */ - public static float getDensity() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - // 屏幕密度(0.75 / 1.0 / 1.5 / 2.0) - return dMetrics.density; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDensity"); - } - return -1; - } - - /** - * 通过上下文获取屏幕密度Dpi - * @return - */ - public static int getDensityDpi() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - // 屏幕密度DPI(120 / 160 / 240 / 320) - return dMetrics.densityDpi; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDensityDpi"); - } - return -1; - } - - /** - * 通过上下文获取屏幕缩放密度 - * @return - */ - public static float getScaledDensity() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - return dMetrics.scaledDensity; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getScaledDensity"); - } - return -1f; - } - - /** - * 获取 X轴 dpi - * @return - */ - public static float getXDpi() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - return dMetrics.xdpi; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getXDpi"); - } - return -1f; - } - - /** - * 获取 Y轴 dpi - * @return - */ - public static float getYDpi() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - return dMetrics.ydpi; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getYDpi"); - } - return -1f; - } - - /** - * 获取 宽度比例 dpi 基准 - * @return - */ - public static float getWidthDpi() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - return dMetrics.widthPixels / dMetrics.density; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getWidthDpi"); - } - return -1f; - } - - /** - * 获取 高度比例 dpi 基准 - * @return - */ - public static float getHeightDpi() { - try { - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - return dMetrics.heightPixels / dMetrics.density; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getHeightDpi"); - } - return -1f; - } - - /** - * 获取屏幕信息 - * @return - */ - public static String getScreenInfo(){ - StringBuilder sBuilder = new StringBuilder(); - // 获取屏幕信息 - DisplayMetrics dMetrics = getDisplayMetrics(); - if (dMetrics != null) { - try { - int heightPixels = dMetrics.heightPixels; - int widthPixels = dMetrics.widthPixels; - - float xdpi = dMetrics.xdpi; - float ydpi = dMetrics.ydpi; - int densityDpi = dMetrics.densityDpi; - - float density = dMetrics.density; - float scaledDensity = dMetrics.scaledDensity; - - float heightDpi = heightPixels / density; - float widthDpi = widthPixels / density; - // - - sBuilder.append("\nheightPixels: " + heightPixels + "px"); - sBuilder.append("\nwidthPixels: " + widthPixels + "px"); - - sBuilder.append("\nxdpi: " + xdpi + "dip"); - sBuilder.append("\nydpi: " + ydpi + "dpi"); - sBuilder.append("\ndensityDpi: " + densityDpi + "dpi"); - - sBuilder.append("\ndensity: " + density); - sBuilder.append("\nscaledDensity: " + scaledDensity); - - sBuilder.append("\nheightDpi: " + heightDpi + "dpi"); - sBuilder.append("\nwidthDpi: " + widthDpi + "dpi"); - - return sBuilder.toString(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getScreenInfo"); - } - } - return sBuilder.toString(); - } - - // == - - /** - * 设置屏幕为全屏 - * @param activity - */ - public static void setFullScreen(@NonNull final Activity activity) { - try { - activity.requestWindowFeature(Window.FEATURE_NO_TITLE);// 隐藏标题 - activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "setFullScreen"); - } - } - - /** - * 设置屏幕为横屏 - * 还有一种就是在 Activity 中加属性 android:screenOrientation="landscape" - * 不设置 Activity 的 android:configChanges 时, - * 切屏会重新调用各个生命周期,切横屏时会执行一次,切竖屏时会执行两次 - * 设置 Activity 的 android:configChanges="orientation"时, - * 切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次 - * 设置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize" - * (4.0 以上必须带最后一个参数)时 - * 切屏不会重新调用各个生命周期,只会执行 onConfigurationChanged 方法 - * @param activity - */ - public static void setLandscape(@NonNull final Activity activity) { - try { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setLandscape"); - } - } - - /** - * 设置屏幕为竖屏 - * @param activity - */ - public static void setPortrait(@NonNull final Activity activity) { - try { - activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setPortrait"); - } - } - - /** - * 判断是否横屏 - * @return true : 是, false : 否 - */ - public static boolean isLandscape() { - try { - return DevUtils.getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isLandscape"); - } - return false; - } - - /** - * 判断是否竖屏 - * @return true : 是, false : 否 - */ - public static boolean isPortrait() { - try { - return DevUtils.getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isPortrait"); - } - return false; - } - - /** - * 获取屏幕旋转角度 - * @param activity - * @return 屏幕旋转角度 - */ - public static int getScreenRotation(@NonNull final Activity activity) { - try { - switch (activity.getWindowManager().getDefaultDisplay().getRotation()) { - case Surface.ROTATION_0: - return 0; - case Surface.ROTATION_90: - return 90; - case Surface.ROTATION_180: - return 180; - case Surface.ROTATION_270: - return 270; - default: - return 0; - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getScreenRotation"); - } - return 0; - } - - /** - * 判断是否锁屏 - * @return true : 是, false : 否 - */ - public static boolean isScreenLock() { - try { - KeyguardManager keyguardManager = (KeyguardManager) DevUtils.getContext().getSystemService(Context.KEYGUARD_SERVICE); - return keyguardManager != null && keyguardManager.inKeyguardRestrictedInputMode(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isScreenLock"); - } - return false; - } - - /** - * 判断是否是平板 - * @return true : 是, false : 否 - */ - public static boolean isTablet() { - try { - return (DevUtils.getContext().getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isTablet"); - } - return false; - } - - // == ----------------------------------------- == - - /** - * 获得状态栏的高度(无关 android:theme 获取状态栏高度) - * @return - */ - public static int getStatusHeight() { - try { - Class clazz = Class.forName("com.android.internal.R$dimen"); - Object object = clazz.newInstance(); - int height = Integer.parseInt(clazz.getField("status_bar_height").get(object).toString()); - return DevUtils.getContext().getResources().getDimensionPixelSize(height); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getStatusHeight"); - } - return -1; - } - - /** - * 获取应用区域 TitleBar 高度 (顶部灰色TitleBar高度,没有设置 android:theme 的 NoTitleBar 时会显示) - * @param activity - * @return - */ - public static int getStatusBarHeight(Activity activity) { - try { - Rect rect = new Rect(); - activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); - return rect.top; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getStatusBarHeight"); - } - return -1; - } - - /** - * 设置进入休眠时长 - 需添加权限 - * @param duration 时长 - */ - public static void setSleepDuration(final int duration) { - try { - Settings.System.putInt(DevUtils.getContext().getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT, duration); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setSleepDuration"); - } - } - - /** - * 获取进入休眠时长 - * @return 进入休眠时长,报错返回-123 - */ - public static int getSleepDuration() { - try { - return Settings.System.getInt(DevUtils.getContext().getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSleepDuration"); - return -1; - } - } - - // == 截图(有用) == - - // https://www.cnblogs.com/angel88/p/7933437.html - - // https://github.com/weizongwei5/AndroidScreenShot_SysApi - - /** - * 获取当前屏幕截图,包含状态栏 (顶部灰色TitleBar高度,没有设置 android:theme 的 NoTitleBar 时会显示) - * @param activity - * @return - */ - public static Bitmap snapShotWithStatusBar(Activity activity) { - try { - View view = activity.getWindow().getDecorView(); - view.setDrawingCacheEnabled(true); - view.buildDrawingCache(); - Bitmap bmp = view.getDrawingCache(); - int[] sParams = getScreenWidthHeight(); - - // 主要是 getWindowVisibleDisplayFrame 内容才不为null - Rect frame = new Rect(); - activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); - // 创建新的图片 - Bitmap bitmap = Bitmap.createBitmap(bmp, 0, 0, sParams[0], sParams[1]); - view.destroyDrawingCache(); - return bitmap; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "snapShotWithStatusBar"); - } - return null; - } - - /** - * 获取当前屏幕截图,不包含状态栏 (如果 android:theme 全屏了,则截图无状态栏) - * @param activity - * @return - */ - public static Bitmap snapShotWithoutStatusBar(Activity activity) { - View view = activity.getWindow().getDecorView(); - view.setDrawingCacheEnabled(true); - view.buildDrawingCache(); - Bitmap bmp = view.getDrawingCache(); - int[] sParams = getScreenWidthHeight(); - - // 获取状态栏高度 - int statusBarHeight = getStatusBarHeight(activity); - if(statusBarHeight == -1) { - statusBarHeight = 0; - } - - Rect frame = new Rect(); - activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); - // 创建新的图片 - Bitmap bitmap = Bitmap.createBitmap(bmp, 0, statusBarHeight, sParams[0], sParams[1] - statusBarHeight); - view.destroyDrawingCache(); - return bitmap; - } - - // == - - /** - * 获取底部导航栏高度 - * @return - */ - public static int getNavigationBarHeight() { - int navigationBarHeight = 0; - try { - Resources resources = DevUtils.getContext().getResources(); - // 获取对应方向字符串 - String orientation = resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape"; - // 获取对应的id - int resourceId = resources.getIdentifier(orientation, "dimen", "android"); - if (resourceId > 0 && checkDeviceHasNavigationBar()) { - navigationBarHeight = resources.getDimensionPixelSize(resourceId); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getNavigationBarHeight"); - } - return navigationBarHeight; - } - - /** - * 检测是否具有底部导航栏 - * hint: 一加手机上判断不准确 - * @return - */ - public static boolean checkDeviceHasNavigationBar() { - boolean hasNavigationBar = false; - try { - Resources resources = DevUtils.getContext().getResources(); - int id = resources.getIdentifier("config_showNavigationBar", "bool", "android"); - if (id > 0) { - hasNavigationBar = resources.getBoolean(id); - } - try { - Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); - Method m = systemPropertiesClass.getMethod("get", String.class); - String navBarOverride = (String) m.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); - if ("1".equals(navBarOverride)) { - hasNavigationBar = false; - } else if ("0".equals(navBarOverride)) { - hasNavigationBar = true; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "checkDeviceHasNavigationBar - SystemProperties"); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "checkDeviceHasNavigationBar"); - } - return hasNavigationBar; - } -} - diff --git a/DevLibUtils/src/main/java/dev/utils/app/ServiceUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ServiceUtils.java deleted file mode 100644 index e4deef4cdc..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ServiceUtils.java +++ /dev/null @@ -1,189 +0,0 @@ -package dev.utils.app; - -import android.app.ActivityManager; -import android.app.ActivityManager.RunningServiceInfo; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; - -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 服务相关工具类 - * Created by Blankj - * http://blankj.com - * Update to Ttt - */ -public final class ServiceUtils { - - private ServiceUtils() { - } - - // 日志TAG - private static final String TAG = ServiceUtils.class.getSimpleName(); - - /** - * 判断服务是否运行 - * @param className 完整包名的服务类名 - * @return true : 是, false : 否 - */ - public static boolean isServiceRunning(final String className) { - try { - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return false; - List listInfos = activityManager.getRunningServices(0x7FFFFFFF); - if (listInfos == null || listInfos.size() == 0) return false; - for (RunningServiceInfo rInfo : listInfos) { - if (className.equals(rInfo.service.getClassName())) return true; - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isServiceRunning"); - } - return false; - } - - /** - * 判断服务是否运行 - * @param cls The service class. - * @return true : 是, false : 否 - */ - public static boolean isServiceRunning(final Class cls) { - return isServiceRunning(cls.getName()); - } - - /** - * 获取所有运行的服务 - * @return 服务名集合 - */ - public static Set getAllRunningService() { - try { - ActivityManager activityManager = (ActivityManager) DevUtils.getContext().getSystemService(Context.ACTIVITY_SERVICE); - if (activityManager == null) return Collections.emptySet(); - List listInfos = activityManager.getRunningServices(0x7FFFFFFF); - if (listInfos == null || listInfos.size() == 0) return null; - Set names = new HashSet<>(); - for (RunningServiceInfo rInfo : listInfos) { - names.add(rInfo.service.getClassName()); - } - return names; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getAllRunningService"); - } - return Collections.emptySet(); - } - - /** - * 启动服务 - * @param className 完整包名的服务类名 - */ - public static void startService(final String className) { - try { - startService(Class.forName(className)); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "startService"); - } - } - - /** - * 启动服务 - * @param cls 服务类 - */ - public static void startService(final Class cls) { - try { - Intent intent = new Intent(DevUtils.getContext(), cls); - DevUtils.getContext().startService(intent); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "startService"); - } - } - - /** - * 停止服务 - * @param className 完整包名的服务类名 - * @return true : 停止成功, false : 停止失败 - */ - public static boolean stopService(final String className) { - try { - return stopService(Class.forName(className)); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "stopService"); - return false; - } - } - - /** - * 停止服务 - * @param cls 服务类 - * @return true : 停止成功, false : 停止失败 - */ - public static boolean stopService(final Class cls) { - try { - Intent intent = new Intent(DevUtils.getContext(), cls); - return DevUtils.getContext().stopService(intent); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "stopService"); - return false; - } - } - - /** - * 绑定服务 - * @param className 完整包名的服务类名 - * @param conn 服务连接对象 - * @param flags 绑定选项 - * ==== - * {@link Context#BIND_AUTO_CREATE} - * {@link Context#BIND_DEBUG_UNBIND} - * {@link Context#BIND_NOT_FOREGROUND} - * {@link Context#BIND_ABOVE_CLIENT} - * {@link Context#BIND_ALLOW_OOM_MANAGEMENT} - * {@link Context#BIND_WAIVE_PRIORITY} - */ - public static void bindService(final String className, final ServiceConnection conn, final int flags) { - try { - bindService(Class.forName(className), conn, flags); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "bindService"); - } - } - - /** - * 绑定服务 - * @param cls 服务类 - * @param conn 服务连接对象 - * @param flags 绑定选项 - * ==== - * {@link Context#BIND_AUTO_CREATE} - * {@link Context#BIND_DEBUG_UNBIND} - * {@link Context#BIND_NOT_FOREGROUND} - * {@link Context#BIND_ABOVE_CLIENT} - * {@link Context#BIND_ALLOW_OOM_MANAGEMENT} - * {@link Context#BIND_WAIVE_PRIORITY} - */ - public static void bindService(final Class cls, final ServiceConnection conn, final int flags) { - try { - Intent intent = new Intent(DevUtils.getContext(), cls); - DevUtils.getContext().bindService(intent, conn, flags); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "bindService"); - } - } - - /** - * 解绑服务 - * @param conn 服务连接对象 - */ - public static void unbindService(final ServiceConnection conn) { - try { - DevUtils.getContext().unbindService(conn); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "unbindService"); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ShapeUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ShapeUtils.java deleted file mode 100644 index dec8114091..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ShapeUtils.java +++ /dev/null @@ -1,430 +0,0 @@ -package dev.utils.app; - -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.ColorRes; -import android.support.annotation.RequiresApi; -import android.support.v4.content.ContextCompat; -import android.text.TextUtils; -import android.view.View; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Shape 工具类 - * Created by Ttt - * https://blog.csdn.net/tanghongchang123/article/details/80283686 - * https://www.cnblogs.com/popfisher/p/5606690.html - * https://www.cnblogs.com/dongdong230/p/4183079.html - * https://www.cnblogs.com/zhongle/archive/2012/08/28/2659902.html - * https://www.2cto.com/kf/201601/456024.html - */ -public final class ShapeUtils { - - // 日志TAG - private static final String TAG = ShapeUtils.class.getSimpleName(); - - private final GradientDrawable drawable; - - private ShapeUtils(Builder builder) { - drawable = builder.gradientDrawable; - } - - public GradientDrawable getDrawable() { - return drawable; - } - - public void setDrawable(View view){ - if (view != null){ - view.setBackground(drawable); - } - } - - // == - - /** - * detail: 构造者模式 - * Created by Ttt - */ - public final static class Builder { - - private GradientDrawable gradientDrawable = new GradientDrawable(); - - public Builder() { - } - - public Builder(GradientDrawable drawable){ - if (drawable != null){ - this.gradientDrawable = drawable; - } - } - - /** - * 获取 Shape 工具类 - * @return - */ - public ShapeUtils build(){ - return new ShapeUtils(this); - } - - // ==== - - // = 设置圆角 = - - /** - * 设置圆角 - * @param radius - * @return - */ - public Builder setRadius(float radius){ - if (gradientDrawable != null){ - gradientDrawable.setCornerRadius(radius); - } - return this; - } - - // == 设置左边 == - - /** - * 设置圆角 - * @param left - * @return - */ - public Builder setRadiusLeft(float left){ - setCornerRadii(left, 0, 0, left); - return this; - } - - /** - * 设置圆角 - * @param leftTop - * @param leftBottom - * @return - */ - public Builder setRadiusLeft(float leftTop, float leftBottom){ - setCornerRadii(leftTop, 0, 0, leftBottom); - return this; - } - - // == 设置右边 == - - /** - * 设置圆角 - * @param right - * @return - */ - public Builder setRadiusRight(float right){ - setCornerRadii(0, right, right, 0); - return this; - } - - /** - * 设置圆角 - * @param rightTop - * @param rightBottom - * @return - */ - public Builder setRadiusRight(float rightTop, float rightBottom){ - setCornerRadii(0, rightTop, rightBottom, 0); - return this; - } - - // == 圆角内部处理 == - - /** - * 内部处理方法 - * @param leftTop - * @param rightTop - * @param rightBottom - * @param leftBottom - */ - public Builder setCornerRadii(float leftTop, float rightTop, float rightBottom, float leftBottom) { -// - - if (gradientDrawable != null){ - // radii 数组分别指定四个圆角的半径,每个角可以指定[X_Radius,Y_Radius],四个圆角的顺序为左上,右上,右下,左下。如果X_Radius,Y_Radius为0表示还是直角。 - gradientDrawable.setCornerRadii(new float[] { leftTop, leftTop, rightTop, rightTop, rightBottom, rightBottom, leftBottom, leftBottom }); - } - return this; - } - - // == 设置背景色(填充) == - -// - - /** - * 设置背景色(填充铺满) - * @param color - * @return - */ - public Builder setColor(String color){ - if (gradientDrawable != null && !TextUtils.isEmpty(color)){ - try { - gradientDrawable.setColor(Color.parseColor(color)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setColor"); - } - } - return this; - } - - /** - * 设置背景色(填充铺满) - * @param color - * @return - */ - public Builder setColor(@ColorRes int color){ - if (gradientDrawable != null){ - try { - gradientDrawable.setColor(ContextCompat.getColor(DevUtils.getContext(), color)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setColor"); - } - } - return this; - } - - // == 设置边框颜色 == - -// - - /** - * 设置大小 - * @param width - * @param height - * @return - */ - public Builder setSize(int width, int height){ - if (gradientDrawable != null){ - try { - gradientDrawable.setSize(width, height); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setSize"); - } - } - return this; - } - - // == 设置边距 == - -// /** -// * 设置边距 -// * @param padding -// * @return -// */ -// public Builder setPadding(int padding){ -// if (gradientDrawable != null){ -// try { -// Rect rect = new Rect(); -// rect.left = padding; -// rect.top = padding; -// rect.right = padding; -// rect.bottom = padding; -// gradientDrawable.getPadding(rect); -// } catch (Exception e){ -// LogPrintUtils.eTag(TAG, e, "setPadding"); -// } -// } -// return this; -// } - - // == 设置渐变 == - -// -// - -// switch (angle) { -// case 0: -// st.mOrientation = Orientation.LEFT_RIGHT; -// break; -// case 45: -// st.mOrientation = Orientation.BL_TR; -// break; -// case 90: -// st.mOrientation = Orientation.BOTTOM_TOP; -// break; -// case 135: -// st.mOrientation = Orientation.BR_TL; -// break; -// case 180: -// st.mOrientation = Orientation.RIGHT_LEFT; -// break; -// case 225: -// st.mOrientation = Orientation.TR_BL; -// break; -// case 270: -// st.mOrientation = Orientation.TOP_BOTTOM; -// break; -// case 315: -// st.mOrientation = Orientation.TL_BR; -// break; -// } - - /** - * 设置渐变颜色 - * @param colors - */ - public Builder(@ColorInt int[] colors){ - this(new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, colors)); - } - - /** - * 设置渐变颜色 - * @param orientation - * @param colors - */ - public Builder(GradientDrawable.Orientation orientation, @ColorInt int[] colors){ - this(new GradientDrawable(orientation, colors)); - } - } - - // == 快捷方法 == - - /** - * 创建新的 Shape Builder 对象 - * @param radius - * @param color - * @return - */ - public static Builder newBuilder(float radius, @ColorRes int color){ - return new Builder().setRadius(radius).setColor(color); - } - - /** - * 创建新的 Shape Builder 对象 - * @param left 通用左上, 左下 - * @param color - * @return - */ - public static Builder newBuilderToLeft(float left, @ColorRes int color){ - return new Builder().setRadiusLeft(left).setColor(color); - } - - /** - * 创建新的 Shape Builder 对象 - * @param right 通用右上, 左下 - * @param color - * @return - */ - public static Builder newBuilderToRight(float right, @ColorRes int color){ - return new Builder().setRadiusRight(right).setColor(color); - } - - /** - * 创建渐变的 Shape Builder 对象 - * @param colors - * @return - */ - public static Builder newBuilderToGradient(@ColorInt int[] colors){ - return new Builder(colors); - } - - /** - * 创建渐变的 Shape Builder 对象 - * @param orientation - * @param colors - * @return - */ - public static Builder newBuilderToGradient(GradientDrawable.Orientation orientation, @ColorInt int[] colors){ - return new Builder(orientation, colors); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ShellUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ShellUtils.java deleted file mode 100644 index a188d94de8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ShellUtils.java +++ /dev/null @@ -1,164 +0,0 @@ -package dev.utils.app; - -import java.io.BufferedReader; -import java.io.DataOutputStream; -import java.io.InputStreamReader; -import java.util.List; - -import dev.utils.LogPrintUtils; - -/** - * detail: Shell 相关工具类 - * Created by Blankj - * http://blankj.com - * Update to Ttt - */ -public final class ShellUtils { - - private ShellUtils() { - } - - // 日志TAG - private static final String TAG = ShellUtils.class.getSimpleName(); - - // 换行符 - private static final String LINE_SEP = System.getProperty("line.separator"); - - /** - * 是否是在 root 下执行命令 - * @param command 命令 - * @param isRoot 是否需要 root 权限执行 - * @return CommandResult - */ - public static CommandResult execCmd(final String command, final boolean isRoot) { - return execCmd(new String[]{ command }, isRoot, true); - } - - /** - * 是否是在 root 下执行命令 - * @param commands 多条命令链表 - * @param isRoot 是否需要 root 权限执行 - * @return CommandResult - */ - public static CommandResult execCmd(final List commands, final boolean isRoot) { - return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRoot, true); - } - - /** - * 是否是在 root 下执行命令 - * @param commands 多条命令数组 - * @param isRoot 是否需要 root 权限执行 - * @return CommandResult - */ - public static CommandResult execCmd(final String[] commands, final boolean isRoot) { - return execCmd(commands, isRoot, true); - } - - /** - * 是否是在 root 下执行命令 - * @param command 命令 - * @param isRoot 是否需要 root 权限执行 - * @param isNeedResultMsg 是否需要结果消息 - * @return CommandResult - */ - public static CommandResult execCmd(final String command, final boolean isRoot, final boolean isNeedResultMsg) { - return execCmd(new String[]{ command }, isRoot, isNeedResultMsg); - } - - /** - * 是否是在 root 下执行命令 - * @param commands 命令链表 - * @param isRoot 是否需要 root 权限执行 - * @param isNeedResultMsg 是否需要结果消息 - * @return CommandResult - */ - public static CommandResult execCmd(final List commands, final boolean isRoot, final boolean isNeedResultMsg) { - return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg); - } - - /** - * 是否是在 root 下执行命令 - * @param commands 命令数组 - * @param isRoot 是否需要 root 权限执行 - * @param isNeedResultMsg 是否需要结果消息 - * @return CommandResult - */ - public static CommandResult execCmd(final String[] commands, final boolean isRoot, final boolean isNeedResultMsg) { - int result = -1; - if (commands == null || commands.length == 0) { - return new CommandResult(result, null, null); - } - Process process = null; - BufferedReader successResult = null; - BufferedReader errorResult = null; - StringBuilder successMsg = null; - StringBuilder errorMsg = null; - DataOutputStream os = null; - try { - process = Runtime.getRuntime().exec(isRoot ? "su" : "sh"); - os = new DataOutputStream(process.getOutputStream()); - for (String command : commands) { - if (command == null) continue; - os.write(command.getBytes()); - os.writeBytes(LINE_SEP); - os.flush(); - } - os.writeBytes("exit" + LINE_SEP); - os.flush(); - result = process.waitFor(); - if (isNeedResultMsg) { - successMsg = new StringBuilder(); - errorMsg = new StringBuilder(); - successResult = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8")); - errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8")); - String line; - if ((line = successResult.readLine()) != null) { - successMsg.append(line); - while ((line = successResult.readLine()) != null) { - successMsg.append(LINE_SEP).append(line); - } - } - if ((line = errorResult.readLine()) != null) { - errorMsg.append(line); - while ((line = errorResult.readLine()) != null) { - errorMsg.append(LINE_SEP).append(line); - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "execCmd"); - } finally { - try { - os.close(); - successResult.close(); - errorResult.close(); - } catch (Exception e) { - } - if (process != null) { - process.destroy(); - } - } - return new CommandResult( - result, - successMsg == null ? null : successMsg.toString(), - errorMsg == null ? null : errorMsg.toString() - ); - } - - /** 返回的命令结果 */ - public static class CommandResult { - - /** 结果码 */ - public int result; - /** 成功信息 */ - public String successMsg; - /** 错误信息 */ - public String errorMsg; - - public CommandResult(final int result, final String successMsg, final String errorMsg) { - this.result = result; - this.successMsg = successMsg; - this.errorMsg = errorMsg; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ShortCutUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ShortCutUtils.java deleted file mode 100644 index 496652077d..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ShortCutUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -package dev.utils.app; - -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.Intent.ShortcutIconResource; -import android.database.Cursor; -import android.net.Uri; - -import dev.utils.LogPrintUtils; - -/** - * detail: 创建删除快捷图标工具类 - * Created by Ttt - * 需要权限: - * com.android.launcher.permission.INSTALL_SHORTCUT - * com.android.launcher.permission.UNINSTALL_SHORTCUT - */ -public final class ShortCutUtils { - - private ShortCutUtils() { - } - - // 日志TAG - private static final String TAG = ShortCutUtils.class.getSimpleName(); - - /** - * 检测是否存在桌面快捷方式 - * @param context - * @param name - * @return 是否存在桌面图标 - */ - public static boolean hasShortcut(Context context, String name) { - boolean isInstallShortcut = false; - Cursor cursor = null; - try { - ContentResolver cr = context.getContentResolver(); - String AUTHORITY = "com.android.launcher.settings"; - Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/favorites?notify=true"); - cursor = cr.query(CONTENT_URI, new String[]{"title", "iconResource"}, "title=?", new String[]{ name }, null); - if (cursor != null && cursor.getCount() > 0) { - isInstallShortcut = true; - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "hasShortcut"); - } finally { - if (cursor != null) { - try { - cursor.close(); - } catch (Exception e) { - } - } - } - return isInstallShortcut; - } - - /** - * 为程序创建桌面快捷方式 - * @param context - * @param clasName - * @param name - * @param res - */ - public static void addShortcut(Context context, String clasName, String name, int res) { - Intent shortcut = new Intent( "com.android.launcher.action.INSTALL_SHORTCUT"); - shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷方式的名称 - shortcut.putExtra("duplicate", false); // 不允许重复创建 - // 设置 快捷方式跳转页面 - Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); - shortcutIntent.setClassName(context, clasName); - shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - // 快捷方式的图标 - ShortcutIconResource iconRes = ShortcutIconResource.fromContext(context, res); - shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); - context.sendBroadcast(shortcut); - } - - /** - * 删除程序的快捷方式 - * @param context - * @param name - */ - public static void delShortcut(Context context, String clasName, String name) { - Intent shortcut = new Intent( "com.android.launcher.action.UNINSTALL_SHORTCUT"); - // 快捷方式的名称 - shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); - String appClass = context.getPackageName() + "." + clasName; - ComponentName comp = new ComponentName(context.getPackageName(), appClass); - shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(Intent.ACTION_MAIN).setComponent(comp)); - context.sendBroadcast(shortcut); - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/app/SignaturesUtils.java b/DevLibUtils/src/main/java/dev/utils/app/SignaturesUtils.java deleted file mode 100644 index 358e768846..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/SignaturesUtils.java +++ /dev/null @@ -1,232 +0,0 @@ -package dev.utils.app; - -import android.content.pm.Signature; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -import javax.security.auth.x500.X500Principal; - -import dev.utils.LogPrintUtils; - -/** - * detail: 签名工具类(获取app,签名信息) - * Created by Ttt - */ -public final class SignaturesUtils { - - private SignaturesUtils() { - } - - // 日志TAG - private static final String TAG = SignaturesUtils.class.getSimpleName(); - - // 如需要小写则把ABCDEF改成小写 - private static final char HEX_DIGITS[] = { '0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - /** - * 检测应用程序是否是用"CN=Android Debug,O=Android,C=US"的debug信息来签名的 - * 判断签名是debug签名还是release签名 - */ - private final static X500Principal DEBUG_DN = new X500Principal("CN=Android Debug,O=Android,C=US"); - - /** - * 进行转换 - * @param bData - * @return - */ - public static String toHexString(byte[] bData) { - StringBuilder sb = new StringBuilder(bData.length * 2); - for (int i = 0, len = bData.length; i < len; i++) { - sb.append(HEX_DIGITS[(bData[i] & 0xf0) >>> 4]); - sb.append(HEX_DIGITS[bData[i] & 0x0f]); - } - return sb.toString(); - } - - /** - * 返回MD5 - * @param signatures - * @return - */ - public static String signatureMD5(Signature[] signatures) { - try { - MessageDigest digest = MessageDigest.getInstance("MD5"); - if (signatures != null) { - for (Signature s : signatures) - digest.update(s.toByteArray()); - } - return toHexString(digest.digest()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "signatureMD5"); - return ""; - } - } - - /** - * SHA1 - */ - public static String signatureSHA1(Signature[] signatures) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - if (signatures != null) { - for (Signature s : signatures) - digest.update(s.toByteArray()); - } - return toHexString(digest.digest()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "signatureSHA1"); - return ""; - } - } - - /** - * SHA256 - */ - public static String signatureSHA256(Signature[] signatures) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - if (signatures != null) { - for (Signature s : signatures) - digest.update(s.toByteArray()); - } - return toHexString(digest.digest()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "signatureSHA256"); - return ""; - } - } - - /** - * 判断签名是debug签名还是release签名 - * @return true = 开发(debug.keystore),false = 上线发布(非.android默认debug.keystore) - */ - public static boolean isDebuggable(Signature[] signatures) { - // 判断是否默认key(默认是) - boolean debuggable = true; - try { - for (int i = 0, len = signatures.length; i < len; i++) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ByteArrayInputStream stream = new ByteArrayInputStream(signatures[i].toByteArray()); - X509Certificate cert = (X509Certificate) cf.generateCertificate(stream); - debuggable = cert.getSubjectX500Principal().equals(DEBUG_DN); - if (debuggable) { - break; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isDebuggable"); - } - return debuggable; - } - - /** - * 获取App 证书对象 - */ - public static X509Certificate getX509Certificate(Signature[] signatures){ - try { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ByteArrayInputStream stream = new ByteArrayInputStream(signatures[0].toByteArray()); - X509Certificate cert = (X509Certificate) cf.generateCertificate(stream); - return cert; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getX509Certificate"); - } - return null; - } - - /** - * 打印签名信息 - * @param signatures - * @return - */ - public static void printSignatureName(Signature[] signatures){ - try { - for (int i = 0, len = signatures.length; i < len; i++) { - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - ByteArrayInputStream stream = new ByteArrayInputStream(signatures[i].toByteArray()); - X509Certificate cert = (X509Certificate) cf.generateCertificate(stream); - - String pubKey = cert.getPublicKey().toString(); // 公钥 - String signNumber = cert.getSerialNumber().toString(); - - LogPrintUtils.dTag(TAG, "signName:" + cert.getSigAlgName());//算法名 - LogPrintUtils.dTag(TAG, "pubKey:" + pubKey); - LogPrintUtils.dTag(TAG, "signNumber:" + signNumber);//证书序列编号 - LogPrintUtils.dTag(TAG, "subjectDN:"+cert.getSubjectDN().toString()); - LogPrintUtils.dTag(TAG, cert.getNotAfter() + "--" + cert.getNotBefore()); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "signatureNameA"); - } - } - - // -- - - // Android的APK应用签名机制以及读取签名的方法 - // http://www.jb51.net/article/79894.htm - - /** - * 从APK中读取签名 - * @param file - * @return - * @throws IOException - */ - public static Signature[] getSignaturesFromApk(File file) { - try { - Certificate[] certificates = getCertificateFromApk(file); - Signature[] signatures = new Signature[] { new Signature(certificates[0].getEncoded()) }; - return signatures; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSignaturesFromApk"); - } - return null; - } - - /** - * 从APK中读取签名 - * @param file - * @return - * @throws IOException - */ - public static Certificate[] getCertificateFromApk(File file) { - try { - JarFile jarFile = new JarFile(file); - JarEntry je = jarFile.getJarEntry("AndroidManifest.xml"); - byte[] readBuffer = new byte[8192]; - return loadCertificates(jarFile, je, readBuffer); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getCertificateFromApk"); - } - return null; - } - - /** - * 加载签名 - * @param jarFile - * @param je - * @param readBuffer - * @return - */ - private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) { - try { - InputStream is = jarFile.getInputStream(je); - while (is.read(readBuffer, 0, readBuffer.length) != -1) { - } - is.close(); - return je != null ? je.getCertificates() : null; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "loadCertificates"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/SizeUtils.java b/DevLibUtils/src/main/java/dev/utils/app/SizeUtils.java deleted file mode 100644 index 8b247c2290..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/SizeUtils.java +++ /dev/null @@ -1,208 +0,0 @@ -package dev.utils.app; - -import android.util.DisplayMetrics; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: dp,px,sp转换、View获取宽高等 - * Created by Ttt - */ -public final class SizeUtils { - - private SizeUtils() { - } - - // 日志TAG - private static final String TAG = SizeUtils.class.getSimpleName(); - - /** - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) - * @param dpValue - */ - public static int dipConvertPx(float dpValue) { - try { - float scale = DevUtils.getContext().getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "dipConvertPx"); - } - return -1; - } - - /** - * 根据手机的分辨率从 px(像素) 的单位 转成为 dp - * @param pxValue - */ - public static int pxConvertDip(float pxValue) { - try { - float scale = DevUtils.getContext().getResources().getDisplayMetrics().density; - return (int) (pxValue / scale + 0.5f); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "pxConvertDip"); - } - return -1; - } - - /** - * 根据手机的分辨率从 px(像素) 的单位 转成为 sp - * @param pxValue - */ - public static int pxConvertSp(float pxValue) { - try { - float scale = DevUtils.getContext().getResources().getDisplayMetrics().scaledDensity; - return (int) (pxValue / scale + 0.5f); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "pxConvertSp"); - } - return -1; - } - - /** - * 根据手机的分辨率从 sp 的单位 转成为 px - * @param spValue - */ - public static int spConvertPx(float spValue) { - try { - float scale = DevUtils.getContext().getResources().getDisplayMetrics().scaledDensity; - return (int) (spValue * scale + 0.5f); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "spConvertPx"); - } - return -1; - } - - /** - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 第二种 - * @param dpValue - */ - public static int dipConvertPx2(float dpValue) { - try { - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, DevUtils.getContext().getResources().getDisplayMetrics()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "dipConvertPx2"); - } - return -1; - } - - /** - * 根据手机的分辨率从 sp 的单位 转成为 px 第二种 - * @param spValue - */ - public static int spConvertPx2(float spValue) { - try { - // android.util.TypedValue - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, DevUtils.getContext().getResources().getDisplayMetrics()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "spConvertPx2"); - } - return -1; - } - - /** - * 各种单位转换 - 该方法存在于 TypedValue - * @param unit 单位 - * @param value 值 - * @param metrics DisplayMetrics - * @return 转换结果 - */ - public static float applyDimension(final int unit, final float value, final DisplayMetrics metrics) { - switch (unit) { - case TypedValue.COMPLEX_UNIT_PX: - return value; - case TypedValue.COMPLEX_UNIT_DIP: - return value * metrics.density; - case TypedValue.COMPLEX_UNIT_SP: - return value * metrics.scaledDensity; - case TypedValue.COMPLEX_UNIT_PT: - return value * metrics.xdpi * (1.0f / 72); - case TypedValue.COMPLEX_UNIT_IN: - return value * metrics.xdpi; - case TypedValue.COMPLEX_UNIT_MM: - return value * metrics.xdpi * (1.0f / 25.4f); - } - return -1; - } - - // == - - /** - * 在 onCreate 中获取视图的尺寸 - 需回调 onGetSizeListener 接口,在 onGetSize 中获取 view 宽高 - * 用法示例如下所示 - * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() { - * Override - * public void onGetSize(final View view) { - * view.getWidth(); - * } - * }); - * @param view 视图 - * @param listener 监听器 - */ - public static void forceGetViewSize(final View view, final onGetSizeListener listener) { - view.post(new Runnable() { - @Override - public void run() { - if (listener != null) { - listener.onGetSize(view); - } - } - }); - } - - /** 获取到 View 尺寸的监听 */ - public interface onGetSizeListener { - void onGetSize(View view); - } - - // == - - /** - * 测量视图尺寸 - * @param view 视图 - * @return arr[0]: 视图宽度, arr[1]: 视图高度 - */ - public static int[] measureView(final View view) { - try { - ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); - if (layoutParams == null) { - layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, layoutParams.width); - int lpHeight = layoutParams.height; - int heightSpec; - if (lpHeight > 0) { - heightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY); - } else { - heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } - view.measure(widthSpec, heightSpec); - return new int[]{ view.getMeasuredWidth(), view.getMeasuredHeight() }; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "measureView"); - return new int[] { 0, 0 }; - } - } - - /** - * 获取测量视图宽度 - * @param view 视图 - * @return 视图宽度 - */ - public static int getMeasuredWidth(final View view) { - return measureView(view)[0]; - } - - /** - * 获取测量视图高度 - * @param view 视图 - * @return 视图高度 - */ - public static int getMeasuredHeight(final View view) { - return measureView(view)[1]; - } -} - diff --git a/DevLibUtils/src/main/java/dev/utils/app/StateListUtils.java b/DevLibUtils/src/main/java/dev/utils/app/StateListUtils.java deleted file mode 100644 index 9bb603c6d4..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/StateListUtils.java +++ /dev/null @@ -1,317 +0,0 @@ -package dev.utils.app; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; -import android.support.annotation.ColorRes; -import android.support.annotation.DrawableRes; -import android.support.v4.content.ContextCompat; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 颜色状态列表 工具类 - * Created by Ttt - * https://blog.csdn.net/wuqilianga/article/details/72964884 - * https://blog.csdn.net/CrazyMo_/article/details/53456541 - */ -public final class StateListUtils { - - private StateListUtils() { - } - - // 日志TAG - private static final String TAG = StateListUtils.class.getSimpleName(); - -// android:state_active 是否处于激活状态 -// android:state_checkable 是否可勾选 -// android:state_checked 是否已勾选 -// android:state_enabled 是否可用 -// android:state_first 是否开始状态 -// android:state_focused 是否已获取焦点 -// android:state_last 是否处于结束 -// android:state_middle 是否处于中间 -// android:state_pressed 是否处于按下状态 . -// android:state_selected 是否处于选中状态 -// android:state_window_focused 是否窗口已获得焦点 - - /** - * 通过上下文获取 - * @param id - * @return - */ - public static ColorStateList getColorStateList(int id) { - try { - return ContextCompat.getColorStateList(DevUtils.getContext(), id); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getColorStateList"); - } - return null; - } - - // === 设置字体颜色 === - - // == 字符串颜色 == - - /** - * 创建 颜色状态列表 => createColorStateList("#ffffffff", "#ff44e6ff") - * @param pressed - * @param normal - * @return - */ - public static ColorStateList createColorStateList(String pressed, String normal) { - // 颜色值 - int[] colors = new int[]{ Color.parseColor(pressed), Color.parseColor(normal) }; - // 状态值 - int[][] states = new int[2][]; - states[0] = new int[]{ android.R.attr.state_pressed }; // 选中颜色 - states[1] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - /** - * 创建 颜色状态列表 - * @param selected - * @param pressed - * @param normal - * @return - */ - public static ColorStateList createColorStateList(String selected, String pressed, String normal) { - // 颜色值 - int[] colors = new int[]{ Color.parseColor(selected), Color.parseColor(pressed), Color.parseColor(normal) }; - // 状态值 - int[][] states = new int[3][]; - states[0] = new int[]{ android.R.attr.state_selected }; // 选中颜色 - states[1] = new int[]{ android.R.attr.state_pressed }; // 点击颜色 - states[2] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - /** - * 创建 颜色状态列表 - * @param selected - * @param pressed - * @param focused - * @param checked - * @param normal - * @return - */ - public static ColorStateList createColorStateList(String selected, String pressed, String focused, String checked, String normal) { - // 颜色值 - int[] colors = new int[]{ Color.parseColor(selected), Color.parseColor(pressed), Color.parseColor(focused), Color.parseColor(checked), Color.parseColor(normal) }; - // 状态值 - int[][] states = new int[5][]; - states[0] = new int[]{ android.R.attr.state_selected }; // 选中颜色 - states[1] = new int[]{ android.R.attr.state_pressed }; // 点击颜色 - states[2] = new int[]{ android.R.attr.state_focused }; // 获取焦点 - states[3] = new int[]{ android.R.attr.state_checked }; // 选中 - states[4] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - // == int == - - /** - * 创建 颜色状态列表 => createColorStateList(R.color.whilte, R.color.black) - * @param pressed - * @param normal - * @return - */ - public static ColorStateList createColorStateList(@ColorRes int pressed, @ColorRes int normal) { - Context context = DevUtils.getContext(); - // 颜色值 - int[] colors = new int[2]; - colors[0] = ContextCompat.getColor(context, pressed); - colors[1] = ContextCompat.getColor(context, normal); - // 状态值 - int[][] states = new int[2][]; - states[0] = new int[]{ android.R.attr.state_pressed }; // 选中颜色 - states[1] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - /** - * 创建 颜色状态列表 - * @param selected - * @param pressed - * @param normal - * @return - */ - public static ColorStateList createColorStateList(@ColorRes int selected, @ColorRes int pressed, @ColorRes int normal) { - Context context = DevUtils.getContext(); - // 颜色值 - int[] colors = new int[3]; - colors[0] = ContextCompat.getColor(context, selected); - colors[1] = ContextCompat.getColor(context, pressed); - colors[2] = ContextCompat.getColor(context, normal); - // 状态值 - int[][] states = new int[3][]; - states[0] = new int[]{ android.R.attr.state_selected }; // 选中颜色 - states[1] = new int[]{ android.R.attr.state_pressed }; // 点击颜色 - states[2] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - /** - * 创建 颜色状态列表 - * @param selected - * @param pressed - * @param focused - * @param checked - * @param normal - * @return - */ - public static ColorStateList createColorStateList(@ColorRes int selected, @ColorRes int pressed, @ColorRes int focused, @ColorRes int checked, @ColorRes int normal) { - Context context = DevUtils.getContext(); - // 颜色值 - int[] colors = new int[5]; - colors[0] = ContextCompat.getColor(context, selected); - colors[1] = ContextCompat.getColor(context, pressed); - colors[2] = ContextCompat.getColor(context, focused); - colors[3] = ContextCompat.getColor(context, checked); - colors[4] = ContextCompat.getColor(context, normal); - // 状态值 - int[][] states = new int[5][]; - states[0] = new int[]{ android.R.attr.state_selected }; // 选中颜色 - states[1] = new int[]{ android.R.attr.state_pressed }; // 点击颜色 - states[2] = new int[]{ android.R.attr.state_focused }; // 获取焦点 - states[3] = new int[]{ android.R.attr.state_checked }; // 选中 - states[4] = new int[]{ }; // 默认颜色 - // 生成对应的List对象 - ColorStateList colorList = new ColorStateList(states, colors); - return colorList; - } - - // === 设置背景切换 Drawable === - - /** - * 创建 Drawable选择切换 list => view.setBackground(Drawable) - * @param pressed - * @param normal - * @return - */ - public static StateListDrawable newSelector(@DrawableRes int pressed, @DrawableRes int normal) { - Context context = DevUtils.getContext(); - // == 获取 Drawable == - Drawable pressedDraw = ContextCompat.getDrawable(context, pressed); - Drawable normalDraw = ContextCompat.getDrawable(context, normal); - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressedDraw); - stateListDrawable.addState(new int[] {}, normalDraw); - return stateListDrawable; - } - - /** - * 创建 Drawable选择切换 list - * @param selected - * @param pressed - * @param normal - * @return - */ - public static StateListDrawable newSelector(@DrawableRes int selected, @DrawableRes int pressed, @DrawableRes int normal) { - Context context = DevUtils.getContext(); - // == 获取 Drawable == - Drawable selectedDraw = ContextCompat.getDrawable(context, selected); - Drawable pressedDraw = ContextCompat.getDrawable(context, pressed); - Drawable normalDraw = ContextCompat.getDrawable(context, normal); - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_selected }, selectedDraw); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressedDraw); - stateListDrawable.addState(new int[] {}, normalDraw); - return stateListDrawable; - } - - /** - * 创建 Drawable选择切换 list - * @param selected - * @param pressed - * @param focused - * @param checked - * @param normal - * @return - */ - public static StateListDrawable newSelector(@DrawableRes int selected, @DrawableRes int pressed, @DrawableRes int focused, @DrawableRes int checked, @DrawableRes int normal) { - Context context = DevUtils.getContext(); - // == 获取 Drawable == - Drawable selectedDraw = ContextCompat.getDrawable(context, selected); - Drawable pressedDraw = ContextCompat.getDrawable(context, pressed); - Drawable focusedDraw = ContextCompat.getDrawable(context, focused); - Drawable checkedDraw = ContextCompat.getDrawable(context, checked); - Drawable normalDraw = ContextCompat.getDrawable(context, normal); - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_selected }, selectedDraw); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressedDraw); - stateListDrawable.addState(new int[] { android.R.attr.state_focused }, focusedDraw); - stateListDrawable.addState(new int[] { android.R.attr.state_checked }, checkedDraw); - stateListDrawable.addState(new int[] {}, normalDraw); - return stateListDrawable; - } - - // === 设置背景切换 Drawable === - - /** - * 创建 Drawable选择切换 list => view.setBackground(Drawable) - * @param pressed - * @param normal - * @return - */ - public static StateListDrawable newSelector(Drawable pressed, Drawable normal) { - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressed); - stateListDrawable.addState(new int[] {}, normal); - return stateListDrawable; - } - - /** - * 创建 Drawable选择切换 list - * @param selected - * @param pressed - * @param normal - * @return - */ - public static StateListDrawable newSelector(Drawable selected, Drawable pressed, Drawable normal) { - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_selected }, selected); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressed); - stateListDrawable.addState(new int[] {}, normal); - return stateListDrawable; - } - - /** - * 创建 Drawable选择切换 list - * @param selected - * @param pressed - * @param focused - * @param checked - * @param normal - * @return - */ - public static StateListDrawable newSelector(Drawable selected, Drawable pressed, Drawable focused, Drawable checked, Drawable normal) { - // 默认初始化 - StateListDrawable stateListDrawable = new StateListDrawable(); - stateListDrawable.addState(new int[] { android.R.attr.state_selected }, selected); - stateListDrawable.addState(new int[] { android.R.attr.state_pressed }, pressed); - stateListDrawable.addState(new int[] { android.R.attr.state_focused }, focused); - stateListDrawable.addState(new int[] { android.R.attr.state_checked }, checked); - stateListDrawable.addState(new int[] {}, normal); - return stateListDrawable; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/TextViewUtils.java b/DevLibUtils/src/main/java/dev/utils/app/TextViewUtils.java deleted file mode 100644 index 099624eec4..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/TextViewUtils.java +++ /dev/null @@ -1,582 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.annotation.ColorInt; -import android.support.annotation.IdRes; -import android.text.Html; -import android.view.View; -import android.view.Window; -import android.widget.TextView; - -import dev.utils.LogPrintUtils; - -/** - * detail: TextView 工具类 - * Created by Ttt - */ -public final class TextViewUtils { - - private TextViewUtils() { - } - - // 日志TAG - private static final String TAG = TextViewUtils.class.getSimpleName(); - - /** - * 获取TextView - * @param view - * @return - */ - public static TextView getTextView(View view){ - if (view != null){ - try { - return (TextView) view; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getTextView"); - } - } - return null; - } - - /** - * 获取TextView - * @param view - * @return - */ - public static TextView getTextView(View view, @IdRes int id){ - if (view != null){ - try { - return view.findViewById(id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getTextView"); - } - } - return null; - } - - /** - * 获取TextView - * @param activity - * @return - */ - public static TextView getTextView(Activity activity, @IdRes int id){ - if (activity != null){ - try { - return activity.findViewById(id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getTextView"); - } - } - return null; - } - - /** - * 获取TextView - * @param window - * @return - */ - public static TextView getTextView(Window window, @IdRes int id){ - if (window != null){ - try { - return window.findViewById(id); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getTextView"); - } - } - return null; - } - - // = - - /** - * 获取文本 - * @param textView - * @return - */ - public static String getText(TextView textView){ - if (textView != null){ - return textView.getText().toString(); - } - return null; - } - - /** - * 获取文本 - * @param view - * @return - */ - public static String getText(View view) { - if (view != null){ - try { - return ((TextView) view).getText().toString(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getText"); - } - } - return null; - } - - - /** - * 获取文本 - * @param view - * @return - */ - public static String getText(View view, @IdRes int id){ - if (view != null){ - try { - return ((TextView) view.findViewById(id)).getText().toString(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getText"); - } - } - return null; - } - - /** - * 获取文本 - * @param activity - * @return - */ - public static String getText(Activity activity, @IdRes int id){ - if (activity != null){ - try { - return ((TextView) activity.findViewById(id)).getText().toString(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getText"); - } - } - return null; - } - - /** - * 获取文本 - * @param window - * @return - */ - public static String getText(Window window, @IdRes int id){ - if (window != null){ - try { - return ((TextView) window.findViewById(id)).getText().toString(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getText"); - } - } - return null; - } - - // = - - - /** - * 设置字体颜色 - * @param view - * @param color - */ - public static void setTextColor(View view, @ColorInt int color){ - if (view != null){ - if (view instanceof TextView){ - ((TextView) view).setTextColor(color); - } - } - } - - /** - * 设置字体颜色 - * @param textView - * @param color - */ - public static void setTextColor(TextView textView, @ColorInt int color){ - if (textView != null){ - textView.setTextColor(color); - } - } - - /** - * 设置字体颜色 - * @param view - * @param id - * @param color - */ - public static void setTextColor(View view, @IdRes int id, @ColorInt int color){ - if (view != null){ - try { - ((TextView) view.findViewById(id)).setTextColor(color); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setTextColor"); - } - } - } - - /** - * 设置字体颜色 - * @param activity - * @param id - * @param color - */ - public static void setTextColor(Activity activity, @IdRes int id, @ColorInt int color){ - if (activity != null){ - try { - ((TextView) activity.findViewById(id)).setTextColor(color); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setTextColor"); - } - } - } - - /** - * 设置字体颜色 - * @param window - * @param id - * @param color - */ - public static void setTextColor(Window window, @IdRes int id, @ColorInt int color){ - if (window != null){ - try { - ((TextView) window.findViewById(id)).setTextColor(color); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setTextColor"); - } - } - } - - // = - - /** - * 设置内容 - * @param view - * @param content - */ - public static void setText(View view, String content){ - if (view != null && content != null){ - if (view instanceof TextView){ - ((TextView) view).setText(content); - } - } - } - - /** - * 设置内容 - * @param view - * @param id - * @param content - */ - public static void setText(View view, @IdRes int id, String content){ - if (view != null){ - try { - ((TextView) view.findViewById(id)).setText(content); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setText"); - } - } - } - - /** - * 设置内容 - * @param activity - * @param id - * @param content - */ - public static void setText(Activity activity, @IdRes int id, String content){ - if (activity != null){ - try { - ((TextView) activity.findViewById(id)).setText(content); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setText"); - } - } - } - - /** - * 设置内容 - * @param window - * @param id - * @param content - */ - public static void setText(Window window, @IdRes int id, String content){ - if (window != null){ - try { - ((TextView) window.findViewById(id)).setText(content); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setText"); - } - } - } - - // = - - /** - * 设置 Html 内容 - * @param view - * @param content - */ - public static void setHtmlText(View view, String content){ - if (view != null && content != null){ - if (view instanceof TextView){ - ((TextView) view).setText(Html.fromHtml(content)); - } - } - } - - /** - * 设置 Html 内容 - * @param view - * @param id - * @param content - */ - public static void setHtmlText(View view, @IdRes int id, String content){ - if (view != null){ - try { - ((TextView) view.findViewById(id)).setText(Html.fromHtml(content)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setHtmlText"); - } - } - } - - /** - * 设置 Html 内容 - * @param activity - * @param id - * @param content - */ - public static void setHtmlText(Activity activity, @IdRes int id, String content){ - if (activity != null){ - try { - ((TextView) activity.findViewById(id)).setText(Html.fromHtml(content)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setHtmlText"); - } - } - } - - /** - * 设置 Html 内容 - * @param window - * @param id - * @param content - */ - public static void setHtmlText(Window window, @IdRes int id, String content){ - if (window != null){ - try { - ((TextView) window.findViewById(id)).setText(Html.fromHtml(content)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setHtmlText"); - } - } - } - - // - - - /** - * 设置内容 - * @param textView - * @param content - */ - public static void setText(TextView textView, String content){ - if (textView != null && content != null){ - textView.setText(content); - } - } - - /** - * 设置 Html 内容 - * @param textView - * @param content - */ - public static void setHtmlText(TextView textView, String content){ - if (textView != null && content != null){ - textView.setText(Html.fromHtml(content)); - } - } - - /** - * 给TextView设置下划线 - * @param textView - */ - public static void setTVUnderLine(TextView textView) { - if (textView != null){ - Paint paint = textView.getPaint(); - paint.setFlags(Paint.UNDERLINE_TEXT_FLAG); - paint.setAntiAlias(true); - } - } - - /** - * 获取字体高度 - * @param textView - * @return - */ - public static int getTextHeight(TextView textView){ - if (textView != null){ - return getTextHeight(textView.getPaint()); - } - return -1; - } - - /** - * 获取字体高度 - * @param paint - * @return - */ - public static int getTextHeight(Paint paint){ - // https://blog.csdn.net/superbigcupid/article/details/47153139 - // 获取字体高度 - Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); - // 计算内容高度 - int tHeight = (int) Math.ceil((fontMetrics.descent - fontMetrics.ascent)); - // 返回字体高度 - return tHeight; - } - - /** - * 获取字体顶部偏移高度 - * @param textView - * @return - */ - public static int getTextTopOffsetHeight(TextView textView){ - if (textView != null){ - return getTextTopOffsetHeight(textView.getPaint()); - } - return -1; - } - - /** - * 获取字体顶部偏移高度 - * @param paint - * @return - */ - public static int getTextTopOffsetHeight(Paint paint){ - // 获取字体高度 - Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); - // 计算字体偏差(顶部偏差) - int baseLine = (int) Math.ceil(Math.abs(fontMetrics.top) - Math.abs(fontMetrics.ascent)); - // 返回顶部偏差 - return baseLine; - } - - /** - * 计算字体宽度 - * @param textView - * @return - */ - public static float getTextWidth(TextView textView){ - return getTextWidth(textView.getPaint(), textView.getText().toString()); - } - - /** - * 计算字体宽度 - * @param paint - * @param hintStr - * @return - */ - public static float getTextWidth(Paint paint, String hintStr){ - return paint.measureText(hintStr); - } - - /** - * 获取画布中间居中位置 - * @param targetRect - * @param paint - * @return - */ - public static int getCenterRectY(Rect targetRect, Paint paint){ - // 将字画在矩形背景的正中位置 - // https://blog.csdn.net/superbigcupid/article/details/47153139 - // 获取字体高度 - Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); - // 获取底部Y轴居中位置 - return targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top; - // canvas.drawText(testString, targetRect.centerX(), baseline, paint); - } - - /** - * 通过需要的高度, 计算字体大小 - * @param tHeight 需要的字体高度 - * @return - */ - public static float reckonTextSize(int tHeight){ - // 创建画笔 - Paint paint = new Paint(); - // 默认字体大小 - float textSize = 90.0f; - // 计算内容高度 - int calcTextHeight = -1; - // 循环计算 - while (true){ - // 设置画笔大小 - paint.setTextSize(textSize); - // 获取字体高度 - Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); - // 计算内容高度 - calcTextHeight = (int) Math.ceil((fontMetrics.descent - fontMetrics.ascent)); - // 符合条件则直接返回 - if (calcTextHeight == tHeight){ - return textSize; - } else if (calcTextHeight > tHeight){ // 如果计算的字体高度大于 - textSize -= 0.5f; - } else { - textSize += 0.5f; - } - } - } - - /** - * 计算第几位超过宽度 - * @param textView - * @param width - * @return -1 表示没超过 - */ - public static int calcTextWidth(TextView textView, float width){ - if (textView != null){ - return calcTextWidth(textView.getPaint(), textView.getText().toString(), width); - } - return -1; - } - - /** - * 计算第几位超过宽度 - * @param text - * @param width - * @return -1 表示没超过 - */ - public static int calcTextWidth(Paint paint, String text, float width){ - // 先获取宽度 - float textWidth = getTextWidth(paint, text); - // 判断是否超过 - if (textWidth <= width){ - return -1; // 表示没超过 - } - // 获取数据长度 - int length = text.length(); - // 循环除2 - while (true) { - // 数据至少为2位以上 - if (length < 2) { - return 0; // 表示第一位已经超过 - } - // 从中间取值 - length = length / 2; - // 计算宽度 - textWidth = getTextWidth(paint, text.substring(0, length)); - // 判断是否小于宽度 - 进行返回长度 - if (textWidth <= width){ - break; - } - } - // 遍历计算 - for (int i = length, len = text.length(); i < len; i++){ - // 获取字体内容宽度 - float tWidth = paint.measureText(text.substring(0, i)); - // 判断是否大于指定宽度 - if (tWidth > width){ - return i - 1; // 返回超过前的长度 - } else if (tWidth == width){ - return i; // 返回超过前的长度 - } - } - return -1; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/UriUtils.java b/DevLibUtils/src/main/java/dev/utils/app/UriUtils.java deleted file mode 100644 index c3f704c1c1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/UriUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.utils.app; - -import android.net.Uri; -import android.os.Build; -import android.support.v4.content.FileProvider; - -import java.io.File; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Uri 工具类 - * Created by Blankj - * http://blankj.com - * Update to Ttt - */ -public final class UriUtils { - - private UriUtils() { - } - - // 日志TAG - private static final String TAG = UriUtils.class.getSimpleName(); - - /** - - - - */ - - /** - * 返回处理后的Uri, 单独传递名字, 自动添加包名 ${applicationId} - * @param file - * @param name - * @return - * getUriForFileToName(file, "fileProvider"); - * getUriForFile(file, "包名.fileProvider"); - */ - public static Uri getUriForFileToName(final File file, String name){ - try { - String authority = DevUtils.getContext().getPackageName() + "." + name; - return getUriForFile(file, authority); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getUriForFileToName"); - } - return null; - } - - - /** - * Return a content URI for a given file. - * @param file The file. - * @return a content URI for a given file - */ - public static Uri getUriForFile(final File file, final String authority) { - if (file == null) return null; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - return FileProvider.getUriForFile(DevUtils.getContext(), authority, file); - } else { - return Uri.fromFile(file); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/VibrationUtils.java b/DevLibUtils/src/main/java/dev/utils/app/VibrationUtils.java deleted file mode 100644 index 3e5715e4e7..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/VibrationUtils.java +++ /dev/null @@ -1,61 +0,0 @@ -package dev.utils.app; - -import android.content.Context; -import android.os.Vibrator; - -import dev.utils.LogPrintUtils; - -/** - * detail: 震动相关工具类 - * Created by Ttt - */ -public final class VibrationUtils { - - private VibrationUtils() { - } - - // 日志TAG - private static final String TAG = VibrationUtils.class.getSimpleName(); - - /** - * 震动 - * - * @param context 上下文 - * @param milliseconds 振动时长 - */ - public static void vibrate(final Context context, final long milliseconds) { - try { - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(milliseconds); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "vibrate"); - } - } - - /** - * 指定手机以pattern模式振动 - * @param context - * @param pattern new long[]{400,800,1200,1600},就是指定在 400ms、800ms、1200ms、1600ms 这些时间点交替启动、关闭手机振动器 - * @param repeat 指定pattern数组的索引,指定pattern数组中从repeat索引开始的振动进行循环。-1表示只振动一次,非-1表示从 pattern的指定下标开始重复振动。 - */ - public static void vibrate(final Context context, final long[] pattern, final int repeat) { - try { - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(pattern, repeat); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "vibrate"); - } - } - - /** - * 取消振动 - * @param context 上下文 - */ - public static void cancel(final Context context) { - try { - ((Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE)).cancel(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "cancel"); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/ViewUtils.java b/DevLibUtils/src/main/java/dev/utils/app/ViewUtils.java deleted file mode 100644 index ec4b9d6e3a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/ViewUtils.java +++ /dev/null @@ -1,640 +0,0 @@ -package dev.utils.app; - -import android.app.Activity; -import android.content.Context; -import android.content.ContextWrapper; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.Window; -import android.widget.AbsListView; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ListView; - -import dev.utils.LogPrintUtils; - -/** - * detail: View 操作相关工具类 - * Created by Ttt - */ -public final class ViewUtils { - - private ViewUtils() { - } - - // 日志TAG - private static final String TAG = ViewUtils.class.getSimpleName(); - - /** - * 获取上下文 - * @param view - * @return - */ - public static Context getContext(View view){ - if (view != null){ - return view.getContext(); - } - return null; - } - - /** - * 判断View 是否为null - * @param view - * @return - */ - public static boolean isEmpty(View view){ - return view == null; - } - - /** - * 判断View 是否为null - * @param views - * @return - */ - public static boolean isEmpty(View... views){ - if (views != null && views.length != 0){ - for (int i = 0, len = views.length; i < len; i++){ - View view = views[i]; - if (view == null){ - return true; - } - } - return false; - } - return true; - } - - /** - * 判断View 是否显示 - * @param view - * @return - */ - public static boolean isVisibility(View view){ - return isVisibility(view, true); - } - - /** - * 判断 View 是否显示 - * @param view - * @param isDf - * @return - */ - public static boolean isVisibility(View view, boolean isDf){ - if (view != null){ - // 判断是否显示 - return (view.getVisibility() == View.VISIBLE); - } - // 出现意外返回默认值 - return isDf; - } - - /** - * 判断 View 是否都显示显示 - * @param views - * @return - */ - public static boolean isVisibilitys(View... views){ - if (views != null && views.length != 0){ - for (int i = 0, len = views.length; i < len; i++){ - View view = views[i]; - if (view != null && view.getVisibility() == View.VISIBLE){ - } else { - return false; - } - } - return true; - } - return false; - } - - /** - * 判断View 是否隐藏占位 - * @param view - * @return - */ - public static boolean isVisibilityIN(View view){ - return isVisibilityIN(view, false); - } - - /** - * 判断View 是否隐藏占位 - * @param view - * @param isDf - * @return - */ - public static boolean isVisibilityIN(View view, boolean isDf){ - if (view != null){ - // 判断是否显示 - return (view.getVisibility() == View.INVISIBLE); - } - // 出现意外返回默认值 - return isDf; - } - - /** - * 判断View 是否隐藏 - * @param view - * @return - */ - public static boolean isVisibilityGone(View view){ - return isVisibilityGone(view, false); - } - - /** - * 判断View 是否隐藏 - * @param view - * @param isDf - * @return - */ - public static boolean isVisibilityGone(View view, boolean isDf){ - if (view != null){ - // 判断是否显示 - return (view.getVisibility() == View.GONE); - } - // 出现意外返回默认值 - return isDf; - } - - // == - - /** - * 获取显示的状态 (View.VISIBLE : View.GONE) - * @param isVisibility - * @return - */ - public static int getVisibility(boolean isVisibility){ - return isVisibility ? View.VISIBLE : View.GONE; - } - - /** - * 获取显示的状态 (View.VISIBLE : View.INVISIBLE) - * @param isVisibility - * @return - */ - public static int getVisibilityIN(boolean isVisibility){ - return isVisibility ? View.VISIBLE : View.INVISIBLE; - } - - // -- - - /** - * 设置View显示状态 - * @param isVisibility - * @param view - */ - public static boolean setVisibility(boolean isVisibility, View view){ - if (view != null){ - view.setVisibility(isVisibility ? View.VISIBLE : View.GONE); - } - return isVisibility; - } - - /** - * 设置View显示的状态 - * @param isVisibility - * @param view - */ - public static boolean setVisibility(int isVisibility, View view){ - if (view != null){ - view.setVisibility(isVisibility); - } - return (isVisibility == View.VISIBLE); - } - - // -- - - /** - * 设置View 显示的状态 - * @param isVisibility - * @param views - */ - public static boolean setVisibilitys(boolean isVisibility, View... views){ - return setVisibilitys(getVisibility(isVisibility), views); - } - - /** - * 设置View 显示的状态 - * @param isVisibility - * @param views - */ - public static boolean setVisibilitys(int isVisibility, View... views){ - if (views != null && views.length != 0){ - for (int i = 0, len = views.length; i < len; i++){ - View view = views[i]; - if (view != null){ - view.setVisibility(isVisibility); - } - } - } - return (isVisibility == View.VISIBLE); - } - - /** - * 切换View 显示的状态 - * @param view - * @param views - */ - public static void toggleVisibilitys(View view, View... views){ - if (view != null){ - view.setVisibility(View.VISIBLE); - } - setVisibilitys(View.GONE, views); - } - - /** - * 切换View 显示的状态 - * @param viewArys - * @param views - */ - public static void toggleVisibilitys(View[] viewArys, View... views){ - toggleVisibilitys(viewArys, View.GONE, views); - } - - /** - * 切换View 显示的状态 - * @param viewArys - * @param status - * @param views - */ - public static void toggleVisibilitys(View[] viewArys, int status, View... views){ - // 默认前面显示 - setVisibilitys(View.VISIBLE, viewArys); - // 更具状态处理 - setVisibilitys(status, views); - } - - // == - - /** - * 切换View状态 - * @param isChange 是否改变 - * @param isVisibility 是否显示 - * @param view 需要判断的View - * @return - */ - public static boolean toogleView(boolean isChange, int isVisibility, View view){ - if (isChange && view != null){ - view.setVisibility(isVisibility); - } - return isChange; - } - - // -- - - /** - * 设置View 图片资源 - * @param draw - * @param views - */ - public static void setViewImageRes(int draw, ImageView... views){ - setViewImageRes(draw, View.VISIBLE, views); - } - - /** - * 设置View 图片资源 - * @param draw - * @param isVisibility - * @param views - */ - public static void setViewImageRes(int draw, int isVisibility, ImageView... views){ - if (views != null && views.length != 0){ - for (int i = 0, len = views.length; i < len; i++){ - ImageView view = views[i]; - if (view != null){ - try { - // 设置背景 - view.setImageResource(draw); - // 是否显示 - view.setVisibility(isVisibility); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setViewImageRes"); - } - } - } - } - } - - // == 初始化View操作等 == - - /** - * 初始化View - * @param view - * @param id - * @param - * @return - */ - public static T findViewById(View view, int id) { - return view.findViewById(id); - } - - /** - * 初始化View - * @param window - * @param id - * @param - * @return - */ - public static T findViewById(Window window, int id){ - return window.findViewById(id); - } - - /** - * 初始化View - * @param activity - * @param id - * @param - * @return - */ - public static T findViewById(Activity activity, int id) { - return activity.findViewById(id); - } - - /** - * 把自身从父View中移除 - * @param view - */ - public static void removeSelfFromParent(View view) { - if (view != null) { - ViewParent parent = view.getParent(); - if (parent != null && parent instanceof ViewGroup) { - ViewGroup group = (ViewGroup) parent; - group.removeView(view); - } - } - } - - /** - * 判断触点是否落在该View上 - * @param ev - * @param v - * @return - */ - public static boolean isTouchInView(MotionEvent ev, View v) { - int[] vLoc = new int[2]; - v.getLocationOnScreen(vLoc); - float motionX = ev.getRawX(); - float motionY = ev.getRawY(); - return motionX >= vLoc[0] && motionX <= (vLoc[0] + v.getWidth()) - && motionY >= vLoc[1] && motionY <= (vLoc[1] + v.getHeight()); - } - - /** - * View 改变请求 - * @param view - * @param isAll - */ - public static void requestLayoutParent(View view, boolean isAll) { - ViewParent parent = view.getParent(); - while (parent != null && parent instanceof View) { - if (!parent.isLayoutRequested()) { - parent.requestLayout(); - if (!isAll) { - break; - } - } - parent = parent.getParent(); - } - } - - /** - * 测量 view - * @param view - */ - public static void measureView(View view) { - ViewGroup.LayoutParams p = view.getLayoutParams(); - if (p == null) { - p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - } - int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, p.width); - int lpHeight = p.height; - int childHeightSpec; - if (lpHeight > 0) { - childHeightSpec = View.MeasureSpec.makeMeasureSpec(lpHeight, View.MeasureSpec.EXACTLY); - } else { - childHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - } - view.measure(childWidthSpec, childHeightSpec); - } - - /** - * 获取view的宽度 - * @param view - * @return - */ - public static int getViewWidth(View view) { - measureView(view); - return view.getMeasuredWidth(); - } - - /** - * 获取view的高度 - * @param view - * @return - */ - public static int getViewHeight(View view) { - measureView(view); - return view.getMeasuredHeight(); - } - - /** - * 获取view的上下文 - * @param view - * @return - */ - public static Activity getActivity(View view) { - try { - Context context = view.getContext(); - while (context instanceof ContextWrapper) { - if (context instanceof Activity) { - return (Activity) context; - } - context = ((ContextWrapper) context).getBaseContext(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getActivity"); - } - return null; - } - - // === ListView === - - /** - * 计算ListView Item 高度 - * @param listView - */ - public static int calcListViewItemHeight(ListView listView) { - return calcListViewItemHeight(listView, false); - } - - /** - * 计算ListView Item 高度 - * @param listView - * @param isSet 是否 setLayoutParams - * == - * hint: 解决 ScrollView 嵌套 ListView 时, 会无法正确的计算ListView的大小 - */ - public static int calcListViewItemHeight(ListView listView, boolean isSet) { - // 获取 Adapter - ListAdapter listAdapter = listView.getAdapter(); - // 防止为null - if (listAdapter == null) { - return 0 ; - } - // 获取总体高度 - int totalHeight = 0; - // 遍历累加 - for (int i = 0, len = listAdapter.getCount(); i < len; i++) { - // 累加 Item 高度 - totalHeight += getItemHeighet(listAdapter, listView, i); - } - // == - // 获取子项间分隔符占用的高度 - // listView.getDividerHeight(); - // 累加分割线高度 - totalHeight += (listView.getDividerHeight() * (listAdapter.getCount() - 1)); - // 判断是否需要设置高度 - if (isSet){ - ViewGroup.LayoutParams params = listView.getLayoutParams(); - params.height = totalHeight; - listView.setLayoutParams(params); - } - // 返回总体高度 - return totalHeight; - } - - // === GridView === - - /** - * 计算GridView Item 高度 - * @param gridView - * @param numColumns - */ - public static int calcGridViewItemHeight(GridView gridView, int numColumns) { - return calcGridViewItemHeight(gridView, numColumns, false); - } - - /** - * 计算GridView Item 高度 - * @param gridView - * @param numColumns - * @param isSet 是否 setLayoutParams - * == - * hint: 解决 ScrollView 嵌套 GridView 时, 会无法正确的计算ListView的大小 - */ - public static int calcGridViewItemHeight(GridView gridView, int numColumns, boolean isSet) { - // 获取 Adapter - ListAdapter listAdapter = gridView.getAdapter(); - // 防止为null - if (listAdapter == null) { - return 0 ; - } - // 最高高度 - int singleMax = 0; - // 获取总体高度 - int totalHeight = 0; - // 获取总数 - int count = listAdapter.getCount(); - // 判断是否整数 - count = (count % numColumns == 0) ? (count / numColumns) : (count / numColumns + 1); - // 遍历累加 - for (int i = 0; i < count; i++) { - // 默认表示第一个的高度 - singleMax = getItemHeighet(listAdapter, gridView, i * numColumns); - // 遍历判断 - for (int eqI = 1; eqI < numColumns; eqI++){ - // 临时高度 - int tempHeight = 0; - // 进行判断处理 - if (i * numColumns + eqI <= count){ - // 获取对应的高度 - tempHeight = getItemHeighet(listAdapter, gridView, i * numColumns + eqI); - } - // 判断是否在最大高度 - if (tempHeight > singleMax){ - singleMax = tempHeight; - } - } - // 累加 Item 高度 - totalHeight += singleMax; - } - // 每列之间的间隔 | - //int hSpaec = gridView.getHorizontalSpacing(); - // 每行之间的间隔 - - int vSpace = gridView.getVerticalSpacing(); - // 最后获取整个gridView完整显示需要的高度 - totalHeight += (vSpace * (count - 1)); - // 判断是否需要设置高度 - if (isSet){ - ViewGroup.LayoutParams params = gridView.getLayoutParams(); - params.height = totalHeight; - gridView.setLayoutParams(params); - } - // 返回总体高度 - return totalHeight; - } - - // == - - /** - * 获取单独一个Item 高度 - * @param absViews - * @param pos - * @return - */ - public static int getItemHeighet(AbsListView absViews, int pos){ - if (absViews != null){ - return getItemHeighet(absViews.getAdapter(), absViews, pos, 0); - } - return 0; - } - - /** - * 获取单独一个Item 高度 - * @param absViews - * @param pos - * @param dfHeight - * @return - */ - public static int getItemHeighet(AbsListView absViews, int pos, int dfHeight){ - if (absViews != null){ - return getItemHeighet(absViews.getAdapter(), absViews, pos, dfHeight); - } - return dfHeight; - } - - /** - * 获取单独一个Item 高度 - * @param listAdapter - * @param absViews - * @param pos - * @return - */ - public static int getItemHeighet(ListAdapter listAdapter, AbsListView absViews, int pos){ - return getItemHeighet(listAdapter, absViews, pos, 0); - } - - /** - * 获取单独一个Item 高度 - * @param listAdapter - * @param absViews - * @param pos - * @param dfHeight - * @return - */ - public static int getItemHeighet(ListAdapter listAdapter, AbsListView absViews, int pos, int dfHeight){ - try { - // listAdapter.getCount()返回数据项的数目 - View listItem = listAdapter.getView(pos, null, absViews); - // 计算子项View 的宽高 - listItem.measure(0, 0); - // 统计所有子项的总高度 - return listItem.getMeasuredHeight(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getItemHeighet"); - return dfHeight; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/anim/AnimationUtils.java b/DevLibUtils/src/main/java/dev/utils/app/anim/AnimationUtils.java deleted file mode 100644 index 7bd4beb5be..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/anim/AnimationUtils.java +++ /dev/null @@ -1,263 +0,0 @@ -package dev.utils.app.anim; - -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.RotateAnimation; -import android.view.animation.ScaleAnimation; - -/** - * detail: 动画工具类 - * Created by Ttt - */ -public final class AnimationUtils { - - private AnimationUtils() { - } - - /** - * 默认动画持续时间 - */ - public static final long DEFAULT_ANIMATION_DURATION = 400; - - /** - * 获取一个旋转动画 - * @param fromDegrees 开始角度 - * @param toDegrees 结束角度 - * @param pivotXType 旋转中心点X轴坐标相对类型 - * @param pivotXValue 旋转中心点X轴坐标 - * @param pivotYType 旋转中心点Y轴坐标相对类型 - * @param pivotYValue 旋转中心点Y轴坐标 - * @param durationMillis 持续时间 - * @param animationListener 动画监听器 - * @return 一个旋转动画 - */ - public static RotateAnimation getRotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue, long durationMillis, AnimationListener animationListener) { - RotateAnimation rotateAnimation = new RotateAnimation(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue); - rotateAnimation.setDuration(durationMillis); - if (animationListener != null) { - rotateAnimation.setAnimationListener(animationListener); - } - return rotateAnimation; - } - - /** - * 获取一个根据视图自身中心点旋转的动画 - * @param durationMillis 动画持续时间 - * @param animationListener 动画监听器 - * @return 一个根据中心点旋转的动画 - */ - public static RotateAnimation getRotateAnimationByCenter(long durationMillis, AnimationListener animationListener) { - return getRotateAnimation(0f, 359f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, durationMillis, animationListener); - } - - /** - * 获取一个根据中心点旋转的动画 - * @param duration 动画持续时间 - * @return 一个根据中心点旋转的动画 - */ - public static RotateAnimation getRotateAnimationByCenter(long duration) { - return getRotateAnimationByCenter(duration, null); - } - - /** - * 获取一个根据视图自身中心点旋转的动画 - * @param animationListener 动画监听器 - * @return 一个根据中心点旋转的动画 - */ - public static RotateAnimation getRotateAnimationByCenter(AnimationListener animationListener) { - return getRotateAnimationByCenter(DEFAULT_ANIMATION_DURATION, animationListener); - } - - /** - * 获取一个根据中心点旋转的动画 - * @return 一个根据中心点旋转的动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static RotateAnimation getRotateAnimationByCenter() { - return getRotateAnimationByCenter(DEFAULT_ANIMATION_DURATION, null); - } - - /** - * 获取一个透明度渐变动画 - * @param fromAlpha 开始时的透明度 - * @param toAlpha 结束时的透明度都 - * @param durationMillis 持续时间 - * @param animationListener 动画监听器 - * @return 一个透明度渐变动画 - */ - public static AlphaAnimation getAlphaAnimation(float fromAlpha, float toAlpha, long durationMillis, AnimationListener animationListener) { - AlphaAnimation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha); - alphaAnimation.setDuration(durationMillis); - if (animationListener != null) { - alphaAnimation.setAnimationListener(animationListener); - } - return alphaAnimation; - } - - /** - * 获取一个透明度渐变动画 - * @param fromAlpha 开始时的透明度 - * @param toAlpha 结束时的透明度都 - * @param durationMillis 持续时间 - * @return 一个透明度渐变动画 - */ - public static AlphaAnimation getAlphaAnimation(float fromAlpha, float toAlpha, long durationMillis) { - return getAlphaAnimation(fromAlpha, toAlpha, durationMillis, null); - } - - /** - * 获取一个透明度渐变动画 - * @param fromAlpha 开始时的透明度 - * @param toAlpha 结束时的透明度都 - * @param animationListener 动画监听器 - * @return 一个透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getAlphaAnimation(float fromAlpha, float toAlpha, AnimationListener animationListener) { - return getAlphaAnimation(fromAlpha, toAlpha, DEFAULT_ANIMATION_DURATION, animationListener); - } - - /** - * 获取一个透明度渐变动画 - * @param fromAlpha 开始时的透明度 - * @param toAlpha 结束时的透明度都 - * @return 一个透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getAlphaAnimation(float fromAlpha, float toAlpha) { - return getAlphaAnimation(fromAlpha, toAlpha, DEFAULT_ANIMATION_DURATION, null); - } - - /** - * 获取一个由完全显示变为不可见的透明度渐变动画 - * @param durationMillis 持续时间 - * @param animationListener 动画监听器 - * @return 一个由完全显示变为不可见的透明度渐变动画 - */ - public static AlphaAnimation getHiddenAlphaAnimation(long durationMillis, AnimationListener animationListener) { - return getAlphaAnimation(1.0f, 0.0f, durationMillis, animationListener); - } - - /** - * 获取一个由完全显示变为不可见的透明度渐变动画 - * @param durationMillis 持续时间 - * @return 一个由完全显示变为不可见的透明度渐变动画 - */ - public static AlphaAnimation getHiddenAlphaAnimation(long durationMillis) { - return getHiddenAlphaAnimation(durationMillis, null); - } - - /** - * 获取一个由完全显示变为不可见的透明度渐变动画 - * @param animationListener 动画监听器 - * @return 一个由完全显示变为不可见的透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getHiddenAlphaAnimation(AnimationListener animationListener) { - return getHiddenAlphaAnimation(DEFAULT_ANIMATION_DURATION, animationListener); - } - - /** - * 获取一个由完全显示变为不可见的透明度渐变动画 - * @return 一个由完全显示变为不可见的透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getHiddenAlphaAnimation() { - return getHiddenAlphaAnimation(DEFAULT_ANIMATION_DURATION, null); - } - - /** - * 获取一个由不可见变为完全显示的透明度渐变动画 - * @param durationMillis 持续时间 - * @param animationListener 动画监听器 - * @return 一个由不可见变为完全显示的透明度渐变动画 - */ - public static AlphaAnimation getShowAlphaAnimation(long durationMillis, AnimationListener animationListener) { - return getAlphaAnimation(0.0f, 1.0f, durationMillis, animationListener); - } - - /** - * 获取一个由不可见变为完全显示的透明度渐变动画 - * @param durationMillis 持续时间 - * @return 一个由不可见变为完全显示的透明度渐变动画 - */ - public static AlphaAnimation getShowAlphaAnimation(long durationMillis) { - return getAlphaAnimation(0.0f, 1.0f, durationMillis, null); - } - - /** - * 获取一个由不可见变为完全显示的透明度渐变动画 - * @param animationListener 动画监听器 - * @return 一个由不可见变为完全显示的透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getShowAlphaAnimation(AnimationListener animationListener) { - return getAlphaAnimation(0.0f, 1.0f, DEFAULT_ANIMATION_DURATION, animationListener); - } - - /** - * 获取一个由不可见变为完全显示的透明度渐变动画 - * @return 一个由不可见变为完全显示的透明度渐变动画,默认持续时间为DEFAULT_ANIMATION_DURATION - */ - public static AlphaAnimation getShowAlphaAnimation() { - return getAlphaAnimation(0.0f, 1.0f, DEFAULT_ANIMATION_DURATION, null); - } - - /** - * 获取一个缩小动画 - * @param durationMillis - * @param animationListener - * @return - */ - public static ScaleAnimation getLessenScaleAnimation(long durationMillis, AnimationListener animationListener) { - ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 0.0f, 1.0f, 0.0f, ScaleAnimation.RELATIVE_TO_SELF, ScaleAnimation.RELATIVE_TO_SELF); - scaleAnimation.setDuration(durationMillis); - scaleAnimation.setAnimationListener(animationListener); - return scaleAnimation; - } - - /** - * 获取一个缩小动画 - * @param durationMillis - * @return - */ - public static ScaleAnimation getLessenScaleAnimation(long durationMillis) { - return getLessenScaleAnimation(durationMillis, null); - } - - /** - * 获取一个缩小动画 - * @param animationListener - * @return - */ - public static ScaleAnimation getLessenScaleAnimation(AnimationListener animationListener) { - return getLessenScaleAnimation(DEFAULT_ANIMATION_DURATION, animationListener); - } - - /** - * 获取一个放大动画 - * @param durationMillis - * @param animationListener - * @return - */ - public static ScaleAnimation getAmplificationAnimation(long durationMillis, AnimationListener animationListener) { - ScaleAnimation scaleAnimation = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f, ScaleAnimation.RELATIVE_TO_SELF, ScaleAnimation.RELATIVE_TO_SELF); - scaleAnimation.setDuration(durationMillis); - scaleAnimation.setAnimationListener(animationListener); - return scaleAnimation; - } - - /** - * 获取一个放大动画 - * @param durationMillis - * @return - */ - public static ScaleAnimation getAmplificationAnimation(long durationMillis) { - return getAmplificationAnimation(durationMillis, null); - } - - /** - * 获取一个放大动画 - * @param animationListener - * @return - */ - public static ScaleAnimation getAmplificationAnimation(AnimationListener animationListener) { - return getAmplificationAnimation(DEFAULT_ANIMATION_DURATION, animationListener); - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/anim/ToolAnimation.java b/DevLibUtils/src/main/java/dev/utils/app/anim/ToolAnimation.java deleted file mode 100644 index 66890943b1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/anim/ToolAnimation.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.utils.app.anim; - -import android.graphics.ColorMatrixColorFilter; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.widget.ImageView; - -/** - * detail: 控件点击效果动画工具类 - * @Description:主要功能:控件点击效果动画工具类 - * @Prject: CommonUtilLibrary - * @Package: com.jingewenku.abrahamcaijin.commonutil - * @author: AbrahamCaiJin - * @date: 2017年05月15日 11:42 - * @Copyright: 个人版权所有 - * @Company: - * @version: 1.0.0 - */ - -public class ToolAnimation { - /** - * 给视图添加点击效果,让背景变深 - * */ - public static void addTouchDrak(View view, boolean isClick) { - view.setOnTouchListener(VIEW_TOUCH_DARK); - - if (!isClick) { - view.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - } - }); - } - } - - /** - * 给视图添加点击效果,让背景变暗 - * */ - public static void addTouchLight(View view, boolean isClick) { - view.setOnTouchListener(VIEW_TOUCH_LIGHT); - - if (!isClick) { - view.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - } - }); - } - } - - /** - * 让控件点击时,颜色变深 - * */ - public static final OnTouchListener VIEW_TOUCH_DARK = new OnTouchListener() { - - public final float[] BT_SELECTED = new float[] { 1, 0, 0, 0, -50, 0, 1, - 0, 0, -50, 0, 0, 1, 0, -50, 0, 0, 0, 1, 0 }; - public final float[] BT_NOT_SELECTED = new float[] { 1, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 }; - - @SuppressWarnings("deprecation") - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (v instanceof ImageView) { - ImageView iv = (ImageView) v; - iv.setColorFilter(new ColorMatrixColorFilter(BT_SELECTED)); - } else { - v.getBackground().setColorFilter( - new ColorMatrixColorFilter(BT_SELECTED)); - v.setBackgroundDrawable(v.getBackground()); - } - } else if (event.getAction() == MotionEvent.ACTION_UP) { - if (v instanceof ImageView) { - ImageView iv = (ImageView) v; - iv.setColorFilter(new ColorMatrixColorFilter( - BT_NOT_SELECTED)); - } else { - v.getBackground().setColorFilter( - new ColorMatrixColorFilter(BT_NOT_SELECTED)); - v.setBackgroundDrawable(v.getBackground()); - } - } - return false; - } - }; - - /** - * 让控件点击时,颜色变暗 - * */ - public static final OnTouchListener VIEW_TOUCH_LIGHT = new OnTouchListener() { - - public final float[] BT_SELECTED = new float[] { 1, 0, 0, 0, 50, 0, 1, - 0, 0, 50, 0, 0, 1, 0, 50, 0, 0, 0, 1, 0 }; - public final float[] BT_NOT_SELECTED = new float[] { 1, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 }; - - @SuppressWarnings("deprecation") - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (v instanceof ImageView) { - ImageView iv = (ImageView) v; - iv.setDrawingCacheEnabled(true); - - iv.setColorFilter(new ColorMatrixColorFilter(BT_SELECTED)); - } else { - v.getBackground().setColorFilter( - new ColorMatrixColorFilter(BT_SELECTED)); - v.setBackgroundDrawable(v.getBackground()); - } - } else if (event.getAction() == MotionEvent.ACTION_UP) { - if (v instanceof ImageView) { - ImageView iv = (ImageView) v; - iv.setColorFilter(new ColorMatrixColorFilter( - BT_NOT_SELECTED)); - } else { - v.getBackground().setColorFilter( - new ColorMatrixColorFilter(BT_NOT_SELECTED)); - v.setBackgroundDrawable(v.getBackground()); - } - } - return false; - } - }; -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java b/DevLibUtils/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java deleted file mode 100644 index cbfaf649a1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java +++ /dev/null @@ -1,519 +0,0 @@ -package dev.utils.app.anim; - -import android.view.View; -import android.view.animation.AlphaAnimation; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.CycleInterpolator; -import android.view.animation.TranslateAnimation; - -/** - * detail: 视图动画工具箱,提供简单的控制视图的动画的工具方法 - * Created by Ttt - */ -public final class ViewAnimationUtils { - - private ViewAnimationUtils() { - } - - // ================== - // 视图透明度渐变动画 - // ================== - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)) - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - * @param durationMillis 持续时间,毫秒 - * @param animationListener 动画监听器 - */ - public static void invisibleViewByAlpha(final View view, long durationMillis, final boolean isBanClick, final AnimationListener animationListener) { - if (view.getVisibility() != View.INVISIBLE) { - view.setVisibility(View.INVISIBLE); - AlphaAnimation hiddenAlphaAnimation = AnimationUtils.getHiddenAlphaAnimation(durationMillis); - hiddenAlphaAnimation.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (isBanClick) { - view.setClickable(false); - } - if (animationListener != null) { - animationListener.onAnimationStart(animation); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - if (animationListener != null) { - animationListener.onAnimationRepeat(animation); - } - } - - @Override - public void onAnimationEnd(Animation animation) { - if (isBanClick) { - view.setClickable(true); - } - if (animationListener != null) { - animationListener.onAnimationEnd(animation); - } - } - }); - view.startAnimation(hiddenAlphaAnimation); - } - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param animationListener 动画监听器 - */ - public static void invisibleViewByAlpha(final View view, long durationMillis, final AnimationListener animationListener) { - invisibleViewByAlpha(view, durationMillis, false, animationListener); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void invisibleViewByAlpha(final View view, long durationMillis, boolean isBanClick) { - invisibleViewByAlpha(view, durationMillis, isBanClick, null); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - */ - public static void invisibleViewByAlpha(final View view, long durationMillis) { - invisibleViewByAlpha(view, durationMillis, false, null); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - * @param animationListener 动画监听器 - */ - public static void invisibleViewByAlpha(final View view, boolean isBanClick, final AnimationListener animationListener) { - invisibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, animationListener); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param animationListener 动画监听器 - */ - public static void invisibleViewByAlpha(final View view, final AnimationListener animationListener) { - invisibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION,false, animationListener); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void invisibleViewByAlpha(final View view, boolean isBanClick) { - invisibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, null); - } - - /** - * 将给定视图渐渐隐去(view.setVisibility(View.INVISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - */ - public static void invisibleViewByAlpha(final View view) { - invisibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION,false, null); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param isBanClick 在执行动画的过程中是否禁止点击 - * @param animationListener 动画监听器 - */ - public static void goneViewByAlpha(final View view, long durationMillis, final boolean isBanClick, final AnimationListener animationListener) { - if (view.getVisibility() != View.GONE) { - view.setVisibility(View.GONE); - AlphaAnimation hiddenAlphaAnimation = AnimationUtils.getHiddenAlphaAnimation(durationMillis); - hiddenAlphaAnimation.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (isBanClick) { - view.setClickable(false); - } - if (animationListener != null) { - animationListener.onAnimationStart(animation); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - if (animationListener != null) { - animationListener.onAnimationRepeat(animation); - } - } - - @Override - public void onAnimationEnd(Animation animation) { - if (isBanClick) { - view.setClickable(true); - } - if (animationListener != null) { - animationListener.onAnimationEnd(animation); - } - } - }); - view.startAnimation(hiddenAlphaAnimation); - } - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param animationListener 动画监听器 - */ - public static void goneViewByAlpha(final View view, long durationMillis, final AnimationListener animationListener) { - goneViewByAlpha(view, durationMillis, false, animationListener); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void goneViewByAlpha(final View view, long durationMillis, final boolean isBanClick) { - goneViewByAlpha(view, durationMillis, isBanClick, null); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - */ - public static void goneViewByAlpha(final View view, long durationMillis) { - goneViewByAlpha(view, durationMillis, false, null); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - * @param animationListener 动画监听器 - */ - public static void goneViewByAlpha(final View view, final boolean isBanClick, final AnimationListener animationListener) { - goneViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, animationListener); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param animationListener 动画监听器 - */ - public static void goneViewByAlpha(final View view, final AnimationListener animationListener) { - goneViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, false, animationListener); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void goneViewByAlpha(final View view, final boolean isBanClick) { - goneViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, null); - } - - /** - * 将给定视图渐渐隐去最后从界面中移除(view.setVisibility(View.GONE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - */ - public static void goneViewByAlpha(final View view) { - goneViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, false,null); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param isBanClick 在执行动画的过程中是否禁止点击 - * @param animationListener 动画监听器 - */ - public static void visibleViewByAlpha(final View view, long durationMillis, final boolean isBanClick, final AnimationListener animationListener) { - if (view.getVisibility() != View.VISIBLE) { - view.setVisibility(View.VISIBLE); - AlphaAnimation showAlphaAnimation = AnimationUtils.getShowAlphaAnimation(durationMillis); - showAlphaAnimation.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (isBanClick) { - view.setClickable(false); - } - if (animationListener != null) { - animationListener.onAnimationStart(animation); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - if (animationListener != null) { - animationListener.onAnimationRepeat(animation); - } - } - - @Override - public void onAnimationEnd(Animation animation) { - if (isBanClick) { - view.setClickable(true); - } - if (animationListener != null) { - animationListener.onAnimationEnd(animation); - } - } - }); - view.startAnimation(showAlphaAnimation); - } - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param animationListener 动画监听器 - */ - public static void visibleViewByAlpha(final View view, long durationMillis, final AnimationListener animationListener) { - visibleViewByAlpha(view, durationMillis, false, animationListener); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void visibleViewByAlpha(final View view, long durationMillis, final boolean isBanClick) { - visibleViewByAlpha(view, durationMillis, isBanClick, null); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)) - * @param view 被处理的视图 - * @param durationMillis 持续时间,毫秒 - */ - public static void visibleViewByAlpha(final View view, long durationMillis) { - visibleViewByAlpha(view, durationMillis, false, null); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param animationListener 动画监听器 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void visibleViewByAlpha(final View view, final boolean isBanClick, final AnimationListener animationListener) { - visibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, animationListener); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param animationListener 动画监听器 - */ - public static void visibleViewByAlpha(final View view, final AnimationListener animationListener) { - visibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION,false, animationListener); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void visibleViewByAlpha(final View view, final boolean isBanClick) { - visibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION, isBanClick, null); - } - - /** - * 将给定视图渐渐显示出来(view.setVisibility(View.VISIBLE)), - * 默认的持续时间为DEFAULT_ALPHA_ANIMATION_DURATION - * @param view 被处理的视图 - */ - public static void visibleViewByAlpha(final View view) { - visibleViewByAlpha(view, AnimationUtils.DEFAULT_ANIMATION_DURATION,false, null); - } - - // ============ - // 视图移动动画 - // ============ - - /** - * 视图移动 - * @param view 要移动的视图 - * @param fromXDelta X轴开始坐标 - * @param toXDelta X轴结束坐标 - * @param fromYDelta Y轴开始坐标 - * @param toYDelta Y轴结束坐标 - * @param cycles 重复 - * @param durationMillis 持续时间 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void translate(final View view, float fromXDelta, float toXDelta, float fromYDelta, float toYDelta, float cycles, long durationMillis, final boolean isBanClick) { - TranslateAnimation translateAnimation = new TranslateAnimation(fromXDelta, toXDelta, fromYDelta, toYDelta); - translateAnimation.setDuration(durationMillis); - if (cycles > 0.0) { - translateAnimation.setInterpolator(new CycleInterpolator(cycles)); - } - translateAnimation.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (isBanClick) { - view.setClickable(false); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - - } - - @Override - public void onAnimationEnd(Animation animation) { - if (isBanClick) { - view.setClickable(true); - } - } - }); - view.startAnimation(translateAnimation); - } - - /** - * 视图移动 - * @param view 要移动的视图 - * @param fromXDelta X轴开始坐标 - * @param toXDelta X轴结束坐标 - * @param fromYDelta Y轴开始坐标 - * @param toYDelta Y轴结束坐标 - * @param cycles 重复 - * @param durationMillis 持续时间 - */ - public static void translate(final View view, float fromXDelta, float toXDelta, float fromYDelta, float toYDelta, float cycles, long durationMillis) { - translate(view, fromXDelta, toXDelta, fromYDelta, toYDelta, cycles, durationMillis, false); - } - - /** - * 视图摇晃 - * @param view 要摇动的视图 - * @param fromXDelta X轴开始坐标 - * @param toXDelta X轴结束坐标 - * @param cycles 重复次数 - * @param durationMillis 持续时间 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void shake(View view, float fromXDelta, float toXDelta, float cycles, long durationMillis, final boolean isBanClick) { - translate(view, fromXDelta, toXDelta, 0.0f, 0.0f, cycles, durationMillis, isBanClick); - } - - /** - * 视图摇晃 - * @param view 要摇动的视图 - * @param fromXDelta X轴开始坐标 - * @param toXDelta X轴结束坐标 - * @param cycles 重复次数 - * @param durationMillis 持续时间 - */ - public static void shake(View view, float fromXDelta, float toXDelta, float cycles, long durationMillis) { - translate(view, fromXDelta, toXDelta, 0.0f, 0.0f, cycles, durationMillis, false); - } - - /** - * 视图摇晃,默认摇晃幅度为10,重复7次 - * @param view - * @param cycles 重复次数 - * @param durationMillis 持续时间 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void shake(View view, float cycles, long durationMillis, final boolean isBanClick) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, cycles, durationMillis, isBanClick); - } - - /** - * 视图摇晃,默认摇晃幅度为10,持续700毫秒 - * @param view - * @param cycles 重复次数 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void shake(View view, float cycles, final boolean isBanClick) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, cycles, 700, isBanClick); - } - - /** - * 视图摇晃,默认摇晃幅度为10 - * @param view - * @param cycles 重复次数 - * @param durationMillis 持续时间 - */ - public static void shake(View view, float cycles, long durationMillis) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, cycles, durationMillis, false); - } - - /** - * 视图摇晃,默认摇晃幅度为10,重复7次 - * @param view - * @param durationMillis 持续时间 - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void shake(View view, long durationMillis, final boolean isBanClick) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, 7, durationMillis, isBanClick); - } - - /** - * 视图摇晃,默认摇晃幅度为10,持续700毫秒 - * @param view 要摇动的视图 - * @param cycles 重复次数 - */ - public static void shake(View view, float cycles) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, cycles, 700, false); - } - - /** - * 视图摇晃,默认摇晃幅度为10,重复7次 - * @param view - * @param durationMillis 持续时间 - */ - public static void shake(View view, long durationMillis) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, 7, durationMillis, false); - } - - /** - * 视图摇晃,默认摇晃幅度为10,重复7次,持续700毫秒 - * @param view - * @param isBanClick 在执行动画的过程中是否禁止点击 - */ - public static void shake(View view, final boolean isBanClick) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, 7, 700, isBanClick); - } - - /** - * 视图摇晃,默认摇晃幅度为10,重复7次,持续700毫秒 - * @param view - */ - public static void shake(View view) { - translate(view, 0.0f, 10.0f, 0.0f, 0.0f, 7, 700, false); - } - -} - diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/AsyncExecutor.java b/DevLibUtils/src/main/java/dev/utils/app/assist/AsyncExecutor.java deleted file mode 100644 index 1dca496507..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/AsyncExecutor.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.utils.app.assist; - -import android.os.Handler; -import android.os.Looper; - -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; - -import dev.utils.LogPrintUtils; - -/** - * detail: 异步执行 - * @author MaTianyu - */ -public class AsyncExecutor { - - // 日志TAG - private final String TAG = AsyncExecutor.class.getSimpleName(); - // 线程池 - private static ExecutorService threadPool; - // 主线程 Hander - public static Handler handler = new Handler(Looper.getMainLooper()); - - public AsyncExecutor() { - this(null); - } - - public AsyncExecutor(ExecutorService threadPool) { - if (AsyncExecutor.threadPool != null) { - shutdownNow(); - } - if (threadPool == null) { - AsyncExecutor.threadPool = Executors.newCachedThreadPool(); - } else { - AsyncExecutor.threadPool = threadPool; - } - } - - public static synchronized void shutdownNow() { - if (threadPool != null && !threadPool.isShutdown()) threadPool.shutdownNow(); - threadPool = null; - } - - /** - * 将任务投入线程池执行 - * @param worker - * @return - */ - public FutureTask execute(final Worker worker) { - Callable call = new Callable() { - @Override - public T call() throws Exception { - return postResult(worker, worker.doInBackground()); - } - }; - FutureTask task = new FutureTask(call) { - @Override - protected void done() { - try { - get(); - } catch (InterruptedException e) { - LogPrintUtils.eTag(TAG, e, "execute"); - worker.abort(); - postCancel(worker); - } catch (ExecutionException e) { - LogPrintUtils.eTag(TAG, e, "execute"); - throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); - } catch (CancellationException e) { - worker.abort(); - postCancel(worker); - LogPrintUtils.eTag(TAG, e, "execute"); - } - } - }; - threadPool.execute(task); - return task; - } - - /** - * 将子线程结果传递到UI线程 - * @param worker - * @param result - * @return - */ - private T postResult(final Worker worker, final T result) { - handler.post(new Runnable() { - @Override - public void run() { - worker.onPostExecute(result); - } - }); - return result; - } - - /** - * 将子线程结果传递到UI线程 - * @param worker - * @return - */ - private void postCancel(final Worker worker) { - handler.post(new Runnable() { - @Override - public void run() { - worker.onCanceled(); - } - }); - } - - public FutureTask execute(Callable call) { - FutureTask task = new FutureTask(call); - threadPool.execute(task); - return task; - } - - public static abstract class Worker { - - protected abstract T doInBackground(); - - protected void onPostExecute(T data) {} - - protected void onCanceled() {} - - protected void abort() {} - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java deleted file mode 100644 index 3f00033fde..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java +++ /dev/null @@ -1,279 +0,0 @@ -package dev.utils.app.assist; - -import android.app.Activity; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.os.Vibrator; -import android.support.annotation.RawRes; - -import java.io.Closeable; - -import dev.utils.LogPrintUtils; - -/** - * detail: 播放“bee”的声音, 并且震动 辅助类 - * Created by Ttt - */ -public final class BeepVibrateAssist implements Closeable { - - // 日志TAG - private static final String TAG = BeepVibrateAssist.class.getSimpleName(); - // 上下文 - private final Context context; - // 播放资源对象 - private MediaPlayer mediaPlayer = null; - // 是否需要震动 - private boolean vibrate = true; - // 震动时间 - private long vibrateDuration = 200L; - - /** - * 构造函数 - * @param context - */ - public BeepVibrateAssist(Context context) { - this.context = context; - } - - /** - * 构造函数 - * @param context - * @param rawId - */ - public BeepVibrateAssist(Context context, @RawRes int rawId) { - this.context = context; - this.mediaPlayer = buildMediaPlayer(context, rawId); - } - - /** - * 构造函数 - * @param context - * @param path 只支持本地资源 - */ - public BeepVibrateAssist(Context context, String path) { - this.context = context; - this.mediaPlayer = buildMediaPlayer(path); - } - - // == 内部判断方法 == - - /** - * 检查是否允许播放声音 - * @return - */ - private boolean shouldBeep() { - boolean shouldPlayBeep = true; - if (shouldPlayBeep) { - try { - // RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(静音)、RINGER_MODE_VIBRATE(震动) - AudioManager audioService = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { - shouldPlayBeep = false; // 进入只有属于, 静音、震动,才不播放 - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "shouldBeep"); - } - } - return shouldPlayBeep; - } - - /** - * 内部检查更新 - */ - private synchronized void update() { - if (shouldBeep() && mediaPlayer != null) { - // The volume on STREAM_SYSTEM is not adjustable, and users found it too loud, - // so we now play on the music stream. - try { - ((Activity) context).setVolumeControlStream(AudioManager.STREAM_MUSIC); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "update"); - } - } - } - - // == 对外公开方法 == - - /** - * 判断是否允许播放声音 - * @return - */ - public boolean isPlayBeep() { - return shouldBeep(); - } - - /** - * 获取是否允许震动 - * @return - */ - public boolean isVibrate() { - return vibrate; - } - - /** - * 设置是否允许震动 - * @param vibrate - */ - public BeepVibrateAssist setVibrate(boolean vibrate) { - setVibrate(vibrate, 200l); - return this; - } - - /** - * 设置是否允许震动 - * @param vibrate - * @param vibrateDuration 震动时间 - */ - public BeepVibrateAssist setVibrate(boolean vibrate, long vibrateDuration) { - this.vibrate = vibrate; - return this; - } - - /** - * 设置播放资源对象 - * @param mediaPlayer - */ - public BeepVibrateAssist setMediaPlayer(MediaPlayer mediaPlayer) { - this.mediaPlayer = mediaPlayer; - // 进行更新 - update(); - return this; - } - - /** - * 进行播放声音, 并且振动 - */ - public synchronized void playBeepSoundAndVibrate() { - // 判断是否允许播放 - if (shouldBeep() && mediaPlayer != null) { - try { - // 播放 - mediaPlayer.start(); - } catch (Exception e){ - } - } - // 判断是否允许震动 - if (vibrate) { - try { - Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); - vibrator.vibrate(vibrateDuration); - } catch (Exception e){ - } - } - } - - @Override - public synchronized void close() { - if (mediaPlayer != null) { - mediaPlayer.release(); - mediaPlayer = null; - } - } - - // == 创建 MediaPlayer 处理 == - - /** - * 创建 MediaPlayer 对象 - * @param context 上下文 - * @param rawId 响声资源id - * @return - */ - public static final MediaPlayer buildMediaPlayer(Context context, @RawRes int rawId) { - return buildMediaPlayer(context, rawId, 0.1f); - } - - /** - * 创建 MediaPlayer 对象 - * @param context 上下文 - * @param rawId 响声资源id - * @param beepVolume 音量 - * @return - */ - public static final MediaPlayer buildMediaPlayer(Context context, @RawRes int rawId, float beepVolume) { - final MediaPlayer mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - LogPrintUtils.dTag(TAG, "buildMediaPlayer - onCompletion"); -// if (mediaPlayer != null){ -// mediaPlayer.seekTo(0); -// } - } - }); - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public synchronized boolean onError(MediaPlayer mp, int what, int extra) { - LogPrintUtils.dTag(TAG, "buildMediaPlayer - onError => what: " + what + ", extra: " + extra); - // 播放异常, 直接不处理 - return true; - } - }); - try { - AssetFileDescriptor file = context.getResources().openRawResourceFd(rawId); - try { - mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(), file.getLength()); - } finally { - file.close(); - } - mediaPlayer.setVolume(beepVolume, beepVolume); - mediaPlayer.prepare(); - return mediaPlayer; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "buildMediaPlayer"); - mediaPlayer.release(); - return null; - } - } - - // = - - /** - * 创建 MediaPlayer 对象 - * @param path 响声资源路径(只支持本地资源) - * @return - */ - public static final MediaPlayer buildMediaPlayer(String path) { - return buildMediaPlayer(path, 0.1f); - } - - /** - * 创建 MediaPlayer 对象 - * @param path 响声资源路径(只支持本地资源) - * @param beepVolume 音量 - * @return - */ - public static final MediaPlayer buildMediaPlayer(String path, float beepVolume) { - final MediaPlayer mediaPlayer = new MediaPlayer(); - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - LogPrintUtils.dTag(TAG, "buildMediaPlayer - onCompletion"); -// if (mediaPlayer != null){ -// mediaPlayer.seekTo(0); -// } - } - }); - mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public synchronized boolean onError(MediaPlayer mp, int what, int extra) { - LogPrintUtils.dTag(TAG, "buildMediaPlayer - onError => what: " + what + ", extra: " + extra); - // 播放异常, 直接不处理 - return true; - } - }); - try { - mediaPlayer.setDataSource(path); - mediaPlayer.setVolume(beepVolume, beepVolume); - mediaPlayer.prepare(); - return mediaPlayer; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "buildMediaPlayer"); - mediaPlayer.release(); - return null; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java deleted file mode 100644 index ecd5f9444c..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java +++ /dev/null @@ -1,145 +0,0 @@ -package dev.utils.app.assist; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.AsyncTask; -import android.os.BatteryManager; -import android.os.Build; - -/** - * detail: Activity 无操作定时辅助类 - * Created by Ttt - * 需要注意的试, 需要在对应的生命周期内,调用对应的 onXXX 方法 - */ -public final class InactivityTimerAssist { - - // 无操作时间(到时间自动关闭) - 默认五分钟 - private long inactivityTime = 5 * 60 * 1000L; - // 对应的页面 - private Activity activity; - // 电池广播(充电中, 则不处理, 主要是为了省点) - private BroadcastReceiver powerStatusReceiver; - // 检查任务 - private AsyncTask inactivityTask; - - // == 构造函数 == - - public InactivityTimerAssist(Activity activity) { - this(activity, 5 * 60 * 1000L); - } - - public InactivityTimerAssist(Activity activity, long inactivityTime) { - this.activity = activity; - this.inactivityTime = inactivityTime; - // 电池广播监听 - powerStatusReceiver = new PowerStatusReceiver(); - // 关闭任务 - cancel(); - } - - // = 内部方法 = - - /** - * 开始任务 - */ - public synchronized void start() { - // 取消任务 - cancel(); - // 注册任务 - inactivityTask = new InactivityAsyncTask(); - // 开启任务 - if (Build.VERSION.SDK_INT >= 11) { - inactivityTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - inactivityTask.execute(); - } - } - - /** - * 取消任务 - */ - private synchronized void cancel() { - AsyncTask task = inactivityTask; - if (task != null) { - task.cancel(true); - // 重置为null - inactivityTask = null; - } - } - - // = - - /** - * 暂停检测 - */ - public synchronized void onPause() { - // 取消任务 - cancel(); - try { - // 取消注册广播 - activity.unregisterReceiver(powerStatusReceiver); - } catch (Exception e){ - } - } - - /** - * 回到页面处理 - */ - public synchronized void onResume() { - try { - // 注册广播 - activity.registerReceiver(powerStatusReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); - } catch (Exception e){ - } - // 开始检测 - start(); - } - - /** - * 页面销毁处理 - */ - public void onDestroy() { - cancel(); - } - - // = - - /** - * 电池广播 - */ - private class PowerStatusReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { - // 0 indicates that we're on battery - boolean onBatteryNow = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) <= 0; - if (onBatteryNow) { // 属于非充电才进行记时 - InactivityTimerAssist.this.start(); - } else { // 充电中, 则不处理 - InactivityTimerAssist.this.cancel(); - } - } - } - } - - /** - * 定时检测任务 - */ - private class InactivityAsyncTask extends AsyncTask { - @Override - protected Object doInBackground(Object... objects) { - try { - Thread.sleep(inactivityTime); - // 关闭页面 - if (activity != null){ - activity.finish(); - } - } catch (InterruptedException e) { - } - return null; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java deleted file mode 100644 index 0828bbd4ff..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java +++ /dev/null @@ -1,254 +0,0 @@ -package dev.utils.app.assist; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Handler; -import android.os.Message; - -import dev.utils.LogPrintUtils; - -/** - * detail: 屏幕传感器(监听是否横竖屏) - * Created by Ttt - */ -public final class ScreenSensorAssist { - - // 日志 TAG - private final String TAG = ScreenSensorAssist.class.getSimpleName(); - - // ========= 重力传感器监听对象 ============= - /** 传感器管理对象 */ - private SensorManager sMamager; - /** 重力传感器 */ - private Sensor sensor; - /** 重力传感器监听事件 */ - private OrientationSensorListener sListener; - // ========= 重力传感器监听对象(改变方向后,具体判断参数不同) ============= - /** 传感器管理对象(切屏后) */ - private SensorManager sManagerChange; - /** 重力传感器(切屏后) */ - private Sensor sensorChange; - /** 重力传感器监听事件(切屏后) */ - private OrientationSensorChangeListener slistenerChange; - // ========= 常量 ========= - /** 坐标索引常量 */ - private final int _DATA_X = 0; - private final int _DATA_Y = 1; - private final int _DATA_Z = 2; - /** 方向未知常量 */ - private final int ORIENTATION_UNKNOWN = -1; - /** 触发屏幕方向改变回调 */ - public static final int CHANGE_ORIENTATION_WHAT = 9919; - // ========= 变量 ============= - /** 是否允许切屏 */ - private boolean isAllowChange = false; - /** 是否是竖屏 */ - private boolean isPortrait = true; - /** 回调操作 */ - private Handler handler; - /** 角度处理Handler */ - private Handler rotateHandler = new Handler(){ - public void handleMessage(Message msg) { - switch (msg.what) { - case CHANGE_ORIENTATION_WHAT: - // 获取角度 - int rotation = msg.arg1; - // - - LogPrintUtils.dTag(TAG,"当前角度: " + rotation); - // 判断角度 - if (rotation > 45 && rotation < 135) { // 横屏 - 屏幕对着别人 - LogPrintUtils.dTag(TAG, "切换成横屏 - 屏幕对着自己"); - // - - if (isPortrait) { - isPortrait = false; - if(handler != null){ - Message vMsg = new Message(); - vMsg.what = CHANGE_ORIENTATION_WHAT; - vMsg.arg1 = 1; - handler.sendMessage(vMsg); - } - } - } else if (rotation > 135 && rotation < 225) { // 竖屏 - 屏幕对着别人 - LogPrintUtils.dTag(TAG,"切换成竖屏 - 屏幕对着别人"); - // - - if (!isPortrait) { - isPortrait = true; - if(handler != null){ - Message vMsg = new Message(); - vMsg.what = CHANGE_ORIENTATION_WHAT; - vMsg.arg1 = 2; - handler.sendMessage(vMsg); - } - } - } else if (rotation > 225 && rotation < 315) { // 横屏 - 屏幕对着自己 - LogPrintUtils.dTag(TAG, "切换成横屏 - 屏幕对着自己"); - // - - if (isPortrait) { - isPortrait = false; - if(handler != null){ - Message vMsg = new Message(); - vMsg.what = CHANGE_ORIENTATION_WHAT; - vMsg.arg1 = 1; - handler.sendMessage(vMsg); - } - } - } else if ((rotation > 315 && rotation < 360) || (rotation > 0 && rotation < 45)) { // 竖屏 - 屏幕对着自己 - LogPrintUtils.dTag(TAG,"切换成竖屏 - 屏幕对着自己"); - // - - if (!isPortrait) { - isPortrait = true; - if(handler != null){ - Message vMsg = new Message(); - vMsg.what = CHANGE_ORIENTATION_WHAT; - vMsg.arg1 = 2; - handler.sendMessage(vMsg); - } - } - } else { - LogPrintUtils.dTag(TAG,"其他角度: " + rotation); - } - break; - } - }; - }; - - // === - /** - * 初始化操作 - * @param context 上下文 - * @param handler 回调Handler - */ - private void init(Context context, Handler handler){ - this.handler = handler; - // 注册重力感应器,监听屏幕旋转 - sMamager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - sensor = sMamager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - sListener = new OrientationSensorListener(); - - // 根据 旋转之后/点击全屏之后 两者方向一致,激活sm. - sManagerChange = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - sensorChange = sManagerChange.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); - slistenerChange = new OrientationSensorChangeListener(); - } - - /** - * 开始监听 - * @param context - */ - public void start(Context context, Handler handler) { - isAllowChange = true; - try { - LogPrintUtils.dTag(TAG, "start orientation listener."); - // 初始化操作 - init(context, handler); - // 监听重力传感器 - sMamager.registerListener(sListener, sensor, SensorManager.SENSOR_DELAY_UI); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "start"); - } - } - - /** 停止监听 */ - public void stop() { - isAllowChange = false; - LogPrintUtils.dTag(TAG, "stop orientation listener."); - try { - sMamager.unregisterListener(sListener); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e ,"stop"); - } - try { - sManagerChange.unregisterListener(slistenerChange); - } catch (Exception e) { - } - } - - /** 是否竖屏 */ - public boolean isPortrait(){ - return this.isPortrait; - } - - /** 是否允许切屏 */ - public boolean isAllowChange(){ - return this.isAllowChange; - } - - // === - - /** 重力传感器监听事件 */ - class OrientationSensorListener implements SensorEventListener { - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - @Override - public void onSensorChanged(SensorEvent event) { - float[] values = event.values; - int orientation = ORIENTATION_UNKNOWN; - float X = -values[_DATA_X]; - float Y = -values[_DATA_Y]; - float Z = -values[_DATA_Z]; - float magnitude = X * X + Y * Y; - // Don't trust the angle if the magnitude is small compared to the y value - if (magnitude * 4 >= Z * Z) { - // 屏幕旋转时 - float OneEightyOverPi = 57.29577957855f; - float angle = (float) Math.atan2(-Y, X) * OneEightyOverPi; - orientation = 90 - (int) Math.round(angle); - // normalize to 0 - 359 range - while (orientation >= 360) { - orientation -= 360; - } - while (orientation < 0) { - orientation += 360; - } - } - if (rotateHandler != null) { - rotateHandler.obtainMessage(CHANGE_ORIENTATION_WHAT, orientation, 0).sendToTarget(); - } - } - } - - /** 重力传感器监听事件(切屏后) */ - class OrientationSensorChangeListener implements SensorEventListener { - @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { - } - @Override - public void onSensorChanged(SensorEvent event) { - float[] values = event.values; - int orientation = ORIENTATION_UNKNOWN; - float X = -values[_DATA_X]; - float Y = -values[_DATA_Y]; - float Z = -values[_DATA_Z]; - float magnitude = X * X + Y * Y; - // Don't trust the angle if the magnitude is small compared to the y value - if (magnitude * 4 >= Z * Z) { - // 屏幕旋转时 - float OneEightyOverPi = 57.29577957855f; - float angle = (float) Math.atan2(-Y, X) * OneEightyOverPi; - orientation = 90 - (int) Math.round(angle); - // normalize to 0 - 359 range - while (orientation >= 360) { - orientation -= 360; - } - while (orientation < 0) { - orientation += 360; - } - } - if (orientation > 225 && orientation < 315) {// 检测到当前实际是横屏 - if (!isPortrait) { - sMamager.registerListener(sListener, sensor, SensorManager.SENSOR_DELAY_UI); - sManagerChange.unregisterListener(slistenerChange); - } - } else if ((orientation > 315 && orientation < 360) || (orientation > 0 && orientation < 45)) {// 检测到当前实际是竖屏 - if (isPortrait) { - sMamager.registerListener(sListener, sensor, SensorManager.SENSOR_DELAY_UI); - sManagerChange.unregisterListener(slistenerChange); - } - } - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/AutoFocusAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/camera/AutoFocusAssist.java deleted file mode 100644 index 3b0aebbf53..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/AutoFocusAssist.java +++ /dev/null @@ -1,208 +0,0 @@ -package dev.utils.app.assist.camera; - -import android.annotation.SuppressLint; -import android.hardware.Camera; -import android.os.AsyncTask; -import android.os.Build; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.concurrent.RejectedExecutionException; - -import dev.utils.LogPrintUtils; - -/** - * detail: 自动获取焦点 辅助类 - * Created by Ttt - */ -public final class AutoFocusAssist implements Camera.AutoFocusCallback { - - // 日志 TAG - private final String TAG = AutoFocusAssist.class.getSimpleName(); - // 设置对焦模式 - private static final Collection FOCUS_MODES_CALLING_AF; - - static { - // 对焦模式 - // https://blog.csdn.net/fulinwsuafcie/article/details/49558001 - FOCUS_MODES_CALLING_AF = new ArrayList<>(2); - FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_AUTO); // 自动对焦 - FOCUS_MODES_CALLING_AF.add(Camera.Parameters.FOCUS_MODE_MACRO); // 微距 - } - - // == 变量 == - // 间隔获取焦点时间 - private long interval = 2000L; - // 摄像头对象 - private final Camera camera; - // 判断摄像头是否使用对焦 - private final boolean useAutoFocus; - // 判断是否停止对焦 - private boolean stopped; - // 判断是否对焦中 - private boolean focusing; - // 对焦任务 - private AsyncTask outstandingTask; - // 判断是否需要自动对焦 - private boolean isAutoFocus = true; - - // == 构造函数 == - - public AutoFocusAssist(Camera camera){ - this(camera, 2000L); - } - - public AutoFocusAssist(Camera camera, long interval) { - this.camera = camera; - this.interval = interval; - // 防止为null - if (camera != null){ - // 获取对象对焦模式 - String currentFocusMode = camera.getParameters().getFocusMode(); - // 判断是否(使用/支持)对焦 - useAutoFocus = FOCUS_MODES_CALLING_AF.contains(currentFocusMode); - } else { - // 不支持对焦 - useAutoFocus = false; - } - // 开始任务 - start(); - } - - /** - * 是否允许自动对焦 - * @return - */ - public boolean isAutoFocus() { - return isAutoFocus; - } - - /** - * 设置是否开启自动对焦 - * @param autoFocus - */ - public void setAutoFocus(boolean autoFocus) { - isAutoFocus = autoFocus; - // 判断是否开启自动对焦 - if (isAutoFocus){ - start(); - } else { - stop(); - } - } - - /** - * Camera.AutoFocusCallback 重写方法 - * @param success 是否对焦成功 - * @param theCamera 对焦的摄像头 - */ - @Override - public synchronized void onAutoFocus(boolean success, Camera theCamera) { - // 对焦结束, 设置非对焦中 - focusing = false; - // 再次自动对焦 - autoFocusAgainLater(); - } - - // = 内部方法 = - - /** - * 再次自动对焦 - */ - @SuppressLint("NewApi") - private synchronized void autoFocusAgainLater() { - // 不属于停止, 并且任务等于null, 才处理 - if (!stopped && outstandingTask == null) { - // 初始化任务 - AutoFocusTask newTask = new AutoFocusTask(); - try { - if (Build.VERSION.SDK_INT >= 11) { - // 默认使用异步任务 - newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - newTask.execute(); - } - outstandingTask = newTask; - } catch (RejectedExecutionException ree) { - LogPrintUtils.eTag(TAG, ree, "autoFocusAgainLater"); - } - } - } - - /** - * 开始对焦 - */ - public synchronized void start() { - // 如果不使用自动对焦, 则不处理 - if (!isAutoFocus){ - return; - } - // 支持对焦才处理 - if (useAutoFocus) { - // 重置任务为null - outstandingTask = null; - // 不属于停止 并且 非对焦中 - if (!stopped && !focusing) { - try { - // 设置自动对焦回调 - camera.autoFocus(this); - // 表示对焦中 - focusing = true; - } catch (RuntimeException re) { - LogPrintUtils.eTag(TAG, re,"start"); - // Try again later to keep cycle going - autoFocusAgainLater(); - } - } - } - } - - /** - * 停止对焦 - */ - public synchronized void stop() { - // 表示属于停止 - stopped = true; - // 判断是否支持对焦 - if (useAutoFocus) { - // 关闭任务 - cancelOutstandingTask(); - try { - // 取消对焦 - camera.cancelAutoFocus(); - } catch (RuntimeException re) { - LogPrintUtils.eTag(TAG, re,"stop"); - } - } - } - - /** - * 取消对焦任务 - */ - private synchronized void cancelOutstandingTask() { - if (outstandingTask != null) { - if (outstandingTask.getStatus() != AsyncTask.Status.FINISHED) { - outstandingTask.cancel(true); - } - outstandingTask = null; - } - } - - /** - * detail: 自动对焦任务 - * Created by Ttt - */ - private final class AutoFocusTask extends AsyncTask { - @Override - protected Object doInBackground(Object... voids) { - try { - // 堵塞时间 - Thread.sleep(interval); - } catch (InterruptedException e) { - } - // 开启定时 - start(); - return null; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraAssist.java deleted file mode 100644 index 83dfa70e41..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraAssist.java +++ /dev/null @@ -1,305 +0,0 @@ -package dev.utils.app.assist.camera; - -import android.content.pm.PackageManager; -import android.hardware.Camera; -import android.view.SurfaceHolder; - -import java.io.IOException; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -import static android.hardware.Camera.Parameters.FLASH_MODE_OFF; -import static android.hardware.Camera.Parameters.FLASH_MODE_TORCH; - -/** - * detail: 摄像头辅助类 - * Created by Ttt - */ -public final class CameraAssist { - - // 日志TAG - private final String TAG = CameraAssist.class.getSimpleName(); - // 摄像头 Camera - private Camera mCamera; - // 是否预览中 - private boolean previewing; - // 自动对焦时间 - private long autoInterval = 2000l; - // 预览通知 - private PreviewNotify previewNotify; - // = 内部工具类 = - // 摄像头大小计算 - private CameraSizeAssist cameraSizeAssist; - // 自动获取焦点辅助类 - private AutoFocusAssist autoFocusAssist; - - public CameraAssist(){ - } - - public CameraAssist(Camera camera) { - setCamera(camera); - } - - public CameraAssist(Camera camera, long interval) { - this.autoInterval = interval; - setCamera(camera); - } - - // == 操作方法 == - - /** - * 打开摄像头程序 - * @param holder - */ - public synchronized CameraAssist openDriver(SurfaceHolder holder) throws IOException { - Camera theCamera = mCamera; - // 设置预览 Holder - theCamera.setPreviewDisplay(holder); -// // 获取之前的摄像头配置信息 -// Camera.Parameters parameters = theCamera.getParameters(); -// String parametersFlattened = parameters == null ? null : parameters.flatten(); -// try { -// // xxxx -// } catch (RuntimeException re) { -// if (parametersFlattened != null) { -// parameters = theCamera.getParameters(); -// parameters.unflatten(parametersFlattened); -// try { -// theCamera.setParameters(parameters); -// } catch (RuntimeException re2) { -// } -// } -// } - return this; - } - - /** - * 关闭相机驱动程 - */ - public synchronized void closeDriver() { - // 释放摄像头资源 - freeCameraResource(); - } - - // == 预览相关 == - - /** - * 开始将Camera画面预览到手机上 - */ - public synchronized void startPreview() { - Camera theCamera = mCamera; - if (theCamera != null && !previewing) { - // 开始预览 - theCamera.startPreview(); - // 表示预览中 - previewing = true; - // 初始化自动获取焦点 - autoFocusAssist = new AutoFocusAssist(mCamera, autoInterval); - // 开始预览通知 - if (previewNotify != null){ - previewNotify.startPreviewNotify(); - } - } - } - - /** - * 停止 Cmaera 画面预览 - */ - public synchronized void stopPreview() { - if (autoFocusAssist != null) { - autoFocusAssist.stop(); - autoFocusAssist = null; - } - if (mCamera != null && previewing) { - // 停止预览 - mCamera.stopPreview(); - // 表示非预览中 - previewing = false; - // 停止预览通知 - if (previewNotify != null){ - previewNotify.stopPreviewNotify(); - } - } - } - - /** - * 释放摄像头资源 - */ - private void freeCameraResource() { - try { - if (mCamera != null) { - mCamera.setPreviewCallback(null); - mCamera.stopPreview(); - mCamera.lock(); - mCamera.release(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "freeCameraResource"); - } finally { - mCamera = null; - } - } - - // == 摄像头相关 == - - // 预览尺寸大小 - private Camera.Size mPreviewSize = null; - - /** - * 获取相机分辨率 - * @return - */ - public Camera.Size getCameraResolution() { - if (mPreviewSize == null){ - // 获取预览大小 - mPreviewSize = cameraSizeAssist.getPreviewSize(); - return mPreviewSize; - } - return mPreviewSize; - } - - /** - * 获取预览大小 - * @return - */ - public Camera.Size getPreviewSize() { - if (null != mCamera) { - return mCamera.getParameters().getPreviewSize(); - } - return null; - } - - /** - * 获取 Camera.Size 计算辅助类 - * @return - */ - public CameraSizeAssist getCameraSizeAssist() { - return cameraSizeAssist; - } - - /** - * 获取摄像头 - * @return - */ - public Camera getCamera() { - return mCamera; - } - - /** - * 设置摄像头 - * @param camera - */ - public void setCamera(Camera camera) { - this.mCamera = camera; - // 初始化 Camera大小 - this.cameraSizeAssist = new CameraSizeAssist(mCamera); - } - - /** - * 设置预览回调 - * @param previewNotify - */ - public CameraAssist setPreviewNotify(PreviewNotify previewNotify) { - this.previewNotify = previewNotify; - return this; - } - - /** - * 设置是否开启自动对焦 - * @param autoFocus - */ - public CameraAssist setAutoFocus(boolean autoFocus) { - if (autoFocusAssist != null){ - autoFocusAssist.setAutoFocus(autoFocus); - } - return this; - } - - /** - * 是否预览中 - * @return - */ - public boolean isPreviewing() { - return previewing; - } - - /** - * 设置自动对焦时间间隔 - * @param autoInterval - */ - public void setAutoInterval(long autoInterval) { - this.autoInterval = autoInterval; - } - - // == 闪光灯相关 == - - // FlashlightUtils - - /** - * 打开闪光灯 - */ - public void setFlashlightOn() { - if (mCamera == null) { - return; - } - Camera.Parameters parameters = mCamera.getParameters(); - if (!FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { - parameters.setFlashMode(FLASH_MODE_TORCH); - mCamera.setParameters(parameters); - } - } - - /** - * 关闭闪光灯 - */ - public void setFlashlightOff() { - if (mCamera == null) { - return; - } - Camera.Parameters parameters = mCamera.getParameters(); - if (FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { - parameters.setFlashMode(FLASH_MODE_OFF); - mCamera.setParameters(parameters); - } - } - - /** - * 是否打开闪光灯 - * @return - */ - public boolean isFlashlightOn() { - if (mCamera == null) { - return false; - } - Camera.Parameters parameters = mCamera.getParameters(); - return FLASH_MODE_TORCH.equals(parameters.getFlashMode()); - } - - /** - * 是否支持手机闪光灯 - * @return - */ - public static boolean isFlashlightEnable() { - return DevUtils.getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); - } - - // = 接口 = - - /** - * detail: 预览通知 - * Created by Ttt - */ - public interface PreviewNotify { - - /** - * 停止预览通知 - */ - void stopPreviewNotify(); - - /** - * 开始预览通知 - */ - void startPreviewNotify(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraSizeAssist.java b/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraSizeAssist.java deleted file mode 100644 index 1093b6b1b2..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/camera/CameraSizeAssist.java +++ /dev/null @@ -1,990 +0,0 @@ -package dev.utils.app.assist.camera; - -import android.graphics.Point; -import android.hardware.Camera; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -import dev.utils.LogPrintUtils; -import dev.utils.app.ScreenUtils; - -/** - * detail: 摄像头 预览、输出大小 辅助类 - * Created by Ttt - * 摄像头 - * 震动 - * 手电筒 - * 正常只需要摄像头权限 - */ -public final class CameraSizeAssist { - - // 日志TAG - private final String TAG = CameraSizeAssist.class.getSimpleName(); - // 摄像头 Camera - private Camera mCamera; - // 默认最大的偏差 - private final double MAX_ASPECT_DISTORTION = 0.15; - // 最小尺寸, 小于该尺寸则不处理 - private final int MIN_PREVIEW_PIXELS = 480 * 320; - - // == 构造函数 == - - public CameraSizeAssist(Camera camera) { - this.mCamera = camera; - } - - /** - * 获取摄像头 - * @return - */ - public Camera getCamera() { - return mCamera; - } - - // ================== - // == 预览大小相关 == - // ================== - - /** - * 设置预览大小 - * @param previewSize - * @return - */ - public CameraSizeAssist setPreviewSize(Camera.Size previewSize){ - return setPreviewSize(mCamera, previewSize); - } - - /** - * 设置预览大小 - * @param camera - * @param previewSize - * @return - */ - public CameraSizeAssist setPreviewSize(Camera camera, Camera.Size previewSize){ - if (camera != null && previewSize != null){ - try { - // 设置预览大小 - camera.getParameters().setPreviewSize(previewSize.width, previewSize.height); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setPreviewSize"); - } - } - return this; - } - - // = - - /** - * 根据手机支持的预览分辨率计算,设置预览尺寸 - * @return - */ - public Camera.Size getPreviewSize(){ - return getPreviewSize(null, -1d); - } - - /** - * 根据手机支持的预览分辨率计算,设置预览尺寸 - * @param point - * @return - */ - public Camera.Size getPreviewSize(Point point) { - return getPreviewSize(point, -1d); - } - - /** - * 根据手机支持的预览分辨率计算,设置预览尺寸 - * @distortion point - * @return - */ - public Camera.Size getPreviewSize(double distortion) { - return getPreviewSize(null, distortion); - } - - /** - * 根据手机支持的预览分辨率计算,设置预览尺寸(无任何操作, 单独把Camera显示到SurfaceView 预览尺寸) - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - * @return - */ - public Camera.Size getPreviewSize(Point point, double distortion) { - if (mCamera == null){ - LogPrintUtils.dTag(TAG, "camera is null"); - return null; - } - try { - // 计算大小并返回 - return calcPreviewSize(point, distortion); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getPreviewSize"); - } - return null; - } - - // ================== - // == 拍照大小相关 == - // ================== - - /** - * 设置拍照图片大小 - * @param pictureSize - * @return - */ - public CameraSizeAssist setPictureSize(Camera.Size pictureSize){ - return setPictureSize(mCamera, pictureSize); - } - - /** - * 设置拍照图片大小 - * @param camera - * @param pictureSize - * @return - */ - public CameraSizeAssist setPictureSize(Camera camera, Camera.Size pictureSize){ - if (camera != null && pictureSize != null){ - try { - // 设置预览大小 - camera.getParameters().setPictureSize(pictureSize.width, pictureSize.height); -// // 设置拍照输出格式 -// camera.getParameters().setPictureFormat(PixelFormat.JPEG); -// // 照片质量 -// camera.getParameters().set("jpeg-quality", 70); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setPictureSize"); - } - } - return this; - } - - // = - - /** - * 根据手机支持的拍照分辨率计算 - * @return - */ - public Camera.Size getPictureSize(){ - return getPictureSize(false, null, -1d); - } - - /** - * 根据手机支持的拍照分辨率计算 - * @param max - * @return - */ - public Camera.Size getPictureSize(boolean max){ - return getPictureSize(max, null, -1d); - } - - /** - * 根据手机支持的拍照分辨率计算 - * @param point - */ - public Camera.Size getPictureSize(Point point) { - return getPictureSize(false, point, -1d); - } - - /** - * 根据手机支持的拍照分辨率计算 - * @param distortion - */ - public Camera.Size getPictureSize(double distortion) { - return getPictureSize(false, null, distortion); - } - - /** - * 根据手机支持的拍照分辨率计算 - * @param point - * @param distortion - */ - public Camera.Size getPictureSize(Point point, double distortion) { - return getPictureSize(false, point, distortion); - } - - /** - * 根据手机支持的拍照分辨率计算,设置预览尺寸 - * @param max 是否使用最大的尺寸 - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - */ - public Camera.Size getPictureSize(boolean max, Point point, double distortion) { - if (mCamera == null){ - LogPrintUtils.dTag(TAG, "camera is null"); - return null; - } - try { - // 计算大小并返回 - return calcPictureSize(max, point, distortion); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getPictureSize"); - } - return null; - } - - // ====================== - // == 视频录制大小相关 == - // ====================== - - /** - * 根据手机支持的视频录制分辨率计算 - * @return - */ - public Camera.Size getVideoSize(){ - return getVideoSize(false, null, -1d, false); - } - - /** - * 根据手机支持的视频录制分辨率计算 - * @param max - * @return - */ - public Camera.Size getVideoSize(boolean max){ - return getVideoSize(max, null, -1d, false); - } - - /** - * 根据手机支持的视频录制分辨率计算 - * @param point - */ - public Camera.Size getVideoSize(Point point) { - return getVideoSize(false, point, -1d, false); - } - - /** - * 根据手机支持的视频录制分辨率计算 - * @param distortion - */ - public Camera.Size getVideoSize(double distortion) { - return getVideoSize(false, null, distortion, false); - } - - /** - * 根据手机支持的视频录制分辨率计算 - * @param point - * @param distortion - */ - public Camera.Size getVideoSize(Point point, double distortion) { - return getVideoSize(false, point, distortion, false); - } - - /** - * 根据手机支持的视频录制分辨率计算,设置预览尺寸 - * @param max 是否使用最大的尺寸 - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - * @param minAccord 是否存在最小使用最小 - */ - public Camera.Size getVideoSize(boolean max, Point point, double distortion, boolean minAccord) { - if (mCamera == null){ - LogPrintUtils.dTag(TAG, "camera is null"); - return null; - } - try { - // 计算大小并返回 - return calcVideoSize(max, point, distortion, minAccord); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getVideoSize"); - } - return null; - } - - // ================== - // == 内部处理方法 == - // ================== - - // 暂时不使用 - /** - * 根据对应的尺寸, 计算相应最符合的大小 - * @param lists 摄像头尺寸大小(预览、拍照、视频) - * @param point point.x = > 宽, point.y => 高 (从指定的宽高, 开始往下(超过的不处理)选择最符合的尺寸) - */ - private Camera.Size calcSize(List lists, Point point) { - if (lists == null){ - return null; - } - // 判断是否竖屏 - boolean isPortrait = false; - // 尺寸大小 - Camera.Size mSize = null; - try { - // 获取屏幕宽、高 - int sWidth = (point != null) ? point.x : ScreenUtils.getScreenWidth(); - int sHeight = (point != null) ? point.y : ScreenUtils.getScreenHeight(); - // 如果高度大于宽度, 则表示属于竖屏 - isPortrait = sHeight > sWidth; - // 进行排序处理 -> 并以宽度为基准降序排序 - Collections.sort(lists, new Comparator() { - @Override - public int compare(Camera.Size lhs, Camera.Size rhs) { - if (lhs.width > rhs.width) { - return -1; - } else if (lhs.width == rhs.width) { - return 0; - } else { - return 1; - } - } - }); - // 遍历尺寸大小 - for (Camera.Size size : lists) { - // 判断横竖屏 - if (isPortrait){ // 属于竖屏 => 高度 > 宽度 - // 因为是竖屏, 所以判断需要倒着过来 - if (sWidth == size.height && sHeight == size.width){ - // 保存符合比例的大小 - mSize = size; - break; - } - // 计算合适的比例 - if (size.width >= sHeight){ - mSize = size; - } - } else { // 属于横屏 => 宽度 > 高度 - // 因为是横屏, 所以判断需要正常 - if (sWidth == size.width && sHeight == size.height){ - // 保存符合比例的大小 - mSize = size; - break; - } - // 计算合适的比例 - if (size.height >= sWidth){ - mSize = size; - } - } - } - // 获取最合适的比例 - LogPrintUtils.dTag(TAG, "返回 calcSize -> 宽度: " + mSize.width + ", 高度: " + mSize.height + ", 是否竖屏: " + isPortrait); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "异常 calcSize - 是否竖屏: " + isPortrait); - } - return mSize; - } - - // ============ - // = 预览大小 = - // ============ - - /** - * 根据对应的尺寸, 计算相应最符合的大小 - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - * hint: point.x = > 宽, point.y => 高 - */ - private Camera.Size calcPreviewSize(Point point, double distortion) { - // 判断是否为null - if (point == null){ - point = ScreenUtils.getScreenWidthHeightToPoint(); - } - // 如果误差为负数, 则使用默认值 - if (distortion < 0){ - distortion = MAX_ASPECT_DISTORTION; - } - // 获取 Camera 参数 - Camera.Parameters params = mCamera.getParameters(); - // 获取手机支持的分辨率集合,并以宽度为基准降序排序 - List listPreviewSizes = params.getSupportedPreviewSizes(); - // 防止数据为null - if (listPreviewSizes == null){ - // 获取默认预览大小 - Camera.Size defaultSize = params.getPreviewSize(); - return defaultSize; - } - - // 进行排序处理 -> 并以宽度 * 高度 为基准降序排序 - Collections.sort(listPreviewSizes, new Comparator() { - @Override - public int compare(Camera.Size lhs, Camera.Size rhs) { - int leftPixels = lhs.height * lhs.width; - int rightPixels = rhs.height * rhs.width; - - if (leftPixels > rightPixels) { - return -1; - } else if (leftPixels == rightPixels) { - return 0; - } else { - return 1; - } - } - }); - - // ==== 打印信息 ==== - if (LogPrintUtils.isPrintLog()){ - StringBuilder builder = new StringBuilder(); - builder.append("预览支持尺寸: \r\n"); - // 打印信息 - for (Camera.Size previewSize : listPreviewSizes){ - // 例: 1080x1920 - builder.append(previewSize.width).append("x").append(previewSize.height).append("\r\n"); - } - // 打印尺寸信息 - LogPrintUtils.dTag(TAG, builder.toString()); - } - - // == - - // 判断是否竖屏 point.x = > 宽, point.y => 高 - boolean isPortrait = point.y > point.x; - // 如果是竖屏, 则修改 - if (isPortrait){ - int tempY = point.y; - int tempX = point.x; - // 进行转换 - point.x = tempY; - point.y = tempX; - } - - // 计算比例值 宽 / 高 - double screenAspectRatio = (double) point.x / (double) point.y; - - // 循环遍历判断 - Iterator iterator = listPreviewSizes.iterator(); - while (iterator.hasNext()) { - // 获取预览大小 - Camera.Size previewSize = iterator.next(); - // 获取宽、高 - int realWidth = previewSize.width; - int realHeight = previewSize.height; - // 小于最小尺寸, 则不处理 - if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { - iterator.remove(); - continue; - } - - // 判断预选的尺寸是否竖屏 - boolean isCandidatePortrait = realWidth < realHeight; - // 翻转宽、高 - int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; - int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; - - // 计算比例 - double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; - double calcDistortion = Math.abs(aspectRatio - screenAspectRatio); - - // 如果大于指定的尺寸比例差, 则跳过 - if (calcDistortion > distortion) { - iterator.remove(); - continue; - } - - // 如果相符, 则直接跳过 - if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { - return previewSize; - } - } - - // 如果没有精确匹配,请使用最大预览大小。这对于旧设备来说不是一个好主意,因为需要额外的计算。我们很可能会在新的Android 4 + 设备上运行,那里的CPU功能更强大。 - if (!listPreviewSizes.isEmpty()) { - // 获取最大的尺寸 - Camera.Size largestPreview = listPreviewSizes.get(0); - return largestPreview; - } - - // = 都不匹配, 则用默认分辨率 = - // 获取默认预览大小 - Camera.Size defaultSize = params.getPreviewSize(); - return defaultSize; - } - - // ============ - // = 拍照大小 = - // ============ - - /** - * 根据对应的尺寸, 计算相应最符合的大小 - * @param max 是否使用最大的尺寸 - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - * hint: point.x = > 宽, point.y => 高 - */ - private Camera.Size calcPictureSize(boolean max, Point point, double distortion) { - // 判断是否为null - if (point == null){ - point = ScreenUtils.getScreenWidthHeightToPoint(); - } - // 如果误差为负数, 则使用默认值 - if (distortion < 0){ - distortion = MAX_ASPECT_DISTORTION; - } - // 获取 Camera 参数 - Camera.Parameters params = mCamera.getParameters(); - // 获取手机支持的分辨率集合,并以宽度为基准降序排序 - List listPictureSizes = params.getSupportedPictureSizes(); - // 防止数据为null - if (listPictureSizes == null){ - // 获取默认拍照大小 - Camera.Size defaultSize = params.getPictureSize(); - return defaultSize; - } - - // 进行排序处理 -> 并以宽度 * 高度 为基准降序排序 - Collections.sort(listPictureSizes, new Comparator() { - @Override - public int compare(Camera.Size lhs, Camera.Size rhs) { - int leftPixels = lhs.height * lhs.width; - int rightPixels = rhs.height * rhs.width; - - if (leftPixels > rightPixels) { - return -1; - } else if (leftPixels == rightPixels) { - return 0; - } else { - return 1; - } - } - }); - - // ==== 打印信息 ==== - if (LogPrintUtils.isPrintLog()) { - StringBuilder builder = new StringBuilder(); - builder.append("拍照支持尺寸: \r\n"); - // 打印信息 - for (Camera.Size pictureSize : listPictureSizes) { - // 例: 1080x1920 - builder.append(pictureSize.width).append("x").append(pictureSize.height).append("\r\n"); - } - // 打印尺寸信息 - LogPrintUtils.dTag(TAG, builder.toString()); - } - - // == - - // 判断是否拿最大支持的尺寸 - if (max){ - if (!listPictureSizes.isEmpty()) { - // 获取最大的尺寸 - Camera.Size largestPicture = listPictureSizes.get(0); - return largestPicture; - } else { - // 获取默认拍照大小 - Camera.Size defaultSize = params.getPictureSize(); - return defaultSize; - } - } - - // 判断是否竖屏 point.x = > 宽, point.y => 高 - boolean isPortrait = point.y > point.x; - // 如果是竖屏, 则修改 - if (isPortrait){ - int tempY = point.y; - int tempX = point.x; - // 进行转换 - point.x = tempY; - point.y = tempX; - } - - // 计算比例值 宽 / 高 - double pictureAspectRatio = (double) point.x / (double) point.y; - - // 判断最大符合 - Camera.Size maxAccordSize = null; - - // 循环遍历判断 - Iterator iterator = listPictureSizes.iterator(); - while (iterator.hasNext()) { - // 获取拍照大小 - Camera.Size pictureSize = iterator.next(); - // 获取宽、高 - int realWidth = pictureSize.width; - int realHeight = pictureSize.height; - // 小于最小尺寸, 则不处理 - if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { - iterator.remove(); - continue; - } - - // 判断预选的尺寸是否竖屏 - boolean isCandidatePortrait = realWidth < realHeight; - // 翻转宽、高 - int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; - int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; - - // 计算比例 - double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; - double calcDistortion = Math.abs(aspectRatio - pictureAspectRatio); - - // 如果大于指定的尺寸比例差, 则跳过 - if (calcDistortion > distortion) { - iterator.remove(); - continue; - } - - // 如果相符, 则直接跳过 - if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { - return pictureSize; - } - - // 保存最大相符的尺寸 - if (maxAccordSize == null){ - maxAccordSize = pictureSize; - } - } - - // 如果存在最相符的则返回 - if (maxAccordSize != null){ - return maxAccordSize; - } - - // 如果没有精确匹配,请使用最大尺寸大小 - if (!listPictureSizes.isEmpty()) { - // 获取最大的尺寸 - Camera.Size largestPicture = listPictureSizes.get(0); - return largestPicture; - } - - // = 都不匹配, 则用默认分辨率 = - // 获取默认拍照大小 - Camera.Size defaultSize = params.getPictureSize(); - return defaultSize; - } - - // ================ - // = 视频录制尺寸 = - // ================ - - /** - * 根据对应的尺寸, 计算相应最符合的大小 - * @param max 是否使用最大的尺寸 - * @param point 指定的尺寸(为null, 则使用屏幕尺寸) (从指定的宽高, 开始往下(超过的不处理) 选择最接近尺寸) - * @param distortion 偏差比例值 - * @param minAccord 是否判断存在最小使用最小 - * hint: point.x = > 宽, point.y => 高 - */ - private Camera.Size calcVideoSize(boolean max, Point point, double distortion, boolean minAccord) { - // 判断是否为null - if (point == null){ - point = ScreenUtils.getScreenWidthHeightToPoint(); - } - // 如果误差为负数, 则使用默认值 - if (distortion < 0){ - distortion = MAX_ASPECT_DISTORTION; - } - // 获取 Camera 参数 - Camera.Parameters params = mCamera.getParameters(); - // 获取手机支持的分辨率集合,并以宽度为基准降序排序 - List listVideoSizes = params.getSupportedVideoSizes(); - // 防止数据为null - if (listVideoSizes == null){ - // 获取默认拍照大小 - Camera.Size defaultSize = params.getPreferredPreviewSizeForVideo(); - return defaultSize; - } - - // 进行排序处理 -> 并以宽度 * 高度 为基准降序排序 - Collections.sort(listVideoSizes, new Comparator() { - @Override - public int compare(Camera.Size lhs, Camera.Size rhs) { - int leftPixels = lhs.height * lhs.width; - int rightPixels = rhs.height * rhs.width; - - if (leftPixels > rightPixels) { - return -1; - } else if (leftPixels == rightPixels) { - return 0; - } else { - return 1; - } - } - }); - - // ==== 打印信息 ==== - if (LogPrintUtils.isPrintLog()) { - StringBuilder builder = new StringBuilder(); - builder.append("视频录制支持尺寸: \r\n"); - // 打印信息 - for (Camera.Size videoSize : listVideoSizes) { - // 例: 1080x1920 - builder.append(videoSize.width).append("x").append(videoSize.height).append("\r\n"); - } - // 打印尺寸信息 - LogPrintUtils.dTag(TAG, builder.toString()); - } - - // == - - // 判断是否拿最大支持的尺寸 - if (max){ - if (!listVideoSizes.isEmpty()) { - // 获取最大的尺寸 - Camera.Size largestVideo = listVideoSizes.get(0); - return largestVideo; - } else { - // 获取默认视频大小 - Camera.Size defaultSize = params.getPreferredPreviewSizeForVideo(); - return defaultSize; - } - } - - // 判断是否竖屏 point.x = > 宽, point.y => 高 - boolean isPortrait = point.y > point.x; - // 如果是竖屏, 则修改 - if (isPortrait){ - int tempY = point.y; - int tempX = point.x; - // 进行转换 - point.x = tempY; - point.y = tempX; - } - - // 计算比例值 宽 / 高 - double videoAspectRatio = (double) point.x / (double) point.y; - - // 判断最大符合 - Camera.Size maxAccordSize = null; - // 判断最小符合 - Camera.Size minAccordSize = null; - - // 循环遍历判断 - Iterator iterator = listVideoSizes.iterator(); - while (iterator.hasNext()) { - // 获取视频大小 - Camera.Size videoSize = iterator.next(); - // 获取宽、高 - int realWidth = videoSize.width; - int realHeight = videoSize.height; - // 小于最小尺寸, 则不处理 - if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { - iterator.remove(); - continue; - } - - // 判断预选的尺寸是否竖屏 - boolean isCandidatePortrait = realWidth < realHeight; - // 翻转宽、高 - int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; - int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; - - // 计算比例 - double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; - double calcDistortion = Math.abs(aspectRatio - videoAspectRatio); - - // 如果大于指定的尺寸比例差, 则跳过 - if (calcDistortion > distortion) { - iterator.remove(); - continue; - } - - // 如果相符, 则直接跳过 - if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { - return videoSize; - } - - // 保存最大相符的尺寸 - if (maxAccordSize == null){ - maxAccordSize = videoSize; - } - // 保存最小符合的 - minAccordSize = videoSize; - } - - if (minAccord && minAccordSize != null){ - return minAccordSize; - } - - // 如果存在最相符的则返回 - if (maxAccordSize != null){ - return maxAccordSize; - } - - // 如果没有精确匹配,请使用最大尺寸大小 - if (!listVideoSizes.isEmpty()) { - // 获取最大的尺寸 - Camera.Size largestVideo = listVideoSizes.get(0); - return largestVideo; - } - - // = 都不匹配, 则用默认分辨率 = - // 获取默认视频大小 - Camera.Size defaultSize = params.getPreferredPreviewSizeForVideo(); - return defaultSize; - } - -// /** -// * 根据手机支持的视频分辨率,设置录制尺寸 -// * @param point point.x = > 宽, point.y => 高 (从指定的宽高, 开始往下(超过的不处理)选择最符合的尺寸) -// * @param designated 指定的尺寸 - 可多个如 竖屏 => new Point(480, 640), new Point(360, 640), 横屏 => new Point(640, 480), new Point(640, 360) -// */ -// public Camera.Size getVideoSize(Point point, Point... designated) { -// if (mCamera == null){ -// // 打印支持的尺寸 -// LogPrintUtils.dTag(TAG, "camera is null"); -// return null; -// } -// // 判断是否竖屏 -// boolean isPortrait = false; -// // 视频尺寸大小 -// Camera.Size mVideoSize = null; -// try { -// // 获取 Camera 参数 -// Camera.Parameters params = mCamera.getParameters(); -// // 小数点处理, 只要后两位 -// DecimalFormat decimalFormat = new DecimalFormat("0.00"); -// decimalFormat.setRoundingMode(RoundingMode.FLOOR); -// // 获取屏幕宽、高 -// int sWidth = (point != null) ? point.x : ScreenUtils.getScreenWidth(); -// int sHeight = (point != null) ? point.y : ScreenUtils.getScreenHeight(); -// // 如果高度大于宽度, 则表示属于竖屏 -// isPortrait = sHeight > sWidth; -// // 判断是否存在指定尺寸 -// boolean isDesignated = (designated != null && designated.length != 0); -// // 打印准备计算的信息 -// LogPrintUtils.dTag(TAG, "getVideoSize - sWidth: " + sWidth + ", sHeight: " + sHeight + ", isPortrait: " + isPortrait + ", isDesignated(是否存在指定尺寸): " + isDesignated); -// // 打印信息 -// if (isDesignated){ -// for (int i = 0, len = designated.length; i < len; i++){ -// Point appoint = designated[i]; -// if (appoint != null){ -// LogPrintUtils.dTag(TAG, "appoint.x: " + appoint.x + ", appoint.y: " + appoint.y); -// } -// } -// } else { -// LogPrintUtils.dTag(TAG, "designated is null or leng == 0"); -// } -// // 获取支持录制的视频尺寸 -// List listVideoSizes = params.getSupportedVideoSizes(); -// // 获取手机支持的分辨率集合, 并以宽度为基准降序排序 -// Collections.sort(listVideoSizes, new Comparator() { -// @Override -// public int compare(Camera.Size lhs, Camera.Size rhs) { -// if (lhs.width > rhs.width) { -// return -1; -// } else if (lhs.width == rhs.width) { -// return 0; -// } else { -// return 1; -// } -// } -// }); -// // 是否跳出循环 -// boolean isBreak = false; -// // 默认是否支持固定的大小 -// Camera.Size fixedSize = null; -// // 计算比例(竖屏 以高度为基准, 高:宽), (横屏 以宽度为基准, 宽:高) -// float ratio = (isPortrait ? ((float) sHeight / (float) sWidth) : ((float) sWidth / (float) sHeight)) - 1; -// // 转换保留两位小数点 -// ratio = Float.parseFloat(decimalFormat.format(ratio)); -// // 遍历预览大小 -// for (Camera.Size size : listVideoSizes) { -// if (isBreak){ -// break; -// } -// // 打印支持的尺寸 -// LogPrintUtils.dTag(TAG, "VideoSizes - 宽度: " + size.width + ", 高度: " + size.height); -// // 判断横竖屏 -// if (isPortrait) { // 属于竖屏 => 高度 > 宽度 -// // 因为是竖屏, 所以判断需要倒着过来 -// if (sWidth == size.height && sHeight == size.width){ -// // 保存符合比例的大小 -// mVideoSize = size; -// } -// // 处理宽大于高的, 因为是使用竖屏, 参数判断都反着处理 -// if (size.width > size.height){ -// // 获取比例 -// float ratioCalc = ((float) size.width / (float) size.height) - 1; -// // 转换保留两位小数点 -// ratioCalc = Float.parseFloat(decimalFormat.format(ratioCalc)); -// // 判断符合规则的 -// if (ratioCalc == ratio){ -// // 保存尺寸 -// fixedSize = size; -// // 如果存在才处理 -// if (isDesignated){ -// for (int i = 0, len = designated.length; i < len; i++){ -// Point appoint = designated[i]; -// if (appoint != null){ -// // 判断是否支持固定的大小 -// if (size.width == appoint.y && size.height == appoint.x) { -// isBreak = true; -// break; -// } -// } -// } -// } -// } else { -// // 最小支持到640 -// if (size.width < 640){ -// break; -// } -// // 保存尺寸 -// fixedSize = size; -// // 如果存在才处理 -// if (isDesignated){ -// for (int i = 0, len = designated.length; i < len; i++){ -// Point appoint = designated[i]; -// if (appoint != null){ -// // 判断是否支持固定的大小 -// if (size.width == appoint.y && size.height == appoint.x) { -// isBreak = true; -// break; -// } -// } -// } -// } -// } -// } -// } else { // 属于横屏 => 宽度 > 高度 -// // 因为是横屏, 所以判断需要正常 -// if (sWidth == size.width && sHeight == size.height){ -// // 保存符合比例的大小 -// mVideoSize = size; -// } -// // 处理高大于宽的, 因为是使用横屏, 参数判断需要正常 -// if (size.height > size.width){ -// // 获取比例 -// float ratioCalc = ((float) size.height / (float) size.width) - 1; -// // 转换保留两位小数点 -// ratioCalc = Float.parseFloat(decimalFormat.format(ratioCalc)); -// // 判断符合规则的 -// if (ratioCalc == ratio){ -// // 保存尺寸 -// fixedSize = size; -// // 如果存在才处理 -// if (isDesignated){ -// for (int i = 0, len = designated.length; i < len; i++){ -// Point appoint = designated[i]; -// if (appoint != null){ -// // 判断是否支持固定的大小 -// if (size.width == appoint.y && size.height == appoint.x) { -//// isBreak = true; -// break; -// } -// } -// } -// } -// } else { -// // 最小支持到 640 -// if (size.height < 640){ -// break; -// } -// // 保存尺寸 -// fixedSize = size; -// // 如果存在才处理 -// if (isDesignated){ -// for (int i = 0, len = designated.length; i < len; i++){ -// Point appoint = designated[i]; -// if (appoint != null){ -// // 判断是否支持固定的大小 -// if (size.width == appoint.y && size.height == appoint.x) { -//// isBreak = true; -// break; -// } -// } -// } -// } -// } -// } -// } -// } -// // 如果支持固定的大小, 则进行处理 -// if (fixedSize != null) { -// // 保存固定支持的尺寸 -// mVideoSize = fixedSize; -// } -// // 获取最合适的比例 -// LogPrintUtils.dTag(TAG, "getVideoSize -> 宽度: " + mVideoSize.width + ", 高度: " + mVideoSize.height + ", 是否竖屏: " + isPortrait); -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getVideoSize - 是否竖屏: " + isPortrait); -// } -// return mVideoSize; -// } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ActivityManager.java b/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ActivityManager.java deleted file mode 100644 index 3100bece34..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ActivityManager.java +++ /dev/null @@ -1,436 +0,0 @@ -package dev.utils.app.assist.manager; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; - -import java.util.Iterator; -import java.util.Stack; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 应用程序Activity管理类:用于Activity管理和应用程序 - * Created by Ttt - */ -public final class ActivityManager { - - /** 禁止构造对象,保证只有一个实例 */ - private ActivityManager() { - } - - // 日志TAG - private static final String TAG = ActivityManager.class.getSimpleName(); - - /** ActivityManager 实例 */ - private static ActivityManager INSTANCE = new ActivityManager(); - - /** 获取 ActivityManager 实例 ,单例模式 */ - public static ActivityManager getInstance() { - return INSTANCE; - } - - /** - * 通过上下文 获取Activity - * @param context - * @return - */ - public static Activity getActivity(Context context) { - try { - Activity activity = (Activity) context; - return activity; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getActivity"); - } - return null; - } - - // == 页面判断处理 == - - /** - * 判断页面是否关闭 - * @param activity - * @return - */ - public static boolean isFinishing(Activity activity){ - if (activity != null){ - return activity.isFinishing(); - } - return false; - } - - /** - * 判断页面是否关闭 - * @param context - * @return - */ - public static boolean isFinishingCtx(Context context){ - if (context != null){ - try { - return ((Activity) context).isFinishing(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "isFinishingCtx"); - } - } - return false; - } - - // ============== - - /** Activity 栈(后进先出) */ - private final Stack activityStacks = new Stack<>(); - - /** - * 获取 Activity 栈 - * @return - */ - public Stack getActivityStacks() { - return activityStacks; - } - - /** - * 保存 Activity - * @param activity - */ - public void addActivity(Activity activity) { - if (activity == null) { - return; - } - synchronized (activityStacks) { - if (activityStacks.contains(activity)) { - return; - } - activityStacks.add(activity); - } - } - - /** - * 移除 Activity - * @param activity - */ - public void removeActivity(Activity activity) { - if (activity == null) { - return; - } - synchronized (activityStacks) { - int index = activityStacks.indexOf(activity); - if (index == -1) { - return; - } - activityStacks.remove(index); - } - } - - /** - * 移除 Activity - * @param activitys - */ - public void removeActivity(Activity... activitys) { - if (activitys != null && activitys.length != 0){ - for (int i = 0, len = activitys.length; i < len; i++){ - removeActivity(activitys[i]); - } - } - } - - /** - * 获取当前Activity - * @return - */ - public Activity currentActivity() { - return activityStacks.lastElement(); - } - - /** - * 结束当前Activity - */ - public void finishActivity() { - finishActivity(activityStacks.lastElement()); - } - - /** - * 结束指定的 Activity - * @param activity - */ - public void finishActivity(Activity activity) { - // 先移除Activity - removeActivity(activity); - // Activity 不为null,并且属于未销毁状态 - if (activity != null && !activity.isFinishing()) { - // finish到Activity - activity.finish(); - } - } - - /** - * 结束指定的 Activity - * @param activitys - */ - public void finishActivity(Activity... activitys) { - if (activitys != null && activitys.length != 0){ - for (int i = 0, len = activitys.length; i < len; i++){ - finishActivity(activitys[i]); - } - } - } - - /** - * 结束指定类名的 Activity - * @param cls Activity.class - */ - public void finishActivity(Class cls) { - synchronized (activityStacks) { - // 保存新的任务,防止出现同步问题 - Stack aStacks = new Stack<>(); - aStacks.addAll(activityStacks); - // 清空全部,便于后续操作处理 - activityStacks.clear(); - // 进行遍历移除 - Iterator iterator = aStacks.iterator(); - while (iterator.hasNext()) { - Activity activity = iterator.next(); - // 判断是否想要关闭的Activity - if (activity != null) { - if (activity.getClass() == cls){ - // 如果页面没有finish 则进行finish - if (!activity.isFinishing()) { - activity.finish(); - } - // 删除对应的Item - iterator.remove(); - } - } else { - // 删除对应的Item - iterator.remove(); - } - } - // 把不符合条件的保存回去 - activityStacks.addAll(aStacks); - // 移除,并且清空内存 - aStacks.clear(); - aStacks = null; - } - } - - /** - * 结束指定类名的 Activity - * @param clss Activity.class, x.class - */ - public void finishActivity(Class... clss) { -// if (clss != null && clss.length != 0) { -// for (int i = 0, len = clss.length; i < len; i++){ -// finishActivity(clss[i]); -// } -// } - if (clss != null && clss.length != 0){ - synchronized (activityStacks) { - // 保存新的任务,防止出现同步问题 - Stack aStacks = new Stack<>(); - aStacks.addAll(activityStacks); - // 清空全部,便于后续操作处理 - activityStacks.clear(); - // 判断是否销毁 - boolean isRemove; - // 进行遍历移除 - Iterator iterator = aStacks.iterator(); - while (iterator.hasNext()) { - Activity activity = iterator.next(); - // 判断是否想要关闭的Activity - if (activity != null) { - // 默认不需要销毁 - isRemove = false; - // 循环判断 - for (int i = 0, len = clss.length; i < len; i++){ - // 判断是否相同 - if (activity.getClass() == clss[i]){ - isRemove = true; - break; - } - } - // 判断是否销毁 - if (isRemove){ - // 如果页面没有finish 则进行finish - if (!activity.isFinishing()) { - activity.finish(); - } - // 删除对应的Item - iterator.remove(); - } - } else { - // 删除对应的Item - iterator.remove(); - } - } - // 把不符合条件的保存回去 - activityStacks.addAll(aStacks); - // 移除,并且清空内存 - aStacks.clear(); - aStacks = null; - } - } - } - - /** - * 结束全部Activity 除忽略的页面外 - * @param cls - */ - public void finishAllActivityToIgnore(Class cls){ - synchronized (activityStacks) { - // 保存新的任务,防止出现同步问题 - Stack aStacks = new Stack<>(); - aStacks.addAll(activityStacks); - // 清空全部,便于后续操作处理 - activityStacks.clear(); - // 进行遍历移除 - Iterator iterator = aStacks.iterator(); - while (iterator.hasNext()) { - Activity activity = iterator.next(); - // 判断是否想要关闭的Activity - if (activity != null) { - if (!(activity.getClass() == cls)){ - // 如果页面没有finish 则进行finish - if (!activity.isFinishing()) { - activity.finish(); - } - // 删除对应的Item - iterator.remove(); - } - } else { - // 删除对应的Item - iterator.remove(); - } - } - // 把不符合条件的保存回去 - activityStacks.addAll(aStacks); - // 移除,并且清空内存 - aStacks.clear(); - aStacks = null; - } - } - - /** - * 结束全部Activity 除忽略的页面外 - * @param clss - */ - public void finishAllActivityToIgnore(Class... clss){ - if (clss != null && clss.length != 0){ - synchronized (activityStacks) { - // 保存新的任务,防止出现同步问题 - Stack aStacks = new Stack<>(); - aStacks.addAll(activityStacks); - // 清空全部,便于后续操作处理 - activityStacks.clear(); - // 判断是否销毁 - boolean isRemove; - // 进行遍历移除 - Iterator iterator = aStacks.iterator(); - while (iterator.hasNext()) { - Activity activity = iterator.next(); - // 判断是否想要关闭的Activity - if (activity != null) { - // 默认需要销毁 - isRemove = true; - // 循环判断 - for (int i = 0, len = clss.length; i < len; i++){ - // 判断是否相同 - if (activity.getClass() == clss[i]){ - isRemove = false; - break; - } - } - // 判断是否销毁 - if (isRemove){ - // 如果页面没有finish 则进行finish - if (!activity.isFinishing()) { - activity.finish(); - } - // 删除对应的Item - iterator.remove(); - } - } else { - // 删除对应的Item - iterator.remove(); - } - } - // 把不符合条件的保存回去 - activityStacks.addAll(aStacks); - // 移除,并且清空内存 - aStacks.clear(); - aStacks = null; - } - } - } - - /** - * 结束所有Activity - */ - public void finishAllActivity() { - synchronized (activityStacks) { - // 保存新的任务,防止出现同步问题 - Stack aStacks = new Stack<>(); - aStacks.addAll(activityStacks); - // 清空全部,便于后续操作处理 - activityStacks.clear(); - // 进行遍历移除 - Iterator iterator = aStacks.iterator(); - while (iterator.hasNext()) { - Activity activity = iterator.next(); - if (activity != null && !activity.isFinishing()) { - activity.finish(); - // 删除对应的Item - iterator.remove(); - } - } - // 移除,并且清空内存 - aStacks.clear(); - aStacks = null; - } - } - - /** - * 退出应用程序 - * @param context - */ - public void appExit(Context context) { - try { - finishAllActivity(); - // -- - android.app.ActivityManager activityMgr = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - activityMgr.restartPackage(context.getPackageName()); - System.exit(0); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "appExit"); - } - } - - /** - * 退出应用程序 - */ - public void appExit() { - try { - finishAllActivity(); - //退出JVM(java虚拟机),释放所占内存资源,0表示正常退出(非0的都为异常退出) - System.exit(0); - //从操作系统中结束掉当前程序的进程 - android.os.Process.killProcess(android.os.Process.myPid()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "appExit"); - // - - System.exit(-1); - } - } - - /** - * 重启app - */ - public static void restartApplication() { - try { - Intent intent = DevUtils.getContext().getPackageManager().getLaunchIntentForPackage(DevUtils.getContext().getPackageName()); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - DevUtils.getContext().startActivity(intent); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "restartApplication"); - } - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ThreadManager.java b/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ThreadManager.java deleted file mode 100644 index 0347d24ef1..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/ThreadManager.java +++ /dev/null @@ -1,132 +0,0 @@ -package dev.utils.app.assist.manager; - -import java.lang.reflect.Method; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import dev.utils.LogPrintUtils; - -/** - * detail: 线程管理类 - 统一使用DevThreadManager, 抛弃该类 - * Created by Ttt - */ -public final class ThreadManager { - - // 日志TAG - private final String TAG = ThreadManager.class.getSimpleName(); - - // 线程池对象 - private final ExecutorService threadPool = Executors.newFixedThreadPool(getThreads()); - - // ThreadManager 实例 - private static ThreadManager INSTANCE = new ThreadManager(); - - /** 禁止构造对象,保证只有一个实例 */ - private ThreadManager() { - } - - /** 获取 ThreadManager 实例 ,单例模式 */ - public static ThreadManager getInstance() { - return INSTANCE; - } - - // == - - /** - * 获取线程数 - * @return - */ - private final int getThreads() { - // 使用计算过后的 - return getCaclThreads(); - // 使用固定自定义的线程数量 - //return 10; - } - - /** - * 获取线程数 - * @return - */ - private final int getCaclThreads() { - // return Runtime.getRuntime().availableProcessors() * 2 + 1 - // -- - // 获取CPU核心数 - int cNumber = Runtime.getRuntime().availableProcessors(); - // 如果小于等于5,则返回5 - if (cNumber <= 5) { - return 5; - } else { // 大于5的情况 - if (cNumber * 2 + 1 >= 10) { // 防止线程数量过大,当大于10 的时候,返回 10 - return 10; - } else { // 不大于10的时候,默认返回 支持的数量 * 2 + 1 - return cNumber * 2 + 1; - } - } - } - - // == - - /** - * 加入到线程池任务队列 - * @param runnable - */ - public void addTask(Runnable runnable) { - threadPool.execute(runnable); - } - - - /** - * 通过反射,调用某个类的方法 - * @param method - * @param _class - */ - public void addTask(final Method method, final Object _class) { - threadPool.execute(new Runnable() { - @Override - public void run() { - try { - method.invoke(_class); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "addTask"); - } - } - }); - } - - // == - - /** - * shutdown 会等待所有提交的任务执行完成,不管是正在执行还是保存在任务队列中的已提交任务 - */ - public void shutdown() { - threadPool.shutdown(); - } - - /** - * shutdownNow会尝试中断正在执行的任务(其主要是中断一些指定方法如sleep方法),并且停止执行等待队列中提交的任务。 - * @return - */ - public List shutdownNow() { - return threadPool.shutdownNow(); - } - - /** - * isShutDown当调用shutdown()方法后返回为true。 - * @return - */ - public boolean isShutdown() { - return threadPool.isShutdown(); - } - - /** - * 若关闭后所有任务都已完成,则返回true. - * 注意除非首先调用shutdown或shutdownNow, 否则isTerminated 永不为true. - * // -- - * isTerminated当调用shutdown()方法后,并且所有提交的任务完成后返回为true - * @return - */ - public boolean isTerminated() { - return threadPool.isTerminated(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/TimerManager.java b/DevLibUtils/src/main/java/dev/utils/app/assist/manager/TimerManager.java deleted file mode 100644 index dd342e52ea..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/assist/manager/TimerManager.java +++ /dev/null @@ -1,490 +0,0 @@ -package dev.utils.app.assist.manager; - -import android.os.Handler; -import android.os.Message; -import android.text.TextUtils; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Timer; - -import dev.utils.LogPrintUtils; - -/** - * detail: 定时器工具类 - * Created by Ttt - * --- - * hint: - * 主要是为了控制整个项目的定时器,防止定时器混乱,或者导致忘记关闭等情况,以及减少初始化等操作代码 - * 主要实现是 AbsTimer、TimerTask 这两个类, - * AbsTimer -> 定时器抽象类,对外提供该类对象,以及内部方法,便于内部实现方法的隐藏,以及达到对定时器任务的控制处理 - * TimerTask -> 内部私有类,实现了具体的定时器操作,以及代码控制等,防止外部直接new,导致定时器混乱 - * --- - * 如果外部想要实现定时器,但是通过内部 ArrayList 控制,也可以通过 实现AbsTimer接口,内部的startTimer()、closeTimer() 进行了对AbsTimer的保存,标记等操作 - * 需要注意的是,实现start(close)Timer() 方法,必须保留 super.start(close)Timer(); -> 内部 ArrayList 进行了操作,而不对外开放(不需要主动调用) - * --- - * startTimer() -> 主要进行添加到 ArrayList, 并且标记不需要回收 - * closeTimer() -> 不直接操作remove,防止出现ConcurrentModificationException 异常, 而是做一个标记,便于后续回收 - */ -public final class TimerManager { - - private TimerManager() { - } - - // 日志TAG - private static final String TAG = TimerManager.class.getSimpleName(); - - /** 内部保存定时器对象,防止忘记关闭等其他情况,以及便于控制处理 */ - private static final ArrayList listAbsTimers = new ArrayList(); - - // ============= ArrayList 对外公开的方法 ============= - /** 回收资源 */ - public static void gc() { - synchronized (listAbsTimers) { - // 临时数据源 - ArrayList lists = new ArrayList(listAbsTimers); - // 清空旧的数据 - listAbsTimers.clear(); - // 开始删除无用资源 - Iterator iterator = lists.iterator(); - while (iterator.hasNext()) { - if (iterator.next().isMarkSweep) { // 需要回收,则进行回收 - iterator.remove(); - } - } - // 把不需要回收的保存回去 - listAbsTimers.addAll(lists); - // 移除旧的 - lists.clear(); - lists = null; - } - } - - /** 获取全部任务总数 */ - public static int timerSize() { - return listAbsTimers.size(); - } - - /** - * 获取属于对应字符串标记的定时器任务(优先获取符合的) - * @param markStr - * @return - */ - public static AbsTimer getTimer(String markStr) { - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - AbsTimer absTimer = listAbsTimers.get(i); - // 判断是否符合标记 , 原本标记不为null,并且符合条件的 - if (!TextUtils.isEmpty(absTimer.getMarkStr()) && absTimer.getMarkStr().equals(markStr)) { - return absTimer; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getTimer"); - } - return null; - } - - /** - * 获取属于标记id的定时器任务(优先获取符合的) - * @param markId - * @return - */ - public static AbsTimer getTimer(int markId) { - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - AbsTimer absTimer = listAbsTimers.get(i); - // 判断是否符合标记 , 原本标记不为null,并且符合条件的 - if (absTimer.getMarkId() == markId) { - return absTimer; - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getTimer"); - } - return null; - } - - /** 关闭全部任务 */ - public static void closeAll() { - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - listAbsTimers.get(i).closeTimer(); // 关闭定时器 - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeAll"); - } - } - - /** 关闭所有无限循环的任务 */ - public static void closeInfiniteTask() { - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - AbsTimer absTimer = listAbsTimers.get(i); - // 判断是否无限运行 - if (absTimer.isInfinite()) { - absTimer.closeTimer(); // 关闭定时器 - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeInfiniteTask"); - } - } - - /** - * 关闭所有符合对应的字符串标记的定时器任务 - * @param markStr - */ - public static void closeMark(String markStr) { - if (markStr == null) { - return; - } - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - AbsTimer absTimer = listAbsTimers.get(i); - // 判断是否符合标记 , 原本标记不为null,并且符合条件的 - if (!TextUtils.isEmpty(absTimer.getMarkStr()) && absTimer.getMarkStr().equals(markStr)) { - absTimer.closeTimer(); // 关闭定时器 - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeMark"); - } - } - - /** - * 关闭所有符合对应的标记id的定时器任务 - * @param markId - */ - public static void closeMark(int markId) { - try { - for (int i = 0, size = listAbsTimers.size(); i < size; i++) { - AbsTimer absTimer = listAbsTimers.get(i); - if (absTimer.getMarkId() == markId) { - absTimer.closeTimer(); // 关闭定时器 - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeMark"); - } - } - - // =============== 对外公开初始化AbsTimer方法(内部控制对 TimerTask的生成) ============= - - /** 创建定时器 => 立即执行,无限循环,通知默认what */ - public static AbsTimer creTimer(Handler handler, long period) { - return creTimer(handler, AbsTimer.TIMER_NOTIFY_WHAT, 0l, period, -1); - } - - /** 创建定时器 => 无限循环,通知默认what */ - public static AbsTimer creTimer(Handler handler, long delay, long period) { - return creTimer(handler, AbsTimer.TIMER_NOTIFY_WHAT, delay, period, -1); - } - - /** 创建定时器 => 立即执行,通知默认what */ - public static AbsTimer creTimer(Handler handler, long period, int triggerLimit) { - return creTimer(handler, AbsTimer.TIMER_NOTIFY_WHAT, 0l, period, triggerLimit); - } - - /** 创建定时器 => 立即执行,无限循环 */ - public static AbsTimer creTimer(Handler handler, int what, long period) { - return creTimer(handler, what, 0l, period, -1); - } - - /** 创建定时器 => 无限循环 */ - public static AbsTimer creTimer(Handler handler, int what, long delay, long period) { - return creTimer(handler, what, delay, period, -1); - } - - /** 创建定时器 => 立即执行 */ - public static AbsTimer creTimer(Handler handler, int what, long period, int triggerLimit) { - return creTimer(handler, what, 0l, period, triggerLimit); - } - - /** - * 创建定时器 - * @param handler 通知的Handler - * @param what 通知的what - * @param delay 延迟时间 - 多少毫秒后开始执行 - * @param period 循环时间 - 每隔多少秒执行一次 - * @param triggerLimit 触发次数上限(-1,表示无限循环) - * @return - */ - public static AbsTimer creTimer(Handler handler, int what, long delay, long period, int triggerLimit) { - return new TimerTask(handler, what, delay, period, triggerLimit); - } - - // =================== 定时器抽象类,开放对定时器操作方法 ================= - - /** - * 定时器抽象类,主要对内部Timer参数进行控制,以及防止外部直接new TimerTask,照成不必要的失误 - */ - public static abstract class AbsTimer { - /** 默认通知的what */ - public static final int TIMER_NOTIFY_WHAT = 50000; - /** 状态标识 - 是否标记清除 */ - private boolean isMarkSweep = true; - // -- - /** int 标记 */ - private int markId = -1; - /** String 标记 */ - private String markStr = null; - - public final int getMarkId() { - return markId; - } - - public final String getMarkStr() { - return markStr; - } - - // -- - - public final AbsTimer setMarkId(int markId) { - this.markId = markId; - return this; - } - - public final AbsTimer setMarkStr(String markStr) { - this.markStr = markStr; - return this; - } - - // ======= 定时器任务,功能实现方法 ========= - - /** 运行定时器 */ - public void startTimer() { // 如果外部通过了creTimer或者直接new AbsTimer 初始化了对象,没有调用startTimer,都不会保存到 listAbsTimers 并不影响对定时器的控制 - // 标记状态 - 不需要回收 - this.isMarkSweep = false; - synchronized (listAbsTimers) { - // 不存在才进行添加 - if (!listAbsTimers.contains(this)) { - listAbsTimers.add(this); - } - } - } - - /** 关闭定时器 */ - public void closeTimer() { - // 标记状态 - 需要回收 - this.isMarkSweep = true; - } - - /** 判断是否运行中 */ - public abstract boolean isRunTimer(); - - /** 获取已经触发的次数 */ - public abstract int getTriggerNumber(); - - /** 获取允许触发的上限次数 */ - public abstract int getTriggerLimit(); - - /** 是否触发结束(到达最大次数) */ - public abstract boolean isTriggerEnd(); - - /** 是否无限循环 */ - public abstract boolean isInfinite(); - - /** - * 设置通知的Handler - * @param handler - */ - public abstract AbsTimer setHandler(Handler handler); - - /** - * 设置通知的What - * @param notifyWhat - */ - public abstract AbsTimer setNotifyWhat(int notifyWhat); - - /** - * 设置通知的Obj - * @param notifyObj - */ - public abstract AbsTimer setNotifyObject(Object notifyObj); - - /** - * 设置时间 - * @param delay 延迟时间 - 多少毫秒后开始执行 - * @param period 循环时间 - 每隔多少秒执行一次 - */ - public abstract AbsTimer setTime(long delay, long period); - - /** - * 设置触发次数上限 - * @param triggerLimit - */ - public abstract AbsTimer setTriggerLimit(int triggerLimit); - } - - // =========== 定时器 具体实现类(不对外开放) =============== - - /** - * 定时器内部封装类 - 定时器任务类 - * 便于快捷使用,并且防止外部new,从而达到对整个项目定时器的控制 - */ - private static final class TimerTask extends AbsTimer { - /** 定时器 */ - private Timer timer; - /** 定时器任务栈 */ - private java.util.TimerTask timerTask; - /** 通知Handler */ - private Handler handler; - /** 通知的数据 */ - private Object notifyObj = null; - /** 通知类型 */ - private int notifyWhat = AbsTimer.TIMER_NOTIFY_WHAT; - /** 延迟时间 - 多少毫秒后开始执行 */ - private long delay; - /** 循环时间 - 每隔多少秒执行一次 */ - private long period; - /** 触发次数上限 */ - private int triggerLimit = 1; - /** 触发次数 */ - private int triggerNumber = 0; - /** 定时器是否运行中 */ - private boolean isRunTimer = false; - - public TimerTask (Handler handler, int what, long delay, long period, int triggerLimit) { - this.handler = handler; - this.notifyWhat = what; - this.delay = delay; - this.period = period; - this.triggerLimit = triggerLimit; - } - - /** 开始定时器任务 */ - private void start() { - // 先关闭旧的定时器 - close(); - // 表示运行定时器中 - isRunTimer = true; - // 每次重置触发次数 - triggerNumber = 0; - // 开启定时器 - timer = new Timer(); // 每次重新new 防止被取消 - // 重新生成定时器 防止出现TimerTask is scheduled already 所以同一个定时器任务只能被放置一次 - timerTask = new java.util.TimerTask() { - @Override - public void run() { - // 表示运行定时器中 - isRunTimer = true; - // 累积触发次数 - triggerNumber++; - // 进行通知 - if(handler != null) { - // 从Message池中返回一个新的Message实例 - 通知what, arg1 = 触发次数, arg2 = 触发上限, obj = notifyObj - Message msg = handler.obtainMessage(notifyWhat, triggerNumber, triggerLimit, notifyObj); - handler.sendMessage(msg); - } - // 如果大于触发次数,则关闭 - if(triggerLimit >= 0 && triggerNumber >= triggerLimit) { - // 关闭任务,进行标记需要回收 - closeTimer(); - } - } - }; - try { - // xx毫秒后执行,每隔xx毫秒再执行一次 - timer.schedule(timerTask, delay, period); - } catch (Exception e) { - // 表示非运行定时器中 - isRunTimer = false; - // 关闭任务,进行标记需要回收 - closeTimer(); // 启动失败,则进行标记,标记需要回收 - } - } - - /** 关闭定时器任务 */ - private void close() { - // 表示非运行定时器中 - isRunTimer = false; - // 取消定时器任务 - try { - if (timer != null) { - timer.cancel(); - timer = null; - } - // -- - if (timerTask != null) { - timerTask.cancel(); - timerTask = null; - } - } catch (Exception e) { - } - } - - // ================ 实现抽象类方法 ================ - - @Override - public void startTimer() { - super.startTimer(); // 必须保留这句话 - // 开始定时器任务 - start(); - } - - @Override - public void closeTimer() { - super.closeTimer(); // 必须保留这句话 - // 关闭定时器任务 - close(); - } - - @Override - public boolean isRunTimer() { - return isRunTimer; - } - - @Override - public int getTriggerNumber() { - return triggerNumber; - } - - @Override - public int getTriggerLimit() { - return triggerLimit; - } - - @Override - public boolean isTriggerEnd() { // 如果为无限触发,则会返回true ,因为触发次数大于 -1 - return (triggerNumber >= triggerLimit); - //return (triggerLimit >= 0 && triggerNumber >= triggerLimit); - } - - @Override - public boolean isInfinite() { - return (triggerLimit <= -1); - } - - @Override - public AbsTimer setHandler(Handler handler) { - this.handler = handler; - return this; - } - - @Override - public AbsTimer setNotifyWhat(int notifyWhat) { - this.notifyWhat = notifyWhat; - return this; - } - - @Override - public AbsTimer setNotifyObject(Object notifyObj) { - this.notifyObj = notifyObj; - return this; - } - - @Override - public AbsTimer setTime(long delay, long period) { - this.delay = delay; - this.period = period; - return this; - } - - @Override - public AbsTimer setTriggerLimit(int triggerLimit) { - this.triggerLimit = triggerLimit; - return this; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCache.java b/DevLibUtils/src/main/java/dev/utils/app/cache/DevCache.java deleted file mode 100644 index 4d958e1af6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCache.java +++ /dev/null @@ -1,692 +0,0 @@ -package dev.utils.app.cache; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 缓存工具类 - * Created by 杨福海(michael) www.yangfuhai.com - * https://github.com/yangfuhai/ASimpleCache - * Update to Ttt - */ -public final class DevCache { - - private DevCache() { - } - -// // ==== 缓存状态标记 ==== -// // 存在数据 -// public static final int EXIST = 1; -// // 不存在数据 -// public static final int NOT_EXIST = 2; -// // 存在数据 - 但是已过期 -// public static final int OVERDUE = 3; -// // 保存成功 -// public static final int SAVE_SUC = 4; -// // 保存失败 -// public static final int SAVE_FAIL = 5; - // ========= - - // 日志Tag - private static final String TAG = DevCache.class.getSimpleName(); - // 缓存文件名 - private static final String DF_FILE_NAME = DevCache.class.getSimpleName(); - // 过期小时(单位秒) = 1小时 - public static final int TIME_HOUR = 60 * 60; - // 一天24小时 - public static final int TIME_DAY = TIME_HOUR * 24; - // 缓存最大值(50mb) - private static final int MAX_SIZE = 1000 * 1000 * 50; - // 不限制存放数据的数量 - private static final int MAX_COUNT = Integer.MAX_VALUE; - // 不同地址配置缓存对象 - private static Map mInstanceMap = new HashMap(); - // 缓存管理类 - private DevCacheManager mCache; - // 缓存地址 - private static File ctxCacheDir = null; - - /** - * 内部处理防止 Context 为null奔溃问题 - * @return - */ - private static Context getContext(Context context){ - if (context != null){ - return context; - } else { - // 设置全局Context - return DevUtils.getContext(); - } - } - - /** - * 获取缓存地址 - * @param ctx - * @return - */ - public static File getCacheDir(Context ctx){ - if (ctxCacheDir == null){ - ctxCacheDir = getContext(ctx).getCacheDir(); - } - return ctxCacheDir; - } - - /** - * 默认缓存地址 - * @param ctx - * @return - */ - public static DevCache get(Context ctx) { - return get(ctx, DF_FILE_NAME); - } - - /** - * 获取缓存地址 - * @param ctx - * @param cacheName - * @return - */ - public static DevCache get(Context ctx, String cacheName) { - // 进行处理 - File file = new File(getCacheDir(ctx), cacheName); - // 获取默认地址 - return get(file, MAX_SIZE, MAX_COUNT); - } - - /** - * 设置自定义缓存地址 - * @param cacheDir - * @return - */ - public static DevCache get(File cacheDir) { - return get(cacheDir, MAX_SIZE, MAX_COUNT); - } - - /** - * 自定义缓存大小 - * @param ctx - * @param max_zise - * @param max_count - * @return - */ - public static DevCache get(Context ctx, long max_zise, int max_count) { - File file = new File(getCacheDir(ctx), DF_FILE_NAME); - // - - return get(file, max_zise, max_count); - } - - /** - * 自定义缓存地址、大小等 - * @param cacheDir - * @param max_zise - * @param max_count - * @return - */ - public static DevCache get(File cacheDir, long max_zise, int max_count) { - // 判断是否存在缓存信息 - DevCache manager = mInstanceMap.get(cacheDir.getAbsoluteFile() + myPid()); - if (manager == null) { - // 初始化新的缓存信息, 并且保存 - manager = new DevCache(cacheDir, max_zise, max_count); - mInstanceMap.put(cacheDir.getAbsolutePath() + myPid(), manager); - } - return manager; - } - - /** - * 获取进程pid - * @return - */ - private static String myPid() { - return "_" + android.os.Process.myPid(); - } - - /** - * 最终初始化方法 - * @param cacheDir - * @param max_size - * @param max_count - */ - private DevCache(File cacheDir, long max_size, int max_count) { - if (cacheDir == null){ - new Exception("cacheDir is null"); - } else if (!cacheDir.exists() && !cacheDir.mkdirs()) { - new Exception("can't make dirs in " + cacheDir.getAbsolutePath()); - } - mCache = new DevCacheManager(cacheDir, max_size, max_count); - } - - /** - * Provides a means to save a cached file before the data are available. - * Since writing about the file is complete, and its close method is called, - * its contents will be registered in the cache. Example of use: - * DevCache cache = new DevCache(this) try { OutputStream stream = - * cache.put("myFileName") stream.write("some bytes".getBytes()); // now - * update cache! stream.close(); } catch(FileNotFoundException e){ - * e.printStackTrace() } - */ - class xFileOutputStream extends FileOutputStream { - File file; - - public xFileOutputStream(File file) throws FileNotFoundException { - super(file); - this.file = file; - } - - public void close() throws IOException { - super.close(); - mCache.put(file); - } - } - - // ===================================== - // ========== String数据 读写 ========== - // ===================================== - - /** - * 保存 String 数据到缓存中 - * @param key 保存的key - * @param value 保存的String数据 - */ - public void put(String key, String value) { - File file = mCache.newFile(key); - if (file == null || value == null){ - return; - } - BufferedWriter out = null; - try { - out = new BufferedWriter(new FileWriter(file), 1024); - out.write(value); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "put"); - } finally { - if (out != null) { - try { - out.flush(); - out.close(); - } catch (IOException e) { - } - } - mCache.put(file); - } - } - - /** - * 保存 String 数据到缓存中 - * @param key 保存的key - * @param value 保存的String数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, String value, int saveTime) { - if (key != null && value != null){ - put(key, DevCacheUtils.newStringWithDateInfo(saveTime, value)); - } - } - - /** - * 读取 String 数据 - * @param key - * @return String 数据 - */ - public String getAsString(String key) { - File file = mCache.get(key); - if (file == null){ - return null; - } - if (!file.exists()) - return null; - boolean removeFile = false; - BufferedReader in = null; - try { - in = new BufferedReader(new FileReader(file)); - String readString = ""; - String currentLine; - while ((currentLine = in.readLine()) != null) { - readString += currentLine; - } - if (!DevCacheUtils.isDue(readString)) { - return DevCacheUtils.clearDateInfo(readString); - } else { - LogPrintUtils.dTag(TAG, "getAsString key: " + key + " -> 文件已过期"); - removeFile = true; - return null; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAsString"); - return null; - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - } - } - if (removeFile) - remove(key); - } - } - - // ===================================== - // ======= JSONObject 数据 读写 ======== - // ===================================== - - /** - * 保存 JSONObject 数据到缓存中 - * @param key 保存的key - * @param value 保存的JSON数据 - */ - public void put(String key, JSONObject value) { - if (value != null){ - try { - put(key, value.toString()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "put JSONObject"); - } - } - } - - /** - * 保存 JSONObject 数据到缓存中 - * @param key 保存的key - * @param value 保存的JSONObject数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, JSONObject value, int saveTime) { - if (value != null){ - try { - put(key, value.toString(), saveTime); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "put JSONObject"); - } - } - } - - /** - * 读取 JSONObject 数据 - * @param key - * @return JSONObject 数据 - */ - public JSONObject getAsJSONObject(String key) { - String JSONString = getAsString(key); - if (JSONString != null){ - try { - JSONObject obj = new JSONObject(JSONString); - return obj; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAsJSONObject"); - } - } - return null; - } - - // ===================================== - // ======== JSONArray 数据 读写 ======== - // ===================================== - - /** - * 保存 JSONArray 数据到缓存中 - * @param key 保存的key - * @param value 保存的JSONArray数据 - */ - public void put(String key, JSONArray value) { - if (value != null){ - try { - put(key, value.toString()); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "put JSONArray"); - } - } - } - - /** - * 保存 JSONArray 数据到缓存中 - * @param key 保存的key - * @param value 保存的JSONArray数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, JSONArray value, int saveTime) { - if (value != null){ - try { - put(key, value.toString(), saveTime); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "put JSONArray"); - } - } - } - - /** - * 读取 JSONArray 数据 - * @param key - * @return JSONArray 数据 - */ - public JSONArray getAsJSONArray(String key) { - String JSONString = getAsString(key); - if (JSONString != null) { - try { - JSONArray obj = new JSONArray(JSONString); - return obj; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAsJSONArray"); - } - } - return null; - } - - // ===================================== - // ========== byte 数据 读写 =========== - // ===================================== - - /** - * 保存 byte 数据到缓存中 - * @param key 保存的key - * @param value 保存的数据 - */ - public void put(String key, byte[] value) { - if (key == null || value == null){ - return; - } - File file = mCache.newFile(key); - FileOutputStream out = null; - try { - out = new FileOutputStream(file); - out.write(value); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "put byte[]"); - } finally { - if (out != null) { - try { - out.flush(); - out.close(); - } catch (IOException e) { - } - } - mCache.put(file); - } - } - - /** - * Cache for a stream - * @param key the file name. - * @return OutputStream stream for writing data. - * @throws FileNotFoundException if the file can not be created. - */ - public OutputStream put(String key) throws FileNotFoundException { - File file = mCache.newFile(key); - if (file != null){ - return new xFileOutputStream(file); - } - return null; - } - - /** - * @param key the file name. - * @return (InputStream or null) stream previously saved in cache. - * @throws FileNotFoundException if the file can not be opened - */ - public InputStream get(String key) throws FileNotFoundException { - File file = mCache.get(key); - if (file != null && file.exists()){ - return new FileInputStream(file); - } - return null; - } - - /** - * 保存 byte 数据到缓存中 - * @param key 保存的key - * @param value 保存的数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, byte[] value, int saveTime) { - put(key, DevCacheUtils.newByteArrayWithDateInfo(saveTime, value)); - } - - /** - * 获取 byte 数据 - * @param key - * @return byte 数据 - */ - public byte[] getAsBinary(String key) { - RandomAccessFile RAFile = null; - boolean removeFile = false; - try { - File file = mCache.get(key); - if (!file.exists()) - return null; - RAFile = new RandomAccessFile(file, "r"); - byte[] byteArray = new byte[(int) RAFile.length()]; - RAFile.read(byteArray); - if (!DevCacheUtils.isDue(byteArray)) { - return DevCacheUtils.clearDateInfo(byteArray); - } else { - LogPrintUtils.dTag(TAG, "getAsBinary - key: " + key + " -> 文件已过期"); - removeFile = true; - return null; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAsBinary"); - return null; - } finally { - if (RAFile != null) { - try { - RAFile.close(); - } catch (IOException e) { - } - } - if (removeFile) - remove(key); - } - } - - // ===================================== - // ========== 序列化 数据 读写 ========= - // ===================================== - - /** - * 保存 Serializable 数据到缓存中 - * @param key 保存的key - * @param value 保存的value - */ - public void put(String key, Serializable value) { - put(key, value, -1); - } - - /** - * 保存 Serializable 数据到缓存中 - * @param key 保存的key - * @param value 保存的value - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, Serializable value, int saveTime) { - ByteArrayOutputStream baos = null; - ObjectOutputStream oos = null; - try { - baos = new ByteArrayOutputStream(); - oos = new ObjectOutputStream(baos); - oos.writeObject(value); - byte[] data = baos.toByteArray(); - if (saveTime != -1) { - put(key, data, saveTime); - } else { - put(key, data); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "put"); - } finally { - if (baos != null) { - try { - baos.close(); - } catch (IOException e) { - } - } - if (oos != null) { - try { - oos.close(); - } catch (IOException e) { - } - } - } - } - - /** - * 读取 Serializable 数据 - * @param key - * @return Serializable 数据 - */ - public Object getAsObject(String key) { - byte[] data = getAsBinary(key); - if (data != null) { - ByteArrayInputStream bais = null; - ObjectInputStream ois = null; - try { - bais = new ByteArrayInputStream(data); - ois = new ObjectInputStream(bais); - Object reObject = ois.readObject(); - return reObject; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAsObject"); - return null; - } finally { - if (bais != null) { - try { - bais.close(); - } catch (IOException e) { - } - } - if (ois != null) { - try { - ois.close(); - } catch (IOException e) { - } - } - } - } - return null; - } - - // ===================================== - // ========== bitmap 数据 读写 ========= - // ===================================== - - /** - * 保存 bitmap 到缓存中 - * @param key 保存的key - * @param value 保存的bitmap数据 - */ - public void put(String key, Bitmap value) { - put(key, DevCacheUtils.bitmap2Bytes(value)); - } - - /** - * 保存 bitmap 到缓存中 - * @param key 保存的key - * @param value 保存的 bitmap 数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, Bitmap value, int saveTime) { - put(key, DevCacheUtils.bitmap2Bytes(value), saveTime); - } - - /** - * 读取 bitmap 数据 - * @param key - * @return bitmap 数据 - */ - public Bitmap getAsBitmap(String key) { - byte[] bytes = getAsBinary(key); - if (bytes == null) { - return null; - } - return DevCacheUtils.bytes2Bimap(bytes); - } - - // ===================================== - // ======== drawable 数据 读写 ========= - // ===================================== - - /** - * 保存 drawable 到缓存中 - * @param key 保存的key - * @param value 保存的drawable数据 - */ - public void put(String key, Drawable value) { - put(key, DevCacheUtils.drawable2Bitmap(value)); - } - - /** - * 保存 drawable 到缓存中 - * @param key 保存的key - * @param value 保存的 drawable 数据 - * @param saveTime 保存的时间,单位:秒 - */ - public void put(String key, Drawable value, int saveTime) { - put(key, DevCacheUtils.drawable2Bitmap(value), saveTime); - } - - /** - * 读取 Drawable 数据 - * @param key - * @return Drawable 数据 - */ - public Drawable getAsDrawable(String key) { - byte[] bytes = getAsBinary(key); - if (bytes == null) { - return null; - } - return DevCacheUtils.bitmap2Drawable(DevCacheUtils.bytes2Bimap(bytes)); - } - - /** - * 获取缓存文件 - * @param key - * @return value 缓存的文件 - */ - public File file(String key) { - File f = mCache.newFile(key); - if (f != null && f.exists()){ - return f; - } - return null; - } - - /** - * 移除某个key - * @param key - * @return 是否移除成功 - */ - public boolean remove(String key) { - return mCache.remove(key); - } - - /** 清除所有数据 */ - public void clear() { - mCache.clear(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheManager.java b/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheManager.java deleted file mode 100644 index 0fbd5e5c05..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheManager.java +++ /dev/null @@ -1,197 +0,0 @@ -package dev.utils.app.cache; - -import java.io.File; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -/** - * detail: 缓存管理类 - * Created by 杨福海(michael) www.yangfuhai.com - * https://github.com/yangfuhai/ASimpleCache - * Update to Ttt - */ -final class DevCacheManager { - - // 总缓存大小 - private final AtomicLong cacheSize; - // 总缓存的文件总数 - private final AtomicInteger cacheCount; - // 大小限制 - private final long sizeLimit; - // 文件总数限制 - private final int countLimit; - // 保存文件时间信息 - 文件地址, 文件最后使用时间 - private final Map lastUsageDates = Collections.synchronizedMap(new HashMap()); - // 文件目录 - protected File cacheDir; - - protected DevCacheManager(File cacheDir, long sizeLimit, int countLimit) { - this.cacheDir = cacheDir; - this.sizeLimit = sizeLimit; - this.countLimit = countLimit; - cacheSize = new AtomicLong(); - cacheCount = new AtomicInteger(); - // 计算文件信息等 - calculateCacheSizeAndCacheCount(); - } - - /** 计算 cacheSize 和 cacheCount */ - private void calculateCacheSizeAndCacheCount() { - new Thread(new Runnable() { - @Override - public void run() { - int size = 0, count = 0; - if (cacheDir != null) { - File[] cachedFiles = cacheDir.listFiles(); - if (cachedFiles != null) { - for (File cachedFile : cachedFiles) { - size += calculateSize(cachedFile); - count += 1; - lastUsageDates.put(cachedFile, cachedFile.lastModified()); - } - cacheSize.set(size); - cacheCount.set(count); - } - } - } - }).start(); - } - - /** - * 添加文件 - * @param file - */ - protected void put(File file) { - // 获取文件总数 - int curCacheCount = cacheCount.get(); - // 判断是否超过数量限制 - while (curCacheCount + 1 > countLimit) { - // 删除文件, 并获取删除的文件大小 - long freedSize = removeNext(); - // 删除文件大小 - cacheSize.addAndGet(-freedSize); - // 递减文件 - curCacheCount = cacheCount.addAndGet(-1); - } - // 累加文件数量 - cacheCount.addAndGet(1); - // 计算文件总大小 - long valueSize = calculateSize(file); - // 判断当前缓存大小 - long curCacheSize = cacheSize.get(); - // 判断是否超过大小限制 - while (curCacheSize + valueSize > sizeLimit) { - long freedSize = removeNext(); - curCacheSize = cacheSize.addAndGet(-freedSize); - } - cacheSize.addAndGet(valueSize); - - Long currentTime = System.currentTimeMillis(); - file.setLastModified(currentTime); - lastUsageDates.put(file, currentTime); - } - - /** - * 获取文件 - * @param key - * @return - */ - protected File get(String key) { - File file = newFile(key); - if (file != null){ - Long currentTime = System.currentTimeMillis(); - file.setLastModified(currentTime); - lastUsageDates.put(file, currentTime); - return file; - } - return file; - } - - /** - * 创建文件对象 - * @param key - * @return - */ - protected File newFile(String key) { - if (key != null){ - return new File(cacheDir, key.hashCode() + ""); - } - return null; - } - - /** - * 删除文件 - * @param key - * @return - */ - protected boolean remove(String key) { - File file = get(key); - if (file != null){ - return file.delete(); - } - return false; - } - - /** - * 清空全部缓存数据 - */ - protected void clear() { - lastUsageDates.clear(); - cacheSize.set(0); - File[] files = cacheDir.listFiles(); - if (files != null) { - for (File f : files) { - f.delete(); - } - } - } - - /** - * 移除旧的文件 - * @return - */ - private long removeNext() { - if (lastUsageDates.isEmpty()) { - return 0; - } - Long oldestUsage = null; - File mostLongUsedFile = null; - Set> entries = lastUsageDates.entrySet(); - synchronized (lastUsageDates) { - for (Map.Entry entry : entries) { - if (mostLongUsedFile == null) { - mostLongUsedFile = entry.getKey(); - oldestUsage = entry.getValue(); - } else { - Long lastValueUsage = entry.getValue(); - if (lastValueUsage < oldestUsage) { - oldestUsage = lastValueUsage; - mostLongUsedFile = entry.getKey(); - } - } - } - } - - long fileSize = calculateSize(mostLongUsedFile); - if (mostLongUsedFile.delete()) { - lastUsageDates.remove(mostLongUsedFile); - } - return fileSize; - } - - /** - * 计算文件大小 - * @param file - * @return - */ - private long calculateSize(File file) { - if (file != null){ - return file.length(); - } - return 0l; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheUtils.java b/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheUtils.java deleted file mode 100644 index 230204ac58..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/cache/DevCacheUtils.java +++ /dev/null @@ -1,274 +0,0 @@ -package dev.utils.app.cache; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.PixelFormat; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; - -import java.io.ByteArrayOutputStream; - -import dev.utils.LogPrintUtils; - -/** - * detail: 缓存检查(时间)工具类 - * Created by 杨福海(michael) www.yangfuhai.com - * https://github.com/yangfuhai/ASimpleCache - * Update to Ttt - */ -final class DevCacheUtils { - - private DevCacheUtils(){ - } - - // 日志Tag - private static final String TAG = DevCacheUtils.class.getSimpleName(); - - /** - * 判断缓存的 String 数据是否到期 - * @param str - * @return true: 到期了 false: 还没有到期 - */ - public static boolean isDue(String str) { - return isDue(str.getBytes()); - } - - /** - * 判断缓存的 byte 数据是否到期 - * @param data - * @return true: 到期了 false: 还没有到期 - */ - public static boolean isDue(byte[] data) { - // 获取时间数据信息 - String[] strs = getDateInfoFromDate(data); - // 判断是否过期 - if (strs != null && strs.length == 2) { - // 保存的时间 - String saveTimeStr = strs[0]; - // 判断是否0开头,是的话裁剪 - while (saveTimeStr.startsWith("0")) { - saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length()); - } - // 转换时间 - long saveTime = Long.valueOf(saveTimeStr); // 保存时间 - long deleteAfter = Long.valueOf(strs[1]); // 过期时间 - // 判断当前时间是否大于 保存时间 + 过期时间 - if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) { - return true; - } - } - return false; - } - - // - - - /** - * 保存数据, 创建时间信息 - * @param second - * @param strInfo - * @return - */ - public static String newStringWithDateInfo(int second, String strInfo) { - return createDateInfo(second) + strInfo; - } - - /** - * 保存数据, 创建时间信息 - * @param second - * @param data - * @return - */ - public static byte[] newByteArrayWithDateInfo(int second, byte[] data) { - if (data != null){ - try { - byte[] dataArys = createDateInfo(second).getBytes(); - byte[] retData = new byte[dataArys.length + data.length]; - System.arraycopy(dataArys, 0, retData, 0, dataArys.length); - System.arraycopy(data, 0, retData, dataArys.length, data.length); - return retData; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "newByteArrayWithDateInfo"); - } - } - return null; - } - - private static final char mSeparator = ' '; - - /** - * 创建时间信息 - * @param second - * @return - */ - private static String createDateInfo(int second) { - String currentTime = System.currentTimeMillis() + ""; - while (currentTime.length() < 13) { - currentTime = "0" + currentTime; - } - return currentTime + "-" + second + mSeparator; - } - - /** - * 清空时间信息 - * @param strInfo - * @return - */ - public static String clearDateInfo(String strInfo) { - if (strInfo != null && hasDateInfo(strInfo.getBytes())) { - strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1, strInfo.length()); - } - return strInfo; - } - - /** - * 清空时间信息 - * @param data - * @return - */ - public static byte[] clearDateInfo(byte[] data) { - if (hasDateInfo(data)) { - try { - return copyOfRange(data, indexOf(data, mSeparator) + 1, data.length); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "clearDateInfo"); - } - } - return data; - } - - /** - * 检验时间信息 - * @param data - * @return - */ - private static boolean hasDateInfo(byte[] data) { - return data != null && data.length > 15 && data[13] == '-' && indexOf(data, mSeparator) > 14; - } - - /** - * 获取时间信息 - 保存时间、过期时间 - * @param data - * @return - */ - private static String[] getDateInfoFromDate(byte[] data) { - if (hasDateInfo(data)) { - try { - // 保存时间 - String saveDate = new String(copyOfRange(data, 0, 13)); - // 过期时间 - String deleteAfter = new String(copyOfRange(data, 14, indexOf(data, mSeparator))); - // 返回数据 - return new String[]{saveDate, deleteAfter}; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getDateInfoFromDate"); - } - } - return null; - } - - private static int indexOf(byte[] data, char c) { - if (data != null) { - for (int i = 0; i < data.length; i++) { - if (data[i] == c) { - return i; - } - } - } - return -1; - } - - private static byte[] copyOfRange(byte[] original, int from, int to) throws Exception { - int newLength = to - from; - if (newLength < 0) - throw new IllegalArgumentException(from + " > " + to); - - byte[] copy = new byte[newLength]; - System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); - return copy; - } - - /** - * Bitmap → byte[] - * @param bm - * @return - */ - public static byte[] bitmap2Bytes(Bitmap bm) { - if (bm == null) { - return null; - } - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - bm.compress(Bitmap.CompressFormat.PNG, 100, baos); - return baos.toByteArray(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "bitmap2Bytes"); - } - return null; - } - - /** - * byte[] → Bitmap - * @param bytes - * @return - */ - public static Bitmap bytes2Bimap(byte[] bytes) { - if (bytes != null && bytes.length != 0) { - try { - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "bytes2Bimap"); - } - } - return null; - } - - /** - * Drawable → Bitmap - * @param drawable - * @return - */ - public static Bitmap drawable2Bitmap(Drawable drawable) { - if (drawable == null) { - return null; - } - try { - // 取 drawable 的长宽 - int w = drawable.getIntrinsicWidth(); - int h = drawable.getIntrinsicHeight(); - // 取 drawable 的颜色格式 - Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; - // 建立对应 bitmap - Bitmap bitmap = Bitmap.createBitmap(w, h, config); - // 建立对应 bitmap 的画布 - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, w, h); - // 把 drawable 内容画到画布中 - drawable.draw(canvas); - return bitmap; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "drawable2Bitmap"); - } - return null; - } - - /** - * Bitmap → Drawable - * @param bm - * @return - */ - @SuppressWarnings("deprecation") - public static Drawable bitmap2Drawable(Bitmap bm) { - if (bm == null) { - return null; - } - try { - BitmapDrawable bd = new BitmapDrawable(bm); - bd.setTargetDensity(bm.getDensity()); - return new BitmapDrawable(bm); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "bitmap2Drawable"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapCropUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/BitmapCropUtils.java deleted file mode 100644 index 5482538c1f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapCropUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.utils.app.image; - -import android.graphics.Bitmap; -import android.text.TextUtils; - -import dev.utils.LogPrintUtils; - -/** - * detail: 图片裁剪工具类 - * Created by Ttt - */ -public final class BitmapCropUtils { - - private BitmapCropUtils(){ - } - - // 日志Tag - private static final String TAG = BitmapCropUtils.class.getSimpleName(); - - // =============== 裁剪图片,裁剪中间部分,防止全图压缩 =============== - /** - * 裁剪图片(默认比例16:9) - * @param fPath 保存路径 - * @param bitmap 图片 - */ - public static void cropBitmap(String fPath, Bitmap bitmap){ - cropBitmap(fPath, bitmap, 16.0f, 9.0f); - } - - /** - * 裁剪图片 - * @param fPath 保存路径 - * @param bitmap 图片 - * @param wScale 宽度比例 - * @param hScale 高度比例 - */ - public static void cropBitmap(String fPath, Bitmap bitmap, float wScale, float hScale){ - if(TextUtils.isEmpty(fPath)){ - return; // 防止保存路径为null - } else if (bitmap == null){ - return; // 防止图片为null - } - // 裁剪处理后的图片 - Bitmap cBitmap = null; - try { - // 获取图片宽度 - int iWidth = bitmap.getWidth(); - // 获取图片高度 - int iHeight = bitmap.getHeight(); - // 获取需要裁剪的高度 - int rHeight = (int) ((iWidth * hScale) / wScale); - // 判断需要裁剪的高度与偏移差距 - int dHeight = iHeight - rHeight; - // -- - // 判断裁剪方式 - if (dHeight >= 0){ // 属于宽度 * 对应比例 >= 高度 -> 以高度做偏移 - // 计算偏移的y轴 - int offsetY = dHeight / 2; - // 创建图片 - cBitmap = Bitmap.createBitmap(bitmap, 0, offsetY, iWidth, rHeight, null, false); - } else { // 以宽度做偏移 - // 获取需要裁剪的宽度 - int rWidth = (int) ((iHeight * wScale) / hScale); - // 判断需要裁剪的宽度与偏移差距 - int dWidth = iWidth - rWidth; - // 计算偏移的X轴 - int offsetX = dWidth / 2; - // 创建图片 - cBitmap = Bitmap.createBitmap(bitmap, offsetX, 0, rWidth, iHeight, null, false); - } - if (cBitmap != null){ - // 保存图片 - BitmapUtils.saveBitmapToSDCardPNG(cBitmap, fPath, 85); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "cropBitmap"); - } finally { - // -- 清空资源 -- - try { - if(cBitmap != null && !cBitmap.isRecycled()){ - cBitmap.recycle(); - } - } catch (Exception e) { - } - cBitmap = null; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapExtendUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/BitmapExtendUtils.java deleted file mode 100644 index a8891f11ce..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapExtendUtils.java +++ /dev/null @@ -1,1224 +0,0 @@ -package dev.utils.app.image; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.LinearGradient; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PaintFlagsDrawFilter; -import android.graphics.PorterDuff.Mode; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader.TileMode; -import android.media.ExifInterface; -import android.net.Uri; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Bitmap工具类主要包括获取Bitmap和对Bitmap的操作 - * Created by Ttt - */ -public final class BitmapExtendUtils { - - private BitmapExtendUtils() { - } - - // 日志Tag - private static final String TAG = BitmapExtendUtils.class.getSimpleName(); - - /** - * 图片压缩处理(使用Options的方法) - * ================ - * 说明 使用方法: - * 首先你要将Options的inJustDecodeBounds属性设置为true,BitmapFactory.decode一次图片 。 - * 然后将Options连同期望的宽度和高度一起传递到到本方法中。 - * 之后再使用本方法的返回值做参数调用BitmapFactory.decode创建图片。 - * ================ - * 说明 BitmapFactory创建bitmap会尝试为已经构建的bitmap分配内存 - * ,这时就会很容易导致OOM出现。为此每一种创建方法都提供了一个可选的Options参数 - * ,将这个参数的inJustDecodeBounds属性设置为true就可以让解析方法禁止为bitmap分配内存 - * ,返回值也不再是一个Bitmap对象, 而是null。虽然Bitmap是null了,但是Options的outWidth、 outHeight和outMimeType属性都会被赋值。 - * - * @param reqWidth 目标宽度,这里的宽高只是阀值,实际显示的图片将小于等于这个值 - * @param reqHeight 目标高度,这里的宽高只是阀值,实际显示的图片将小于等于这个值 - */ - public static BitmapFactory.Options calculateInSampleSize(final BitmapFactory.Options options, final int reqWidth, final int reqHeight) { - // 源图片的高度和宽度 - final int height = options.outHeight; - final int width = options.outWidth; - int inSampleSize = 1; - if (height > 400 || width > 450) { - if (height > reqHeight || width > reqWidth) { - // 计算出实际宽高和目标宽高的比率 - final int heightRatio = Math.round((float) height / (float) reqHeight); - final int widthRatio = Math.round((float) width / (float) reqWidth); - // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 - // 一定都会大于等于目标的宽和高。 - inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; - } - } - // 设置压缩比例 - options.inSampleSize = inSampleSize; - options.inJustDecodeBounds = false; - return options; - } - - /** - * 获取一个指定大小的bitmap - * @param res Resources - * @param resId 图片ID - * @param reqWidth 目标宽度 - * @param reqHeight 目标高度 - */ - public static Bitmap getBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { - // BitmapFactory.Options options = new BitmapFactory.Options(); - // options.inJustDecodeBounds = true; - // BitmapFactory.decodeResource(res, resId, options); - // options = BitmapHelper.calculateInSampleSize(options, reqWidth, reqHeight); - // return BitmapFactory.decodeResource(res, resId, options); - - // 通过JNI的形式读取本地图片达到节省内存的目的 - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Config.RGB_565; - options.inPurgeable = true; - options.inInputShareable = true; - InputStream is = res.openRawResource(resId); - return getBitmapFromStream(is, null, reqWidth, reqHeight); - } - - /** - * 获取一个指定大小的bitmap - * @param reqWidth 目标宽度 - * @param reqHeight 目标高度 - */ - public static Bitmap getBitmapFromFile(String pathName, int reqWidth, int reqHeight) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(pathName, options); - options = calculateInSampleSize(options, reqWidth, reqHeight); - return BitmapFactory.decodeFile(pathName, options); - } - - /** - * 获取一个指定大小的bitmap - * @param data Bitmap的byte数组 - * @param offset image从byte数组创建的起始位置 - * @param length the number of bytes, 从offset处开始的长度 - * @param reqWidth 目标宽度 - * @param reqHeight 目标高度 - */ - public static Bitmap getBitmapFromByteArray(byte[] data, int offset, int length, int reqWidth, int reqHeight) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeByteArray(data, offset, length, options); - options = calculateInSampleSize(options, reqWidth, reqHeight); - return BitmapFactory.decodeByteArray(data, offset, length, options); - } - - // == - - - /** - * Stream转换成Byte - * @param inputStream InputStream - * @return Byte数组 - */ - public static byte[] getBytesFromStream(InputStream inputStream) { - ByteArrayOutputStream os = new ByteArrayOutputStream(1024); - byte[] buffer = new byte[1024]; - int len; - try { - while ((len = inputStream.read(buffer)) >= 0) { - os.write(buffer, 0, len); - } - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "getBytesFromStream"); - } - return os.toByteArray(); - } - - - /** - * 获取一个指定大小的bitmap - * @param is 从输入流中读取Bitmap - * @param outPadding If not null, return the padding rect for the bitmap if it - * exists, otherwise set padding to [-1,-1,-1,-1]. If no bitmap - * is returned (null) then padding is unchanged. - * @param reqWidth 目标宽度 - * @param reqHeight 目标高度 - */ - public static Bitmap getBitmapFromStream(InputStream is, Rect outPadding, int reqWidth, int reqHeight) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, outPadding, options); - options = calculateInSampleSize(options, reqWidth, reqHeight); - return BitmapFactory.decodeStream(is, outPadding, options); - } - - // == - - /** - * 合并Bitmap - * @param bgd 背景Bitmap - * @param fg 前景Bitmap - * @return 合成后的Bitmap - */ - public static Bitmap combineImages(Bitmap bgd, Bitmap fg) { - Bitmap bmp; - - int width = bgd.getWidth() > fg.getWidth() ? bgd.getWidth() : fg.getWidth(); - int height = bgd.getHeight() > fg.getHeight() ? bgd.getHeight() : fg.getHeight(); - - bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888); - Paint paint = new Paint(); - paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); - - Canvas canvas = new Canvas(bmp); - canvas.drawBitmap(bgd, 0, 0, null); - canvas.drawBitmap(fg, 0, 0, paint); - - return bmp; - } - - /** - * 合并Bitmap - * @param bgd 后景Bitmap - * @param fg 前景Bitmap - * @return 合成后Bitmap - */ - public static Bitmap combineImagesToSameSize(Bitmap bgd, Bitmap fg) { - Bitmap bmp; - - int width = bgd.getWidth() < fg.getWidth() ? bgd.getWidth() : fg.getWidth(); - int height = bgd.getHeight() < fg.getHeight() ? bgd.getHeight() : fg.getHeight(); - - if (fg.getWidth() != width && fg.getHeight() != height) { - fg = zoom(fg, width, height); - } - if (bgd.getWidth() != width && bgd.getHeight() != height) { - bgd = zoom(bgd, width, height); - } - - bmp = Bitmap.createBitmap(width, height, Config.ARGB_8888); - Paint paint = new Paint(); - paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); - - Canvas canvas = new Canvas(bmp); - canvas.drawBitmap(bgd, 0, 0, null); - canvas.drawBitmap(fg, 0, 0, paint); - - return bmp; - } - - /** - * 放大缩小图片 - * @param bitmap 源Bitmap - * @param w 宽 - * @param h 高 - * @return 目标Bitmap - */ - public static Bitmap zoom(Bitmap bitmap, int w, int h) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Matrix matrix = new Matrix(); - float scaleWidht = ((float) w / width); - float scaleHeight = ((float) h / height); - matrix.postScale(scaleWidht, scaleHeight); - Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); - return newbmp; - } - - /** - * 获得圆角图片的方法 - * @param bitmap 源Bitmap - * @param roundPx 圆角大小 - * @return 期望Bitmap - */ - public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) { - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(output); - - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); - final RectF rectF = new RectF(rect); - - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - canvas.drawRoundRect(rectF, roundPx, roundPx, paint); - - paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); - - return output; - } - - /** - * 获得带倒影的图片方法 - * @param bitmap 源Bitmap - * @return 带倒影的Bitmap - */ - public static Bitmap createReflectionBitmap(Bitmap bitmap) { - final int reflectionGap = 4; - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - - Matrix matrix = new Matrix(); - matrix.preScale(1, -1); - - Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height / 2, width, height / 2, matrix, false); - - Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height / 2), Config.ARGB_8888); - - Canvas canvas = new Canvas(bitmapWithReflection); - canvas.drawBitmap(bitmap, 0, 0, null); - Paint deafalutPaint = new Paint(); - canvas.drawRect(0, height, width, height + reflectionGap, deafalutPaint); - - canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); - - Paint paint = new Paint(); - LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0, - bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff,0x00ffffff, TileMode.CLAMP); - paint.setShader(shader); - // Set the Transfer mode to be porter duff and destination in - paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); - // Draw a rectangle using the paint with our linear gradient - canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint); - - return bitmapWithReflection; - } - - /** - * 压缩图片大小 - * @param image 源Bitmap - * @param size kb为单位, 大于xx大小, 则一直压缩 - * @return 压缩后的Bitmap - */ - public static Bitmap compressImage(Bitmap image, long size) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 - int options = 100; - while (baos.toByteArray().length / 1024 > size) { // 循环判断如果压缩后图片是否大于100kb,大于继续压缩 - baos.reset();// 重置baos即清空baos - image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 这里压缩options%,把压缩后的数据存放到baos中 - options -= 10;// 每次都减少10 - } - ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把压缩后的数据baos存放到ByteArrayInputStream中 - Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream数据生成图片 - return bitmap; - } - - /** - * 将彩色图转换为灰度图 - * @param img 源Bitmap - * @return 返回转换好的位图 - */ - public static Bitmap convertGreyImg(Bitmap img) { - int width = img.getWidth(); // 获取位图的宽 - int height = img.getHeight(); // 获取位图的高 - - int[] pixels = new int[width * height]; // 通过位图的大小创建像素点数组 - - img.getPixels(pixels, 0, width, 0, 0, width, height); - int alpha = 0xFF << 24; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - int grey = pixels[width * i + j]; - - int red = ((grey & 0x00FF0000) >> 16); - int green = ((grey & 0x0000FF00) >> 8); - int blue = (grey & 0x000000FF); - - grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11); - grey = alpha | (grey << 16) | (grey << 8) | grey; - pixels[width * i + j] = grey; - } - } - Bitmap result = Bitmap.createBitmap(width, height, Config.RGB_565); - result.setPixels(pixels, 0, width, 0, 0, width, height); - return result; - } - - /** - * 转换图片成圆形 - * @param bitmap 传入Bitmap对象 - * @return 圆形Bitmap - */ - public static Bitmap getRoundBitmap(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - float roundPx; - float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom; - if (width <= height) { - roundPx = width / 2; - top = 0; - bottom = width; - left = 0; - right = width; - height = width; - dst_left = 0; - dst_top = 0; - dst_right = width; - dst_bottom = width; - } else { - roundPx = height / 2; - float clip = (width - height) / 2; - left = clip; - right = width - clip; - top = 0; - bottom = height; - width = height; - dst_left = 0; - dst_top = 0; - dst_right = height; - dst_bottom = height; - } - - Bitmap output = Bitmap.createBitmap(width, height, Config.ARGB_8888); - Canvas canvas = new Canvas(output); - - final int color = 0xff424242; - final Paint paint = new Paint(); - final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom); - final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom); - final RectF rectF = new RectF(dst); - - paint.setAntiAlias(true); - - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - canvas.drawRoundRect(rectF, roundPx, roundPx, paint); - - paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); - canvas.drawBitmap(bitmap, src, dst, paint); - return output; - } - - /** - * Returns a Bitmap representing the thumbnail of the specified Bitmap. The - * size of the thumbnail is defined by the dimension - * android.R.dimen.launcher_application_icon_size. - * This method is not thread-safe and should be invoked on the UI thread only. - * @param bitmap The bitmap to get a thumbnail of. - * @return A thumbnail for the specified bitmap or the bitmap itself if the thumbnail could not be created. - */ - public static Bitmap createThumbnailBitmap(Bitmap bitmap) { - int sIconWidth = -1; - int sIconHeight = -1; - sIconWidth = sIconHeight = (int) DevUtils.getContext().getResources().getDimension(android.R.dimen.app_icon_size); - - final Paint sPaint = new Paint(); - final Rect sBounds = new Rect(); - final Rect sOldBounds = new Rect(); - Canvas sCanvas = new Canvas(); - - int width = sIconWidth; - int height = sIconHeight; - - sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG)); - - final int bitmapWidth = bitmap.getWidth(); - final int bitmapHeight = bitmap.getHeight(); - - if (width > 0 && height > 0) { - if (width < bitmapWidth || height < bitmapHeight) { - final float ratio = (float) bitmapWidth / bitmapHeight; - - if (bitmapWidth > bitmapHeight) { - height = (int) (width / ratio); - } else if (bitmapHeight > bitmapWidth) { - width = (int) (height * ratio); - } - - final Config c = (width == sIconWidth && height == sIconHeight) ? bitmap.getConfig() : Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); - final Canvas canvas = sCanvas; - final Paint paint = sPaint; - canvas.setBitmap(thumb); - paint.setDither(false); - paint.setFilterBitmap(true); - sBounds.set((sIconWidth - width) / 2,(sIconHeight - height) / 2, width, height); - sOldBounds.set(0, 0, bitmapWidth, bitmapHeight); - canvas.drawBitmap(bitmap, sOldBounds, sBounds, paint); - return thumb; - } else if (bitmapWidth < width || bitmapHeight < height) { - final Config c = Config.ARGB_8888; - final Bitmap thumb = Bitmap.createBitmap(sIconWidth, sIconHeight, c); - final Canvas canvas = sCanvas; - final Paint paint = sPaint; - canvas.setBitmap(thumb); - paint.setDither(false); - paint.setFilterBitmap(true); - canvas.drawBitmap(bitmap, (sIconWidth - bitmapWidth) / 2,(sIconHeight - bitmapHeight) / 2, paint); - return thumb; - } - } - return bitmap; - } - - /** - * 生成水印图片 水印在右下角 - * @param src the bitmap object you want proecss - * @param watermark the water mark above the src - * @return return a bitmap object ,if paramter's length is 0,return null - */ - public static Bitmap createWatermarkBitmap(Bitmap src, Bitmap watermark) { - if (src == null) { - return null; - } - int w = src.getWidth(); - int h = src.getHeight(); - int ww = watermark.getWidth(); - int wh = watermark.getHeight(); - // create the new blank bitmap - Bitmap newb = Bitmap.createBitmap(w, h, Config.ARGB_8888);// 创建一个新的和SRC长度宽度一样的位图 - Canvas cv = new Canvas(newb); - // draw src into - cv.drawBitmap(src, 0, 0, null);// 在 0,0坐标开始画入src - // draw watermark into - cv.drawBitmap(watermark, w - ww + 5, h - wh + 5, null);// 在src的右下角画入水印 - // save all clip - cv.save(Canvas.ALL_SAVE_FLAG);// 保存 - // store - cv.restore();// 存储 - return newb; - } - - /** - * 重新编码Bitmap - * @param src 需要重新编码的Bitmap - * @param format 编码后的格式(目前只支持png和jpeg这两种格式) - * @param quality 重新生成后的bitmap的质量 - * @return 返回重新生成后的bitmap - */ - public static Bitmap codec(Bitmap src, Bitmap.CompressFormat format, int quality) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - src.compress(format, quality, os); - byte[] array = os.toByteArray(); - return BitmapFactory.decodeByteArray(array, 0, array.length); - } - - /** - * 图片压缩方法:(使用compress的方法) - * 说明 如果bitmap本身的大小小于maxSize,则不作处理 - * @param bitmap 要压缩的图片 - * @param maxSize 压缩后的大小,单位kb - */ - public static void compress(Bitmap bitmap, double maxSize) { - // 将bitmap放至数组中,意在获得bitmap的大小(与实际读取的原文件要大) - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - // 格式、质量、输出流 - bitmap.compress(Bitmap.CompressFormat.PNG, 70, baos); - byte[] b = baos.toByteArray(); - // 将字节换成KB - double mid = b.length / 1024; - // 获取bitmap大小 是允许最大大小的多少倍 - double i = mid / maxSize; - // 判断bitmap占用空间是否大于允许最大空间 如果大于则压缩 小于则不压缩 - if (i > 1) { - // 缩放图片 此处用到平方根 将宽带和高度压缩掉对应的平方根倍 - // (保持宽高不变,缩放后也达到了最大占用空间的大小) - bitmap = scale(bitmap, bitmap.getWidth() / Math.sqrt(i),bitmap.getHeight() / Math.sqrt(i)); - } - } - - /** - * 图片的缩放方法 - * @param src 源图片资源 - * @param newWidth 缩放后宽度 - * @param newHeight 缩放后高度 - */ - public static Bitmap scale(Bitmap src, double newWidth, double newHeight) { - // 记录src的宽高 - float width = src.getWidth(); - float height = src.getHeight(); - // 创建一个matrix容器 - Matrix matrix = new Matrix(); - // 计算缩放比例 - float scaleWidth = ((float) newWidth) / width; - float scaleHeight = ((float) newHeight) / height; - // 开始缩放 - matrix.postScale(scaleWidth, scaleHeight); - // 创建缩放后的图片 - return Bitmap.createBitmap(src, 0, 0, (int) width, (int) height, matrix, true); - } - - /** - * 图片的缩放方法 - * @param src 源图片资源 - * @param scaleMatrix 缩放规则 - */ - public static Bitmap scale(Bitmap src, Matrix scaleMatrix) { - return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), scaleMatrix, true); - } - - /** - * 图片的缩放方法 - * @param src 源图片资源 - * @param scaleX 横向缩放比例 - * @param scaleY 纵向缩放比例 - */ - public static Bitmap scale(Bitmap src, float scaleX, float scaleY) { - Matrix matrix = new Matrix(); - matrix.postScale(scaleX, scaleY); - return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - } - - /** - * 图片的缩放方法 - * @param src 源图片资源 - * @param scale 缩放比例 - */ - public static Bitmap scale(Bitmap src, float scale) { - return scale(src, scale, scale); - } - - /** - * 旋转图片 - * @param angle 旋转角度 - * @param bitmap 要旋转的图片 - * @return 旋转后的图片 - */ - public static Bitmap rotate(Bitmap bitmap, int angle) { - Matrix matrix = new Matrix(); - matrix.postRotate(angle); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - - /** - * 水平翻转处理 - * @param bitmap 原图 - * @return 水平翻转后的图片 - */ - public static Bitmap reverseByHorizontal(Bitmap bitmap) { - Matrix matrix = new Matrix(); - matrix.preScale(-1, 1); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 垂直翻转处理 - * @param bitmap 原图 - * @return 垂直翻转后的图片 - */ - public static Bitmap reverseByVertical(Bitmap bitmap) { - Matrix matrix = new Matrix(); - matrix.preScale(1, -1); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 更改图片色系,变亮或变暗 - * @param delta 图片的亮暗程度值,越小图片会越亮,取值范围(0,24) - * @return - */ - public static Bitmap adjustTone(Bitmap src, int delta) { - if (delta >= 24 || delta <= 0) { - return null; - } - // 设置高斯矩阵 - int[] gauss = new int[]{1, 2, 1, 2, 4, 2, 1, 2, 1}; - int width = src.getWidth(); - int height = src.getHeight(); - Bitmap bitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - int pixColor = 0; - int newR = 0; - int newG = 0; - int newB = 0; - int idx = 0; - int[] pixels = new int[width * height]; - - src.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - idx = 0; - for (int m = -1; m <= 1; m++) { - for (int n = -1; n <= 1; n++) { - pixColor = pixels[(i + m) * width + k + n]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR += (pixR * gauss[idx]); - newG += (pixG * gauss[idx]); - newB += (pixB * gauss[idx]); - idx++; - } - } - newR /= delta; - newG /= delta; - newB /= delta; - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - pixels[i * width + k] = Color.argb(255, newR, newG, newB); - newR = 0; - newG = 0; - newB = 0; - } - } - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return bitmap; - } - - /** - * 将彩色图转换为黑白图 - * @param bmp 位图 - * @return 返回转换好的位图 - */ - public static Bitmap convertToBlackWhite(Bitmap bmp) { - int width = bmp.getWidth(); - int height = bmp.getHeight(); - int[] pixels = new int[width * height]; - bmp.getPixels(pixels, 0, width, 0, 0, width, height); - - int alpha = 0xFF << 24; // 默认将bitmap当成24色图片 - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - int grey = pixels[width * i + j]; - - int red = ((grey & 0x00FF0000) >> 16); - int green = ((grey & 0x0000FF00) >> 8); - int blue = (grey & 0x000000FF); - - grey = (int) (red * 0.3 + green * 0.59 + blue * 0.11); - grey = alpha | (grey << 16) | (grey << 8) | grey; - pixels[width * i + j] = grey; - } - } - Bitmap newBmp = Bitmap.createBitmap(width, height, Config.RGB_565); - newBmp.setPixels(pixels, 0, width, 0, 0, width, height); - return newBmp; - } - - /** - * 读取图片属性 图片被旋转的角度 - * @param path 图片绝对路径 - * @return 旋转的角度 - */ - public static int getImageDegree(String path) { - int degree = 0; - try { - ExifInterface exifInterface = new ExifInterface(path); - int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_90: - degree = 90; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - degree = 180; - break; - case ExifInterface.ORIENTATION_ROTATE_270: - degree = 270; - break; - } - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "getImageDegree"); - } - return degree; - } - - /** - * 饱和度处理 - * @param bitmap 原图 - * @param saturationValue 新的饱和度值 - * @return 改变了饱和度值之后的图片 - */ - public static Bitmap saturation(Bitmap bitmap, int saturationValue) { - // 计算出符合要求的饱和度值 - float newSaturationValue = saturationValue * 1.0F / 127; - // 创建一个颜色矩阵 - ColorMatrix saturationColorMatrix = new ColorMatrix(); - // 设置饱和度值 - saturationColorMatrix.setSaturation(newSaturationValue); - // 创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(saturationColorMatrix)); - // 创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - // 将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 亮度处理 - * @param bitmap 原图 - * @param lumValue 新的亮度值 - * @return 改变了亮度值之后的图片 - */ - public static Bitmap lum(Bitmap bitmap, int lumValue) { - // 计算出符合要求的亮度值 - float newlumValue = lumValue * 1.0F / 127; - // 创建一个颜色矩阵 - ColorMatrix lumColorMatrix = new ColorMatrix(); - // 设置亮度值 - lumColorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); - // 创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(lumColorMatrix)); - // 创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - // 将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 色相处理 - * @param bitmap 原图 - * @param hueValue 新的色相值 - * @return 改变了色相值之后的图片 - */ - public static Bitmap hue(Bitmap bitmap, int hueValue) { - // 计算出符合要求的色相值 - float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; - // 创建一个颜色矩阵 - ColorMatrix hueColorMatrix = new ColorMatrix(); - // 控制让红色区在色轮上旋转的角度 - hueColorMatrix.setRotate(0, newHueValue); - // 控制让绿红色区在色轮上旋转的角度 - hueColorMatrix.setRotate(1, newHueValue); - // 控制让蓝色区在色轮上旋转的角度 - hueColorMatrix.setRotate(2, newHueValue); - // 创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(hueColorMatrix)); - // 创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - // 将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 亮度、色相、饱和度处理 - * @param bitmap 原图 - * @param lumValue 亮度值 - * @param hueValue 色相值 - * @param saturationValue 饱和度值 - * @return 亮度、色相、饱和度处理后的图片 - */ - public static Bitmap lumAndHueAndSaturation(Bitmap bitmap, int lumValue, int hueValue, int saturationValue) { - // 计算出符合要求的饱和度值 - float newSaturationValue = saturationValue * 1.0F / 127; - // 计算出符合要求的亮度值 - float newlumValue = lumValue * 1.0F / 127; - // 计算出符合要求的色相值 - float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; - // 创建一个颜色矩阵并设置其饱和度 - ColorMatrix colorMatrix = new ColorMatrix(); - // 设置饱和度值 - colorMatrix.setSaturation(newSaturationValue); - // 设置亮度值 - colorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); - // 控制让红色区在色轮上旋转的角度 - colorMatrix.setRotate(0, newHueValue); - // 控制让绿红色区在色轮上旋转的角度 - colorMatrix.setRotate(1, newHueValue); - // 控制让蓝色区在色轮上旋转的角度 - colorMatrix.setRotate(2, newHueValue); - // 创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); - // 创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - // 将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 怀旧效果处理 - * @param bitmap 原图 - * @return 怀旧效果处理后的图片 - */ - public static Bitmap nostalgic(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - int pixColor = 0; - int pixR = 0; - int pixG = 0; - int pixB = 0; - int newR = 0; - int newG = 0; - int newB = 0; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 0; i < height; i++) { - for (int k = 0; k < width; k++) { - pixColor = pixels[width * i + k]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - newR = (int) (0.393 * pixR + 0.769 * pixG + 0.189 * pixB); - newG = (int) (0.349 * pixR + 0.686 * pixG + 0.168 * pixB); - newB = (int) (0.272 * pixR + 0.534 * pixG + 0.131 * pixB); - int newColor = Color.argb(255, newR > 255 ? 255 : newR, - newG > 255 ? 255 : newG, newB > 255 ? 255 : newB); - pixels[width * i + k] = newColor; - } - } - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 柔化效果处理 - * @param bitmap 原图 - * @return 柔化效果处理后的图片 - */ - public static Bitmap soften(Bitmap bitmap) { - // 高斯矩阵 - int[] gauss = new int[]{1, 2, 1, 2, 4, 2, 1, 2, 1}; - - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int delta = 16; // 值越小图片会越亮,越大则越暗 - - int idx = 0; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - idx = 0; - for (int m = -1; m <= 1; m++) { - for (int n = -1; n <= 1; n++) { - pixColor = pixels[(i + m) * width + k + n]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = newR + (int) (pixR * gauss[idx]); - newG = newG + (int) (pixG * gauss[idx]); - newB = newB + (int) (pixB * gauss[idx]); - idx++; - } - } - newR /= delta; - newG /= delta; - newB /= delta; - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[i * width + k] = Color.argb(255, newR, newG, newB); - - newR = 0; - newG = 0; - newB = 0; - } - } - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 光照效果处理 - * @param bitmap 原图 - * @param centerX 光源在X轴的位置 - * @param centerY 光源在Y轴的位置 - * @return 光照效果处理后的图片 - */ - public static Bitmap sunshine(Bitmap bitmap, int centerX, int centerY) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - int radius = Math.min(centerX, centerY); - - final float strength = 150F; // 光照强度 100~150 - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = pixR; - newG = pixG; - newB = pixB; - - // 计算当前点到光照中心的距离,平面座标系中求两点之间的距离 - int distance = (int) (Math.pow((centerY - i), 2) + Math.pow(centerX - k, 2)); - if (distance < radius * radius) { - // 按照距离大小计算增加的光照值 - int result = (int) (strength * (1.0 - Math.sqrt(distance) / radius)); - newR = pixR + result; - newG = pixG + result; - newB = pixB + result; - } - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[pos] = Color.argb(255, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 底片效果处理 - * @param bitmap 原图 - * @return 底片效果处理后的图片 - */ - public static Bitmap film(Bitmap bitmap) { - // RGBA的最大值 - final int MAX_VALUE = 255; - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = MAX_VALUE - pixR; - newG = MAX_VALUE - pixG; - newB = MAX_VALUE - pixB; - - newR = Math.min(MAX_VALUE, Math.max(0, newR)); - newG = Math.min(MAX_VALUE, Math.max(0, newG)); - newB = Math.min(MAX_VALUE, Math.max(0, newB)); - - pixels[pos] = Color.argb(MAX_VALUE, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 锐化效果处理 - * @param bitmap 原图 - * @return 锐化效果处理后的图片 - */ - public static Bitmap sharpen(Bitmap bitmap) { - // 拉普拉斯矩阵 - int[] laplacian = new int[]{-1, -1, -1, -1, 9, -1, -1, -1, -1}; - - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int idx = 0; - float alpha = 0.3F; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - idx = 0; - for (int m = -1; m <= 1; m++) { - for (int n = -1; n <= 1; n++) { - pixColor = pixels[(i + n) * width + k + m]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = newR + (int) (pixR * laplacian[idx] * alpha); - newG = newG + (int) (pixG * laplacian[idx] * alpha); - newB = newB + (int) (pixB * laplacian[idx] * alpha); - idx++; - } - } - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[i * width + k] = Color.argb(255, newR, newG, newB); - newR = 0; - newG = 0; - newB = 0; - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 浮雕效果处理 - * @param bitmap 原图 - * @return 浮雕效果处理后的图片 - */ - public static Bitmap emboss(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - pixColor = pixels[pos + 1]; - newR = Color.red(pixColor) - pixR + 127; - newG = Color.green(pixColor) - pixG + 127; - newB = Color.blue(pixColor) - pixB + 127; - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[pos] = Color.argb(255, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 将YUV格式的图片的源数据从横屏模式转为竖屏模式,注意:将源图片的宽高互换一下就是新图片的宽高 - * @param sourceData YUV格式的图片的源数据 - * @param width 源图片的宽 - * @param height 源图片的高 - * @return - */ - public static final byte[] yuvLandscapeToPortrait(byte[] sourceData, int width, int height) { - byte[] rotatedData = new byte[sourceData.length]; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) - rotatedData[x * height + height - y - 1] = sourceData[x + y * width]; - } - return rotatedData; - } - - /** - * 比较安全的 解码(decodeStream) 方法 - * rather than the one of {@link BitmapFactory} - * which will be easy to get OutOfMemory Exception - * while loading a big image file. - * @param uri 压缩相册返回的照片 - * @return - * @throws FileNotFoundException - */ - public static Bitmap safeDecodeStream(Uri uri, int targetWidth, int targetHeight) throws FileNotFoundException { - BitmapFactory.Options options = new BitmapFactory.Options(); - android.content.ContentResolver resolver = DevUtils.getContext().getContentResolver(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(new BufferedInputStream(resolver.openInputStream(uri), 16 * 1024), null, options); - // 获取图片的宽度、高度; - float imgWidth = options.outWidth; - float imgHeight = options.outHeight; - // 分别计算图片宽度、高度与目标宽度、高度的比例;取大于等于该比例的最小整数; - int widthRatio = (int) Math.ceil(imgWidth / (float) targetWidth); - int heightRatio = (int) Math.ceil(imgHeight / (float) targetHeight); - options.inSampleSize = 1; - if (widthRatio > 1 || widthRatio > 1) { - if (widthRatio > heightRatio) { - options.inSampleSize = widthRatio; - } else { - options.inSampleSize = heightRatio; - } - } - // 设置好缩放比例后,加载图片进内容; - options.inJustDecodeBounds = false; - return BitmapFactory.decodeStream(new BufferedInputStream(resolver.openInputStream(uri), 16 * 1024), null, options); - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/BitmapUtils.java deleted file mode 100644 index 9cd1069885..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/BitmapUtils.java +++ /dev/null @@ -1,540 +0,0 @@ -package dev.utils.app.image; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PixelFormat; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.widget.ImageView; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; - -import dev.utils.LogPrintUtils; -import dev.utils.app.AppUtils; -import dev.utils.app.ScreenUtils; - -/** - * detail: Bitmap工具类 - * Created by Ttt - */ -public final class BitmapUtils { - - private BitmapUtils() { - } - - // 日志Tag - private static final String TAG = BitmapUtils.class.getSimpleName(); - - /** - * 通过Resources获取Bitmap - * @param context - * @param resId - * @return - */ - public static Bitmap getBitmapFromResources(Context context, int resId) { - Resources res = context.getResources(); - return BitmapFactory.decodeResource(res, resId); - } - - /** - * 通过Resources获取Drawable - * @param context - * @param resId - * @return - */ - public static Drawable getDrawableFromResources(Context context, int resId) { - Resources res = context.getResources(); - return res.getDrawable(resId); - } - - /** - * 获取本地SDCard 图片 - * @param fPath 图片地址 - * @return - */ - public static Bitmap getSDCardBitmapStream(String fPath) { - try { - FileInputStream fis = new FileInputStream(new File(fPath));//文件输入流 - Bitmap bmp = BitmapFactory.decodeStream(fis); - return bmp; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSDCardBitmapStream"); - } - return null; - } - - /** - * 获取本地SDCard 图片 - * @param fPath 图片地址 - * @return - */ - public static Bitmap getSDCardBitmapFile(String fPath) { - try { - Bitmap bmp = BitmapFactory.decodeFile(fPath); - return bmp; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSDCardBitmapFile"); - } - return null; - } - - /** - * 获取Bitmap - * @param is - * @return - */ - public static Bitmap getBitmap(final InputStream is) { - if (is == null) return null; - return BitmapFactory.decodeStream(is); - } - - // ===== - - /** - * Bitmay 转换成byte数组 - * @param bitmap - * @return - */ - public static byte[] bitmapToByte(Bitmap bitmap) { - return bitmapToByte(bitmap, 100, Bitmap.CompressFormat.PNG); - } - - /** - * Bitmay 转换成byte数组 - * @param bitmap - * @param format - * @return - */ - public static byte[] bitmapToByte(Bitmap bitmap, Bitmap.CompressFormat format) { - return bitmapToByte(bitmap, 100, format); - } - - /** - * Bitmay 转换成byte数组 - * @param bitmap - * @param quality - * @param format - * @return - */ - public static byte[] bitmapToByte(Bitmap bitmap, int quality, Bitmap.CompressFormat format) { - if (bitmap == null) { - return null; - } - ByteArrayOutputStream o = new ByteArrayOutputStream(); - bitmap.compress(format, quality, o); - return o.toByteArray(); - } - - // = - - /** - * Drawable 转换成 byte数组 - * @param drawable - * @return - */ - public static byte[] drawableToByte(Drawable drawable) { - return bitmapToByte(drawableToBitmap(drawable)); - } - - /** - * Drawable 转换成 byte数组 - * @param drawable - * @param format - * @return - */ - public static byte[] drawableToByte(Drawable drawable, Bitmap.CompressFormat format) { - return bitmapToByte(drawableToBitmap(drawable), format); - } - - /** - * Drawable 转换成 byte数组 - * @param drawable - * @return - */ - public static byte[] drawableToByte2(Drawable drawable) { - return drawable == null ? null : bitmapToByte(drawable2Bitmap(drawable)); - } - - /** - * Drawable 转换成 byte数组 - * @param drawable - * @param format - * @return - */ - public static byte[] drawableToByte2(Drawable drawable, Bitmap.CompressFormat format) { - return drawable == null ? null : bitmapToByte(drawable2Bitmap(drawable), format); - } - - // == - - /** - * byte 数组转换为Bitmap - * @param bytes - * @return - */ - public static Bitmap byteToBitmap(byte[] bytes) { - return (bytes == null || bytes.length == 0) ? null : BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - } - - /** - * Drawable 转换成 Bitmap - * @param drawable - * @return - */ - public static Bitmap drawableToBitmap(Drawable drawable) { - return drawable == null ? null : ((BitmapDrawable) drawable).getBitmap(); - } - - /** - * Bitmap 转换成 Drawable - * @param bitmap - * @return - */ - public static Drawable bitmapToDrawable(Bitmap bitmap) { - return bitmap == null ? null : new BitmapDrawable(AppUtils.getResources(), bitmap); - } - - /** - * byte数组转换成Drawable - * @param bytes - * @return - */ - public static Drawable byteToDrawable(byte[] bytes) { - return bitmapToDrawable(byteToBitmap(bytes)); - } - - /** - * Drawable 转换 Bitmap - * @param drawable The drawable. - * @return bitmap - */ - public static Bitmap drawable2Bitmap(final Drawable drawable) { - if (drawable instanceof BitmapDrawable) { - BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; - if (bitmapDrawable.getBitmap() != null) { - return bitmapDrawable.getBitmap(); - } - } - Bitmap bitmap; - if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { - bitmap = Bitmap.createBitmap(1, 1, - drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); - } else { - bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), - drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); - } - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - drawable.draw(canvas); - return bitmap; - } - - // === - - /** - * 保存图片到SD卡 - JPEG - * @param bitmap 需要保存的数据 - * @param path 保存路径 - * @param quality 压缩比例 - */ - public static boolean saveBitmapToSDCardJPEG(Bitmap bitmap, String path, int quality) { - return saveBitmapToSDCard(bitmap, path, Bitmap.CompressFormat.JPEG, quality); - } - - /** - * 保存图片到SD卡 - PNG - * @param bitmap 需要保存的数据 - * @param path 保存路径 - */ - public static boolean saveBitmapToSDCardPNG(Bitmap bitmap, String path) { - return saveBitmapToSDCard(bitmap, path, Bitmap.CompressFormat.PNG, 80); - } - - /** - * 保存图片到SD卡 - PNG - * @param bitmap 需要保存的数据 - * @param path 保存路径 - * @param quality 压缩比例 - */ - public static boolean saveBitmapToSDCardPNG(Bitmap bitmap, String path, int quality) { - return saveBitmapToSDCard(bitmap, path, Bitmap.CompressFormat.PNG, quality); - } - - /** - * 保存图片到SD卡 - PNG - * @param bitmap 需要保存的数据 - * @param path 保存路径 - * @param quality 压缩比例 - * @return - */ - public static boolean saveBitmapToSDCard(Bitmap bitmap, String path, int quality) { - return saveBitmapToSDCard(bitmap, path, Bitmap.CompressFormat.PNG, quality); - } - - /** - * 保存图片到SD卡 - * @param bitmap 图片资源 - * @param path 保存路径 - * @param format 如 Bitmap.CompressFormat.PNG - * @param quality 保存的图片质量, 100 则完整质量不压缩保存 - * @return 保存结果 - */ - public static boolean saveBitmapToSDCard(Bitmap bitmap, String path, Bitmap.CompressFormat format, int quality) { - FileOutputStream fos = null; - try { - fos = new FileOutputStream(path); - if (fos != null) { - bitmap.compress(format, quality, fos); - fos.close(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveBitmapToSDCard"); - return false; - } finally { - if(fos != null) { - try { - fos.close(); - } catch (IOException e) { - } - } - } - return true; - } - - // == - - /** - * 将Drawable转化为Bitmap - * @param drawable Drawable - * @return Bitmap - */ - public static Bitmap getBitmapFromDrawable(Drawable drawable) { - try { - int width = drawable.getIntrinsicWidth(); - int height = drawable.getIntrinsicHeight(); - Bitmap bitmap = Bitmap.createBitmap(width, height, drawable .getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, width, height); - drawable.draw(canvas); - return bitmap; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getBitmapFromDrawable"); - } - return null; - } - - /** - * 通过View, 获取背景转换Bitmap - * @param view The view. - * @return bitmap - */ - public static Bitmap bitmapToViewBackGround(View view) { - if (view == null) return null; - try { - Bitmap ret = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(ret); - Drawable bgDrawable = view.getBackground(); - if (bgDrawable != null) { - bgDrawable.draw(canvas); - } else { - canvas.drawColor(Color.WHITE); - } - view.draw(canvas); - return ret; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "bitmapToViewBackGround"); - } - return null; - } - - /** - * 通过View, 获取Bitmap -> 绘制整个View - * @param view View - * @return Bitmap - */ - public static Bitmap getBitmapFromView(View view) { - if (view == null) return null; - try { - Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); - view.draw(canvas); - return bitmap; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getBitmapFromView"); - } - return null; - } - - /** - * 把一个View的对象转换成bitmap - * @param view View - * @return Bitmap - */ - public static Bitmap getBitmapFromView2(View view) { - if (view == null) return null; - try { - view.clearFocus(); - view.setPressed(false); - // 能画缓存就返回false - boolean willNotCache = view.willNotCacheDrawing(); - view.setWillNotCacheDrawing(false); - int color = view.getDrawingCacheBackgroundColor(); - view.setDrawingCacheBackgroundColor(0); - if (color != 0) { - view.destroyDrawingCache(); - } - view.buildDrawingCache(); - Bitmap cacheBitmap = view.getDrawingCache(); - if (cacheBitmap == null) { - return null; - } - Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); - // Restore the view - view.destroyDrawingCache(); - view.setWillNotCacheDrawing(willNotCache); - view.setDrawingCacheBackgroundColor(color); - return bitmap; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "getBitmapFromView2"); - } - return null; - } - - // === - - /** - * 计算视频宽高大小,视频比例xxx*xxx按屏幕比例放大或者缩小 - * @param width 高度比例 - * @param height 宽度比例 - * @return 返回宽高 0 = 宽,1 = 高 - */ - public static int[] reckonVideoWidthHeight(float width, float height) { - try { - // 获取屏幕宽度 - int sWidth = ScreenUtils.getScreenWidth(); - // 判断宽度比例 - float wRatio = 0.0f; - // 计算比例 - wRatio = (sWidth - width) / width; - // 等比缩放 - int nWidth = sWidth; - int nHeight = (int) (height * (wRatio + 1)); - return new int []{ nWidth, nHeight }; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "reckonVideoWidthHeight"); - } - return null; - } - - /** - * 根据需求的宽和高以及图片实际的宽和高计算SampleSize - * @param options - * @param reqWidth - * @param reqHeight - * @return - */ - public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { - int width = options.outWidth; - int height = options.outHeight; - - int inSampleSize = 1; - if (width > reqWidth || height > reqHeight) { - int widthRadio = Math.round(width * 1.0f / reqWidth); - int heightRadio = Math.round(height * 1.0f / reqHeight); - inSampleSize = Math.max(widthRadio, heightRadio); - } - return inSampleSize; - } - - // ================ ImageView 相关 ================== - - /** - * 根据ImageView获适当的压缩的宽和高 - * @param imageView - * @return - */ - public static int[] getImageViewSize(ImageView imageView) { - int[] imgSize = new int[] {0, 0}; - // -- - DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics(); - LayoutParams lp = imageView.getLayoutParams(); - // 获取imageview的实际宽度 - int width = imageView.getWidth(); - if (width <= 0) { - width = lp.width; // 获取imageview在layout中声明的宽度 - } - if (width <= 0) { - // width = imageView.getMaxWidth(); // 检查最大值 - width = getImageViewFieldValue(imageView, "mMaxWidth"); - } - if (width <= 0) { - width = displayMetrics.widthPixels; - } - // -- - // 获取imageview的实际高度 - int height = imageView.getHeight(); - if (height <= 0) { - height = lp.height; // 获取imageview在layout中声明的宽度 - } - if (height <= 0) { - height = getImageViewFieldValue(imageView, "mMaxHeight"); // 检查最大值 - } - if (height <= 0) { - height = displayMetrics.heightPixels; - } - // -- - imgSize[0] = width; - imgSize[1] = height; - return imgSize; - } - - /** - * 通过反射获取imageview的某个属性值 - * @param object - * @param fieldName - * @return - */ - private static int getImageViewFieldValue(Object object, String fieldName) { - int value = 0; - try { - Field field = ImageView.class.getDeclaredField(fieldName); - field.setAccessible(true); - int fieldValue = field.getInt(object); - if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { - value = fieldValue; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getImageViewFieldValue"); - } - return value; - } - - /** - * 获取图片宽度高度(不加载解析图片) - * @param path - * @return - */ - public static int[] getImageWidthHeight(String path){ - BitmapFactory.Options options = new BitmapFactory.Options(); - // 不解析图片信息 - options.inJustDecodeBounds = true; - // 此时返回的bitmap为null - Bitmap bitmap = BitmapFactory.decodeFile(path, options); - // options.outHeight为原始图片的高 - return new int[]{options.outWidth,options.outHeight}; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/FastBlurUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/FastBlurUtils.java deleted file mode 100644 index 3925a69905..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/FastBlurUtils.java +++ /dev/null @@ -1,259 +0,0 @@ -package dev.utils.app.image; - -import android.graphics.Bitmap; -import android.media.ThumbnailUtils; - -/** - * detail: 毛玻璃效果 - * Created by Ttt - */ -public final class FastBlurUtils { - - private FastBlurUtils() { - } - - /** - * 对图片进行毛玻璃化 - * @param sentBitmap 位图 - * @param radius 虚化程度 - * @param canReuseInBitmap 是否重用 - * @return 位图 - */ - public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { - Bitmap bitmap; - if (canReuseInBitmap) { - bitmap = sentBitmap; - } else { - bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); - } - - if (radius < 1) { - return (null); - } - - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - - int[] pix = new int[w * h]; - bitmap.getPixels(pix, 0, w, 0, 0, w, h); - - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; - - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; - - int divsum = (div + 1) >> 1; - divsum *= divsum; - int dv[] = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); - } - - yw = yi = 0; - - int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } - } - - bitmap.setPixels(pix, 0, w, 0, 0, w, h); - return (bitmap); - } - - /** - * 对图片进行毛玻璃化 数值越大效果越明显 - * @param originBitmap 位图 - * @param scaleRatio 缩放比率 - * @param blurRadius 毛玻璃化比率,虚化程度 - * @return 位图 - */ - public static Bitmap doBlur(Bitmap originBitmap, int scaleRatio, int blurRadius){ - Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap, - originBitmap.getWidth() / scaleRatio, - originBitmap.getHeight() / scaleRatio, - false); - Bitmap blurBitmap = doBlur(scaledBitmap, blurRadius, false); - scaledBitmap.recycle(); - return blurBitmap; - } - - - /** - * 对图片进行 毛玻璃化,虚化 数值越大效果越明显 - * @param originBitmap 位图 - * @param width 缩放后的期望宽度 - * @param height 缩放后的期望高度 - * @param blurRadius 虚化程度 - * @return 位图 - */ - public static Bitmap doBlur(Bitmap originBitmap, int width, int height, int blurRadius){ - Bitmap thumbnail = ThumbnailUtils.extractThumbnail(originBitmap, width, height); - Bitmap blurBitmap = doBlur(thumbnail, blurRadius, true); - thumbnail.recycle(); - return blurBitmap; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/ImageBmpUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/ImageBmpUtils.java deleted file mode 100644 index f499c3effb..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/ImageBmpUtils.java +++ /dev/null @@ -1,205 +0,0 @@ -package dev.utils.app.image; - -import android.graphics.Bitmap; - -import java.io.File; -import java.io.FileOutputStream; - -import dev.utils.LogPrintUtils; - -/** - * detail: 图片Buf转换 - 转换BMP图片 - * Created by Ttt - */ -public final class ImageBmpUtils { - - private ImageBmpUtils(){ - } - - // 日志Tag - private static final String TAG = ImageBmpUtils.class.getSimpleName(); - - // ===================== 转换BMP图片 ======================= - - /** - * bmp位图,头结构 - * @param size 文件总大小(字节数) - * @return - */ - private static byte[] addBMPImageHeader(int size) { - byte[] buffer = new byte[14]; - // -- 文件标识 BM -- - buffer[0] = 0x42; - buffer[1] = 0x4D; - // -- 位图文件大小 -- - buffer[2] = (byte) (size >> 0); - buffer[3] = (byte) (size >> 8); - buffer[4] = (byte) (size >> 16); - buffer[5] = (byte) (size >> 24); - // -- 位图文件保留字,必须为0 -- - buffer[6] = 0x00; - buffer[7] = 0x00; - buffer[8] = 0x00; - buffer[9] = 0x00; - // -- 位图数据开始之间的偏移量 -- - buffer[10] = 0x36; - buffer[11] = 0x00; - buffer[12] = 0x00; - buffer[13] = 0x00; - return buffer; - } - - /** - * bmp位图,头信息 - * @param w 宽度 - * @param h 高度 - * @return - */ - private static byte[] addBMPImageInfosHeader(int w, int h) { - byte[] buffer = new byte[40]; - // -- - buffer[0] = 0x28; - buffer[1] = 0x00; - buffer[2] = 0x00; - buffer[3] = 0x00; - // -- - buffer[4] = (byte) (w >> 0); - buffer[5] = (byte) (w >> 8); - buffer[6] = (byte) (w >> 16); - buffer[7] = (byte) (w >> 24); - // -- - buffer[8] = (byte) (h >> 0); - buffer[9] = (byte) (h >> 8); - buffer[10] = (byte) (h >> 16); - buffer[11] = (byte) (h >> 24); - // -- - buffer[12] = 0x01; - buffer[13] = 0x00; - // -- - buffer[14] = 0x20; // 位数 0x20 - 32位 - buffer[15] = 0x00; - // -- - buffer[16] = 0x00; - buffer[17] = 0x00; - buffer[18] = 0x00; - buffer[19] = 0x00; - // -- - buffer[20] = 0x00; - buffer[21] = 0x00; - buffer[22] = 0x00; - buffer[23] = 0x00; - // -- - buffer[24] = (byte) 0xE0; - buffer[25] = 0x01; - buffer[26] = 0x00; - buffer[27] = 0x00; - // -- - buffer[28] = 0x02; - buffer[29] = 0x03; - buffer[30] = 0x00; - buffer[31] = 0x00; - // -- - buffer[32] = 0x00; - buffer[33] = 0x00; - buffer[34] = 0x00; - buffer[35] = 0x00; - // -- - buffer[36] = 0x00; - buffer[37] = 0x00; - buffer[38] = 0x00; - buffer[39] = 0x00; - return buffer; - } - - /** - * 增加位图 ARGB值 - * @param b 数据 - * @param w 宽度 - * @param h 高度 - * @return - */ - private static byte[] addBMP_ARGB_8888(int[] b, int w, int h) { - int len = b.length; - byte[] buffer = new byte[w * h * 4]; // A + R + G + B = 4 - int offset = 0; // 计算偏移量 - for (int i = len - 1; i >= 0; i -= w) { - // DIB文件格式最后一行为第一行,每行按从左到右顺序 - int end = i, start = i - w + 1; - for (int j = start; j <= end; j++) { - buffer[offset] = (byte) (b[j] >> 0); - buffer[offset + 1] = (byte) (b[j] >> 8); - buffer[offset + 2] = (byte) (b[j] >> 16); - buffer[offset + 3] = (byte) (b[j] >> 24); - offset += 4; - } - } - return buffer; - } - - /** - * 获取bmp位图 byte数据 - * @param bitmap - * @return - */ - private static byte[] getBmpBytes(Bitmap bitmap){ - try { - if(bitmap != null){ - int w = bitmap.getWidth(), h = bitmap.getHeight(); - int[] pixels = new int[w * h]; - bitmap.getPixels(pixels, 0, w, 0, 0, w, h); - // -- - byte[] rgb = addBMP_ARGB_8888(pixels, w, h); - byte[] header = addBMPImageHeader(rgb.length); - byte[] infos = addBMPImageInfosHeader(w, h); - // -- - byte[] buffer = new byte[54 + rgb.length]; - // -- - System.arraycopy(header, 0, buffer, 0, header.length); - System.arraycopy(infos, 0, buffer, 14, infos.length); - System.arraycopy(rgb, 0, buffer, 54, rgb.length); - // -- - return buffer; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getBmpBytes"); - } - return null; - } - - // ================================================================= - - /** - * 保存Bmp图片 - * @param path 保存路径 - * @param bitmap 图片信息 - * @return - */ - public static boolean saveBmpImg(String path, Bitmap bitmap){ - FileOutputStream fos = null; - try { - // 转换Bmp byte数据 - byte[] buffer = getBmpBytes(bitmap); - // 创建写入流 - fos = new FileOutputStream(new File(path)); - // 写入数据 - fos.write(buffer); - return true; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveBmpImg"); - } finally { // 最终关闭写入流 - try { - if(fos != null){ - fos.flush(); - } - } catch (Exception e) { - } - try { - if(fos != null){ - fos.close(); - } - } catch (Exception e) { - } - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/ImageProcessor.java b/DevLibUtils/src/main/java/dev/utils/app/image/ImageProcessor.java deleted file mode 100644 index 4f747e46df..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/ImageProcessor.java +++ /dev/null @@ -1,704 +0,0 @@ -package dev.utils.app.image; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.LinearGradient; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; - -import java.io.InputStream; - -/** - * detail: 图片处理器 - * Created by Ttt - */ -public class ImageProcessor { - - // 需要处理的Bitmap - private Bitmap bitmap; - - /** - * 构造方法 - * @param bitmap 需要处理的bitmap - */ - public ImageProcessor(Bitmap bitmap) { - this.bitmap = bitmap; - } - - /** - * 缩放处理 - * @param scaling 缩放比例 - * @return 缩放后的图片 - */ - public Bitmap scale(float scaling) { - Matrix matrix = new Matrix(); - matrix.postScale(scaling, scaling); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } - - /** - * 缩放处理 - * @param newWidth 新的宽度 - * @return Bitmap - */ - public Bitmap scaleByWidth(int newWidth) { - return scale((float) newWidth / bitmap.getWidth()); - } - - /** - * 缩放处理 - * @param newHeight 新的高度 - * @return Bitmap - */ - public Bitmap scaleByHeight(int newHeight) { - return scale((float) newHeight / bitmap.getHeight()); - } - - /** - * 水平翻转处理 - * @param bitmap 原图 - * @return 水平翻转后的图片 - */ - public Bitmap reverseByHorizontal(Bitmap bitmap){ - Matrix matrix = new Matrix(); - matrix.preScale(-1, 1); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 垂直翻转处理 - * @param bitmap 原图 - * @return 垂直翻转后的图片 - */ - public Bitmap reverseByVertical(Bitmap bitmap){ - Matrix matrix = new Matrix(); - matrix.preScale(1, -1); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 将给定资源ID的Drawable转换成Bitmap - * @param context 上下文 - * @param resId Drawable资源文件的ID - * @return 新的Bitmap - */ - public Bitmap drawableToBitmap(Context context, int resId) { - BitmapFactory.Options opt = new BitmapFactory.Options(); - opt.inPreferredConfig = Bitmap.Config.RGB_565; - opt.inPurgeable = true; - opt.inInputShareable = true; - InputStream is = context.getResources().openRawResource(resId); - return BitmapFactory.decodeStream(is, null, opt); - } - - /** - * 圆角处理 - * @param pixels 角度,度数越大圆角越大 - * @return 转换成圆角后的图片 - */ - public Bitmap roundCorner(float pixels) { - Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(output); - Paint paint = new Paint(); - Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); // 创建一个同原图一样大小的矩形,用于把原图绘制到这个矩形上 - RectF rectF = new RectF(rect); // 创建一个精度更高的矩形,用于画出圆角效果 - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); // 涂上黑色全透明的底色 - paint.setColor(0xff424242); // 设置画笔的颜色为不透明的灰色 - canvas.drawRoundRect(rectF, pixels, pixels, paint); // 用给给定的画笔把给定的矩形以给定的圆角的度数画到画布 - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, rect, rect, paint); // 用画笔paint将原图bitmap根据新的矩形重新绘制 - return output; - } - - /** - * 倒影处理 - * @param reflectionSpacing 原图与倒影之间的间距 - * @return 加上倒影后的图片 - */ - public Bitmap reflection(int reflectionSpacing, int reflectionHeight) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - - /* 获取倒影图片,并创建一张宽度与原图相同,但高度等于原图的高度加上间距加上倒影的高度的图片,并创建画布。画布分为上中下三部分,上:是原图;中:是原图与倒影的间距;下:是倒影 */ - Bitmap reflectionImage = reverseByVertical(bitmap);// - Bitmap bitmapWithReflection = Bitmap.createBitmap(width, height + reflectionSpacing + reflectionHeight, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmapWithReflection); - - /* 将原图画到画布的上半部分,将倒影画到画布的下半部分,倒影与画布顶部的间距是原图的高度加上原图与倒影之间的间距 */ - canvas.drawBitmap(bitmap, 0, 0, null); - canvas.drawBitmap(reflectionImage, 0, height + reflectionSpacing, null); - reflectionImage.recycle(); - - /* 将倒影改成半透明,创建画笔,并设置画笔的渐变从半透明的白色到全透明的白色,然后再倒影上面画半透明效果 */ - Paint paint = new Paint(); - paint.setShader(new LinearGradient(0, bitmap.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionSpacing, 0x70ffffff, 0x00ffffff, Shader.TileMode.CLAMP)); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); - canvas.drawRect(0, height+reflectionSpacing, width, bitmapWithReflection.getHeight() + reflectionSpacing, paint); - - return bitmapWithReflection; - } - - /** - * 倒影处理 - * @return 加上倒影后的图片 - */ - public Bitmap reflection() { - return reflection(4, bitmap.getHeight() / 2); - } - - /** - * 旋转处理 - * @param angle 旋转角度 - * @param px 旋转中心点在X轴的坐标 - * @param py 旋转中心点在Y轴的坐标 - * @return 旋转后的图片 - */ - public Bitmap rotate(float angle, float px, float py){ - Matrix matrix = new Matrix(); - matrix.postRotate(angle, px, py); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 旋转后处理 - * @param angle 旋转角度 - * @return 旋转后的图片 - */ - public Bitmap rotate(float angle){ - Matrix matrix = new Matrix(); - matrix.postRotate(angle); - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - } - - /** - * 饱和度处理 - * @param saturationValue 新的饱和度值 - * @return 改变了饱和度值之后的图片 - */ - public Bitmap saturation(int saturationValue){ - //计算出符合要求的饱和度值 - float newSaturationValue = saturationValue * 1.0F / 127; - //创建一个颜色矩阵 - ColorMatrix saturationColorMatrix = new ColorMatrix(); - //设置饱和度值 - saturationColorMatrix.setSaturation(newSaturationValue); - //创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(saturationColorMatrix)); - //创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - //将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 亮度处理 - * @param lumValue 新的亮度值 - * @return 改变了亮度值之后的图片 - */ - public Bitmap lum(int lumValue){ - //计算出符合要求的亮度值 - float newlumValue = lumValue * 1.0F / 127; - //创建一个颜色矩阵 - ColorMatrix lumColorMatrix = new ColorMatrix(); - //设置亮度值 - lumColorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); - //创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(lumColorMatrix)); - //创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - //将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 色相处理 - * @param hueValue 新的色相值 - * @return 改变了色相值之后的图片 - */ - public Bitmap hue(int hueValue){ - //计算出符合要求的色相值 - float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; - //创建一个颜色矩阵 - ColorMatrix hueColorMatrix = new ColorMatrix(); - // 控制让红色区在色轮上旋转的角度 - hueColorMatrix.setRotate(0, newHueValue); - // 控制让绿红色区在色轮上旋转的角度 - hueColorMatrix.setRotate(1, newHueValue); - // 控制让蓝色区在色轮上旋转的角度 - hueColorMatrix.setRotate(2, newHueValue); - //创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(hueColorMatrix)); - //创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - //将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 亮度、色相、饱和度处理 - * @param lumValue 亮度值 - * @param hueValue 色相值 - * @param saturationValue 饱和度值 - * @return 亮度、色相、饱和度处理后的图片 - */ - public Bitmap lumAndHueAndSaturation(int lumValue, int hueValue, int saturationValue){ - //计算出符合要求的饱和度值 - float newSaturationValue = saturationValue * 1.0F / 127; - //计算出符合要求的亮度值 - float newlumValue = lumValue * 1.0F / 127; - //计算出符合要求的色相值 - float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; - - //创建一个颜色矩阵并设置其饱和度 - ColorMatrix colorMatrix = new ColorMatrix(); - - //设置饱和度值 - colorMatrix.setSaturation(newSaturationValue); - //设置亮度值 - colorMatrix.setScale(newlumValue, newlumValue, newlumValue, 1); - // 控制让红色区在色轮上旋转的角度 - colorMatrix.setRotate(0, newHueValue); - // 控制让绿红色区在色轮上旋转的角度 - colorMatrix.setRotate(1, newHueValue); - // 控制让蓝色区在色轮上旋转的角度 - colorMatrix.setRotate(2, newHueValue); - - //创建一个画笔并设置其颜色过滤器 - Paint paint = new Paint(); - paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); - //创建一个新的图片并创建画布 - Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(newBitmap); - //将原图使用给定的画笔画到画布上 - canvas.drawBitmap(bitmap, 0, 0, paint); - return newBitmap; - } - - /** - * 怀旧效果处理 - * @param bitmap 原图 - * @return 怀旧效果处理后的图片 - */ - public Bitmap nostalgic(Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - int pixColor = 0; - int pixR = 0; - int pixG = 0; - int pixB = 0; - int newR = 0; - int newG = 0; - int newB = 0; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 0; i < height; i++) { - for (int k = 0; k < width; k++) { - pixColor = pixels[width * i + k]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - newR = (int) (0.393 * pixR + 0.769 * pixG + 0.189 * pixB); - newG = (int) (0.349 * pixR + 0.686 * pixG + 0.168 * pixB); - newB = (int) (0.272 * pixR + 0.534 * pixG + 0.131 * pixB); - int newColor = Color.argb(255, newR > 255 ? 255 : newR, newG > 255 ? 255 : newG, newB > 255 ? 255 - : newB); - pixels[width * i + k] = newColor; - } - } - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 模糊效果处理 - * @return 模糊效果处理后的图片 - */ - public Bitmap blur() { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int newColor = 0; - - int[][] colors = new int[9][3]; - for (int i = 1, length = width - 1; i < length; i++) { - for (int k = 1, len = height - 1; k < len; k++) { - for (int m = 0; m < 9; m++) { - int s = 0; - int p = 0; - switch (m) { - case 0: - s = i - 1; - p = k - 1; - break; - case 1: - s = i; - p = k - 1; - break; - case 2: - s = i + 1; - p = k - 1; - break; - case 3: - s = i + 1; - p = k; - break; - case 4: - s = i + 1; - p = k + 1; - break; - case 5: - s = i; - p = k + 1; - break; - case 6: - s = i - 1; - p = k + 1; - break; - case 7: - s = i - 1; - p = k; - break; - case 8: - s = i; - p = k; - } - pixColor = bitmap.getPixel(s, p); - colors[m][0] = Color.red(pixColor); - colors[m][1] = Color.green(pixColor); - colors[m][2] = Color.blue(pixColor); - } - - for (int m = 0; m < 9; m++) { - newR += colors[m][0]; - newG += colors[m][1]; - newB += colors[m][2]; - } - - newR = (int) (newR / 9F); - newG = (int) (newG / 9F); - newB = (int) (newB / 9F); - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - newColor = Color.argb(255, newR, newG, newB); - newBitmap.setPixel(i, k, newColor); - - newR = 0; - newG = 0; - newB = 0; - } - } - return newBitmap; - } - - /** - * 柔化效果处理 - * @return 柔化效果处理后的图片 - */ - public Bitmap soften() { - // 高斯矩阵 - int[] gauss = new int[] { 1, 2, 1, 2, 4, 2, 1, 2, 1 }; - - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int delta = 16; // 值越小图片会越亮,越大则越暗 - - int idx = 0; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - idx = 0; - for (int m = -1; m <= 1; m++) { - for (int n = -1; n <= 1; n++) { - pixColor = pixels[(i + m) * width + k + n]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = newR + (int) (pixR * gauss[idx]); - newG = newG + (int) (pixG * gauss[idx]); - newB = newB + (int) (pixB * gauss[idx]); - idx++; - } - } - - newR /= delta; - newG /= delta; - newB /= delta; - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[i * width + k] = Color.argb(255, newR, newG, newB); - - newR = 0; - newG = 0; - newB = 0; - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 光照效果处理 - * @param centerX 光源在X轴的位置 - * @param centerY 光源在Y轴的位置 - * @return 光照效果处理后的图片 - */ - public Bitmap sunshine(int centerX, int centerY) { - final int width = bitmap.getWidth(); - final int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - int radius = Math.min(centerX, centerY); - - final float strength = 150F; // 光照强度 100~150 - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = pixR; - newG = pixG; - newB = pixB; - - // 计算当前点到光照中心的距离,平面座标系中求两点之间的距离 - int distance = (int) ( - Math.pow((centerY - i), 2) + Math.pow(centerX - k, 2)); - if (distance < radius * radius) { - // 按照距离大小计算增加的光照值 - int result = (int) (strength * (1.0 - Math.sqrt(distance) / radius)); - newR = pixR + result; - newG = pixG + result; - newB = pixB + result; - } - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[pos] = Color.argb(255, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 底片效果处理 - * @return 底片效果处理后的图片 - */ - public Bitmap film() { - // RGBA的最大值 - final int MAX_VALUE = 255; - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = MAX_VALUE - pixR; - newG = MAX_VALUE - pixG; - newB = MAX_VALUE - pixB; - - newR = Math.min(MAX_VALUE, Math.max(0, newR)); - newG = Math.min(MAX_VALUE, Math.max(0, newG)); - newB = Math.min(MAX_VALUE, Math.max(0, newB)); - - pixels[pos] = Color.argb(MAX_VALUE, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 锐化效果处理 - * @return 锐化效果处理后的图片 - */ - public Bitmap sharpen() { - // 拉普拉斯矩阵 - int[] laplacian = new int[] { -1, -1, -1, -1, 9, -1, -1, -1, -1 }; - - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int idx = 0; - float alpha = 0.3F; - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - idx = 0; - for (int m = -1; m <= 1; m++) { - for (int n = -1; n <= 1; n++) { - pixColor = pixels[(i + n) * width + k + m]; - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - newR = newR + (int) (pixR * laplacian[idx] * alpha); - newG = newG + (int) (pixG * laplacian[idx] * alpha); - newB = newB + (int) (pixB * laplacian[idx] * alpha); - idx++; - } - } - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[i * width + k] = Color.argb(255, newR, newG, newB); - newR = 0; - newG = 0; - newB = 0; - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } - - /** - * 浮雕效果处理 - * @return 浮雕效果处理后的图片 - */ - public Bitmap emboss() { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); - - int pixR = 0; - int pixG = 0; - int pixB = 0; - - int pixColor = 0; - - int newR = 0; - int newG = 0; - int newB = 0; - - int[] pixels = new int[width * height]; - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); - int pos = 0; - for (int i = 1, length = height - 1; i < length; i++) { - for (int k = 1, len = width - 1; k < len; k++) { - pos = i * width + k; - pixColor = pixels[pos]; - - pixR = Color.red(pixColor); - pixG = Color.green(pixColor); - pixB = Color.blue(pixColor); - - pixColor = pixels[pos + 1]; - newR = Color.red(pixColor) - pixR + 127; - newG = Color.green(pixColor) - pixG + 127; - newB = Color.blue(pixColor) - pixB + 127; - - newR = Math.min(255, Math.max(0, newR)); - newG = Math.min(255, Math.max(0, newG)); - newB = Math.min(255, Math.max(0, newB)); - - pixels[pos] = Color.argb(255, newR, newG, newB); - } - } - - newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); - return newBitmap; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/image/ImageUtils.java b/DevLibUtils/src/main/java/dev/utils/app/image/ImageUtils.java deleted file mode 100644 index b4afb59e0b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/image/ImageUtils.java +++ /dev/null @@ -1,1517 +0,0 @@ -package dev.utils.app.image; - -import android.annotation.TargetApi; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.BitmapFactory; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.LinearGradient; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; -import android.media.ExifInterface; -import android.os.Build; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RenderScript; -import android.renderscript.ScriptIntrinsicBlur; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.FloatRange; -import android.support.annotation.IntRange; -import android.support.v4.content.ContextCompat; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.common.CloseUtils; - -/** - * detail: 图片相关工具类 - * Created by Ttt - */ -public final class ImageUtils { - - private ImageUtils() { - } - - // 日志Tag - private static final String TAG = ImageUtils.class.getSimpleName(); - - /** - * 获取 bitmap - * @param file The file. - * @return bitmap - */ - public static Bitmap getBitmap(final File file) { - if (file == null) return null; - return BitmapFactory.decodeFile(file.getAbsolutePath()); - } - - /** - * 获取 bitmap - * @param file The file. - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return bitmap - */ - public static Bitmap getBitmap(final File file, final int maxWidth, final int maxHeight) { - if (file == null) return null; - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(file.getAbsolutePath(), options); - options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - return BitmapFactory.decodeFile(file.getAbsolutePath(), options); - } - - /** - * 获取 bitmap - * @param filePath The path of file. - * @return bitmap - */ - public static Bitmap getBitmap(final String filePath) { - if (isSpace(filePath)) return null; - return BitmapFactory.decodeFile(filePath); - } - - /** - * 获取 bitmap - * @param filePath The path of file. - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return bitmap - */ - public static Bitmap getBitmap(final String filePath, final int maxWidth, final int maxHeight) { - if (isSpace(filePath)) return null; - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(filePath, options); - options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - return BitmapFactory.decodeFile(filePath, options); - } - - /** - * 获取 bitmap - * @param is 输入流 - * @return bitmap - */ - public static Bitmap getBitmap(final InputStream is) { - if (is == null) return null; - return BitmapFactory.decodeStream(is); - } - - /** - * 获取 bitmap - * @param resId 资源 id - * @return bitmap - */ - public static Bitmap getBitmap(@DrawableRes final int resId) { - Drawable drawable = ContextCompat.getDrawable(DevUtils.getContext(), resId); - Canvas canvas = new Canvas(); - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - canvas.setBitmap(bitmap); - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - drawable.draw(canvas); - return bitmap; - } - - /** - * 获取 bitmap - * @param resId 资源 id - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return bitmap - */ - public static Bitmap getBitmap(@DrawableRes final int resId, final int maxWidth, final int maxHeight) { - BitmapFactory.Options options = new BitmapFactory.Options(); - final Resources resources = DevUtils.getContext().getResources(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeResource(resources, resId, options); - options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - return BitmapFactory.decodeResource(resources, resId, options); - } - - /** - * 获取 bitmap - * @param fd 文件描述 - * @return bitmap - */ - public static Bitmap getBitmap(final FileDescriptor fd) { - if (fd == null) return null; - return BitmapFactory.decodeFileDescriptor(fd); - } - - /** - * 获取 bitmap - * @param fd 文件描述 - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return bitmap - */ - public static Bitmap getBitmap(final FileDescriptor fd, final int maxWidth, final int maxHeight) { - if (fd == null) return null; - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeFileDescriptor(fd, null, options); - options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - return BitmapFactory.decodeFileDescriptor(fd, null, options); - } - - /** - * 缩放图片 - * @param src 源图片 - * @param newWidth 新宽度 - * @param newHeight 新高度 - * @return 缩放后的图片 - */ - public static Bitmap scale(final Bitmap src, final int newWidth, final int newHeight) { - return scale(src, newWidth, newHeight, false); - } - - /** - * 缩放图片 - * @param src 源图片 - * @param newWidth 新宽度 - * @param newHeight 新高度 - * @param recycle 是否回收 - * @return 缩放后的图片 - */ - public static Bitmap scale(final Bitmap src, final int newWidth, final int newHeight, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = Bitmap.createScaledBitmap(src, newWidth, newHeight, true); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 缩放图片 - * @param src 源图片 - * @param scaleWidth 缩放宽度倍数 - * @param scaleHeight 缩放高度倍数 - * @return 缩放后的图片 - */ - public static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight) { - return scale(src, scaleWidth, scaleHeight, false); - } - - /** - * 缩放图片 - * @param src 源图片 - * @param scaleWidth 缩放宽度倍数 - * @param scaleHeight 缩放高度倍数 - * @param recycle 是否回收 - * @return 缩放后的图片 - */ - public static Bitmap scale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Matrix matrix = new Matrix(); - matrix.setScale(scaleWidth, scaleHeight); - Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 裁剪图片 - * @param src 源图片 - * @param x 开始坐标 x - * @param y 开始坐标 y - * @param width 裁剪宽度 - * @param height 裁剪高度 - * @return 裁剪后的图片 - */ - public static Bitmap clip(final Bitmap src, final int x, final int y, final int width, final int height) { - return clip(src, x, y, width, height, false); - } - - /** - * 裁剪图片 - * @param src 源图片 - * @param x 开始坐标 x - * @param y 开始坐标 y - * @param width 裁剪宽度 - * @param height 裁剪高度 - * @param recycle 是否回收 - * @return 裁剪后的图片 - */ - public static Bitmap clip(final Bitmap src, final int x, final int y, final int width, final int height, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = Bitmap.createBitmap(src, x, y, width, height); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 倾斜图片 - * @param src 源图片 - * @param kx 倾斜因子 x - * @param ky 倾斜因子 y - * @return 倾斜后的图片 - */ - public static Bitmap skew(final Bitmap src, final float kx, final float ky) { - return skew(src, kx, ky, 0, 0, false); - } - - /** - * 倾斜图片 - * @param src 源图片 - * @param kx 倾斜因子 x - * @param ky 倾斜因子 y - * @param recycle 是否回收 - * @return 倾斜后的图片 - */ - public static Bitmap skew(final Bitmap src, final float kx, final float ky, final boolean recycle) { - return skew(src, kx, ky, 0, 0, recycle); - } - - /** - * 倾斜图片 - * @param src 源图片 - * @param kx 倾斜因子 x - * @param ky 倾斜因子 y - * @param px 平移因子 x - * @param py 平移因子 y - * @return 倾斜后的图片 - */ - public static Bitmap skew(final Bitmap src, final float kx, final float ky, final float px, final float py) { - return skew(src, kx, ky, px, py, false); - } - - /** - * 倾斜图片 - * @param src 源图片 - * @param kx 倾斜因子 x - * @param ky 倾斜因子 y - * @param px 平移因子 x - * @param py 平移因子 y - * @param recycle 是否回收 - * @return 倾斜后的图片 - */ - public static Bitmap skew(final Bitmap src, final float kx, final float ky, final float px, final float py, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Matrix matrix = new Matrix(); - matrix.setSkew(kx, ky, px, py); - Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 旋转图片 - * @param src 源图片 - * @param degrees 旋转角度 - * @param px 旋转点横坐标 - * @param py 旋转点纵坐标 - * @return 旋转后的图片 - */ - public static Bitmap rotate(final Bitmap src, final int degrees, final float px, final float py) { - return rotate(src, degrees, px, py, false); - } - - /** - * 旋转图片 - * @param src 源图片 - * @param degrees 旋转角度 - * @param px 旋转点横坐标 - * @param py 旋转点纵坐标 - * @param recycle 是否回收 - * @return 旋转后的图片 - */ - public static Bitmap rotate(final Bitmap src, final int degrees, final float px, final float py, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - if (degrees == 0) return src; - Matrix matrix = new Matrix(); - matrix.setRotate(degrees, px, py); - Bitmap ret = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 获取图片旋转角度 - 返回 -1 表示异常 - * @param filePath The path of file. - * @return 旋转角度 - */ - public static int getRotateDegree(final String filePath) { - try { - ExifInterface exifInterface = new ExifInterface(filePath); - int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_90: - return 90; - case ExifInterface.ORIENTATION_ROTATE_180: - return 180; - case ExifInterface.ORIENTATION_ROTATE_270: - return 270; - default: - return 0; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getRotateDegree"); - return -1; - } - } - - /** - * 转为圆形图片 - * @param src 源图片 - * @return 圆形图片 - */ - public static Bitmap toRound(final Bitmap src) { - return toRound(src, 0, 0, false); - } - - /** - * 转为圆形图片 - * @param src 源图片 - * @param recycle 是否回收 - * @return 圆形图片 - */ - public static Bitmap toRound(final Bitmap src, final boolean recycle) { - return toRound(src, 0, 0, recycle); - } - - /** - * 转为圆形图片 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param borderColor 边框颜色 - * @return 圆形图片 - */ - public static Bitmap toRound(final Bitmap src, @IntRange(from = 0) int borderSize, @ColorInt int borderColor) { - return toRound(src, borderSize, borderColor, false); - } - - /** - * 转为圆形图片 - * @param src 源图片 - * @param recycle 是否回收 - * @param borderSize 边框尺寸 - * @param borderColor 边框颜色 - * @return 圆形图片 - */ - public static Bitmap toRound(final Bitmap src, @IntRange(from = 0) int borderSize, @ColorInt int borderColor, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - int width = src.getWidth(); - int height = src.getHeight(); - int size = Math.min(width, height); - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig()); - float center = size / 2f; - RectF rectF = new RectF(0, 0, width, height); - rectF.inset((width - size) / 2f, (height - size) / 2f); - Matrix matrix = new Matrix(); - matrix.setTranslate(rectF.left, rectF.top); - matrix.preScale((float) size / width, (float) size / height); - BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - shader.setLocalMatrix(matrix); - paint.setShader(shader); - Canvas canvas = new Canvas(ret); - canvas.drawRoundRect(rectF, center, center, paint); - if (borderSize > 0) { - paint.setShader(null); - paint.setColor(borderColor); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(borderSize); - float radius = center - borderSize / 2f; - canvas.drawCircle(width / 2f, height / 2f, radius, paint); - } - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 转为圆角图片 - * @param src 源图片 - * @param radius 圆角的度数 - * @return 圆角图片 - */ - public static Bitmap toRoundCorner(final Bitmap src, final float radius) { - return toRoundCorner(src, radius, 0, 0, false); - } - - /** - * 转为圆角图片 - * @param src 源图片 - * @param radius 圆角的度数 - * @param recycle 是否回收 - * @return 圆角图片 - */ - public static Bitmap toRoundCorner(final Bitmap src, final float radius, final boolean recycle) { - return toRoundCorner(src, radius, 0, 0, recycle); - } - - /** - * 转为圆角图片 - * @param src 源图片 - * @param radius 圆角的度数 - * @param borderSize 边框尺寸 - * @param borderColor 边框颜色 - * @return 圆角图片 - */ - public static Bitmap toRoundCorner(final Bitmap src, final float radius, @IntRange(from = 0) int borderSize, @ColorInt int borderColor) { - return toRoundCorner(src, radius, borderSize, borderColor, false); - } - - /** - * 转为圆角图片 - * @param src 源图片 - * @param radius 圆角的度数 - * @param borderSize 边框尺寸 - * @param borderColor 边框颜色 - * @param recycle 是否回收 - * @return 圆角图片 - */ - public static Bitmap toRoundCorner(final Bitmap src, final float radius, @IntRange(from = 0) int borderSize, @ColorInt int borderColor, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - int width = src.getWidth(); - int height = src.getHeight(); - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - Bitmap ret = Bitmap.createBitmap(width, height, src.getConfig()); - BitmapShader shader = new BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - paint.setShader(shader); - Canvas canvas = new Canvas(ret); - RectF rectF = new RectF(0, 0, width, height); - float halfBorderSize = borderSize / 2f; - rectF.inset(halfBorderSize, halfBorderSize); - canvas.drawRoundRect(rectF, radius, radius, paint); - if (borderSize > 0) { - paint.setShader(null); - paint.setColor(borderColor); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(borderSize); - paint.setStrokeCap(Paint.Cap.ROUND); - canvas.drawRoundRect(rectF, radius, radius, paint); - } - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 添加圆角边框 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param color 边框颜色 - * @param cornerRadius 圆角半径 - * @return 圆角边框图 - */ - public static Bitmap addCornerBorder(final Bitmap src, @IntRange(from = 1) final int borderSize, @ColorInt final int color, @FloatRange(from = 0) final float cornerRadius) { - return addBorder(src, borderSize, color, false, cornerRadius, false); - } - - /** - * 添加圆角边框 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param color 边框颜色 - * @param cornerRadius 圆角半径 - * @param recycle 是否回收 - * @return 圆角边框图 - */ - public static Bitmap addCornerBorder(final Bitmap src, @IntRange(from = 1) final int borderSize, @ColorInt final int color, @FloatRange(from = 0) final float cornerRadius, final boolean recycle) { - return addBorder(src, borderSize, color, false, cornerRadius, recycle); - } - - /** - * 添加圆形边框 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param color 边框颜色 - * @return 圆形边框图 - */ - public static Bitmap addCircleBorder(final Bitmap src, @IntRange(from = 1) final int borderSize, @ColorInt final int color) { - return addBorder(src, borderSize, color, true, 0, false); - } - - /** - * 添加圆形边框 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param color 边框颜色 - * @param recycle 是否回收 - * @return 圆形边框图 - */ - public static Bitmap addCircleBorder(final Bitmap src, @IntRange(from = 1) final int borderSize, @ColorInt final int color, final boolean recycle) { - return addBorder(src, borderSize, color, true, 0, recycle); - } - - /** - * 添加边框 - * @param src 源图片 - * @param borderSize 边框尺寸 - * @param color 边框颜色 - * @param isCircle 是否画圆 - * @param cornerRadius 圆角半径 - * @param recycle 是否回收 - * @return 边框图 - */ - private static Bitmap addBorder(final Bitmap src, @IntRange(from = 1) final int borderSize, @ColorInt final int color, - final boolean isCircle, final float cornerRadius, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = recycle ? src : src.copy(src.getConfig(), true); - int width = ret.getWidth(); - int height = ret.getHeight(); - Canvas canvas = new Canvas(ret); - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - paint.setColor(color); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(borderSize); - if (isCircle) { - float radius = Math.min(width, height) / 2f - borderSize / 2f; - canvas.drawCircle(width / 2f, height / 2f, radius, paint); - } else { - int halfBorderSize = borderSize >> 1; - RectF rectF = new RectF(halfBorderSize, halfBorderSize,width - halfBorderSize, height - halfBorderSize); - canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint); - } - return ret; - } - - /** - * 添加倒影 - * @param src 源图片的 - * @param reflectionHeight 倒影高度 - * @return 带倒影图片 - */ - public static Bitmap addReflection(final Bitmap src, final int reflectionHeight) { - return addReflection(src, reflectionHeight, false); - } - - /** - * 添加倒影 - * @param src 源图片的 - * @param reflectionHeight 倒影高度 - * @param recycle 是否回收 - * @return 带倒影图片 - */ - public static Bitmap addReflection(final Bitmap src, final int reflectionHeight, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - // 原图与倒影之间的间距 - final int REFLECTION_GAP = 0; - int srcWidth = src.getWidth(); - int srcHeight = src.getHeight(); - Matrix matrix = new Matrix(); - matrix.preScale(1, -1); - Bitmap reflectionBitmap = Bitmap.createBitmap(src, 0, srcHeight - reflectionHeight, srcWidth, reflectionHeight, matrix, false); - Bitmap ret = Bitmap.createBitmap(srcWidth, srcHeight + reflectionHeight, src.getConfig()); - Canvas canvas = new Canvas(ret); - canvas.drawBitmap(src, 0, 0, null); - canvas.drawBitmap(reflectionBitmap, 0, srcHeight + REFLECTION_GAP, null); - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - LinearGradient shader = new LinearGradient(0, srcHeight,0, ret.getHeight() + REFLECTION_GAP,0x70FFFFFF,0x00FFFFFF, Shader.TileMode.MIRROR); - paint.setShader(shader); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); - canvas.drawRect(0, srcHeight + REFLECTION_GAP, srcWidth, ret.getHeight(), paint); - if (!reflectionBitmap.isRecycled()) reflectionBitmap.recycle(); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 添加文字水印 - * @param src 源图片 - * @param content 水印文本 - * @param textSize 水印字体大小 - * @param color 水印字体颜色 - * @param x 起始坐标 x - * @param y 起始坐标 y - * @return 带有文字水印的图片 - */ - public static Bitmap addTextWatermark(final Bitmap src, final String content, final int textSize, - @ColorInt final int color, final float x, final float y) { - return addTextWatermark(src, content, textSize, color, x, y, false); - } - - /** - * 添加文字水印 - * @param src 源图片 - * @param content 水印文本 - * @param textSize 水印字体大小 - * @param color 水印字体颜色 - * @param x 起始坐标 x - * @param y 起始坐标 y - * @param recycle 是否回收 - * @return 带有文字水印的图片 - */ - public static Bitmap addTextWatermark(final Bitmap src, final String content, final float textSize, - @ColorInt final int color, final float x, final float y, final boolean recycle) { - if (isEmptyBitmap(src) || content == null) return null; - Bitmap ret = src.copy(src.getConfig(), true); - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - Canvas canvas = new Canvas(ret); - paint.setColor(color); - paint.setTextSize(textSize); - Rect bounds = new Rect(); - paint.getTextBounds(content, 0, content.length(), bounds); - canvas.drawText(content, x, y + textSize, paint); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 添加图片水印 - * @param src 源图片 - * @param watermark 图片水印 - * @param x 起始坐标 x - * @param y 起始坐标 y - * @param alpha 透明度 - * @return 带有图片水印的图片 - */ - public static Bitmap addImageWatermark(final Bitmap src, final Bitmap watermark, final int x, final int y, final int alpha) { - return addImageWatermark(src, watermark, x, y, alpha, false); - } - - /** - * 添加图片水印 - * @param src 源图片 - * @param watermark 图片水印 - * @param x 起始坐标 x - * @param y 起始坐标 y - * @param alpha 透明度 - * @param recycle 是否回收 - * @return 带有图片水印的图片 - */ - public static Bitmap addImageWatermark(final Bitmap src, final Bitmap watermark, final int x, final int y, final int alpha, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = src.copy(src.getConfig(), true); - if (!isEmptyBitmap(watermark)) { - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - Canvas canvas = new Canvas(ret); - paint.setAlpha(alpha); - canvas.drawBitmap(watermark, x, y, paint); - } - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 转为 alpha 位图 - * @param src 源图片 - * @return alpha 位图 - */ - public static Bitmap toAlpha(final Bitmap src) { - return toAlpha(src, false); - } - - /** - * 转为 alpha 位图 - * @param src 源图片 - * @param recycle 是否回收 - * @return alpha 位图 - */ - public static Bitmap toAlpha(final Bitmap src, final Boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = src.extractAlpha(); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 转为灰度图片 - * @param src 源图片 - * @return 灰度图 - */ - public static Bitmap toGray(final Bitmap src) { - return toGray(src, false); - } - - /** - * 转为灰度图片 - * @param src 源图片 - * @param recycle 是否回收 - * @return 灰度图 - */ - public static Bitmap toGray(final Bitmap src, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - Bitmap ret = Bitmap.createBitmap(src.getWidth(), src.getHeight(), src.getConfig()); - Canvas canvas = new Canvas(ret); - Paint paint = new Paint(); - ColorMatrix colorMatrix = new ColorMatrix(); - colorMatrix.setSaturation(0); - ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix); - paint.setColorFilter(colorMatrixColorFilter); - canvas.drawBitmap(src, 0, 0, paint); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * 快速模糊 - 先缩小原图,对小图进行模糊,再放大回原先尺寸 - * @param src 源图片 - * @param scale 缩放比例(0...1) - * @param radius 模糊半径 - * @return 模糊后的图片 - */ - public static Bitmap fastBlur(final Bitmap src, - @FloatRange(from = 0, to = 1, fromInclusive = false) final float scale, - @FloatRange(from = 0, to = 25, fromInclusive = false) final float radius) { - return fastBlur(src, scale, radius, false); - } - - /** - * 快速模糊图片 - 先缩小原图,对小图进行模糊,再放大回原先尺寸 - * @param src 源图片 - * @param scale 缩放比例(0...1) - * @param radius 模糊半径(0...25) - * @param recycle 是否回收 - * @return 模糊后的图片 - */ - public static Bitmap fastBlur(final Bitmap src, - @FloatRange(from = 0, to = 1, fromInclusive = false) final float scale, - @FloatRange(from = 0, to = 25, fromInclusive = false) final float radius, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - int width = src.getWidth(); - int height = src.getHeight(); - Matrix matrix = new Matrix(); - matrix.setScale(scale, scale); - Bitmap scaleBitmap = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true); - Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); - Canvas canvas = new Canvas(); - PorterDuffColorFilter filter = new PorterDuffColorFilter(Color.TRANSPARENT, PorterDuff.Mode.SRC_ATOP); - paint.setColorFilter(filter); - canvas.scale(scale, scale); - canvas.drawBitmap(scaleBitmap, 0, 0, paint); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - scaleBitmap = renderScriptBlur(scaleBitmap, radius, recycle); - } else { - scaleBitmap = stackBlur(scaleBitmap, (int) radius, recycle); - } - if (scale == 1) { - if (recycle && !src.isRecycled()) src.recycle(); - return scaleBitmap; - } - Bitmap ret = Bitmap.createScaledBitmap(scaleBitmap, width, height, true); - if (!scaleBitmap.isRecycled()) scaleBitmap.recycle(); - if (recycle && !src.isRecycled()) src.recycle(); - return ret; - } - - /** - * renderScript 模糊图片 - API 大于 17 - * @param src 源图片 - * @param radius 模糊半径(0...25) - * @return 模糊后的图片 - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public static Bitmap renderScriptBlur(final Bitmap src, @FloatRange(from = 0, to = 25, fromInclusive = false) final float radius) { - return renderScriptBlur(src, radius, false); - } - - /** - * renderScript 模糊图片 - API 大于 17 - * @param src 源图片 - * @param radius 模糊半径(0...25) - * @param recycle 是否回收 - * @return 模糊后的图片 - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - public static Bitmap renderScriptBlur(final Bitmap src, @FloatRange(from = 0, to = 25, fromInclusive = false) final float radius, final boolean recycle) { - RenderScript rs = null; - Bitmap ret = recycle ? src : src.copy(src.getConfig(), true); - try { - rs = RenderScript.create(DevUtils.getContext()); - rs.setMessageHandler(new RenderScript.RSMessageHandler()); - Allocation input = Allocation.createFromBitmap(rs, ret, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - Allocation output = Allocation.createTyped(rs, input.getType()); - ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - blurScript.setInput(input); - blurScript.setRadius(radius); - blurScript.forEach(output); - output.copyTo(ret); - } finally { - if (rs != null) { - rs.destroy(); - } - } - return ret; - } - - /** - * stack 模糊图片 - * @param src 源图片 - * @param radius 模糊半径 - * @return stack 模糊后的图片 - */ - public static Bitmap stackBlur(final Bitmap src, final int radius) { - return stackBlur(src, radius, false); - } - - /** - * stack 模糊图片 - * @param src 源图片 - * @param radius 模糊半径 - * @param recycle 是否回收 - * @return stack 模糊后的图片 - */ - public static Bitmap stackBlur(final Bitmap src, int radius, final boolean recycle) { - Bitmap ret = recycle ? src : src.copy(src.getConfig(), true); - if (radius < 1) { - radius = 1; - } - int w = ret.getWidth(); - int h = ret.getHeight(); - - int[] pix = new int[w * h]; - ret.getPixels(pix, 0, w, 0, 0, w, h); - - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; - - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; - - int divsum = (div + 1) >> 1; - divsum *= divsum; - int dv[] = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); - } - - yw = yi = 0; - - int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; - } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } - } - ret.setPixels(pix, 0, w, 0, 0, w, h); - return ret; - } - - /** - * 保存图片 - * @param src 源图片 - * @param filePath The path of file. - * @param format 格式 - * @return true : 成功, false : 失败 - */ - public static boolean save(final Bitmap src, final String filePath, final CompressFormat format) { - return save(src, getFileByPath(filePath), format, false); - } - - /** - * 保存图片 - * @param src 源图片 - * @param file The file. - * @param format 格式 - * @return true : 成功, false : 失败 - */ - public static boolean save(final Bitmap src, final File file, final CompressFormat format) { - return save(src, file, format, false); - } - - /** - * 保存图片 - * @param src 源图片 - * @param filePath The path of file. - * @param format 格式 - * @param recycle 是否回收 - * @return true : 成功, false : 失败 - */ - public static boolean save(final Bitmap src, final String filePath, final CompressFormat format, final boolean recycle) { - return save(src, getFileByPath(filePath), format, recycle); - } - - /** - * 保存图片 - * @param src 源图片 - * @param file The file. - * @param format 格式 - * @param recycle 是否回收 - * @return true : 成功, false : 失败 - */ - public static boolean save(final Bitmap src, final File file, final CompressFormat format, final boolean recycle) { - if (isEmptyBitmap(src) || !createFileByDeleteOldFile(file)) return false; - OutputStream os = null; - boolean ret = false; - try { - os = new BufferedOutputStream(new FileOutputStream(file)); - ret = src.compress(format, 100, os); - if (recycle && !src.isRecycled()) src.recycle(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "save"); - } finally { - CloseUtils.closeIO(os); - } - return ret; - } - - /** - * 根据文件名判断文件是否为图片 - * @param file The file. - * @return true : 是, false : 否 - */ - public static boolean isImage(final File file) { - return file != null && isImage(file.getPath()); - } - - /** - * 根据文件名判断文件是否为图片 - * @param filePath The path of file. - * @return true : 是, false : 否 - */ - public static boolean isImage(final String filePath) { - String path = filePath.toUpperCase(); - return path.endsWith(".PNG") || path.endsWith(".JPG") || path.endsWith(".JPEG") || path.endsWith(".BMP") || path.endsWith(".GIF"); - } - - /** - * 获取图片类型 - * @param filePath The path of file. - * @return 图片类型 - */ - public static String getImageType(final String filePath) { - return getImageType(getFileByPath(filePath)); - } - - /** - * 获取图片类型 - * @param file The file. - * @return 图片类型 - */ - public static String getImageType(final File file) { - if (file == null) return null; - InputStream is = null; - try { - is = new FileInputStream(file); - return getImageType(is); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getImageType"); - return null; - } finally { - CloseUtils.closeIO(is); - } - } - - /** - * 流获取图片类型 - * @param is 图片输入流 - * @return 图片类型 - */ - public static String getImageType(final InputStream is) { - if (is == null) return null; - try { - byte[] bytes = new byte[8]; - return is.read(bytes, 0, 8) != -1 ? getImageType(bytes) : null; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getImageType"); - return null; - } - } - - /** - * 获取图片类型 - * @param bytes bitmap 的前 8 字节 - * @return 图片类型 - */ - public static String getImageType(final byte[] bytes) { - if (isJPEG(bytes)) return "JPEG"; - if (isGIF(bytes)) return "GIF"; - if (isPNG(bytes)) return "PNG"; - if (isBMP(bytes)) return "BMP"; - return null; - } - - /** - * 判断是否jpg图片 - * @param b - * @return - */ - private static boolean isJPEG(final byte[] b) { - return b.length >= 2 && (b[0] == (byte) 0xFF) && (b[1] == (byte) 0xD8); - } - - /** - * 判断是否gif图片 - * @param b - * @return - */ - private static boolean isGIF(final byte[] b) { - return b.length >= 6 && b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' && (b[4] == '7' || b[4] == '9') && b[5] == 'a'; - } - - /** - * 判断是否png图片 - * @param b - * @return - */ - private static boolean isPNG(final byte[] b) { - return b.length >= 8 - && (b[0] == (byte) 137 && b[1] == (byte) 80 - && b[2] == (byte) 78 && b[3] == (byte) 71 - && b[4] == (byte) 13 && b[5] == (byte) 10 - && b[6] == (byte) 26 && b[7] == (byte) 10); - } - - /** - * 判断是否bmp图片 - * @param b - * @return - */ - private static boolean isBMP(final byte[] b) { - return b.length >= 2 && (b[0] == 0x42) && (b[1] == 0x4d); - } - - /** - * 判断 bitmap 对象是否为空 - * @param src 源图片 - * @return true : 是, false : 否 - */ - private static boolean isEmptyBitmap(final Bitmap src) { - return src == null || src.getWidth() == 0 || src.getHeight() == 0; - } - - // ============== - // 下方和压缩有关 - // ============== - - /** - * 按缩放压缩 - * @param src 源图片 - * @param newWidth 新宽度 - * @param newHeight 新高度 - * @return 缩放压缩后的图片 - */ - public static Bitmap compressByScale(final Bitmap src, final int newWidth, final int newHeight) { - return scale(src, newWidth, newHeight, false); - } - - /** - * 按缩放压缩 - * @param src 源图片 - * @param newWidth 新宽度 - * @param newHeight 新高度 - * @param recycle 是否回收 - * @return 缩放压缩后的图片 - */ - public static Bitmap compressByScale(final Bitmap src, final int newWidth, final int newHeight, final boolean recycle) { - return scale(src, newWidth, newHeight, recycle); - } - - /** - * 按缩放压缩 - * @param src 源图片 - * @param scaleWidth 缩放宽度倍数 - * @param scaleHeight 缩放高度倍数 - * @return 缩放压缩后的图片 - */ - public static Bitmap compressByScale(final Bitmap src, final float scaleWidth, final float scaleHeight) { - return scale(src, scaleWidth, scaleHeight, false); - } - - /** - * 按缩放压缩 - * @param src 源图片 - * @param scaleWidth 缩放宽度倍数 - * @param scaleHeight 缩放高度倍数 - * @param recycle 是否回收 - * @return 缩放压缩后的图片 - */ - public static Bitmap compressByScale(final Bitmap src, final float scaleWidth, final float scaleHeight, final boolean recycle) { - return scale(src, scaleWidth, scaleHeight, recycle); - } - - /** - * 按质量压缩 - * @param src 源图片 - * @param quality 质量 - * @return 质量压缩后的图片 - */ - public static Bitmap compressByQuality(final Bitmap src, @IntRange(from = 0, to = 100) final int quality) { - return compressByQuality(src, quality, false); - } - - /** - * 按质量压缩 - * @param src 源图片 - * @param quality 质量 - * @param recycle 是否回收 - * @return 质量压缩后的图片 - */ - public static Bitmap compressByQuality(final Bitmap src, @IntRange(from = 0, to = 100) final int quality, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - src.compress(CompressFormat.JPEG, quality, baos); - byte[] bytes = baos.toByteArray(); - if (recycle && !src.isRecycled()) src.recycle(); - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - } - - /** - * 按质量压缩 - * @param src 源图片 - * @param maxByteSize 允许最大值字节数 - * @return 质量压缩压缩过的图片 - */ - public static Bitmap compressByQuality(final Bitmap src, final long maxByteSize) { - return compressByQuality(src, maxByteSize, false); - } - - /** - * 按质量压缩 - * @param src 源图片 - * @param maxByteSize 允许最大值字节数 - * @param recycle 是否回收 - * @return 质量压缩压缩过的图片 - */ - public static Bitmap compressByQuality(final Bitmap src, final long maxByteSize, final boolean recycle) { - if (isEmptyBitmap(src) || maxByteSize <= 0) return null; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - src.compress(CompressFormat.JPEG, 100, baos); - byte[] bytes; - if (baos.size() <= maxByteSize) {// 最好质量的不大于最大字节,则返回最佳质量 - bytes = baos.toByteArray(); - } else { - baos.reset(); - src.compress(CompressFormat.JPEG, 0, baos); - if (baos.size() >= maxByteSize) { // 最差质量不小于最大字节,则返回最差质量 - bytes = baos.toByteArray(); - } else { - // 二分法寻找最佳质量 - int st = 0; - int end = 100; - int mid = 0; - while (st < end) { - mid = (st + end) / 2; - baos.reset(); - src.compress(CompressFormat.JPEG, mid, baos); - int len = baos.size(); - if (len == maxByteSize) { - break; - } else if (len > maxByteSize) { - end = mid - 1; - } else { - st = mid + 1; - } - } - if (end == mid - 1) { - baos.reset(); - src.compress(CompressFormat.JPEG, st, baos); - } - bytes = baos.toByteArray(); - } - } - if (recycle && !src.isRecycled()) src.recycle(); - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length); - } - - /** - * 按采样大小压缩 - * @param src 源图片 - * @param sampleSize 采样率大小 - * @return 按采样率压缩后的图片 - */ - - public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize) { - return compressBySampleSize(src, sampleSize, false); - } - - /** - * 按采样大小压缩 - * @param src 源图片 - * @param sampleSize 采样率大小 - * @param recycle 是否回收 - * @return 按采样率压缩后的图片 - */ - public static Bitmap compressBySampleSize(final Bitmap src, final int sampleSize, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = sampleSize; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - src.compress(CompressFormat.JPEG, 100, baos); - byte[] bytes = baos.toByteArray(); - if (recycle && !src.isRecycled()) src.recycle(); - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - } - - /** - * 按采样大小压缩 - * @param src 源图片 - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return 按采样率压缩后的图片 - */ - public static Bitmap compressBySampleSize(final Bitmap src, final int maxWidth, final int maxHeight) { - return compressBySampleSize(src, maxWidth, maxHeight, false); - } - - /** - * 按采样大小压缩 - * @param src 源图片 - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @param recycle 是否回收 - * @return 按采样率压缩后的图片 - */ - public static Bitmap compressBySampleSize(final Bitmap src, final int maxWidth, final int maxHeight, final boolean recycle) { - if (isEmptyBitmap(src)) return null; - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - src.compress(CompressFormat.JPEG, 100, baos); - byte[] bytes = baos.toByteArray(); - BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); - options.inJustDecodeBounds = false; - if (recycle && !src.isRecycled()) src.recycle(); - return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - } - - /** - * 计算采样大小 - * @param options 选项 - * @param maxWidth 最大宽度 - * @param maxHeight 最大高度 - * @return 采样大小 - */ - private static int calculateInSampleSize(final BitmapFactory.Options options, final int maxWidth, final int maxHeight) { - int height = options.outHeight; - int width = options.outWidth; - int inSampleSize = 1; - while ((width >>= 1) >= maxWidth && (height >>= 1) >= maxHeight) { - inSampleSize <<= 1; - } - return inSampleSize; - } - - // = - - /** - * 获取文件 - * @param filePath - * @return - */ - private static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 判断文件是否存在,存在则在创建之前删除 - * @param file - * @return true : 创建成功, false : 创建失败 - */ - private static boolean createFileByDeleteOldFile(final File file) { - if (file == null) return false; - // 文件存在并且删除失败返回 false - if (file.exists() && !file.delete()) return false; - // 创建目录失败返回 false - if (!createOrExistsDir(file.getParentFile())) return false; - try { - return file.createNewFile(); - } catch (IOException e) { - LogPrintUtils.eTag(TAG, e, "createFileByDeleteOldFile"); - return false; - } - } - - /** - * 判断目录是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsDir(final File file) { - // 如果存在,是目录则返回 true,是文件则返回 false,不存在则返回是否创建成功 - return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/info/ApkInfoItem.java b/DevLibUtils/src/main/java/dev/utils/app/info/ApkInfoItem.java deleted file mode 100644 index 5707bbd116..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/info/ApkInfoItem.java +++ /dev/null @@ -1,213 +0,0 @@ -package dev.utils.app.info; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; -import android.text.TextUtils; -import android.text.format.Formatter; - -import java.io.File; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; -import java.util.ArrayList; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.R; -import dev.utils.app.AppCommonUtils; -import dev.utils.app.SignaturesUtils; -import dev.utils.common.FileUtils; - -/** - * detail: Apk 信息Item - * Created by Ttt - */ -public final class ApkInfoItem { - - // 日志Tag - private static final String TAG = ApkInfoItem.class.getSimpleName(); - // apk文件地址 - private String apkUri; - // App 信息实体类 - private AppInfoBean appInfoBean; - // App 参数集 - private ArrayList listKeyValues = new ArrayList<>(); - - private ApkInfoItem(){ - } - - public static ApkInfoItem obtain(String apkUri) throws Exception { - // 如果地址为null, 则直接不处理 - if (TextUtils.isEmpty(apkUri)){ - return null; - } - // 文件信息 - File apkFile = new File(apkUri); - // 如果文件不存在, 则不处理 - if (!apkFile.exists()){ - return null; - } - - // https://blog.csdn.net/sljjyy/article/details/17370665 - - // 获取上下文 - Context context = DevUtils.getContext(); - // 初始化包管理类 - PackageManager pManager = context.getPackageManager(); - // 获取对应的PackageInfo(原始的PackageInfo 获取 signatures 等于null,需要这样获取) - PackageInfo pInfo = pManager.getPackageArchiveInfo(apkUri, PackageManager.GET_ACTIVITIES); - // = 设置 apk 位置信息 = - ApplicationInfo appInfo = pInfo.applicationInfo; - /* 必须加这两句,不然下面icon获取是default icon而不是应用包的icon */ - appInfo.sourceDir = apkUri; - appInfo.publicSourceDir = apkUri; - // 初始化实体类 - ApkInfoItem appInfoItem = new ApkInfoItem(); - // 保存apk文件地址 - appInfoItem.apkUri = apkUri; - // 获取app 信息 - appInfoItem.appInfoBean = new AppInfoBean(pInfo, pManager); - // == 获取 == - // 格式化日期 - SimpleDateFormat dFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - // 获取签名信息 - Signature[] signatures = SignaturesUtils.getSignaturesFromApk(apkFile); - // =========== - // app 签名MD5 - String md5 = SignaturesUtils.signatureMD5(signatures); - // app SHA1 - String sha1 = SignaturesUtils.signatureSHA1(signatures); - // app SHA256 - String sha256 = SignaturesUtils.signatureSHA256(signatures); - // app 最低支持版本 - int minSdkVersion = -1; - // 属于7.0以上才有的方法 - if (AppCommonUtils.isN()){ - minSdkVersion = pInfo.applicationInfo.minSdkVersion; - } - // app 兼容sdk版本 - int targetSdkVersion = pInfo.applicationInfo.targetSdkVersion; - // 获取 app 安装包大小 - String apkLength = Formatter.formatFileSize(DevUtils.getContext(), FileUtils.getFileLength(apkFile)); - - // = 临时数据存储 = - // 是否保存 - boolean isError = false; - // 临时签名信息 - ArrayList listTemps = new ArrayList<>(); - - // Android的APK应用签名机制以及读取签名的方法 - // http://www.jb51.net/article/79894.htm - try { - // 获取证书对象 - X509Certificate cert = SignaturesUtils.getX509Certificate(signatures); - // 设置有效期 - StringBuilder sbEffective = new StringBuilder(); - sbEffective.append(dFormat.format(cert.getNotBefore())); - sbEffective.append(" " + context.getString(R.string.dev_str_to) + " "); // 至 - sbEffective.append(dFormat.format(cert.getNotAfter())); - sbEffective.append("\n\n"); - sbEffective.append(cert.getNotBefore()); - sbEffective.append(" " + context.getString(R.string.dev_str_to) + " "); - sbEffective.append(cert.getNotAfter()); - // 获取有效期 - String effective = sbEffective.toString(); - // 证书是否过期 true = 过期,false = 未过期 - boolean isEffective = false; - try { - cert.checkValidity(); - // CertificateExpiredException - 如果证书已过期。 - // CertificateNotYetValidException - 如果证书不再有效。 - } catch (CertificateExpiredException ce) { - isEffective = true; - } catch (CertificateNotYetValidException ce) { - isEffective = true; - } - // 判断是否过期 - String isEffectiveState = isEffective ? context.getString(R.string.dev_str_overdue) : context.getString(R.string.dev_str_notoverdue); - // 证书发布方 - String principal = cert.getIssuerX500Principal().toString(); - // 证书版本号 - String version = cert.getVersion() + ""; - // 证书算法名称 - String sigalgname = cert.getSigAlgName(); - // 证书算法OID - String sigalgoid = cert.getSigAlgOID(); - // 证书机器码 - String serialnumber = cert.getSerialNumber().toString(); - // 证书 DER编码 - String dercode = SignaturesUtils.toHexString(cert.getTBSCertificate()); - // 获取有效期 - listTemps.add(KeyValueBean.get(R.string.dev_str_effective, effective)); - // 判断是否过期 - listTemps.add(KeyValueBean.get(R.string.dev_str_iseffective, isEffectiveState)); - // 证书发布方 - listTemps.add(KeyValueBean.get(R.string.dev_str_principal, principal)); - // 证书版本号 - listTemps.add(KeyValueBean.get(R.string.dev_str_version, version)); - // 证书算法名称 - listTemps.add(KeyValueBean.get(R.string.dev_str_sigalgname, sigalgname)); - // 证书算法OID - listTemps.add(KeyValueBean.get(R.string.dev_str_sigalgoid, sigalgoid)); - // 证书机器码 - listTemps.add(KeyValueBean.get(R.string.dev_str_dercode, serialnumber)); - // 证书 DER编码 - listTemps.add(KeyValueBean.get(R.string.dev_str_serialnumber, dercode)); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "obtain"); - isError = true; - } - - // ================ - // === 保存集合 === - // ================ - // app 包名 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_packname, appInfoItem.appInfoBean.getAppPackName())); - // 没报错才存储 MD5 信息 - if (!isError) { - // app 签名MD5 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_md5, md5)); - } - // app 版本号 - 主要用于app内部版本判断 int 类型 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_version_code, appInfoItem.appInfoBean.getVersionCode() + "")); - // app 版本名 - 主要用于对用户显示版本信息 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_version_name, appInfoItem.appInfoBean.getVersionName())); - // 安装包地址 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_apk_uri, apkUri)); - // 没报错才存储 SHA 信息 - if (!isError) { - // app SHA1 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sha1, sha1)); - // app SHA256. - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sha256, sha256)); - } - // app 最低支持版本 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_minsdkversion, minSdkVersion + " ( " + AppCommonUtils.convertSDKVersion(minSdkVersion) + "+ )")); - // app 兼容sdk版本 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_targetsdkversion, targetSdkVersion + " ( " + AppCommonUtils.convertSDKVersion(targetSdkVersion) + "+ )")); - // 获取 apk 大小 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_apk_length, apkLength)); - // 没报错才存储 其他签名信息 - if (!isError) { - appInfoItem.listKeyValues.addAll(listTemps); - } - // 返回实体类 - return appInfoItem; - } - - public String getApkUri() { - return apkUri; - } - - public AppInfoBean getAppInfoBean() { - return appInfoBean; - } - - public ArrayList getListKeyValues() { - return listKeyValues; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoBean.java b/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoBean.java deleted file mode 100644 index a520efaa7f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoBean.java +++ /dev/null @@ -1,236 +0,0 @@ -package dev.utils.app.info; - -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; -import dev.utils.common.FileUtils; - -/** - * detail: app 信息实体类 - * Created by Ttt - */ -public class AppInfoBean { - - // https://my.oschina.net/orgsky/blog/368768 - - // 日志Tag - private static final String TAG = AppInfoBean.class.getSimpleName(); - // app 包名 - private String appPackName; - // app 名 - private String appName; - // app 图标 - private transient Drawable appIcon; - // App 类型 - private AppType appType; - // 获取版本号 - private int versionCode; - // 获取版本名 - private String versionName; - // app 首次安装时间 - private long firstInstallTime; - // 获取最后一次更新时间 - private long lastUpdateTime; - // 获取 app 地址 - private String sourceDir; - // APK 大小 - private long apkSize; - // 申请的权限 - private String [] apkPermissionsArys; - - /** - * 通过 apk路径 初始化 App 信息实体类 - * @param apkUri apk路径 - */ - public static AppInfoBean obtainUri(String apkUri){ - try { - // https://blog.csdn.net/sljjyy/article/details/17370665 - PackageManager pManager = DevUtils.getApplication().getPackageManager(); - PackageInfo pInfo = pManager.getPackageArchiveInfo(apkUri, PackageManager.GET_ACTIVITIES); - // = 设置 apk 位置信息 = - ApplicationInfo appInfo = pInfo.applicationInfo; - /* 必须加这两句,不然下面icon获取是default icon而不是应用包的icon */ - appInfo.sourceDir = apkUri; - appInfo.publicSourceDir = apkUri; - return new AppInfoBean(pInfo, pManager); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "obtainUri"); - } - return null; - } - - /** - * 通过包名 初始化 App 信息实体类 - * @param pckName 包名 - */ - public static AppInfoBean obtainPck(String pckName){ - try { - // https://blog.csdn.net/sljjyy/article/details/17370665 - PackageManager pManager = DevUtils.getApplication().getPackageManager(); - // 获取对应的PackageInfo(原始的PackageInfo 获取 signatures 等于null,需要这样获取) - PackageInfo pInfo = pManager.getPackageInfo(pckName, PackageManager.GET_SIGNATURES); // 64 - // 返回app信息 - return new AppInfoBean(pInfo, pManager); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "obtainPck"); - } - return null; - } - - /** - * 初始化当前 App 信息实体类 - */ - public static AppInfoBean obtain(){ - try { - // https://blog.csdn.net/sljjyy/article/details/17370665 - PackageManager pManager = DevUtils.getApplication().getPackageManager(); - // 获取对应的PackageInfo(原始的PackageInfo 获取 signatures 等于null,需要这样获取) - PackageInfo pInfo = pManager.getPackageInfo(DevUtils.getContext().getPackageName(), PackageManager.GET_SIGNATURES); // 64 - // 返回app信息 - return new AppInfoBean(pInfo, pManager); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "obtain"); - } - return null; - } - - /** - * 初始化 App 信息实体类 - * @param pInfo - * @param pManager - */ - public AppInfoBean(PackageInfo pInfo, PackageManager pManager){ - // app 包名 - appPackName = pInfo.applicationInfo.packageName; - // app 名 - appName = pManager.getApplicationLabel(pInfo.applicationInfo).toString(); - // app 图标 - appIcon = pManager.getApplicationIcon(pInfo.applicationInfo); - // 获取App 类型 - appType = AppInfoBean.getAppType(pInfo); - // 获取版本号 - versionCode = pInfo.versionCode; - // 获取版本名 - versionName = pInfo.versionName; - // app 首次安装时间 - firstInstallTime = pInfo.firstInstallTime; - // 获取最后一次更新时间 - lastUpdateTime = pInfo.lastUpdateTime; - // 获取 app 地址 - sourceDir = pInfo.applicationInfo.sourceDir; - // 获取 APK 大小 - apkSize = FileUtils.getFileLength(sourceDir); - try { - // 获取权限 - apkPermissionsArys = pInfo.requestedPermissions; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "AppInfoBean"); - } - } - - /** - * 获取App 包名 - * @return - */ - public String getAppPackName() { - return appPackName; - } - - /** - * 获取App 名 - * @return - */ - public String getAppName() { - return appName; - } - - /** - * 获取App 图标 - * @return - */ - public Drawable getAppIcon() { - return appIcon; - } - - /** - * 获取 App 类型 - * @return - */ - public AppType getAppType() { - return appType; - } - - // = - - /** App 类型 */ - public enum AppType { - - USER, // 用户 App - - SYSTEM, // 系统 App - - ALL, // 全部 App - } - - /** - * 获取App 类型 - * @param pInfo - * @return - */ - public static AppType getAppType(PackageInfo pInfo){ - if (!isSystemApp(pInfo) && !isSystemUpdateApp(pInfo)){ - return AppType.USER; - } - return AppType.SYSTEM; - } - - /** - * 表示系统程序 - * @param pInfo - * @return - */ - public static boolean isSystemApp(PackageInfo pInfo) { - return ((pInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); - } - - /** - * 表示系统程序被手动更新后,也成为第三方应用程序 - * @param pInfo - * @return - */ - public static boolean isSystemUpdateApp(PackageInfo pInfo) { - return ((pInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); - } - - public int getVersionCode() { - return versionCode; - } - - public String getVersionName() { - return versionName; - } - - public long getFirstInstallTime() { - return firstInstallTime; - } - - public long getLastUpdateTime() { - return lastUpdateTime; - } - - public String getSourceDir() { - return sourceDir; - } - - public long getApkSize() { - return apkSize; - } - - public String[] getApkPermissionsArys() { - return apkPermissionsArys; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoItem.java b/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoItem.java deleted file mode 100644 index 1c9b566b21..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoItem.java +++ /dev/null @@ -1,166 +0,0 @@ -package dev.utils.app.info; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.text.TextUtils; -import android.text.format.Formatter; - -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; -import java.util.ArrayList; - -import dev.DevUtils; -import dev.utils.R; -import dev.utils.app.AppCommonUtils; -import dev.utils.app.SignaturesUtils; -import dev.utils.common.FileUtils; - -/** - * detail: App 信息Item - * Created by Ttt - */ -public final class AppInfoItem { - - // App 信息实体类 - private AppInfoBean appInfoBean; - // App 参数集 - private ArrayList listKeyValues = new ArrayList<>(); - - private AppInfoItem(){ - } - - public static AppInfoItem obtain(String packName) throws Exception { - // 如果包名为null, 则直接不处理 - if (TextUtils.isEmpty(packName)){ - return null; - } - // 获取上下文 - Context context = DevUtils.getContext(); - // 初始化包管理类 - PackageManager pManager = context.getPackageManager(); - // 获取对应的PackageInfo(原始的PackageInfo 获取 signatures 等于null,需要这样获取) - PackageInfo pInfo = pManager.getPackageInfo(packName, PackageManager.GET_SIGNATURES); // 64 - // 初始化实体类 - AppInfoItem appInfoItem = new AppInfoItem(); - // 获取app 信息 - appInfoItem.appInfoBean = new AppInfoBean(pInfo, pManager); - // == 获取 == - // 格式化日期 - SimpleDateFormat dFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - // =========== - // app 签名MD5 - String md5 = SignaturesUtils.signatureMD5(pInfo.signatures); - // app SHA1 - String sha1 = SignaturesUtils.signatureSHA1(pInfo.signatures); - // app SHA256 - String sha256 = SignaturesUtils.signatureSHA256(pInfo.signatures); - // app 首次安装时间 - String firstInstallTime = dFormat.format(pInfo.firstInstallTime); - // 获取最后一次更新时间 - String lastUpdateTime = dFormat.format(pInfo.lastUpdateTime); - // app 最低支持版本 - int minSdkVersion = -1; - // 属于7.0以上才有的方法 - if (AppCommonUtils.isN()){ - minSdkVersion = pInfo.applicationInfo.minSdkVersion; - } - // app 兼容sdk版本 - int targetSdkVersion = pInfo.applicationInfo.targetSdkVersion; - // 获取 app 安装包大小 - String apkLength = Formatter.formatFileSize(DevUtils.getContext(), FileUtils.getFileLength(appInfoItem.appInfoBean.getSourceDir())); - // 获取证书对象 - X509Certificate cert = SignaturesUtils.getX509Certificate(pInfo.signatures); - // 设置有效期 - StringBuilder sbEffective = new StringBuilder(); - sbEffective.append(dFormat.format(cert.getNotBefore())); - sbEffective.append(" " + context.getString(R.string.dev_str_to) + " "); // 至 - sbEffective.append(dFormat.format(cert.getNotAfter())); - sbEffective.append("\n\n"); - sbEffective.append(cert.getNotBefore()); - sbEffective.append(" " + context.getString(R.string.dev_str_to) + " "); - sbEffective.append(cert.getNotAfter()); - // 获取有效期 - String effective = sbEffective.toString(); - // 证书是否过期 true = 过期,false = 未过期 - boolean isEffective = false; - try { - cert.checkValidity(); - // CertificateExpiredException - 如果证书已过期。 - // CertificateNotYetValidException - 如果证书不再有效。 - } catch (CertificateExpiredException ce) { - isEffective = true; - } catch (CertificateNotYetValidException ce) { - isEffective = true; - } - // 判断是否过期 - String isEffectiveState = isEffective ? context.getString(R.string.dev_str_overdue) : context.getString(R.string.dev_str_notoverdue); - // 证书发布方 - String principal = cert.getIssuerX500Principal().toString(); - // 证书版本号 - String version = cert.getVersion() + ""; - // 证书算法名称 - String sigalgname = cert.getSigAlgName(); - // 证书算法OID - String sigalgoid = cert.getSigAlgOID(); - // 证书机器码 - String serialnumber = cert.getSerialNumber().toString(); - // 证书 DER编码 - String dercode = SignaturesUtils.toHexString(cert.getTBSCertificate()); - - // ================ - // === 保存集合 === - // ================ - // app 包名 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_packname, appInfoItem.appInfoBean.getAppPackName())); - // app 签名MD5 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_md5, md5)); - // app 版本号 - 主要用于app内部版本判断 int 类型 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_version_code, appInfoItem.appInfoBean.getVersionCode() + "")); - // app 版本名 - 主要用于对用户显示版本信息 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_version_name, appInfoItem.appInfoBean.getVersionName())); - // app SHA1 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sha1, sha1)); - // app SHA256. - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sha256, sha256)); - // app 首次安装时间 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_first_install_time, firstInstallTime)); - // 获取最后一次更新时间 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_last_update_time, lastUpdateTime)); - // app 最低支持版本 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_minsdkversion, minSdkVersion + " ( " + AppCommonUtils.convertSDKVersion(minSdkVersion) + "+ )")); - // app 兼容sdk版本 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_targetsdkversion, targetSdkVersion + " ( " + AppCommonUtils.convertSDKVersion(targetSdkVersion) + "+ )")); - // 获取 apk 大小 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_apk_length, apkLength)); - // 获取有效期 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_effective, effective)); - // 判断是否过期 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_iseffective, isEffectiveState)); - // 证书发布方 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_principal, principal)); - // 证书版本号 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_version, version)); - // 证书算法名称 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sigalgname, sigalgname)); - // 证书算法OID - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_sigalgoid, sigalgoid)); - // 证书机器码 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_dercode, serialnumber)); - // 证书 DER编码 - appInfoItem.listKeyValues.add(KeyValueBean.get(R.string.dev_str_serialnumber, dercode)); - - // 返回实体类 - return appInfoItem; - } - - public AppInfoBean getAppInfoBean() { - return appInfoBean; - } - - public ArrayList getListKeyValues() { - return listKeyValues; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoUtils.java b/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoUtils.java deleted file mode 100644 index dcf08fc0e5..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/info/AppInfoUtils.java +++ /dev/null @@ -1,200 +0,0 @@ -package dev.utils.app.info; - -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PermissionGroupInfo; -import android.content.pm.PermissionInfo; - -import java.util.ArrayList; -import java.util.List; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: App 信息获取工具类 - * Created by Ttt - */ -public final class AppInfoUtils { - - private AppInfoUtils(){ - } - - // 日志Tag - private static final String TAG = AppInfoUtils.class.getSimpleName(); - - /** - * 通过 apk路径 初始化 App 信息实体类 - * @param apkUri apk路径 - */ - public static AppInfoBean obtainUri(String apkUri){ - return AppInfoBean.obtainUri(apkUri); - } - - /** - * 通过包名 初始化 App 信息实体类 - * @param pckName 包名 - */ - public static AppInfoBean obtainPck(String pckName){ - return AppInfoBean.obtainPck(pckName); - } - - /** - * 初始化当前 App 信息实体类 - */ - public static AppInfoBean obtain(){ - return AppInfoBean.obtain(); - } - - // ================== - // == 获取详细信息 == - // ================== - - /** - * 获取 apk 详细信息 - * @param apkUri - * @return - */ - public static ApkInfoItem getApkInfoItem(String apkUri){ - try { - return ApkInfoItem.obtain(apkUri); - } catch (Exception e){ - return null; - } - } - - /** - * 获取 app 详细信息 - * @param pckName - * @return - */ - public static AppInfoItem getAppInfoItem(String pckName){ - try { - return AppInfoItem.obtain(pckName); - } catch (Exception e){ - return null; - } - } - - /** - * 获取 app 详细信息 - * @return - */ - public static AppInfoItem getAppInfoItem(){ - return getAppInfoItem(DevUtils.getContext().getPackageName()); - } - - // = - - /** - * 获取全部App 列表 - * @return - */ - public static ArrayList getAppLists() { - return getAppLists(AppInfoBean.AppType.ALL); - } - - /** - * 获取 App 列表 - * @param appType app类型 - * @return - */ - public static ArrayList getAppLists(AppInfoBean.AppType appType) { - // App信息 - ArrayList listApps = new ArrayList<>(); - // 防止为null - if (appType != null) { - // 管理应用程序包 - PackageManager pManager = DevUtils.getContext().getPackageManager(); - // 获取手机内所有应用 - List packlist = pManager.getInstalledPackages(0); - // 判断是否属于添加全部 - if (appType == AppInfoBean.AppType.ALL){ - // 遍历 app 列表 - for (int i = 0, len = packlist.size(); i < len; i++) { - PackageInfo pInfo = packlist.get(i); - // 添加符合条件的 App 应用信息 - listApps.add(new AppInfoBean(pInfo, pManager)); - } - } else { - // 遍历 app 列表 - for (int i = 0, len = packlist.size(); i < len; i++) { - PackageInfo pInfo = packlist.get(i); - // 获取app 类型 - AppInfoBean.AppType cAppType = AppInfoBean.getAppType(pInfo); - // 判断类型 - if (appType == cAppType){ - // 添加符合条件的 App 应用信息 - listApps.add(new AppInfoBean(pInfo, pManager)); - } -// // 判断类型 -// switch (cAppType) { -// case USER: -// // 添加符合条件的 App 应用信息 -// listApps.add(new AppInfoBean(pInfo, pManager)); -// break; -// case SYSTEM: -// // 添加符合条件的 App 应用信息 -// listApps.add(new AppInfoBean(pInfo, pManager)); -// break; -// } - } - } - } - return listApps; - } - - // = - - /** - * 获取 APK 权限 - * @param pckName - * https://www.cnblogs.com/leaven/p/5485864.html - */ - public static String [] getApkPermission(String pckName){ - try { - PackageManager packageManager = DevUtils.getApplication().getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(pckName, PackageManager.GET_PERMISSIONS); - return packageInfo.requestedPermissions; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getUsesPermission"); - } - return null; - } - - /** - * 打印 APK 权限 - * @param pckName - * https://www.cnblogs.com/leaven/p/5485864.html - */ - public static void printApkPermission(String pckName){ - try { - PackageManager packageManager = DevUtils.getApplication().getPackageManager(); - PackageInfo packageInfo = packageManager.getPackageInfo(pckName, PackageManager.GET_PERMISSIONS); - String [] usesPermissionsArray = packageInfo.requestedPermissions; - for (int i = 0; i < usesPermissionsArray.length; i++) { - // 获取每个权限的名字,如:android.permission.INTERNET - String usesPermissionName = usesPermissionsArray[i]; - LogPrintUtils.dTag(TAG, "usesPermissionName = " + usesPermissionName); - - // 通过usesPermissionName获取该权限的详细信息 - PermissionInfo permissionInfo = packageManager.getPermissionInfo(usesPermissionName, 0); - - // 获得该权限属于哪个权限组,如:网络通信 - PermissionGroupInfo permissionGroupInfo = packageManager.getPermissionGroupInfo(permissionInfo.group, 0); - LogPrintUtils.dTag(TAG, "permissionGroup = " + permissionGroupInfo.loadLabel(packageManager).toString()); - - // 获取该权限的标签信息,比如:完全的网络访问权限 - String permissionLabel = permissionInfo.loadLabel(packageManager).toString(); - LogPrintUtils.dTag(TAG, "permissionLabel = " + permissionLabel); - - // 获取该权限的详细描述信息,比如:允许该应用创建网络套接字和使用自定义网络协议 - // 浏览器和其他某些应用提供了向互联网发送数据的途径,因此应用无需该权限即可向互联网发送数据. - String permissionDescription = permissionInfo.loadDescription(packageManager).toString(); - LogPrintUtils.dTag(TAG, "permissionDescription = " + permissionDescription); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "printApkPermission"); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/info/KeyValueBean.java b/DevLibUtils/src/main/java/dev/utils/app/info/KeyValueBean.java deleted file mode 100644 index b02baae48a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/info/KeyValueBean.java +++ /dev/null @@ -1,44 +0,0 @@ -package dev.utils.app.info; - -import android.support.annotation.StringRes; - -import dev.DevUtils; - -/** - * detail: 键对值 实体类 - * Created by Ttt - */ -public class KeyValueBean { - - // 键 - 提示 - protected String key = ""; - // 值 - 参数值 - protected String value = ""; - - /** - * 构造函数 - * @param key - * @param value - */ - public KeyValueBean(String key, String value) { - this.key = key; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return key + ": " + value; - } - - public static KeyValueBean get(@StringRes int resId, String value) { - return new KeyValueBean(DevUtils.getContext().getString(resId), value); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/DevLogger.java b/DevLibUtils/src/main/java/dev/utils/app/logger/DevLogger.java deleted file mode 100644 index 7138344a7d..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/DevLogger.java +++ /dev/null @@ -1,246 +0,0 @@ -package dev.utils.app.logger; - -/** - * detail: 日志操作类(对外公开直接调用) - * Created by Ttt - */ -public final class DevLogger { - - private DevLogger() { - } - - /** 包下LoggerPrinter类持有对象 */ - private static final IPrinter printer = new LoggerPrinter(); - - // -- 配置方法 -- - - /** - * 使用单次其他日志配置 - * @param lConfig - * @return - */ - public static IPrinter other(LogConfig lConfig) { - return printer.other(lConfig); - } - - /** - * 获取日志配置信息 - * @return - */ - public static LogConfig getLogConfig() { - return printer.getLogConfig(); - } - - /** - * 初始化日志配置信息(可以不调用,使用了App默认配置) - * @return - */ - public static LogConfig init() { - return printer.init(); - } - - /** - * 手动改变日志配置信息(非单次,一直持续) - * @param lConfig - */ - public static void init(LogConfig lConfig) { - printer.init(lConfig); - } - - // ========= 使用默认TAG ========= - - // -- 日志打印方法 -- - /** - * Log.DEBUG - * @param message - * @param args - */ - public static void d(String message, Object... args) { - printer.d(message, args); - } - - /** - * Log.ERROR - * @param message - * @param args - */ - public static void e(String message, Object... args) { - printer.e(message, args); - } - - /** - * Log.ERROR - * @param throwable - */ - public static void e(Throwable throwable) { - printer.e(throwable, null); - } - - /** - * Log.ERROR - * @param throwable - * @param message - * @param args - */ - public static void e(Throwable throwable, String message, Object... args) { - printer.e(throwable, message, args); - } - - /** - * Log.WARN - * @param message - * @param args - */ - public static void w(String message, Object... args) { - printer.w(message, args); - } - - /** - * Log.INFO - * @param message - * @param args - */ - public static void i(String message, Object... args) { - printer.i(message, args); - } - - /** - * Log.VERBOSE - * @param message - * @param args - */ - public static void v(String message, Object... args) { - printer.v(message, args); - } - - /** - * Log.ASSERT - * @param message - * @param args - */ - public static void wtf(String message, Object... args) { - printer.wtf(message, args); - } - - // -- 其他特殊方法 -- - - /** - * 格式化Json格式数据,并打印 - * @param json - */ - public static void json(String json) { - printer.json(json); - } - - /** - * 格式化XML格式数据,并打印 - * @param xml - */ - public static void xml(String xml) { - printer.xml(xml); - } - - - // ========= 使用自定义TAG ========= - - // -- 日志打印方法 -- - /** - * Log.DEBUG - * @param tag - * @param message - * @param args - */ - public static void dTag(String tag, String message, Object... args) { - printer.dTag(tag, message, args); - } - - /** - * Log.ERROR - * @param tag - * @param message - * @param args - */ - public static void eTag(String tag, String message, Object... args) { - printer.eTag(tag, message, args); - } - - /** - * Log.ERROR - * @param tag - * @param throwable - * @param message - * @param args - */ - public static void eTag(String tag, Throwable throwable, String message, Object... args) { - printer.eTag(tag, throwable, message, args); - } - - /** - * Log.ERROR - * @param tag - * @param throwable - */ - public static void eTag(String tag, Throwable throwable) { - printer.eTag(tag, throwable, null); - } - - /** - * Log.WARN - * @param tag - * @param message - * @param args - */ - public static void wTag(String tag, String message, Object... args) { - printer.wTag(tag, message, args); - } - - /** - * Log.INFO - * @param tag - * @param message - * @param args - */ - public static void iTag(String tag, String message, Object... args) { - printer.iTag(tag, message, args); - } - - /** - * Log.VERBOSE - * @param tag - * @param message - * @param args - */ - public static void vTag(String tag, String message, Object... args) { - printer.vTag(tag, message, args); - } - - /** - * Log.ASSERT - * @param tag - * @param message - * @param args - */ - public static void wtfTag(String tag, String message, Object... args) { - printer.wtfTag(tag, message, args); - } - - // -- 其他特殊方法 -- - - /** - * 格式化Json格式数据,并打印 - * @param tag - * @param json - */ - public static void jsonTag(String tag, String json) { - printer.jsonTag(tag, json); - } - - /** - * 格式化XML格式数据,并打印 - * @param tag - * @param xml - */ - public static void xmlTag(String tag, String xml) { - printer.xmlTag(tag, xml); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/DevLoggerUtils.java b/DevLibUtils/src/main/java/dev/utils/app/logger/DevLoggerUtils.java deleted file mode 100644 index 1e484bcc81..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/DevLoggerUtils.java +++ /dev/null @@ -1,224 +0,0 @@ -package dev.utils.app.logger; - -import android.content.Context; -import android.text.TextUtils; - -import java.io.File; - -import dev.utils.LogPrintUtils; - -/** - * detail: 日志操作工具类 - * Created by Ttt - */ -public final class DevLoggerUtils { - - private DevLoggerUtils(){ - } - - // 日志TAG - private static final String TAG = DevLoggerUtils.class.getSimpleName(); - - /** - * App初始化调用方法(获取版本号) - * @param context 上下文 - */ - public static void appInit(Context context) { - // 保存APP版本信息 - Utils.appInit(context); - } - - // ================= 内部快速初始化LogConfig ================== - - /** - * 获取发布Log配置(打印线程信息,显示方法总数3,从0开始,不进行排序, 默认属于ERROR级别日志) - * @param tag - * @return - */ - public static LogConfig getReleaseLogConfig(String tag) { - return getLogConfig(tag, 3, 0, false, true, false, LogLevel.ERROR); - } - - /** - * 获取发布Log配置(打印线程信息,显示方法总数3,从0开始,不进行排序) - * @param tag - * @param lLevel 日志级别(分四种) - LogLevel - * @return - */ - public static LogConfig getReleaseLogConfig(String tag, LogLevel lLevel) { - return getLogConfig(tag, 3, 0, false, true, false, lLevel); - } - - // -- - - /** - * 获取调试Log配置(打印线程信息,显示方法总数3,从0开始,不进行排序, 默认属于ERROR级别日志) - * @param tag - * @return - */ - public static LogConfig getDebugLogConfig(String tag) { - return getLogConfig(tag, 3, 0, false, true, false, LogLevel.DEBUG); - } - - /** - * 获取调试Log配置(打印线程信息,显示方法总数3,从0开始,进行排序) - * @param tag - * @param lLevel 日志级别(分四种) - LogLevel - * @return - */ - public static LogConfig getDebugLogConfig(String tag, LogLevel lLevel) { - return getLogConfig(tag, 3, 0, false, true, false, lLevel); - } - - // -- - - /** - * 获取Log配置(打印线程信息,显示方法总数3,从0开始,并且美化日志信息, 默认属于DEBUG级别日志) - * @param tag 日志Tag - * @return - */ - public static LogConfig getSortLogConfig(String tag) { - return getLogConfig(tag, 3, 0, false, true, true, LogLevel.DEBUG); - } - - /** - * 获取Log配置(打印线程信息,显示方法总数3,从0开始,并且美化日志信息) - * @param tag 日志Tag - * @param lLevel 日志级别(分四种) - LogLevel - * @return - */ - public static LogConfig getSortLogConfig(String tag, LogLevel lLevel) { - return getLogConfig(tag, 3, 0, false, true, true, lLevel); - } - - // -- - - /** - * 获取Log配置 - * @param tag 日志Tag - * @param mCount 显示的方法总数(推荐3) - * @param mOffset 方法偏移索引(从第几个方法开始打印,默认推荐0) - * @param mAll 是否打印全部方法 - * @param tInfo 是否显示线程信息 - * @param isSortLog 是否排序日志(美化) - * @param lLevel 日志级别(分四种) - LogLevel - * @return - */ - public static LogConfig getLogConfig(String tag, int mCount, int mOffset, boolean mAll, boolean tInfo, boolean isSortLog, LogLevel lLevel) { - // 生成默认配置信息 - LogConfig logConfig = new LogConfig(); - // 堆栈方法总数(显示经过的方法) - logConfig.methodCount = mCount; - // 堆栈方法索引偏移(0 = 最新经过调用的方法信息,偏移则往上推,如 1 = 倒数第二条经过调用的方法信息) - logConfig.methodOffset = mOffset; - // 是否输出全部方法(在特殊情况下,如想要打印全部经过的方法,但是不知道经过的总数) - logConfig.isOutputMethodAll = mAll; - // 显示日志线程信息(特殊情况,显示经过的线程信息,具体情况如上) - logConfig.isDisplayThreadInfo = tInfo; - // 是否排序日志(格式化) - logConfig.isSortLog = isSortLog; - // 日志级别 - logConfig.logLevel = lLevel; - // 设置Tag(特殊情况使用,不使用全部的Tag时,如单独输出在某个Tag下) - logConfig.tag = tag; - // 返回日志配置 - return logConfig; - } - - // =============== 错误日志处理 ================== - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param fPath 保存路径 + 文件名(含后缀) - * @param isNewLines 是否换行 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - */ - public static boolean saveErrorLog(Throwable ex, String fPath, boolean isNewLines, String... eHint) { - return saveErrorLog(ex,null, null, fPath, isNewLines, eHint); - } - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 + 文件名(含后缀) - * @param isNewLines 是否换行 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - */ - public static boolean saveErrorLog(Throwable ex, String head, String bottom, String fPath, boolean isNewLines, String... eHint) { - if(TextUtils.isEmpty(fPath)) { - return false; - } - try { - File file = new File(fPath); - // 获取文件名 - String fName = file.getName(); - // 判断是否这个文件名结尾 - if(fPath.endsWith(fName)) { - // 重新裁剪 - fPath = fPath.substring(0, fPath.length() - fName.length()); - // 进行保存 - return saveErrorLog(ex, head, bottom, fPath, fName, isNewLines, eHint); - } else { - // 进行保存 - return saveErrorLog(ex, head, bottom, fPath, fPath, isNewLines, eHint); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveErrorLog"); - } - return false; - } - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param isNewLines 是否换行 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveErrorLog(Throwable ex, String head, String bottom, String fPath, String fName, boolean isNewLines, String... eHint) { - if(TextUtils.isEmpty(fPath)) { - return false; - } else if (TextUtils.isEmpty(fName)) { - return false; - } - return Utils.saveErrorLog(ex, head, bottom, fPath, fName, isNewLines, eHint); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String fPath, String fName, String... eHint){ - return saveLog(log, null, null, fPath, fName, eHint); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String head, String bottom, String fPath, String fName, String... eHint) { - if(TextUtils.isEmpty(fPath)) { - return false; - } else if (TextUtils.isEmpty(fName)) { - return false; - } - return Utils.saveLog(log, head, bottom, fPath, fName, eHint); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/IPrinter.java b/DevLibUtils/src/main/java/dev/utils/app/logger/IPrinter.java deleted file mode 100644 index 13ef2528d6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/IPrinter.java +++ /dev/null @@ -1,109 +0,0 @@ -package dev.utils.app.logger; - -/** - * detail: 日志接口 - * Created by Ttt - */ -public interface IPrinter { - - // ==================== - // ----- 配置方法 ----- - // ==================== - - /** - * 使用单次其他日志配置 - * @param lConfig 日志配置 - * @return - */ - IPrinter other(LogConfig lConfig); - - /** - * 获取日志配置信息 - * @return 日志配置 - */ - LogConfig getLogConfig(); - - /** - * 初始化日志配置信息(可以不调用,使用了App默认配置) - * @return 日志配置 - */ - LogConfig init(); - - /** - * 手动改变日志配置信息(非单次,一直持续) - * @param lConfig 日志配置 - */ - void init(LogConfig lConfig); - - // ========= 使用默认TAG ========= - - // -- 日志打印方法 -- - - /** Log.DEBUG */ - void d(String message, Object... args); - - /** Log.ERROR */ - void e(String message, Object... args); - - /** Log.ERROR,并且输出错误信息Throwable */ - void e(Throwable throwable); - - /** Log.ERROR,并且输出错误信息Throwable */ - void e(Throwable throwable, String message, Object... args); - - /** Log.WARN */ - void w(String message, Object... args); - - /** Log.INFO */ - void i(String message, Object... args); - - /** Log.VERBOSE */ - void v(String message, Object... args); - - /** Log.ASSERT */ - void wtf(String message, Object... args); - - // -- 其他特殊方法 -- - - /** 格式化Json格式数据,并打印 */ - void json(String json); - - /** 格式化xml格式数据,并打印 */ - void xml(String xml); - - // ========= 使用自定义TAG ========= - - // -- 日志打印方法 -- - - /** Log.DEBUG */ - void dTag(String tag, String message, Object... args); - - /** Log.ERROR */ - void eTag(String tag, String message, Object... args); - - /** Log.ERROR,并且输出错误信息Throwable */ - void eTag(String tag, Throwable throwable); - - /** Log.ERROR,并且输出错误信息Throwable */ - void eTag(String tag, Throwable throwable, String message, Object... args); - - /** Log.WARN */ - void wTag(String tag, String message, Object... args); - - /** Log.INFO */ - void iTag(String tag, String message, Object... args); - - /** Log.VERBOSE */ - void vTag(String tag, String message, Object... args); - - /** Log.ASSERT */ - void wtfTag(String tag, String message, Object... args); - - // -- 其他特殊方法 -- - - /** 格式化Json格式数据,并打印 */ - void jsonTag(String tag, String json); - - /** 格式化xml格式数据,并打印 */ - void xmlTag(String tag, String xml); -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/LogConfig.java b/DevLibUtils/src/main/java/dev/utils/app/logger/LogConfig.java deleted file mode 100644 index 609ab48fac..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/LogConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.utils.app.logger; - -/** - * detail: 日志设置类 - * Created by Ttt - */ -public class LogConfig { - - /** 堆栈方法总数(显示经过的方法) = 默认 3 */ - public int methodCount = LogConstants.DEFAULT_LOG_METHOD_COUNT; - - /** 堆栈方法索引偏移(0 = 最新经过调用的方法信息,偏移则往上推,如 1 = 倒数第二条经过调用的方法信息) = 默认 0 */ - public int methodOffset = LogConstants.DEFAULT_LOG_METHOD_OFFSET; - - /** 是否输出全部方法(在特殊情况下,如想要打印全部经过的方法,但是不知道经过的总数) = 默认 false */ - public boolean isOutputMethodAll = LogConstants.JUDGE_OUTPUT_METHOD_ALL; - - /** 显示日志线程信息(特殊情况,显示经过的线程信息,具体情况如上) = 默认 false */ - public boolean isDisplayThreadInfo = LogConstants.JUDGE_DISPLAY_THREAD_LOG; - - /** 是否排序日志(格式化) = 默认 false */ - public boolean isSortLog = LogConstants.JUDGE_SORT_LOG; - - /** 日志级别 - 默认异常级别(只有 e,wtf 才进行显示) */ - public LogLevel logLevel = LogConstants.DEFAULT_LOG_LEVEL; - - /** 设置Tag特殊情况使用,不使用全部的Tag时,如单独输出在某个Tag下) */ - public String tag = LogConstants.DEFAULT_LOG_TAG; -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/LogConstants.java b/DevLibUtils/src/main/java/dev/utils/app/logger/LogConstants.java deleted file mode 100644 index ebddbc41d0..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/LogConstants.java +++ /dev/null @@ -1,62 +0,0 @@ -package dev.utils.app.logger; - -/** - * detail: 日志常量类 - * Created by Ttt - */ -final class LogConstants { - - // ======================= 日志配置常量 ============================ - - /** 判断是否排序日志 */ - public static final boolean JUDGE_SORT_LOG = false; - - /** 判断是否输出全部方法 - 异常的全部方法 */ - public static final boolean JUDGE_OUTPUT_METHOD_ALL = false; - - /** 判断是否显示日志线程信息 */ - public static final boolean JUDGE_DISPLAY_THREAD_LOG = false; - - /** 默认的日志Tag */ - public static final String DEFAULT_LOG_TAG = "DevLogger"; - - /** 默认输出方法数量 */ - public static final int DEFAULT_LOG_METHOD_COUNT = 3; - - /** 默认方法索引偏移 */ - public static final int DEFAULT_LOG_METHOD_OFFSET = 0; - - /** 默认日志级别 - 异常级别(只有 e,wtf 才进行显示) */ - public static final LogLevel DEFAULT_LOG_LEVEL = LogLevel.ERROR; - - // =================== 日志配置信息 ======================= - - /** - * Android's max limit for a log entry is ~4076 bytes, - * so 4000 bytes is used as chunk size since default charset is UTF-8 - * ---------------------------------------- - * Android的最大限制为一个日志条目 4076字节,4000字节作为块的大小从默认字符集是UTF-8 - */ - public static final int CHUNK_SIZE = 4000; - - /** JSON格式内容缩进 */ - public static final int JSON_INDENT = 4; - - /** 最小堆栈跟踪索引,在本类中启动2次本地调用。 */ - public static final int MIN_STACK_OFFSET = 3; - - // ========================= - // == 绘制日志工具 - 字符 == - // ========================= - - public static final char TOP_LEFT_CORNER = '╔'; - public static final char BOTTOM_LEFT_CORNER = '╚'; - public static final char MIDDLE_CORNER = '╟'; - public static final char HORIZONTAL_DOUBLE_LINE = '║'; - public static final String DOUBLE_DIVIDER = "═══════"; - public static final String SINGLE_DIVIDER = "───────"; - // -- - public static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; - public static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; - public static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER; -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/LogLevel.java b/DevLibUtils/src/main/java/dev/utils/app/logger/LogLevel.java deleted file mode 100644 index 788a21369f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/LogLevel.java +++ /dev/null @@ -1,31 +0,0 @@ -package dev.utils.app.logger; - -/** - * detail: 日志级别 - * Created by Ttt - */ -public enum LogLevel { - - /** ================================================================== */ - // Log.v 输出颜色为黑色,任何消息都会输出,这里的v代表verbose啰嗦的意思,平时使用就是Log.v(,); - // Log.d 输出颜色为蓝色,仅输出debug调试的意思,但他会输出上层的信息,过滤起来可以通过DDMS的Logcat标签来选择 - // Log.i 输出颜色为绿色,一般提示性的消息information,它不会输出Log.v和Log.d的信息,但会显示i、w和e的信息 - // Log.w 输出颜色为橙色,可以看作为warning警告,一般需要我们注意优化Android代码,同时选择它后还会输出Log.e的信息。 - // Log.e 输出颜色为红色,可以想到error错误,这里仅显示红色的错误信息,这些错误就需要我们认真的分析,查看栈的信息了。 - /** ================================================================== */ - - /** 全部不打印 */ - NONE, - - /** 调试级别 v,d - 全部打印 */ - DEBUG, - - /** 正常级别 i */ - INFO, - - /** 警告级别 w */ - WARN, - - /** 异常级别 e,wtf */ - ERROR; -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/LoggerPrinter.java b/DevLibUtils/src/main/java/dev/utils/app/logger/LoggerPrinter.java deleted file mode 100644 index c1b3fa57aa..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/LoggerPrinter.java +++ /dev/null @@ -1,735 +0,0 @@ -package dev.utils.app.logger; - -import android.text.TextUtils; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.StringReader; -import java.io.StringWriter; - -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - -/** - * detail: 日志输出类(处理方法) - * Created by Ttt - */ -final class LoggerPrinter implements IPrinter { - - /** 日志配置 */ - private static LogConfig LOG_CONFIG = null; - - /** 每个线程的日志配置信息 */ - private static final ThreadLocal LOCAL_LOG_CONFIGS = new ThreadLocal(); - - // ================== 实现IPrinter接口,对外公开方法 =================== - - /** - * 其他(特殊情况,如不使用默认配置,单使用一次自定义配置) - * @param lConfig 对应的配置信息 - */ - @Override - public IPrinter other(LogConfig lConfig) { - if(lConfig != null) { - LOCAL_LOG_CONFIGS.set(lConfig); - } - return this; - } - - /** - * 返回日志配置 - * @return - */ - @Override - public LogConfig getLogConfig() { - return LOG_CONFIG; - } - - /** - * 默认参数初始化,防止设置信息为null - * @return 日志配置 - */ - @Override - public LogConfig init() { - // 判断日志配置信息是否等于null - if(LOG_CONFIG == null) { - // 生成默认配置信息 - LOG_CONFIG = new LogConfig(); - } - // 返回配置信息 - return LOG_CONFIG; - } - - /** - * 自定义日志配置 - * @param lConfig 日志配置 - */ - @Override - public void init(LogConfig lConfig) { - LOG_CONFIG = lConfig; - // 防止日志配置参数为null - init(); - } - - // ========= 使用默认TAG ========= - - // -- 日志打印方法 -- - - @Override - public void d(String message, Object... args) { - logHandle(Log.DEBUG, message, args); - } - - @Override - public void e(String message, Object... args) { - e(null, message, args); - } - - @Override - public void e(Throwable throwable) { - e(throwable, null); - } - - @Override - public void e(Throwable throwable, String message, Object... args) { - if (throwable != null && message != null) { - message += " : " + throwable.toString(); - } else if (throwable != null && message == null) { - message = throwable.toString(); - } else if (message == null) { - // 没有日志信息,也没有异常信息传入 - message = "No message/exception is set"; - } - logHandle(Log.ERROR, message, args); - } - - @Override - public void w(String message, Object... args) { - logHandle(Log.WARN, message, args); - } - - @Override - public void i(String message, Object... args) { - logHandle(Log.INFO, message, args); - } - - @Override - public void v(String message, Object... args) { - logHandle(Log.VERBOSE, message, args); - } - - @Override - public void wtf(String message, Object... args) { - logHandle(Log.ASSERT, message, args); - } - - // -- 其他特殊方法 -- - - @Override - public void json(String json) { - // 获取当前线程日志配置信息 - LogConfig lConfig = getThreadLogConfig(); - // 判断是否打印日志(日志级别) - if(!isPrintLog(lConfig, Log.DEBUG)) { - return; - } - // 日志Tag - String tag = lConfig.tag; - // 判断传入JSON格式信息是否为null - if (TextUtils.isEmpty(json)) { - logHandle(lConfig, tag, Log.DEBUG, "Empty/Null json content"); - return; - } - try { - // 属于对象的JSON格式信息 - if (json.startsWith("{")) { - JSONObject jsonObject = new JSONObject(json); - // 进行缩进 - String message = jsonObject.toString(LogConstants.JSON_INDENT); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } else if (json.startsWith("[")) { - // 属于数据的JSON格式信息 - JSONArray jsonArray = new JSONArray(json); - // 进行缩进 - String message = jsonArray.toString(LogConstants.JSON_INDENT); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - logHandle(lConfig, tag, Log.ERROR, eHint + "\n" + json); - - } - } - - @Override - public void xml(String xml) { - // 获取当前线程日志配置信息 - LogConfig lConfig = getThreadLogConfig(); - // 判断是否打印日志(日志级别) - if(!isPrintLog(lConfig, Log.DEBUG)) { - return; - } - // 日志Tag - String tag = lConfig.tag; - // 判断传入XML格式信息是否为null - if (TextUtils.isEmpty(xml)) { - logHandle(lConfig, tag, Log.DEBUG, "Empty/Null xml content"); - return; - } - try { - Source xmlInput = new StreamSource(new StringReader(xml)); - StreamResult xmlOutput = new StreamResult(new StringWriter()); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - transformer.transform(xmlInput, xmlOutput); - // 获取打印消息 - String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - logHandle(lConfig, tag, Log.ERROR, eHint + "\n" + xml); - } - } - - // ========= 使用自定义TAG ========= - - // -- 日志打印方法 -- - - @Override - public void dTag(String tag, String message, Object... args) { - logHandle(tag, Log.DEBUG, message, args); - } - - @Override - public void eTag(String tag, String message, Object... args) { - eTag(tag, null, message, args); - } - - @Override - public void eTag(String tag, Throwable throwable) { - eTag(tag, throwable, null); - } - - @Override - public void eTag(String tag, Throwable throwable, String message, Object... args) { - if (throwable != null && message != null) { - message += " : " + throwable.toString(); - } else if (throwable != null && message == null) { - message = throwable.toString(); - } else if (message == null) { - // 没有日志信息,也没有异常信息传入 - message = "No message/exception is set"; - } - logHandle(tag, Log.ERROR, message, args); - } - - @Override - public void wTag(String tag, String message, Object... args) { - logHandle(tag, Log.WARN, message, args); - } - - @Override - public void iTag(String tag, String message, Object... args) { - logHandle(tag, Log.INFO, message, args); - } - - @Override - public void vTag(String tag, String message, Object... args) { - logHandle(tag, Log.VERBOSE, message, args); - } - - @Override - public void wtfTag(String tag, String message, Object... args) { - logHandle(tag, Log.ASSERT, message, args); - } - - // -- 其他特殊方法 -- - - @Override - public void jsonTag(String tag, String json) { - // 获取当前线程日志配置信息 - LogConfig lConfig = getThreadLogConfig(); - // 判断是否打印日志(日志级别) - if(!isPrintLog(lConfig, Log.DEBUG)) { - return; - } - // 判断传入JSON格式信息是否为null - if (TextUtils.isEmpty(json)) { - logHandle(lConfig, tag, Log.DEBUG, "Empty/Null json content"); - return; - } - try { - // 属于对象的JSON格式信息 - if (json.startsWith("{")) { - JSONObject jsonObject = new JSONObject(json); - // 进行缩进 - String message = jsonObject.toString(LogConstants.JSON_INDENT); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } else if (json.startsWith("[")) { - // 属于数据的JSON格式信息 - JSONArray jsonArray = new JSONArray(json); - // 进行缩进 - String message = jsonArray.toString(LogConstants.JSON_INDENT); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - logHandle(lConfig, tag, Log.ERROR, eHint + "\n" + json); - } - } - - @Override - public void xmlTag(String tag, String xml) { - // 获取当前线程日志配置信息 - LogConfig lConfig = getThreadLogConfig(); - // 判断是否打印日志(日志级别) - if(!isPrintLog(lConfig, Log.DEBUG)) { - return; - } - // 判断传入XML格式信息是否为null - if (TextUtils.isEmpty(xml)) { - logHandle(lConfig, tag, Log.DEBUG, "Empty/Null xml content"); - return; - } - try { - Source xmlInput = new StreamSource(new StringReader(xml)); - StreamResult xmlOutput = new StreamResult(new StringWriter()); - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); - transformer.transform(xmlInput, xmlOutput); - // 获取打印消息 - String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); - // 打印信息 - logHandle(lConfig, tag, Log.DEBUG, message); - } catch (Exception e) { - String eHint = "null"; - if (e != null){ - Throwable throwable = e.getCause(); - if (throwable != null){ - eHint = throwable.getMessage(); - } else { - try { - eHint = e.getMessage(); - } catch (Exception e1){ - eHint = e1.getMessage(); - } - } - } - logHandle(lConfig, tag, Log.ERROR, eHint + "\n" + xml); - } - } - - // ======================= 内部判断方法 ========================== - - /** - * 是否打印日志 - * @param lConfig 日志配置 - * @param logType 日志类型 - * @return - */ - private boolean isPrintLog(LogConfig lConfig, int logType) { - // 是否打印日志 - 默认不打印 - boolean isPrint = false; - // 日志级别 - LogLevel lLevel = lConfig.logLevel; - // -- - switch(lLevel) { - /** 全部不打印 */ - case NONE: - break; - /** 调试级别 v,d - 全部打印*/ - case DEBUG: - isPrint = true; - break; - /** 正常级别 i */ - case INFO: - /** 警告级别 w */ - case WARN: - /** 异常级别 e,wtf */ - case ERROR: - isPrint = checkLogLevel(lLevel, logType); - break; - default: - break; - } - return isPrint; - } - - /** - * 判断日志级别是否允许输出 - * @param lLevel 日志级别 - * @param logType 日志类型 - * @return - */ - private boolean checkLogLevel(LogLevel lLevel, int logType) { - switch(lLevel) { - /** 正常级别 i */ - case INFO: - if(logType != Log.VERBOSE && logType != Log.DEBUG) { - return true; - } - break; - /** 警告级别 w */ - case WARN: - if(logType != Log.VERBOSE && logType != Log.DEBUG && logType != Log.INFO) { - return true; - } - break; - /** 异常级别 e,wtf */ - case ERROR: - if(logType == Log.ERROR || logType == Log.ASSERT) { - return true; - } - break; - default: - break; - } - return false; - } - - // ======================= 打印日志处理方法 ========================== - /** - * 最终打印方法 f = Final - * @param logType 日志类型 - * @param tag 日志Tag - * @param msg 打印信息 - */ - private void fLogPrinter(int logType, String tag, String msg) { - switch (logType) { - case Log.VERBOSE: - Log.v(tag, msg); - break; - case Log.DEBUG: - Log.d(tag, msg); - break; - case Log.INFO: - Log.i(tag, msg); - break; - case Log.WARN: - Log.w(tag, msg); - break; - case Log.ERROR: - Log.e(tag, msg); - break; - case Log.ASSERT: - Log.wtf(tag, msg); - break; - default: // 默认使用,自定义级别 - Log.wtf(tag, msg); - break; - } - } - - /** - * 日志处理方法(统一调用这个) - * @param logType 日志类型 - * @param msg 打印信息 - * @param args 占位符替换 - */ - private void logHandle(int logType, String msg, Object... args) { - logHandle(null, null, logType, msg, args); - } - - /** - * 日志处理方法(统一调用这个) - * @param tag 日志Tag - * @param logType 日志类型 - * @param msg 打印信息 - * @param args 占位符替换 - */ - private void logHandle(String tag, int logType, String msg, Object... args) { - logHandle(null, tag, logType, msg, args); - } - - /** - * 日志处理方法(统一调用这个) -- 此方法是同步的,以避免混乱的日志的顺序。 - * @param lConfig 配置信息 - * @param tag 日志Tag - * @param logType 日志类型 - * @param msg 打印信息 - * @param args 占位符替换 - */ - private synchronized void logHandle(LogConfig lConfig, String tag, int logType, String msg, Object... args) { - if(lConfig == null) { // 如果配置为null,才进行获取 - // 获取当前线程日志配置信息 - lConfig = getThreadLogConfig(); - } - // 判断是否打印日志(日志级别) - if(!isPrintLog(lConfig, logType)) { - return; - } - // 防止TAG为null - if(TextUtils.isEmpty(tag)) { - // 获取配置的TAG - tag = lConfig.tag; - // 防止配置的TAG也为null - if(TextUtils.isEmpty(tag)) { - // 使用默认的TAG - tag = LogConstants.DEFAULT_LOG_TAG; - } - } - // 判断是否显示排序后的日志(如果不排序,则显示默认) - if(!lConfig.isSortLog) { - fLogPrinter(logType, tag, createMessage(msg, args)); - return; - } - /** ============== 日志配置信息获取 ========== */ - // 获取方法总数 - int methodCount = lConfig.methodCount; - // 获取方法偏移索引 - int methodOffset = lConfig.methodOffset; - // 如果出现小于0的设置,则设置默认值处理 - if(methodOffset < 0) { - methodOffset = LogConstants.DEFAULT_LOG_METHOD_OFFSET; - } - // 如果出现小于0的设置,则设置默认值处理 - if(methodCount < 0) { - methodCount = LogConstants.DEFAULT_LOG_METHOD_COUNT; - } - // 获取打印的日志信息 - String message = createMessage(msg, args); - // 打印头部 - logTopBorder(logType, tag); - // 打印头部线程信息 - logHeaderContent(lConfig,logType, tag, methodCount, methodOffset); - // 获取系统的默认字符集的信息字节(UTF-8) - byte[] bytes = message.getBytes(); - // 获取字节总数 - int length = bytes.length; - // 判断是否超过总数,没有超过则一次性打印,超过则遍历打印 - if (length <= LogConstants.CHUNK_SIZE) { - if (methodCount > 0) { - logDivider(logType, tag); - } - // 打印日志内容 - logContent(logType, tag, message); - // 打印结尾 - logBottomBorder(logType, tag); - return; - } - // 打印换行符 - if (methodCount > 0) { - // 换行 - logDivider(logType, tag); - } - // 因为超过系统打印字节总数,遍历打印 - for (int i = 0; i < length; i += LogConstants.CHUNK_SIZE) { - int count = Math.min(length - i, LogConstants.CHUNK_SIZE); - // 创建系统的默认字符集的一个新的字符串(UTF-8),并打印日志内容 - logContent(logType, tag, new String(bytes, i, count)); - } - // 打印结尾 - logBottomBorder(logType, tag); - } - - // ========================== 日志格式拼接 ========================== - /** - * 日志线程信息主体部分 - * @param lConfig 日志配置 - * @param logType 日志类型 - * @param tag 日志Tag - * @param methodCount 方法总数 - * @param methodOffset 方法偏移索引 - */ - private void logHeaderContent(LogConfig lConfig, int logType, String tag, int methodCount, int methodOffset) { - StackTraceElement[] trace = Thread.currentThread().getStackTrace(); - // 判断是否显示日志线程信息 - if (lConfig.isDisplayThreadInfo) { - // 打印线程信息(线程名) - fLogPrinter(logType, tag, LogConstants.HORIZONTAL_DOUBLE_LINE + " Thread: " + Thread.currentThread().getName()); - // 进行换行 - logDivider(logType, tag); - } else { - // 不打印线程信息,都设置为0 - methodCount = methodOffset = 0; - return; - } - // 手动进行偏移 - String level = ""; - // 堆栈总数 - int traceCount = trace.length; - // 获取堆栈偏移量 - int stackOffset = getStackOffset(trace) + methodOffset; - // 对应的方法计数与当前堆栈可能超过,进行堆栈跟踪。修剪计数 - if (methodCount + stackOffset > traceCount) { - methodCount = traceCount - stackOffset - 1; - } - // 判断是否显示全部方法 - if(lConfig.isOutputMethodAll) { - // 设置方法总数 - methodCount = traceCount; - // 设置方法偏移索引为0 - stackOffset = 0; - } else if (methodCount <= 0) { - // 如果打印数小于等于0,则直接跳过 - return; - } - // 遍历打印的方法数量(类名、行数、操作的方法名) - for (int i = methodCount; i > 0; i--) { - int stackIndex = i + stackOffset; - if (stackIndex >= traceCount) { - continue; - } - // 拼接中间内容,以及操作的类名,行数,方法名等信息 - StringBuilder builder = new StringBuilder(); - builder.append("║ "); - builder.append(level); - builder.append(getSimpleClassName(trace[stackIndex].getClassName())); - builder.append(".").append(trace[stackIndex].getMethodName()); - builder.append(" ("); - builder.append(trace[stackIndex].getFileName()); - builder.append(":"); - builder.append(trace[stackIndex].getLineNumber()); - builder.append(")"); - level += " "; - // 打印日志信息 - fLogPrinter(logType, tag, builder.toString()); - } - } - - /** - * 日志顶部 - * @param logType 日志类型 - * @param tag 日志Tag - */ - private void logTopBorder(int logType, String tag) { - fLogPrinter(logType, tag, LogConstants.TOP_BORDER); - } - - /** - * 日志结尾 - * @param logType 日志类型 - * @param tag 日志Tag - */ - private void logBottomBorder(int logType, String tag) { - fLogPrinter(logType, tag, LogConstants.BOTTOM_BORDER); - } - - /** - * 日志换行 - * @param logType 日志类型 - * @param tag 日志Tag - */ - private void logDivider(int logType, String tag) { - fLogPrinter(logType, tag, LogConstants.MIDDLE_BORDER); - } - - /** - * 日志内容 - * @param logType 日志类型 - * @param tag 日志Tag - * @param msg 日志信息 - */ - private void logContent(int logType, String tag, String msg) { - String[] lines = msg.split(System.getProperty("line.separator")); - for (String line : lines) { - fLogPrinter(logType, tag, LogConstants.HORIZONTAL_DOUBLE_LINE + " " + line); - } - } - - /** - * 处理信息 - * @param message 打印信息 - * @param args 占位符替换 - * @return - */ - private String createMessage(String message, Object... args) { - try { - return args.length == 0 ? message : String.format(message, args); - } catch (Exception e) { - } - return ""; - } - - // =================== 获取堆栈信息 ============================ - /** - * 获取类名 - * @param name 类名.class - */ - private String getSimpleClassName(String name) { - int lastIndex = name.lastIndexOf("."); - return name.substring(lastIndex + 1); - } - - /** - * 确定该类的方法调用后的堆栈跟踪的起始索引。 - * @param trace 堆栈 - * @return 堆栈跟踪索引 - */ - private int getStackOffset(StackTraceElement[] trace) { - for (int i = LogConstants.MIN_STACK_OFFSET, len = trace.length; i < len; i++) { - StackTraceElement e = trace[i]; - String name = e.getClassName(); - if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(DevLogger.class.getName())) { - return --i; - } - } - return -1; - } - - // ========================== 日志配置获取 ========================== - /** - * 返回对应线程的日志配置信息 - */ - private LogConfig getThreadLogConfig() { - // 获取当前线程的日志配置信息 - LogConfig lConfig = LOCAL_LOG_CONFIGS.get(); - // 如果等于null,则返回默认配置信息 - if(lConfig == null) { - return init(); - } else { - LOCAL_LOG_CONFIGS.remove(); - } - // 如果存在当前线程的配置信息,则返回 - return lConfig; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/logger/Utils.java b/DevLibUtils/src/main/java/dev/utils/app/logger/Utils.java deleted file mode 100644 index b71e4a0afc..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/logger/Utils.java +++ /dev/null @@ -1,488 +0,0 @@ -package dev.utils.app.logger; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; -import java.lang.reflect.Field; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import dev.utils.LogPrintUtils; - -/** - * detail: 内部快捷操作工具类(便于单独提取Logger,不依赖其他工具类) - * Created by Ttt - */ -final class Utils { - - private Utils(){ - } - - // 日志TAG - private static final String TAG = Utils.class.getSimpleName(); - - // =================== 配置信息 ======================= - - /** App版本(如1.0.01) 显示给用户看的 */ - static String APP_VERSION_NAME = ""; - - /** android:versionCode——整数值,代表应用程序代码的相对版本,也就是版本更新过多少次。(不显示给用户看) */ - static String APP_VERSION_CODE = ""; - - /** 设备信息 */ - static String DEVICE_INFO_STR = null; - - /** 用来存储设备信息 */ - static HashMap DEVICE_INFO_MAPS = new HashMap(); - - /** 换行字符串 */ - static final String NEW_LINE_STR = System.getProperty("line.separator"); - - /** 换行字符串 - 两行 */ - static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - // ================== APP、设备信息处理 =================== - - /** - * 获取app版本信息 - * @param context 上下文 - */ - static String[] getAppVersion(Context context) { - String[] aVersion = null; - try { - PackageManager pm = context.getPackageManager(); - PackageInfo pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES); - if (pi != null) { - String versionName = pi.versionName == null ? "null" : pi.versionName; - String versionCode = pi.versionCode + ""; - // -- - aVersion = new String[]{versionName,versionCode}; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getAppVersion"); - } - return aVersion; - } - - /** - * 获取设备信息 - * @param dInfoMaps 传入设备信息传出HashMap - */ - static void getDeviceInfo(HashMap dInfoMaps) { - // 获取设备信息类的所有申明的字段,即包括public、private和proteced, 但是不包括父类的申明字段。 - Field[] fields = Build.class.getDeclaredFields(); - // 遍历字段 - for (Field field : fields) { - try { - // 取消java的权限控制检查 - field.setAccessible(true); - // 获取类型对应字段的数据,并保存 - dInfoMaps.put(field.getName(), field.get(null).toString()); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getDeviceInfo"); - } - } - } - - /** - * 处理设备信息 - * @param eHint 错误提示,如获取设备信息失败 - */ - static String handleDeviceInfo(String eHint) { - try { - // 如果不为null,则直接返回之前的信息 - if(!TextUtils.isEmpty(DEVICE_INFO_STR)) { - return DEVICE_INFO_STR; - } - // 初始化StringBuilder,拼接字符串 - StringBuilder sBuilder = new StringBuilder(); - // 获取设备信息 - Iterator> mapIter = DEVICE_INFO_MAPS.entrySet().iterator(); - // 遍历设备信息 - while (mapIter.hasNext()) { - // 获取对应的key-value - Map.Entry rnEntry = (Map.Entry) mapIter.next(); - String rnKey = (String) rnEntry.getKey(); // key - String rnValue = (String) rnEntry.getValue(); // value - // 保存设备信息 - sBuilder.append(rnKey); - sBuilder.append(" = "); - sBuilder.append(rnValue); - sBuilder.append(NEW_LINE_STR); - } - // 保存设备信息 - DEVICE_INFO_STR = sBuilder.toString(); - // 返回设备信息 - return DEVICE_INFO_STR; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleDeviceInfo"); - } - return eHint; - } - - // ================== 时间格式化 =================== - - /** 日期格式类型 */ - static final String yyyyMMddHHmmss = "yyyy-MM-dd HH:mm:ss"; - - /** - * 获取当前日期的字符串 - * @return 字符串 - */ - @SuppressLint("SimpleDateFormat") - static String getDateNow() { - try { - Calendar cld = Calendar.getInstance(); - DateFormat df = new SimpleDateFormat(yyyyMMddHHmmss); - return df.format(cld.getTime()); - } catch (Exception e) { - } - return null; - } - - - // ================== 文件操作 =================== - - /** - * 判断某个文件夹是否创建,未创建则创建(不能加入文件名) - * @param fPath 文件夹路径 - */ - static File createFile(String fPath) { - try { - File file = new File(fPath); - // 当这个文件夹不存在的时候则创建文件夹 - if(!file.exists()) { - // 允许创建多级目录 - file.mkdirs(); - } - return file; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createFile"); - } - return null; - } - - /** - * 保存文件 - * @param txt 保存内容 - * @param fUrl 保存路径(包含文件名.后缀) - * @return 是否保存成功 - */ - static boolean saveFile(String txt, String fUrl) { - try { - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(fUrl); - fos.write(txt.getBytes()); - fos.close(); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "saveFile"); - return false; - } - return true; - } - - - // ================== 异常信息处理 =================== - - /** - * 获取错误信息(无换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - static String getThrowableMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 写入错误栈信息 - ex.printStackTrace(printWriter); - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getThrowableMsg"); - } finally { - if(printWriter != null) { - printWriter.close(); - } - } - return eHint; - } - - /** - * 获取错误信息(有换行) - * @param eHint 获取失败提示 - * @param ex 错误信息 - * @return - */ - static String getThrowableNewLinesMsg(String eHint, Throwable ex) { - PrintWriter printWriter = null; - try { - if(ex != null) { - // 初始化Writer,PrintWriter打印流 - Writer writer = new StringWriter(); - printWriter = new PrintWriter(writer); - // 获取错误栈信息 - StackTraceElement[] stElement = ex.getStackTrace(); - // 标题,提示属于什么异常 - printWriter.append(ex.toString()); - printWriter.append(NEW_LINE_STR); - // 遍历错误栈信息,并且进行换行,缩进 - for(StackTraceElement st : stElement) { - printWriter.append("\tat "); - printWriter.append(st.toString()); - printWriter.append(NEW_LINE_STR); - } - // 关闭流 - printWriter.close(); - return writer.toString(); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getThrowableNewLinesMsg"); - } finally { - if(printWriter != null) { - printWriter.close(); - } - } - return eHint; - } - - // ================== 对外公开方法 =================== - - /** - * App初始化调用方法 - * @param context 上下文 - */ - public static void appInit(Context context) { - // 如果版本信息为null,才进行处理 - if (TextUtils.isEmpty(APP_VERSION_CODE) || TextUtils.isEmpty(APP_VERSION_NAME)) { - // 获取app版本信息 - String[] aVersion = getAppVersion(context); - // 保存app版本信息 - APP_VERSION_NAME = aVersion[0]; - APP_VERSION_CODE = aVersion[1]; - } - // 判断是否存在设备信息 - if (DEVICE_INFO_MAPS.size() == 0) { - // 获取设备信息 - getDeviceInfo(DEVICE_INFO_MAPS); - // 转换字符串 - handleDeviceInfo(""); - } - } - - // ========= 保存错误日志信息 ========== - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param isNewLines 是否换行 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveErrorLog(Throwable ex, String fPath, String fName, boolean isNewLines, String... eHint) { - return saveErrorLog(ex, null, null, fPath, fName, isNewLines, eHint); - } - - /** - * 保存app错误日志 - * @param ex 错误信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param isNewLines 是否换行 - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveErrorLog(Throwable ex, String head, String bottom, String fPath, String fName, boolean isNewLines, String... eHint) { - // 处理可变参数(错误提示) - eHint = handleVariable(2, eHint); - // 日志拼接 - StringBuilder sBuilder = new StringBuilder(); - // 防止文件夹不存在 - createFile(fPath); - // 设备信息 - String dInfo = handleDeviceInfo(eHint[0]); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(head)) { - sBuilder.append(head); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 保存App信息 - sBuilder.append("date: " + getDateNow()); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionName: " + APP_VERSION_NAME); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionCode: " + APP_VERSION_CODE); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - // 保存设备信息 - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(dInfo); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - // ============ - // 错误信息 - String eMsg = null; - // 是否换行 - if(isNewLines) { - eMsg = getThrowableNewLinesMsg(eHint[1], ex); - } else { - eMsg = getThrowableMsg(eHint[1], ex); - } - // 保存异常信息 - sBuilder.append(eMsg); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(bottom)) { - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(bottom); - } - // 保存日志到文件 - return saveFile(sBuilder.toString(), fPath + File.separator + fName); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String fPath, String fName, String... eHint){ - return saveLog(log, null, null, fPath, fName, eHint); - } - - /** - * 保存app日志 - * @param log 日志信息 - * @param head 顶部标题 - * @param bottom 底部内容 - * @param fPath 保存路径 - * @param fName 文件名(含后缀) - * @param eHint 错误提示(无设备信息、失败信息获取失败) - * @return - */ - public static boolean saveLog(String log, String head, String bottom, String fPath, String fName, String... eHint){ - // 处理可变参数(错误提示) - eHint = handleVariable(2, eHint); - // 日志拼接 - StringBuilder sBuilder = new StringBuilder(); - // 防止文件夹不存在 - createFile(fPath); - // 设备信息 - String dInfo = handleDeviceInfo(eHint[0]); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(head)) { - sBuilder.append(head); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - } - // ============ - // 保存App信息 - sBuilder.append("date: " + getDateNow()); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionName: " + APP_VERSION_NAME); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("versionCode: " + APP_VERSION_CODE); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append("============================"); - // 保存设备信息 - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(dInfo); - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - // ============ - // 保存日志信息 - sBuilder.append(log); - // 如果存在顶部内容,则进行添加 - if(!TextUtils.isEmpty(bottom)) { - sBuilder.append(NEW_LINE_STR); - sBuilder.append("============================"); - sBuilder.append(NEW_LINE_STR_X2); - sBuilder.append(bottom); - } - // 保存日志到文件 - return saveFile(sBuilder.toString(), fPath + File.separator + fName); - } - - // == - - /** - * 处理可变参数 - * @param length 保留长度 - * @param vArrays 可变参数数组 - * @return - */ - public static String[] handleVariable(int length, String[] vArrays) { - // 处理后的数据, - String[] hArrays = new String[length]; - // 是否统一处理 - boolean isUnifiedHandler = true; - try { - if(vArrays != null) { - // 获取可变参数数组长度 - int vLength = vArrays.length; - // 如果长度超出预留长度 - if(vLength >= length) { - for(int i = 0;i < length;i++) { - if(vArrays[i] == null) { - hArrays[i] = ""; - } else { - hArrays[i] = vArrays[i]; - } - } - // 但可变参数长度,超过预留长度时,已经处理完毕,不需要再次处理,节省遍历资源 - isUnifiedHandler = false; - } else { - for(int i = 0;i < vLength;i++) { - if(vArrays[i] == null) { - hArrays[i] = ""; - } else { - hArrays[i] = vArrays[i]; - } - } - } - } - if(isUnifiedHandler) { - // 统一处理,如果数据未null,则设置为“”,防止拼接出现 "null" - for(int i = 0;i < length;i++) { - if(hArrays[i] == null) { - hArrays[i] = ""; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handleVariable"); - } - return hArrays; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/share/IPreference.java b/DevLibUtils/src/main/java/dev/utils/app/share/IPreference.java deleted file mode 100644 index 831757d4c8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/share/IPreference.java +++ /dev/null @@ -1,143 +0,0 @@ -package dev.utils.app.share; - -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * detail: SharedPreferences 操作接口 - * Created by Ttt - * http://www.jianshu.com/p/fcd75a324c35 - */ -public interface IPreference { - - /** 枚举: 存储或取出的数据类型 */ - enum DataType { - INTEGER, LONG, BOOLEAN, FLOAT, STRING, STRING_SET - } - - // == - - /** - * 保存一个数据 - * @param key - * @param value - */ - void put(String key, T value); - - /** - * 保存一个Map集合(只能是 Integer,Long,Boolean,Float,String,Set) - * @param map - */ - void putAll(Map map); - - /** - * 保存一个List集合 - * @param key - * @param list - */ - void putAll(String key, List list); - - /** - * 保存一个List集合,并且自定义保存顺序 - * @param key - * @param list - * @param comparator - */ - void putAll(String key, List list, Comparator comparator); - - /** - * 根据key取出一个数据 - * @param key - */ - T get(String key, DataType type); - - /** - * 取出全部数据 - * @return - */ - Map getAll(); - - /** - * 取出一个List集合 - * @param key - * @return - */ - List getAll(String key); - - /** - * 移除一个数据 - * @param key - * @return - */ - void remove(String key); - - /** - * 移除一个集合的数据 - * @param keys - * @return - */ - void removeAll(List keys); - - /** - * 移除一个数组的数据 - * @param keys - * @return - */ - void removeAll(String[] keys); - - /** - * 是否存在key - * @return - */ - boolean contains(String key); - - /** - * 清除全部数据 - */ - void clear(); - - // == - /** - * 获取int类型的数据 - * @return - */ - int getInt(String key); - - /** - * 获取Float类型的数据 - * @param key - * @return - */ - float getFloat(String key); - - /** - * 获取long类型的数据 - * @param key - * @return - */ - long getLong(String key); - - - /** - * 获取boolean类型的数据 - * @param key - * @return - */ - boolean getBoolean(String key); - - /** - * 获取String类型的数据 - * @param key - * @return - */ - String getString(String key); - - /** - * 获取Set类型的数据 - * @param key - * @return - */ - Set getSet(String key); -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/share/IPreferenceHolder.java b/DevLibUtils/src/main/java/dev/utils/app/share/IPreferenceHolder.java deleted file mode 100644 index f83b322858..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/share/IPreferenceHolder.java +++ /dev/null @@ -1,71 +0,0 @@ -package dev.utils.app.share; - -import android.content.Context; - -import java.util.HashMap; - -/** - * detail: IPreference 持有类,内部返回实现类 - * Created by Ttt - */ -class IPreferenceHolder { - - /** HashMap 保存持有对象 */ - private static final HashMap hashMap = new HashMap<>(); - - /** - * 初始化 - * @param context - */ - public static IPreference getPreference(Context context) { - // 判断是否为存在对应的持有类 - IPreference ipref = hashMap.get(null); - // 判断是否为存在 - if (ipref != null) { - return ipref; - } - // 初始化并保存 - ipref = new PreferenceImpl(context); - hashMap.put(null, ipref); - return ipref; - } - - /** - * 初始化 - * @param context - * @param fName - */ - public static IPreference getPreference(Context context, String fName) { - // 判断是否为存在对应的持有类 - IPreference ipref = hashMap.get(fName); - // 判断是否为存在 - if (ipref != null) { - return ipref; - } - // 初始化并保存 - ipref = new PreferenceImpl(context, fName); - hashMap.put(fName, ipref); - return ipref; - } - - /** - * 初始化 - * @param context - * @param fName - * @param mode - */ - public static IPreference getPreference(Context context, String fName, int mode) { - String key = fName + "_" + mode; - // 判断是否为存在对应的持有类 - IPreference ipref = hashMap.get(key); - // 判断是否为存在 - if (ipref != null) { - return ipref; - } - // 初始化并保存 - ipref = new PreferenceImpl(context, fName, mode); - hashMap.put(key, ipref); - return ipref; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/share/PreferenceImpl.java b/DevLibUtils/src/main/java/dev/utils/app/share/PreferenceImpl.java deleted file mode 100644 index 0f7b6cd2f8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/share/PreferenceImpl.java +++ /dev/null @@ -1,242 +0,0 @@ -package dev.utils.app.share; - -import android.content.Context; -import android.content.SharedPreferences; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -/** - * detail: SharedPreferences 操作接口具体实现类 - * Created by Ttt - * hint: - * 1.apply没有返回值而 commit返回boolean表明修改是否提交成功 - * 2.apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘 - * 3.apply方法不会提示任何失败的提示 apply的效率高一些,如果没有必要确认是否提交成功建议使用apply。 - */ -final class PreferenceImpl implements IPreference { - - /** 文件名 */ - private static final String NAME = "SPConfig"; - /** 默认SharedPreferences对象 */ - private SharedPreferences preferences = null; - - // ============== - // == 构造函数 == - // ============== - - /** - * 初始化 - * @param context - */ - public PreferenceImpl(Context context) { - preferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); - } - - /** - * 初始化 - * @param context - * @param fName - */ - public PreferenceImpl(Context context, String fName) { - preferences = context.getSharedPreferences(fName, Context.MODE_PRIVATE); - } - - /** - * 初始化 - * @param context - * @param fName - * @param mode - */ - public PreferenceImpl(Context context, String fName, int mode) { - preferences = context.getSharedPreferences(fName, mode); - } - - // ============== - // == 内部方法 == - // ============== - - /** - * 保存数据 - * @param editor - * @param key - * @param obj - */ - @SuppressWarnings("unchecked") - private void put(SharedPreferences.Editor editor, String key, Object obj) { - // key 不为null时再存入,否则不存储 - if (key != null) { - if (obj instanceof Integer) { - editor.putInt(key, (Integer) obj); - } else if (obj instanceof Long) { - editor.putLong(key, (Long) obj); - } else if (obj instanceof Boolean) { - editor.putBoolean(key, (Boolean) obj); - } else if (obj instanceof Float) { - editor.putFloat(key, (Float) obj); - } else if (obj instanceof Set) { - editor.putStringSet(key, (Set) obj); - } else if (obj instanceof String) { - editor.putString(key, String.valueOf(obj)); - } - } - } - - /** - * 根据 Key 和 数据类型 取出数据 - * @param key - * @param type - * @return - */ - private Object getValue(String key, DataType type) { - switch (type) { - case INTEGER: - return preferences.getInt(key, -1); - case FLOAT: - return preferences.getFloat(key, -1f); - case BOOLEAN: - return preferences.getBoolean(key, false); - case LONG: - return preferences.getLong(key, -1L); - case STRING: - return preferences.getString(key, null); - case STRING_SET: - return preferences.getStringSet(key, null); - default: // 默认取出String类型的数据 - return null; - } - } - - /** - * 默认比较器,当存储List集合中的String类型数据时,没有指定比较器,就使用默认比较器 - */ - class ComparatorImpl implements Comparator { - @Override - public int compare(String lhs, String rhs) { - return lhs.compareTo(rhs); - } - } - - // ================== - // == 接口实现方法 == - // ================== - - - @Override - public void put(String key, T value) { - SharedPreferences.Editor edit = preferences.edit(); - put(edit, key, value); - edit.apply(); - } - - @Override - public void putAll(Map map) { - SharedPreferences.Editor edit = preferences.edit(); - for(Map.Entry entry : map.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - put(edit, key, value); - } - edit.apply(); - } - - @Override - public void putAll(String key, List list) { - putAll(key, list, new ComparatorImpl()); - } - - @Override - public void putAll(String key, List list, Comparator comparator) { - Set set = new TreeSet<>(comparator); - for(String value : list) { - set.add(value); - } - preferences.edit().putStringSet(key, set).apply(); - } - - @Override - public T get(String key, DataType type) { - return (T) getValue(key, type); - } - - @Override - public Map getAll() { - return preferences.getAll(); - } - - @Override - public List getAll(String key) { - List list = new ArrayList(); - Set set = get(key, DataType.STRING_SET); - for(String value : set) { - list.add(value); - } - return list; - } - - @Override - public void remove(String key) { - preferences.edit().remove(key).apply(); - } - - @Override - public void removeAll(List keys) { - SharedPreferences.Editor edit = preferences.edit(); - for (String k : keys) { - edit.remove(k); - } - edit.apply(); - } - - @Override - public void removeAll(String[] keys) { - removeAll(Arrays.asList(keys)); - } - - @Override - public boolean contains(String key) { - return preferences.contains(key); - } - - @Override - public void clear() { - preferences.edit().clear().apply(); - } - - // == - - @Override - public int getInt(String key) { - return get(key, DataType.INTEGER); - } - - @Override - public float getFloat(String key) { - return get(key, DataType.FLOAT); - } - - @Override - public long getLong(String key) { - return get(key, DataType.LONG); - } - - @Override - public boolean getBoolean(String key) { - return get(key, DataType.BOOLEAN); - } - - @Override - public String getString(String key) { - return get(key, DataType.STRING); - } - - @Override - public Set getSet(String key) { - return get(key, DataType.STRING_SET); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/share/SPUtils.java b/DevLibUtils/src/main/java/dev/utils/app/share/SPUtils.java deleted file mode 100644 index 7293f102a6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/share/SPUtils.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.utils.app.share; - -/** - * detail: SharedPreferences 工具类 - * Created by Ttt - */ -public final class SPUtils extends IPreferenceHolder { - - private SPUtils(){ - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/share/SharedUtils.java b/DevLibUtils/src/main/java/dev/utils/app/share/SharedUtils.java deleted file mode 100644 index ab95be05a0..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/share/SharedUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -package dev.utils.app.share; - -import android.content.Context; - -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * detail: SPUtils 工具类 - 直接单独使用 - * Created by Ttt - */ -public final class SharedUtils { - - private SharedUtils(){ - } - - /** 全局上下文 */ - private static Context mContext; - - /** - * 初始化操作 - * @param context - */ - public static void init(Context context) { - if (mContext == null && context != null) { - // 初始化全局上下文 - mContext = context.getApplicationContext(); - } - } - - public static void put(String key, T value) { - SPUtils.getPreference(mContext).put(key, value); - } - - public static void putAll(Map map) { - SPUtils.getPreference(mContext).putAll(map); - } - - public static void putAll(String key, List list) { - SPUtils.getPreference(mContext).putAll(key, list); - } - - public static void putAll(String key, List list, Comparator comparator) { - SPUtils.getPreference(mContext).putAll(key, list, comparator); - } - - public static T get(String key, IPreference.DataType type) { - return SPUtils.getPreference(mContext).get(key, type); - } - - public static Map getAll() { - return SPUtils.getPreference(mContext).getAll(); - } - - public static List getAll(String key) { - return SPUtils.getPreference(mContext).getAll(key); - } - - public static void remove(String key) { - SPUtils.getPreference(mContext).remove(key); - } - - public static void removeAll(List keys) { - SPUtils.getPreference(mContext).removeAll(keys); - } - - public static void removeAll(String[] keys) { - SPUtils.getPreference(mContext).removeAll(keys); - } - - public static boolean contains(String key) { - return SPUtils.getPreference(mContext).contains(key); - } - - public static void clear() { - SPUtils.getPreference(mContext).clear(); - } - - // == - - public static int getInt(String key) { - return SPUtils.getPreference(mContext).getInt(key); - } - - public static float getFloat(String key) { - return SPUtils.getPreference(mContext).getFloat(key); - } - - public static long getLong(String key) { - return SPUtils.getPreference(mContext).getLong(key); - } - - public static boolean getBoolean(String key) { - return SPUtils.getPreference(mContext).getBoolean(key); - } - - public static String getString(String key) { - return SPUtils.getPreference(mContext).getString(key); - } - - public static Set getSet(String key) { - return SPUtils.getPreference(mContext).getSet(key); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/toast/ToastUtils.java b/DevLibUtils/src/main/java/dev/utils/app/toast/ToastUtils.java deleted file mode 100644 index 39f61355b6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/toast/ToastUtils.java +++ /dev/null @@ -1,284 +0,0 @@ -package dev.utils.app.toast; - -import android.content.Context; -import android.widget.Toast; - -import dev.DevUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: 自定义Toast工具类 - * Created by Ttt - */ -public final class ToastUtils { - - private ToastUtils() { - } - - // 日志TAG - private static final String TAG = ToastUtils.class.getSimpleName(); - - /** 内部持有唯一 */ - private static Toast mToast = null; - - /** - * 内部处理防止Context 为null奔溃问题 - * @return - */ - private static Context getContext(Context context){ - if (context != null){ - return context; - } else { - // 设置全局Context - return DevUtils.getContext(); - } - } - - /** - * 获取内部唯一Toast对象 - * @return - */ - public static Toast getSignleToast(){ - return mToast; - } - - // ===================== - // === 统一显示Toast === - // ===================== - - // ======================== - // == Toast.LENGTH_SHORT == - // ======================== - - public static Toast showShort(Context context, String text) { - return handlerToastStr(true, context, text, Toast.LENGTH_SHORT); - } - - public static Toast showShort(Context context, String text, Object... objs) { - return handlerToastStr(true, context, text, Toast.LENGTH_SHORT, objs); - } - - public static Toast showShort(Context context, int resId) { - return handlerToastRes(true, context, resId, Toast.LENGTH_SHORT); - } - - public static Toast showShort(Context context, int resId, Object... objs) { - return handlerToastRes(true, context, resId, Toast.LENGTH_SHORT, objs); - } - - // ======================== - // == Toast.LENGTH_LONG === - // ======================== - - public static Toast showLong(Context context, String text) { - return handlerToastStr(true, context, text, Toast.LENGTH_LONG); - } - - public static Toast showLong(Context context, String text, Object... objs) { - return handlerToastStr(true, context, text, Toast.LENGTH_LONG, objs); - } - - public static Toast showLong(Context context, int resId) { - return handlerToastRes(true, context, resId, Toast.LENGTH_LONG); - } - - public static Toast showLong(Context context, int resId, Object...objs) { - return handlerToastRes(true, context, resId, Toast.LENGTH_LONG, objs); - } - - // ======================= - // ==== 最终Toast方法 ==== - // ======================= - - /** - * 显示Toast - * @param context - * @param resId - * @param duration - */ - public static Toast showToast(Context context, int resId, int duration) { - return handlerToastRes(true, context, resId, duration); - } - - /** - * 显示Toast - * @param context - * @param text - * @param duration - */ - public static Toast showToast(Context context, String text, int duration) { - return showToast(true, context, text, duration); - } - - // == - - /** - * 最终显示的Toast方法 - * @param isSingle - * @param context - * @param text - * @param duration - * @return Toast - */ - private static Toast showToast(boolean isSingle, Context context, String text, int duration) { - // 尽心设置为null, 便于提示排查 - if (text == null) { - text = "null"; - } - // 判断是否显示唯一, 单独共用一个 - if (isSingle) { - try { - if (mToast != null) { - mToast.setDuration(duration); - mToast.setText(text); - } else { - if (context != null) { - mToast = Toast.makeText(context, text, duration); - } - } - // 处理后,不为null,才进行处理 - if (mToast != null) { - mToast.show(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "showToast"); - } - return mToast; - } else { - Toast toast = null; - try { - toast = Toast.makeText(context, text, duration); - toast.show(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "showToast"); - } - return toast; - } - } - - // ===== - - /** - * 处理 R.string 资源Toast的格式化 - * @param isSingle 是否单独共用显示一个 - * @param context - * @param resId - * @param duration - * @param objs - */ - private static Toast handlerToastRes(boolean isSingle, Context context, int resId, int duration, Object... objs) { - if (getContext(context) != null) { - String text; - try { - // 获取字符串并且进行格式化 - if (objs != null && objs.length != 0) { - text = getContext(context).getString(resId, objs); - } else { - text = getContext(context).getString(resId); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "handlerToastRes"); - text = e.getMessage(); - } - return showToast(isSingle, context, text, duration); - } - return null; - } - - /** - * 处理字符串Toast的格式化 - * @param context - * @param text - * @param duration - * @param objs - */ - private static Toast handlerToastStr(boolean isSingle, Context context, String text, int duration, Object... objs) { - // 防止上下文为null - if (context != null) { - // 表示需要格式化字符串,只是为了减少 format步骤,增加判断,为null不影响 - if (objs != null && objs.length != 0) { - if (text != null) { // String.format() 中的 objs 可以为null,但是 text不能为null - try { - return showToast(isSingle, context, String.format(text, objs), duration); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "handlerToastStr"); - return showToast(isSingle, context, e.getMessage(), duration); - } - } else { - return showToast(isSingle, context, text, duration); - } - } else { - return showToast(isSingle, context, text, duration); - } - } - return null; - } - - // ===================== - // == 非统一显示Toast == - // ===================== - - // ======================== - // == Toast.LENGTH_SHORT == - // ======================== - - public static Toast showShortNew(Context context, String text) { - return handlerToastStr(false, context, text, Toast.LENGTH_SHORT); - } - - public static Toast showShortNew(Context context, String text, Object... objs) { - return handlerToastStr(false, context, text, Toast.LENGTH_SHORT, objs); - } - - public static Toast showShortNew(Context context, int resId) { - return handlerToastRes(false, context, resId, Toast.LENGTH_SHORT); - } - - public static Toast showShortNew(Context context, int resId, Object... objs) { - return handlerToastRes(false, context, resId, Toast.LENGTH_SHORT, objs); - } - - // ======================== - // == Toast.LENGTH_LONG === - // ======================== - - public static Toast showLongNew(Context context, String text) { - return handlerToastStr(false, context, text, Toast.LENGTH_LONG); - } - - public static Toast showLongNew(Context context, String text, Object... objs) { - return handlerToastStr(false, context, text, Toast.LENGTH_LONG, objs); - } - - public static Toast showLongNew(Context context, int resId) { - return handlerToastRes(false, context, resId, Toast.LENGTH_LONG); - } - - public static Toast showLongNew(Context context, int resId, Object...objs) { - return handlerToastRes(false, context, resId, Toast.LENGTH_LONG, objs); - } - - // ======================= - // ==== 最终Toast方法 ==== - // ======================= - - /** - * 显示Toast - * @param context - * @param resId - * @param duration - */ - public static Toast showToastNew(Context context, int resId, int duration) { - return handlerToastRes(false, context, resId, duration); - } - - /** - * 显示Toast - * @param context - * @param text - * @param duration - */ - public static Toast showToastNew(Context context, String text, int duration) { - return showToast(false, context, text, duration); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/toast/ToastViewUtils.java b/DevLibUtils/src/main/java/dev/utils/app/toast/ToastViewUtils.java deleted file mode 100644 index 555857e517..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/toast/ToastViewUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -package dev.utils.app.toast; - -import android.content.Context; -import android.view.View; -import android.widget.Toast; - -import dev.DevUtils; - -/** - * detail: 自定义Toast 使用View 工具类 - * Created by Ttt - */ -public final class ToastViewUtils { - - private ToastViewUtils() { - } - - /** 内部持有唯一 */ - private static Toast mToast = null; - - /** - * 内部处理防止Context 为null奔溃问题 - * @return - */ - private static Context getContext(Context context){ - if (context != null){ - return context; - } else { - // 设置全局Context - return DevUtils.getContext(); - } - } - - /** - * 获取内部唯一Toast对象 - * @return - */ - public static Toast getSignleToast(){ - return mToast; - } - - /** - * 最终显示Toast方法 - * @param context - * @param view - * @param duration - * @param isNewToast - */ - public static void showToast(Context context, View view, int duration, boolean isNewToast) { - // 防止上下文为null - if (context == null){ - return; - } else if (view == null) { // 防止显示的View 为null - return; - } - try { - // 判断是否显示新的 Toast - if (isNewToast){ - // 生成新的Toast - Toast toast = new Toast(context); - // 设置显示的View - toast.setView(view); - // 设置显示的时间 - toast.setDuration(duration); - // 显示Toast - toast.show(); - } else { - if (mToast == null){ - // 生成新的Toast - mToast = new Toast(context); - } - // 设置显示的View - mToast.setView(view); - // 设置显示的时间 - mToast.setDuration(duration); - // 显示Toast - mToast.show(); - } - } catch (Exception e){ - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/toast/cus/Toasty.java b/DevLibUtils/src/main/java/dev/utils/app/toast/cus/Toasty.java deleted file mode 100644 index 352b5a05ba..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/toast/cus/Toasty.java +++ /dev/null @@ -1,405 +0,0 @@ -package dev.utils.app.toast.cus; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.support.annotation.CheckResult; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import dev.utils.LogPrintUtils; -import dev.utils.R; - -/** - * detail: Toast 工具类(美化后,使用Layout显示) - * Created by Ttt - * https://github.com/GrenderG/Toasty - */ -@SuppressLint("InflateParams") -public final class Toasty { - - private Toasty() { - } - - // 日志TAG - private static final String TAG = Toasty.class.getSimpleName(); - - /** 内部持有唯一 */ - private static Toast mToast; - - // =================== - // == Toast配置信息 == - // =================== - - // === Toast 使用的配置信息 === - @ColorInt - static int DEFAULT_TEXT_COLOR = Color.parseColor("#FFFFFF"); // 白色 - @ColorInt - static int ERROR_COLOR = Color.parseColor("#D50000"); // 红色 - @ColorInt - static int INFO_COLOR = Color.parseColor("#3F51B5"); // 海洋蓝 - @ColorInt - static int SUCCESS_COLOR = Color.parseColor("#388E3C"); // 绿色 - @ColorInt - static int WARNING_COLOR = Color.parseColor("#FFA900"); // 橙色 - @ColorInt - static int NORMAL_COLOR = Color.parseColor("#353A3E"); // 灰色 - // 默认字体 - static final Typeface LOADED_TOAST_TYPEFACE = Typeface.create("sans-serif-condensed", Typeface.NORMAL); - // 当前字体样式 - static Typeface currentTypeface = LOADED_TOAST_TYPEFACE; - // 字体大小 - static int textSize = 16; // in SP - // 是否渲染图标 - static boolean tintIcon = true; - // 是否使用新的Toast - static boolean isNewToast = true; - - /** - * detail: Toast 配置 - * Created by Ttt - */ - public static class Config { - - @ColorInt - int DEFAULT_TEXT_COLOR = Toasty.DEFAULT_TEXT_COLOR; - @ColorInt - int ERROR_COLOR = Toasty.ERROR_COLOR; - @ColorInt - int INFO_COLOR = Toasty.INFO_COLOR; - @ColorInt - int SUCCESS_COLOR = Toasty.SUCCESS_COLOR; - @ColorInt - int WARNING_COLOR = Toasty.WARNING_COLOR; - // 当前字体样式 - Typeface typeface = Toasty.currentTypeface; - // 字体大小 - int textSize = Toasty.textSize; - // 是否渲染图标 - boolean tintIcon = Toasty.tintIcon; - // 是否使用新的Toast - boolean isNewToast = Toasty.isNewToast; - - // 私有构造函数 - Config() { - } - - @CheckResult - public static Config getInstance() { - return new Config(); - } - - // == - - @CheckResult - public Config setTextColor(@ColorInt int textColor) { - DEFAULT_TEXT_COLOR = textColor; - return this; - } - - @CheckResult - public Config setErrorColor(@ColorInt int errorColor) { - ERROR_COLOR = errorColor; - return this; - } - - @CheckResult - public Config setInfoColor(@ColorInt int infoColor) { - INFO_COLOR = infoColor; - return this; - } - - @CheckResult - public Config setSuccessColor(@ColorInt int successColor) { - SUCCESS_COLOR = successColor; - return this; - } - - @CheckResult - public Config setWarningColor(@ColorInt int warningColor) { - WARNING_COLOR = warningColor; - return this; - } - - @CheckResult - public Config setToastTypeface(@NonNull Typeface typeface) { - this.typeface = typeface; - return this; - } - - @CheckResult - public Config setTextSize(int sizeInSp) { - this.textSize = sizeInSp; - return this; - } - - @CheckResult - public Config setTintIcon(boolean tintIcon) { - this.tintIcon = tintIcon; - return this; - } - - @CheckResult - public Config setNewToast(boolean isNewToast) { - this.isNewToast = isNewToast; - return this; - } - - /** 应用配置参数生效 */ - public void apply() { - Toasty.DEFAULT_TEXT_COLOR = DEFAULT_TEXT_COLOR; - Toasty.ERROR_COLOR = ERROR_COLOR; - Toasty.INFO_COLOR = INFO_COLOR; - Toasty.SUCCESS_COLOR = SUCCESS_COLOR; - Toasty.WARNING_COLOR = WARNING_COLOR; - Toasty.currentTypeface = typeface; - Toasty.textSize = textSize; - Toasty.tintIcon = tintIcon; - Toasty.isNewToast = isNewToast; - } - - /** 重置默认参数 */ - public static void reset() { - Toasty.DEFAULT_TEXT_COLOR = Color.parseColor("#FFFFFF"); - Toasty.ERROR_COLOR = Color.parseColor("#D50000"); - Toasty.INFO_COLOR = Color.parseColor("#3F51B5"); - Toasty.SUCCESS_COLOR = Color.parseColor("#388E3C"); - Toasty.WARNING_COLOR = Color.parseColor("#FFA900"); - Toasty.currentTypeface = LOADED_TOAST_TYPEFACE; - Toasty.textSize = 16; - Toasty.tintIcon = true; - Toasty.isNewToast = false; - } - } - - // =================== - // == Toast显示操作 == - // =================== - - // === normal === - - public static void normal(@NonNull Context context, @NonNull CharSequence message) { - normal(context, message, Toast.LENGTH_SHORT, null); - } - - public static void normal(@NonNull Context context, @NonNull CharSequence message, Drawable icon) { - normal(context, message, Toast.LENGTH_SHORT, icon); - } - - public static void normal(@NonNull Context context, @NonNull CharSequence message, int duration) { - normal(context, message, duration, null); - } - - public static void normal(@NonNull Context context, @NonNull CharSequence message, int duration, Drawable icon) { - custom(context, message, icon, NORMAL_COLOR, duration, true); - } - - // === warning === - - public static void warning(@NonNull Context context, @NonNull CharSequence message) { - warning(context, message, Toast.LENGTH_SHORT, true); - } - - public static void warning(@NonNull Context context, @NonNull CharSequence message, int duration) { - warning(context, message, duration, true); - } - - public static void warning(@NonNull Context context, @NonNull CharSequence message, int duration, boolean withIcon) { - custom(context, message, ToastyUtils.getDrawable(context, R.drawable.dev_toast_ic_error_outline_white), WARNING_COLOR, duration, withIcon); - } - - // === info === - - public static void info(@NonNull Context context, @NonNull CharSequence message) { - info(context, message, Toast.LENGTH_SHORT, true); - } - - public static void info(@NonNull Context context, @NonNull CharSequence message, int duration) { - info(context, message, duration, true); - } - - public static void info(@NonNull Context context, @NonNull CharSequence message, int duration, boolean withIcon) { - custom(context, message, ToastyUtils.getDrawable(context, R.drawable.dev_toast_ic_info_outline_white), INFO_COLOR, duration, withIcon); - } - - // === success === - - public static void success(@NonNull Context context, @NonNull CharSequence message) { - success(context, message, Toast.LENGTH_SHORT, true); - } - - public static void success(@NonNull Context context, @NonNull CharSequence message, int duration) { - success(context, message, duration, true); - } - - public static void success(@NonNull Context context, @NonNull CharSequence message, int duration, boolean withIcon) { - custom(context, message, ToastyUtils.getDrawable(context, R.drawable.dev_toast_ic_check_white), SUCCESS_COLOR, duration, withIcon); - } - - // === error === - - public static void error(@NonNull Context context, @NonNull CharSequence message) { - error(context, message, Toast.LENGTH_SHORT, true); - } - - public static void error(@NonNull Context context, @NonNull CharSequence message, int duration) { - error(context, message, duration, true); - } - - public static void error(@NonNull Context context, @NonNull CharSequence message, int duration, boolean withIcon) { - custom(context, message, ToastyUtils.getDrawable(context, R.drawable.dev_toast_ic_clear_white), ERROR_COLOR, duration, withIcon); - } - - // === custom === - - public static void custom(@NonNull Context context, @NonNull CharSequence message) { - custom(context, message, null, -1, Toast.LENGTH_SHORT, false); - } - - public static void custom(@NonNull Context context, @NonNull CharSequence message, Drawable icon) { - custom(context, message, icon, -1, Toast.LENGTH_SHORT, true); - } - - public static void custom(@NonNull Context context, @NonNull CharSequence message, @DrawableRes int iconRes, @ColorInt int tintColor) { - custom(context, message, ToastyUtils.getDrawable(context, iconRes), tintColor, Toast.LENGTH_SHORT, true); - } - /** - * 通用自定义显示Toast - * @param context 上下文 - * @param message 显示的内容 - * @param icon 图标 - * @param tintColor 背景颜色渲染 - * @param duration 显示时间 - * @param withIcon 是否显示图标 - */ - public static void custom(@NonNull Context context, @NonNull CharSequence message, Drawable icon, @ColorInt int tintColor, int duration, boolean withIcon) { - custom(context, message, icon, tintColor, duration, withIcon, tintIcon); - } - - /** - * 通用自定义显示Toast - * @param context 上下文 - * @param message 显示的内容 - * @param icon 图标 - * @param tintColor 背景颜色渲染 - * @param duration 显示时间 - * @param withIcon 是否显示图标 - * @param tintIcon 是否处理图标 - */ - public static void custom(@NonNull Context context, @NonNull CharSequence message, Drawable icon, @ColorInt int tintColor, int duration, boolean withIcon, boolean tintIcon) { - // 初始化View - View view = inflaterView(context, message, icon, tintColor, withIcon, tintIcon); - // 显示Toast - showToasty(context, view, duration, isNewToast); - } - - /** - * 实例化 Layout View - * @param context - * @param message - * @param icon - * @param tintColor - * @param withIcon - * @param tintIcon - * @return - */ - static View inflaterView(@NonNull Context context, @NonNull CharSequence message, Drawable icon, @ColorInt int tintColor, boolean withIcon, boolean tintIcon){ - if (context != null){ - try { - // 引入View - final View toastLayout = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.dev_toast_layout, null); - // 初始化View - final ImageView toastIcon = (ImageView) toastLayout.findViewById(R.id.vid_dtl_toast_igview); - final TextView toastTextView = (TextView) toastLayout.findViewById(R.id.vid_dtl_toast_tv); - // 背景图片 - Drawable drawableFrame; - // 判断是否渲染背景 - if (tintColor != -1) { // 根据背景色进行渲染透明图片 - drawableFrame = ToastyUtils.tint9PatchDrawableFrame(context, tintColor); - } else { // 获取背景透明图片 - drawableFrame = ToastyUtils.getDrawable(context, R.drawable.dev_toast_frame); - } - // 进行设置背景 - ToastyUtils.setBackground(toastLayout, drawableFrame); - // 判断是否显示图标 - if (withIcon && icon != null) { - // 是否渲染图标 - if (tintIcon) { - // 进行渲染图标 - icon = ToastyUtils.tintIcon(icon, DEFAULT_TEXT_COLOR); - } - // 设置图标背景图 - ToastyUtils.setBackground(toastIcon, icon); - } else { - // 隐藏图标 - toastIcon.setVisibility(View.GONE); - } - // 设置字体颜色 - toastTextView.setTextColor(DEFAULT_TEXT_COLOR); - // 设置消息内容 - toastTextView.setText(message); - // 设置字体 - toastTextView.setTypeface(currentTypeface); - // 设置字体大小 - toastTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize); - // 返回View - return toastLayout; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "inflaterView"); - } - } - // 默认返回null - return null; - } - - /** - * 最终显示Toast方法 - * @param context - * @param view - * @param duration - * @param isNewToast - */ - public static void showToasty(Context context, View view, int duration, boolean isNewToast) { - // 防止上下文为null - if (context == null){ - return; - } else if (view == null) { // 防止显示的View 为null - return; - } - try { - // 判断是否显示新的 Toast - if (isNewToast){ - // 生成新的Toast - Toast toast = new Toast(context); - // 设置显示的View - toast.setView(view); - // 设置显示的时间 - toast.setDuration(duration); - // 显示Toast - toast.show(); - } else { - if (mToast == null){ - // 生成新的Toast - mToast = new Toast(context); - } - // 设置显示的View - mToast.setView(view); - // 设置显示的时间 - mToast.setDuration(duration); - // 显示Toast - mToast.show(); - } - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "showToasty"); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/toast/cus/ToastyUtils.java b/DevLibUtils/src/main/java/dev/utils/app/toast/cus/ToastyUtils.java deleted file mode 100644 index e90b94d031..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/toast/cus/ToastyUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -package dev.utils.app.toast.cus; - -import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.os.Build; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.NonNull; -import android.view.View; - -import dev.utils.R; - -/** - * detail: Toasty 快捷操作工具类 - * Created by Ttt - * https://github.com/GrenderG/Toasty - */ -public final class ToastyUtils { - - private ToastyUtils() { - } - - public static Drawable tintIcon(@NonNull Drawable drawable, @ColorInt int tintColor) { - drawable.setColorFilter(tintColor, PorterDuff.Mode.SRC_IN); - return drawable; - } - - public static Drawable tint9PatchDrawableFrame(@NonNull Context context, @ColorInt int tintColor) { - final NinePatchDrawable toastDrawable = (NinePatchDrawable) getDrawable(context, R.drawable.dev_toast_frame); - return tintIcon(toastDrawable, tintColor); - } - - public static void setBackground(@NonNull View view, Drawable drawable) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) - view.setBackground(drawable); - else - view.setBackgroundDrawable(drawable); - } - - public static Drawable getDrawable(@NonNull Context context, @DrawableRes int id) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - return context.getDrawable(id); - else - return context.getResources().getDrawable(id); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiHotUtils.java b/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiHotUtils.java deleted file mode 100644 index f0385382ff..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiHotUtils.java +++ /dev/null @@ -1,527 +0,0 @@ -package dev.utils.app.wifi; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.DhcpInfo; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.support.annotation.RequiresApi; -import android.text.TextUtils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.lang.reflect.Method; - -import dev.utils.LogPrintUtils; - -/** - * detail: Wifi 热点工具类 - * Created by Ttt - * - * - * - * - * - * - * Android 8.0 开始, 热点操作方法, 已经变更 - * https://blog.csdn.net/bukker/article/details/78649504 - * android 7.1 系统以上不支持自动开启热点,需要手动开启热点 - * https://www.jianshu.com/p/9dbb02c3e21f - */ -public class WifiHotUtils { - - // 日志TAG - private final static String TAG = WifiHotUtils.class.getSimpleName(); - // 对应的上下文 - private Context mContext; - /** 定义WifiManager对象 */ - private WifiManager mWifiManager; - // 热点 Wifi 配置 - private WifiConfiguration apWifiConfig; - - /** 构造器(只能进行初始化WifiManager操作,其他靠方法定义) */ - public WifiHotUtils(Context context) { - this.mContext = context; - // 初始化WifiManager对象 - mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - } - - // == Wifi 操作 == - - /** - * 创建Wifi配置信息(无其他操作,单独返回WifiConfig) => Wifi热点 (就支持 无密码/WPA2 PSK) - * @param ssid - * @param pwd 密码需要大于等于8位 - * @return - */ - public static WifiConfiguration createWifiConfigToAp(String ssid, String pwd) { - try { - // 创建一个新的网络配置 - WifiConfiguration wifiConfig = new WifiConfiguration(); - wifiConfig.allowedAuthAlgorithms.clear(); - wifiConfig.allowedGroupCiphers.clear(); - wifiConfig.allowedKeyManagement.clear(); - wifiConfig.allowedPairwiseCiphers.clear(); - wifiConfig.allowedProtocols.clear(); - wifiConfig.priority = 0; - /** 设置连接的SSID */ - wifiConfig.SSID = ssid; - // 判断密码 - if (TextUtils.isEmpty(pwd)) { - wifiConfig.hiddenSSID = true; - wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); - } else { - wifiConfig.preSharedKey = pwd; - wifiConfig.hiddenSSID = true; - wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); - wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); -// wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); - wifiConfig.status = WifiConfiguration.Status.ENABLED; - } - return wifiConfig; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "createWifiConfigToAp"); - } - return null; - } - - /** - * 开启Wifi热点 - * @param wifiConfig wifi配置 - */ - public void stratWifiAp(WifiConfiguration wifiConfig) { - this.apWifiConfig = wifiConfig; - // 大于 8.0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - // 关闭热点 - if (mReservation != null){ - mReservation.close(); - } - // 清空信息 - apWifiSSID = apWifiPwd = null; - // Android 8.0 是基于应用开启的, 必须使用固定生成的配置进行发送连接, 无法进行控制(App 关闭后, 热点自动关闭) - // 必须有定位权限 - mWifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() { - @Override - public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { - super.onStarted(reservation); - // 保存信息 - mReservation = reservation; - // 获取配置信息 - WifiConfiguration wifiConfiguration = reservation.getWifiConfiguration(); - apWifiSSID = wifiConfiguration.SSID; - apWifiPwd = wifiConfiguration.preSharedKey; - // 打印信息 - LogPrintUtils.dTag(TAG, "Android 8.0 wifi Ap ssid: " + apWifiSSID + ", pwd: " + apWifiPwd); - // 触发回调 - if (wifiAPListener != null){ - wifiAPListener.onStarted(wifiConfiguration); - } - } - - @Override - public void onStopped() { - super.onStopped(); - // 打印信息 - LogPrintUtils.dTag(TAG, "Android 8.0 onStopped wifiAp"); - // 触发回调 - if (wifiAPListener != null){ - wifiAPListener.onStopped(); - } - } - - @Override - public void onFailed(int reason) { - super.onFailed(reason); - // 打印信息 - LogPrintUtils.eTag(TAG, "Android 8.0 onFailed wifiAp, reason : " + reason); - // 触发回调 - if (wifiAPListener != null){ - wifiAPListener.onFailed(reason); - } - } - }, null); - } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1){ // android 7.1 系统以上不支持自动开启热点,需要手动开启热点 - // 先设置wifi热点信息, 这样跳转前保存热点信息, 开启热点则是对应设置的信息 - boolean setResult = setWifiApConfiguration(wifiConfig); - // 打印日志 - LogPrintUtils.dTag(TAG, "设置Wifi 热点信息是否成功: " + setResult); - // https://www.cnblogs.com/bluestorm/p/3665555.html - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_MAIN); - intent.setComponent(new ComponentName("com.android.settings", "com.android.settings.TetherSettings")); - mContext.startActivity(intent); - } else { - try { - // 获取设置Wifi热点方法 - Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class); - // 开启Wifi热点 - method.invoke(mWifiManager, wifiConfig, true); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "stratWifiAp"); - } - } - } - - /** - * 关闭Wifi热点 - */ - public void closeWifiAp(){ - // 大于 8.0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - // 关闭热点 - if (mReservation != null){ - mReservation.close(); - } - // 清空信息 - apWifiSSID = apWifiPwd = null; - return; - } - try { - // 获取设置Wifi热点方法 - Method method = mWifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class); - // 创建一个新的网络配置 - WifiConfiguration wifiConfig = new WifiConfiguration(); - wifiConfig.allowedAuthAlgorithms.clear(); - wifiConfig.allowedGroupCiphers.clear(); - wifiConfig.allowedKeyManagement.clear(); - wifiConfig.allowedPairwiseCiphers.clear(); - wifiConfig.allowedProtocols.clear(); - wifiConfig.priority = 0; - // 设置Wifi SSID - wifiConfig.SSID = "CloseWifiAp"; // formatSSID(ssid,true); - // 设置Wifi密码 - wifiConfig.preSharedKey = "CloseWifiAp"; - // 设置Wifi属性 - wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); - wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); - wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); - wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); - // 开启Wifi热点 - method.invoke(mWifiManager, wifiConfig, false); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "closeWifiAp"); - } - } - - // ========================== 手机热点功能 ========================== - /** Wifi热点正在关闭 -- WifiManager.WIFI_AP_STATE_DISABLING */ - public static final int WIFI_AP_STATE_DISABLING = 10; - /** Wifi热点已关闭 -- WifiManager.WIFI_AP_STATE_DISABLED */ - public static final int WIFI_AP_STATE_DISABLED = 11; - /** Wifi热点正在打开 -- WifiManager.WIFI_AP_STATE_ENABLING */ - public static final int WIFI_AP_STATE_ENABLING = 12; - /** Wifi热点已打开 -- WifiManager.WIFI_AP_STATE_ENABLED */ - public static final int WIFI_AP_STATE_ENABLED = 13; - /** Wifi热点状态未知 -- WifiManager.WIFI_AP_STATE_FAILED */ - public static final int WIFI_AP_STATE_FAILED = 14; - - /** - * 获取Wifi热点状态 - * @return - */ - public int getWifiApState() { - try { - // 反射获取方法 - Method method = mWifiManager.getClass().getMethod("getWifiApState"); - // 调用方法,获取状态 - int wifiApState = (Integer) method.invoke(mWifiManager); - // 打印状态 - LogPrintUtils.dTag(TAG, "WifiApState: " + wifiApState); - return wifiApState; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getWifiApState"); - } - return WIFI_AP_STATE_FAILED; - } - - /** - * 获取Wifi热点配置信息 - * @return - */ - public WifiConfiguration getWifiApConfiguration(){ - try { - // 获取Wifi热点方法 - Method method = mWifiManager.getClass().getMethod("getWifiApConfiguration"); - // 获取配置 - WifiConfiguration wifiApConfig = (WifiConfiguration) method.invoke(mWifiManager); - // 返回配置信息 - return wifiApConfig; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getWifiApConfiguration"); - } - return null; - } - - /** - * 设置Wifi热点配置信息 - * @param apWifiConfig - * @return 是否成功 - */ - public boolean setWifiApConfiguration(WifiConfiguration apWifiConfig){ - try { - // 获取设置Wifi热点方法 - Method method = mWifiManager.getClass().getMethod("setWifiApConfiguration", WifiConfiguration.class); - // 开启Wifi热点 - return (boolean) method.invoke(mWifiManager, apWifiConfig); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, e, "setWifiApConfiguration"); - } - return false; - } - - // = - - /** - * 判断是否打开Wifi热点 - * @return - */ - public boolean isOpenWifiAp(){ - // 判断是否开启热点(默认未打开) - boolean isOpen = false; - // 获取当前Wifi热点状态 - int wifiApState = getWifiApState(); - switch(wifiApState){ - case WIFI_AP_STATE_DISABLING: // Wifi热点正在关闭 - break; - case WIFI_AP_STATE_DISABLED: // Wifi热点已关闭 - break; - case WIFI_AP_STATE_ENABLING: // Wifi热点正在打开 - break; - case WIFI_AP_STATE_ENABLED: // Wifi热点已打开 - isOpen = true; - break; - case WIFI_AP_STATE_FAILED: // Wifi热点状态未知 - break; - } - return isOpen; - } - - /** - * 关闭Wifi热点(判断当前状态) - * @param isExecute 是否执行关闭 - * @return 之前是否打开热点 - */ - public boolean closeWifiApCheck(boolean isExecute){ - // 判断是否开启热点(默认是) - boolean isOpen = true; - // 获取当前Wifi热点状态 - int wifiApState = getWifiApState(); - switch(wifiApState){ - case WIFI_AP_STATE_DISABLING: // Wifi热点正在关闭 - isExecute = false; - break; - case WIFI_AP_STATE_DISABLED: // Wifi热点已关闭 - isOpen = false; - break; - case WIFI_AP_STATE_ENABLING: // Wifi热点正在打开 - break; - case WIFI_AP_STATE_ENABLED: // Wifi热点已打开 - break; - case WIFI_AP_STATE_FAILED: // Wifi热点状态未知 - break; - } - // 如果属于开启,则进行关闭 - if(isOpen && isExecute){ - closeWifiAp(); - } - return isOpen; - } - - /** - * 是否有连接热点 - * @return - */ - public boolean isConnectHot(){ - try { - BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); - String line; - while ((line = br.readLine()) != null) { - String[] splitted = line.split(" +"); - if (splitted != null && splitted.length >= 4) { - String ipAdr = splitted[0]; // Ip地址 - // 防止地址为null,并且需要以.拆分 存在4个长度 255.255.255.255 - if(ipAdr != null && ipAdr.split("\\.").length >= 3){ - return true; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isConnectHot"); - } - return false; - } - - /** - * 获取热点主机ip地址 - * @return - */ - public String getHotspotServiceIp(){ - try { - // 获取网关信息 - DhcpInfo dhcpinfo = mWifiManager.getDhcpInfo(); - // 获取服务器地址 - return intToString(dhcpinfo.serverAddress); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getHotspotServiceIp"); - } - return null; - } - - /** - * 获取连接上的子网关热点IP(一个) - * @return - */ - public String getHotspotAllotIp(){ - try { - BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); - String line; - while ((line = br.readLine()) != null) { - String[] splitted = line.split(" +"); - if (splitted != null && splitted.length >= 4) { - String ipAdr = splitted[0]; // Ip地址 - // 防止地址为null,并且需要以.拆分 存在4个长度 255.255.255.255 - if(ipAdr != null && ipAdr.split("\\.").length >= 3){ - return ipAdr; - } - } - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getHotspotAllotIp"); - } - return null; - } - - /** - * 获取热点拼接后的ip网关掩码 - * @param df 默认网关掩码 - * @param ipAdr ip地址 - * @return - */ - public String getHotspotSplitIpMask(String df, String ipAdr){ - // 网关掩码 - String hsMask = df; - // 获取网关掩码 - if(ipAdr != null){ - try { - int length = ipAdr.lastIndexOf("."); - // 进行裁剪 - hsMask = ipAdr.substring(0,length) + ".255"; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getHotspotSplitIpMask"); - } - } - return hsMask; - } - -// /** -// * 获取连接的热点信息(暂时不用) -// * @return -// */ -// private void getConnectedHotMsg() { -// try { -// BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); -// String line; -// while ((line = br.readLine()) != null) { -// String[] splitted = line.split(" +"); -// if (splitted != null && splitted.length >= 4) { -// String ip = splitted[0]; // Ip地址 -// String mac = splitted[3]; // Mac 地址 -// } -// } -// } catch (Exception e) { -// LogPrintUtils.eTag(TAG, e, "getConnectedHotMsg"); -// } -// } - - /** - * 转换ip地址 - * @param data 需要转换的数据 - * @return - */ - private String intToString(int data) { - StringBuffer sb = new StringBuffer(); - int b = (data >> 0) & 0xff; - sb.append(b + "."); - b = (data >> 8) & 0xff; - sb.append(b + "."); - b = (data >> 16) & 0xff; - sb.append(b + "."); - b = (data >> 24) & 0xff; - sb.append(b); - return sb.toString(); - } - - // == Android 8.0相关 == - - // Wifi ssid - private String apWifiSSID; - // wifi 密码 - private String apWifiPwd; - // wifi 热点对象 - private WifiManager.LocalOnlyHotspotReservation mReservation; - - /** - * 获取Wifi 热点名 - * @return - */ - public String getApWifiSSID() { - // 大于 8.0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - return apWifiSSID; - } else { - if (apWifiConfig != null){ - return apWifiConfig.SSID; - } - } - return null; - } - - /** - * 获取Wifi 热点密码 - * @return - */ - public String getApWifiPwd() { - // 大于 8.0 - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - return apWifiPwd; - } else { - if (apWifiConfig != null){ - return apWifiConfig.preSharedKey; - } - } - return null; - } - - // = - - private onWifiAPListener wifiAPListener; - - @RequiresApi(api = Build.VERSION_CODES.O) - public void setOnWifiAPListener(onWifiAPListener wifiAPListener) { - this.wifiAPListener = wifiAPListener; - } - - /** - * detail: Android Wifi监听 - * Created by Ttt - */ - public interface onWifiAPListener { - - void onStarted(WifiConfiguration wifiConfig); - - void onStopped(); - - void onFailed(int reason); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiUtils.java b/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiUtils.java deleted file mode 100644 index 3ffb795904..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiUtils.java +++ /dev/null @@ -1,923 +0,0 @@ -package dev.utils.app.wifi; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.wifi.ScanResult; -import android.net.wifi.WifiConfiguration; -import android.net.wifi.WifiConfiguration.KeyMgmt; -import android.net.wifi.WifiInfo; -import android.net.wifi.WifiManager; -import android.os.Build; -import android.text.TextUtils; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.net.InetAddress; -import java.util.ArrayList; -import java.util.List; - -import dev.utils.LogPrintUtils; - -/** - * detail: wifi工具类 - * Created by Ttt - * - * - * - */ -public final class WifiUtils { - - // 日志TAG - private final static String TAG = WifiUtils.class.getSimpleName(); - - // ======= wifi管理类对象 ======= - /** 定义WifiManager对象 */ - private WifiManager mWifiManager; - // ======= 常量 ======= - /** 没有密码 */ - public static final int NOPWD = 0; - /** wep加密方式 */ - public static final int WEP = 1; - /** wpa加密方式 */ - public static final int WPA = 2; - // ==================================================== - - /** 构造器(只能进行初始化WifiManager操作,其他靠方法定义) */ - public WifiUtils(Context context) { - // 初始化WifiManager对象 - mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - } - - /** - * 获取wifi管理对象 - * @return - */ - public WifiManager getWifiManager(){ - return this.mWifiManager; - } - - // ================= wifi 开关、连接状态获取 ================= - - /** 判断是否打开wifi */ - public boolean isOpenWifi(){ - return mWifiManager.isWifiEnabled(); - } - - /** 打开WIFI */ - public void openWifi() { - // 如果没有打开wifi,才进行打开 - if (!isOpenWifi()) { - mWifiManager.setWifiEnabled(true); - } - } - - /** 关闭WIFI */ - public void closeWifi() { - // 如果已经打开了wifi,才进行关闭 - if (isOpenWifi()) { - mWifiManager.setWifiEnabled(false); - } - } - - /** 自动切换wifi开关状态 */ - public void toggleWifiEnabled(){ - // 如果打开了,则关闭 - // 如果关闭了,则打开 - // ================= - mWifiManager.setWifiEnabled(!isOpenWifi()); - } - - /** 获取当前WIFI连接状态 */ - public int getWifiState() { - // WifiManager.WIFI_STATE_ENABLED: // 已打开 - // WifiManager.WIFI_STATE_ENABLING: // 正在打开 - // WifiManager.WIFI_STATE_DISABLED: // 已关闭 - // WifiManager.WIFI_STATE_DISABLING: // 正在关闭 - // WifiManager.WIFI_STATE_UNKNOWN: // 未知 - return mWifiManager.getWifiState(); - } - - // ================= GET 操作 ================= - - /** 开始扫描wifi */ - public boolean startScan(){ - // 开始扫描 - return mWifiManager.startScan(); - } - - /** 获取已配置的网络 */ - public List getConfiguration() { - return mWifiManager.getConfiguredNetworks(); - } - - /** 获取网络列表 */ - public List getWifiList() { - return mWifiManager.getScanResults(); - } - - /** 获取WifiInfo对象 */ - public WifiInfo getWifiInfo(){ - return mWifiManager.getConnectionInfo(); - } - - /** 获取MAC地址 */ - public String getMacAddress(WifiInfo wifiInfo) { - return wifiInfo.getMacAddress(); - } - - /** 获取接入点的BSSID */ - public String getBSSID(WifiInfo wifiInfo) { - return wifiInfo.getBSSID(); - } - - /** 获取IP地址 */ - public int getIPAddress(WifiInfo wifiInfo) { - return wifiInfo.getIpAddress(); - } - - /** 获取连接的ID */ - public int getNetworkId(WifiInfo wifiInfo) { - return wifiInfo.getNetworkId(); - } - - // === - - /** - * 获取SSID - * @param wifiInfo wifi信息 - * @return - */ - public static String getSSID(WifiInfo wifiInfo){ - try { - // 获取SSID,并进行处理 - return formatSSID(wifiInfo.getSSID(), false); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSSID"); - } - return null; - } - - /** - * 通过上下文获取当前连接的ssid - * @param context - */ - public static String getSSID(Context context){ - try { - // 初始化WifiManager对象 - WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - // 获取当前连接的wifi - WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); - // 获取wifi - SSID - return formatSSID(wifiInfo.getSSID(), false); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "getSSID"); - } - return null; - } - - // === - - /** - * 判断是否存在\"ssid\",存在则裁剪返回 - */ - public static String formatSSID(String ssid){ - // 自动去掉SSID - if(ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"")){ - try { - // 裁剪连接的ssid,并返回 - return ssid.substring(1, ssid.length() - 1); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "formatSSID"); - } - } - return ssid; - } - - /** - * 格式化,处理SSID - * @param ssid - * @param isHandler true = 添加引号,false = 删除引号 - * @return - */ - public static String formatSSID(String ssid, boolean isHandler){ - if(isHandler){ - return "\"" + ssid + "\""; - } else { - return formatSSID(ssid); - } - } - - /** - * 获取密码(经过处理) - * @param pwd 需要处理的密码 - * @param isJudge 是否需要判断 - * @return - */ - public String getPassword(String pwd, boolean isJudge){ - if (isJudge && isHexWepKey(pwd)) { - return pwd; - } else { - return "\"" + pwd + "\""; - } - } - - protected static boolean isHexWepKey(String wepKey) { - // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?) - int len = wepKey.length(); - if (len != 10 && len != 26 && len != 58) { - return false; - } - return isHex(wepKey); - } - - protected static boolean isHex(String key) { - for (int i = key.length() - 1;i >= 0;i--) { - char c = key.charAt(i); - if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) { - return false; - } - } - return true; - } - - // ================= 快捷操作 ================= - - /** - * 获取加密类型(int常量) - 判断String - * @param type - */ - public static int getWifiType(String type) { - // WPA 是本机的用法 - if (type.contains("WPA")) { - return WPA; - } else if (type.contains("WEP")) { - return WEP; - } - // 默认没有密码 - return NOPWD; - } - - /** - * 获取加密类型(int常量) - 判断int(String) - * @param type - */ - public static int getWifiTypeInt(String type){ - // WPA 是本机的用法 - if (type.equals("2")) { - return WPA; - } else if (type.equals("1")) { - return WEP; - } - // 默认没有密码 - return NOPWD; - } - - /** - * 获取加密类型(int常量) - * @param type - */ - public static String getWifiType(int type){ - switch(type){ - case WPA: - return "2"; - case WEP: - return "1"; - case NOPWD: - return "0"; - } - return "0"; - } - - /** - * 获取加密类型(String) - * @param type - * @return - */ - public static String getWifiTypeStr(int type) { - switch(type){ - case WPA: - return "WPA"; - case WEP: - return "WEP"; - default: - return ""; - } - } - - /** - * 判断是否连接为null - - * @param ssid - * @return - */ - public static boolean isConnNull(String ssid){ - if(ssid == null){ - return true; - } else if (ssid.indexOf("unknown") != -1){ // - return true; - } - return false; - } - - /** - * 判断是否连接上Wifi(非连接中) - * @return 返回ssid - */ - public static String isConnectAphot(Context context){ - try { - // 连接管理 - ConnectivityManager cManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - // 连接状态 - NetworkInfo.State nState = cManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState(); - if((nState == NetworkInfo.State.CONNECTED)){ - // 获取连接的ssid - return getSSID(context); - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "isConnectAphot"); - } - return null; - } - - - // ================= Wifi配置操作 ================= - - /** 默认没有密码 */ - static final int SECURITY_NONE = 0; - /** WEP加密方式 */ - static final int SECURITY_WEP = 1; - /** PSK加密方式 */ - static final int SECURITY_PSK = 2; - /** EAP加密方式 */ - static final int SECURITY_EAP = 3; - - /** - * 获取Wifi配置,加密类型 - * @param wifiConfig - * @return - */ - public static int getSecurity(WifiConfiguration wifiConfig) { - if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { - return SECURITY_PSK; - } - if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_EAP) - || wifiConfig.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { - return SECURITY_EAP; - } - return (wifiConfig.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; - } - - /** - * 获知Wifi配置,是否属于密码加密类型 - * @param wifiConfig - * @return - */ - public static boolean isExsitsPwd(WifiConfiguration wifiConfig){ - int wifiSecurity = getSecurity(wifiConfig); - // 判断是否加密 - return (wifiSecurity != SECURITY_NONE); - } - - /** - * 查看以前是否也配置过这个网络 - * @param ssid 需要判断的wifi SSID - */ - public WifiConfiguration isExsits(String ssid){ - // 获取wifi 连接过的配置信息 - List listWifiConfigs = getConfiguration(); - // 防止为null - if(listWifiConfigs == null){ - return null; - } - // 遍历判断是否存在 - for(int i = 0, len = listWifiConfigs.size();i < len ;i++){ - WifiConfiguration wConfig = listWifiConfigs.get(i); - if(wConfig != null){ - if (wConfig.SSID.equals("\"" + ssid + "\"")){ - return wConfig; - } - } - } - return null; - } - - /** - * 查看以前是否也配置过这个网络 - * @param networkId 网络id - */ - public WifiConfiguration isExsits(int networkId){ - // 获取wifi 连接过的配置信息 - List listWifiConfigs = getConfiguration(); - // 防止为null - if(listWifiConfigs == null){ - return null; - } - // 遍历判断是否存在 - for(int i = 0, len = listWifiConfigs.size();i < len ;i++){ - WifiConfiguration wConfig = listWifiConfigs.get(i); - if(wConfig != null){ - if (wConfig.networkId == networkId){ - return wConfig; - } - } - } - return null; - } - - // ================= 配置操作 ================= - /** - * 删除指定的 Wifi(SSID) 配置信息 - * @param context - * @param ssid - * @return 删除结果 - */ - public static boolean delWifiConfig(Context context, String ssid){ - try { - // 初始化WifiManager对象 - WifiManager mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - // 获取wifi 连接过的配置信息 - List listWifiConfigs = mWifiManager.getConfiguredNetworks(); - // 防止为null - if(listWifiConfigs != null){ - // 遍历判断是否存在 - for(int i = 0, len = listWifiConfigs.size();i < len ;i++){ - WifiConfiguration wConfig = listWifiConfigs.get(i); - if(wConfig != null){ - if (wConfig.SSID.equals("\"" + ssid + "\"")){ - // 删除操作 - mWifiManager.removeNetwork(wConfig.networkId); - } - } - } - // 保存操作 - mWifiManager.saveConfiguration(); - return true; - } - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "delWifiConfig"); - } - return false; - } - - /** - * 快速连接Wifi(不使用静态ip方式) - * @param ssid wifi SSID - * @param pwd wifi密码 - * @param wType 加密类型 - */ - public WifiConfiguration quickConnWifi(String ssid, String pwd, int wType){ - return quickConnWifi(ssid, pwd, wType, false, null); - } - - /** - * 快速连接Wifi - * @param ssid wifi SSID - * @param pwd wifi密码 - * @param wType 加密类型 - * @param isStatic 是否使用静态ip连接 - * @param ip 静态IP地址 - * @return 连接成功的 WifiConfiguration - */ - public WifiConfiguration quickConnWifi(String ssid, String pwd, int wType, boolean isStatic, String ip){ - // 步骤 - // 1.创建Wifi静态Ip连接配置 - // 2.创建正常Wifi连接配置 - // 3.查询准备连接的Wifi-SSID 是否存在配置文件,准备进行删除 - // 4.查询当前连接的Wifi-SSID 准备进行断开 - // 5.同步进行断开,删除操作,并且进行保存 - // 6.调用连接方法 - // 7.返回连接的配置信息 - // ============= - // -- - try { - // 正常的Wifi连接配置 - WifiConfiguration connWifiConfig = null; - // 如果需要通过静态ip方式连接,则进行设置 - if(isStatic && !TextUtils.isEmpty(ip)){ - // 创建Wifi静态IP连接配置 - WifiConfiguration staticWifiConfig = setStaticWifiConfig(createWifiConfig(ssid, pwd, wType, true), ip); - // 如果静态ip方式,配置失败,则初始化正常连接的Wifi配置 - if(staticWifiConfig == null){ - // 创建正常的配置信息 - connWifiConfig = createWifiConfig(ssid, pwd, wType, true); - // -- - LogPrintUtils.dTag(TAG, "属于正常方式连接(DHCP)"); - } else { - // 设置静态信息 - connWifiConfig = staticWifiConfig; - // -- - LogPrintUtils.dTag(TAG, "属于静态IP方式连接"); - } - } else { - // 创建正常的配置信息 - connWifiConfig = createWifiConfig(ssid, pwd, wType, true); - // -- - LogPrintUtils.dTag(TAG, "属于正常方式连接(DHCP)"); - } - // 判断当前准备连接的wifi,是否存在配置文件 - WifiConfiguration preWifiConfig = this.isExsits(ssid); - // -- - if(preWifiConfig != null){ - // 存在则删除 - boolean isRemove = mWifiManager.removeNetwork(preWifiConfig.networkId); - // 打印结果 - LogPrintUtils.dTag(TAG, "删除旧的配置信息 - " + preWifiConfig.SSID + ", isRemove: " + isRemove); - // 保存配置 - mWifiManager.saveConfiguration(); - } - // -- - // 连接网络 - int _nId = mWifiManager.addNetwork(connWifiConfig); - if(_nId != -1){ - try { - // 获取当前连接的Wifi对象 - WifiInfo wifiInfo = getWifiInfo(); - // 获取连接的id - int networdId = wifiInfo.getNetworkId(); - // 禁用网络 - boolean isDisable = mWifiManager.disableNetwork(networdId); - // 断开之前的连接 - boolean isDisConnect = mWifiManager.disconnect(); - // 打印断开连接结果 - LogPrintUtils.dTag(TAG, "isDisConnect : " + isDisConnect + ", isDisable : " + isDisable); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "quickConnWifi - 关闭连接出错:" + _nId); - } - // 开始连接 - boolean isResult = mWifiManager.enableNetwork(_nId, true); - // -- - if(!isResult){ - isResult = mWifiManager.enableNetwork(_nId, true); - } - // 打印结果 - LogPrintUtils.dTag(TAG, "addNetwork(enableNetwork) - result : " + isResult); - } else { - // 尝试不带引号SSID连接 - connWifiConfig.SSID = formatSSID(connWifiConfig.SSID, false); - // 连接网络 - _nId = mWifiManager.addNetwork(connWifiConfig); - if(_nId !=-1){ - try { - // 获取当前连接的Wifi对象 - WifiInfo wifiInfo = getWifiInfo(); - // 获取连接的id - int networdId = wifiInfo.getNetworkId(); - // 禁用网络 - boolean isDisable = mWifiManager.disableNetwork(networdId); - // 断开之前的连接 - boolean isDisConnect = mWifiManager.disconnect(); - // 打印断开连接结果 - LogPrintUtils.dTag(TAG, "isDisConnect : " + isDisConnect + ", isDisable : " + isDisable); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "quickConnWifi - 关闭连接出错:" + _nId); - } - // 开始连接 - boolean isResult = mWifiManager.enableNetwork(_nId, true); - // -- - if(!isResult){ - isResult = mWifiManager.enableNetwork(_nId, true); - } - // 打印结果 - LogPrintUtils.dTag(TAG, "addNetwork(enableNetwork) - result : " + isResult); - } - } - // 保存id - connWifiConfig.networkId = _nId; - // 连接的networkId - LogPrintUtils.dTag(TAG, "连接的SSID - networkId : " + _nId); - // 返回连接的信息 - return connWifiConfig; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, "quickConnWifi", e); - } - return null; - } - - /** - * 创建Wifi配置信息(无其他操作,单独返回WifiConfig) - * @param ssid - * @param pwd - * @param wType - * @param isHandler 是否处理双引号 - */ - public static WifiConfiguration createWifiConfig(String ssid, String pwd, int wType, boolean isHandler) { - try { - // 创建一个新的网络配置 - WifiConfiguration wifiConfig = new WifiConfiguration(); - wifiConfig.allowedAuthAlgorithms.clear(); - wifiConfig.allowedGroupCiphers.clear(); - wifiConfig.allowedKeyManagement.clear(); - wifiConfig.allowedPairwiseCiphers.clear(); - wifiConfig.allowedProtocols.clear(); - wifiConfig.priority = 0; - /** 设置连接的SSID */ - if (isHandler) { - wifiConfig.SSID = formatSSID(ssid, true); - } else { - wifiConfig.SSID = ssid; - } - switch(wType){ - case WifiUtils.NOPWD: // 不存在密码 - wifiConfig.hiddenSSID = true; - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - break; - case WifiUtils.WEP: // WEP 加密方式 - wifiConfig.hiddenSSID = true; - if (isHandler) { - if (isHexWepKey(pwd)) { - wifiConfig.wepKeys[0] = pwd; - } else { - wifiConfig.wepKeys[0] = "\"" + pwd + "\""; - } - } else { - wifiConfig.wepKeys[0] = pwd; - } - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); - // wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); - wifiConfig.wepTxKeyIndex = 0; - break; - case WifiUtils.WPA: // WPA 加密方式 - if (isHandler) { - wifiConfig.preSharedKey = "\"" + pwd + "\""; - } else { - wifiConfig.preSharedKey = pwd; - } - wifiConfig.hiddenSSID = true; - wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); - wifiConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK); -// wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); - wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); - wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); - wifiConfig.status = WifiConfiguration.Status.ENABLED; - break; - } - return wifiConfig; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createWifiConfig"); - } - return null; - } - - // ==================== 连接操作 ======================== - /** - * 移除某个Wifi配置信息 - * @param wcg - * @return - */ - public boolean removeWifiConfig(WifiConfiguration wcg){ - // 如果等于null则直接返回 - if(wcg == null) - return false; - try { - // 删除配置 - boolean isResult = mWifiManager.removeNetwork(wcg.networkId); - // 保存操作 - mWifiManager.saveConfiguration(); - // 返回删除结果 - return isResult; - } catch (Exception e){ - LogPrintUtils.eTag(TAG, "removeWifiConfig", e); - } - return false; - } - - /** - * 断开指定ID的网络 - * @param netId wifiid - */ - public void disconnectWifi(int netId) { - try { - mWifiManager.disableNetwork(netId); - mWifiManager.disconnect(); - } catch (Exception e){ - LogPrintUtils.eTag(TAG, "disconnectWifi", e); - } - } - - // ========================== 设置静态ip,域名,等信息 ===================================== - - /** - * 设置静态Wifi信息 - * @param wifiConfig Wifi配置信息 - * @param ip 静态ip - * @return - */ - private WifiConfiguration setStaticWifiConfig(WifiConfiguration wifiConfig, String ip){ - String gateway = null; - String dns = null; - if(ip != null){ - try { - InetAddress intetAddress = InetAddress.getByName(ip); - int intIp = inetAddressToInt(intetAddress); - dns = (intIp & 0xFF) + "." + ((intIp >> 8) & 0xFF) + "." + ((intIp >> 16) & 0xFF) + ".1"; - gateway = dns; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "setStaticWifiConfig"); - return null; - } - } - // 暂时不需要设置dns,所以dns参数传入null - return setStaticWifiConfig(wifiConfig, ip, gateway, null, 24); - } - - /** - * 设置静态Wifi信息 - * @param wifiConfig Wifi配置信息 - * @param ip 静态ip - * @param gateway 网关 - * @param dns dns - * @param networkPrefixLength 网络前缀长度 - * @return - */ - private WifiConfiguration setStaticWifiConfig(WifiConfiguration wifiConfig, String ip, String gateway, String dns, int networkPrefixLength){ - try { - if(ip == null || gateway == null){ - return null; - } - // 设置Inet地址 - InetAddress intetAddress = InetAddress.getByName(ip); - if(Build.VERSION.SDK_INT <= 20){ // 旧的版本,5.0之前 - // 设置IP分配方式,静态ip - setEnumField(wifiConfig, "STATIC", "ipAssignment"); - // 设置不用代理 - setEnumField(wifiConfig, "NONE", "proxySettings"); - // 设置ip地址 - setIpAddress(intetAddress, networkPrefixLength, wifiConfig); - // 设置网关 - setGateway(InetAddress.getByName(gateway), wifiConfig); - if(dns != null){ // 判断是否需要设置域名 - // 设置DNS - setDNS(InetAddress.getByName(dns), wifiConfig); - } - } else { // 5.0新版本改变到其他地方 - Object obj = getDeclaredField(wifiConfig, "mIpConfiguration"); - // 设置IP分配方式,静态ip - setEnumField(obj, "STATIC", "ipAssignment"); - // 设置不用代理 - setEnumField(obj, "NONE", "proxySettings"); - // 设置ip地址、网关、DNS - setStaticIpConfig(ip, gateway, dns, networkPrefixLength, obj); - } - return wifiConfig; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "setStaticWifiConfig"); - } - return null; - } - - private int inetAddressToInt(InetAddress inetAddr) throws Exception { - byte[] addr = inetAddr.getAddress(); - if (addr.length != 4) { - throw new IllegalArgumentException("Not an IPv4 address"); - } - return ((addr[3] & 0xff) << 24) | ((addr[2] & 0xff) << 16) | ((addr[1] & 0xff) << 8) | (addr[0] & 0xff); - } - - /** - * 设置DNS - * @param dns - * @param wifiConfig - * @throws Exception - */ - private void setDNS(InetAddress dns, WifiConfiguration wifiConfig) throws Exception { - Object linkProperties = getField(wifiConfig, "linkProperties"); - if (linkProperties == null) - throw new NullPointerException(); - - ArrayList mDnses = (ArrayList) getDeclaredField(linkProperties, "mDnses"); - mDnses.clear(); // or add a new dns address , here I just want to replace DNS1 - mDnses.add(dns); - } - - /** - * 设置网关 - * @param gateway - * @param wifiConfig - * @throws Exception - */ - private void setGateway(InetAddress gateway, WifiConfiguration wifiConfig) throws Exception { - Object linkProperties = getField(wifiConfig, "linkProperties"); - if (linkProperties == null) - throw new NullPointerException(); - - Class routeInfoClass = Class.forName("android.net.RouteInfo"); - Constructor routeInfoConstructor = routeInfoClass.getConstructor(new Class[] { InetAddress.class }); - Object routeInfo = routeInfoConstructor.newInstance(gateway); - ArrayList mRoutes = (ArrayList) getDeclaredField(linkProperties, "mRoutes"); - mRoutes.clear(); - mRoutes.add(routeInfo); - } - - /** - * 设置Ip地址 - * @param addr ip地址 - * @param prefixLength 网络前缀长度 - * @param wifiConfig Wifi配置信息 - * @throws Exception - */ - private void setIpAddress(InetAddress addr, int prefixLength, WifiConfiguration wifiConfig) throws Exception { - Object linkProperties = getField(wifiConfig, "linkProperties"); - if (linkProperties == null) - throw new NullPointerException(); - - Class laClass = Class.forName("android.net.LinkAddress"); - Constructor laConstructor = laClass.getConstructor(new Class[] { InetAddress.class, int.class }); - Object linkAddress = laConstructor.newInstance(addr, prefixLength); - ArrayList mLinkAddresses = (ArrayList) getDeclaredField(linkProperties, "mLinkAddresses"); - mLinkAddresses.clear(); - mLinkAddresses.add(linkAddress); - } - - /** - * 设置Ip地址、网关、DNS(5.0之后) - * @param ip 静态ip - * @param gateway 网关 - * @param dns dns - * @param prefixLength 网络前缀长度 - * @param obj Wifi配置信息 - * @throws Exception - */ - private void setStaticIpConfig(String ip, String gateway, String dns, int prefixLength, Object obj) throws Exception { - // 从WifiConfig -> mIpConfiguration 获取staticIpConfiguration - // 获取 staticIpConfiguration 变量 - Object staticIpConfigClass = getField(obj, "staticIpConfiguration"); - if(staticIpConfigClass == null){ - // 创建静态ip配置类 - staticIpConfigClass = Class.forName("android.net.StaticIpConfiguration").newInstance(); - } - // 初始化LinkAddress 并设置ip地址 - Class laClass = Class.forName("android.net.LinkAddress"); - Constructor laConstructor = laClass.getConstructor(new Class[] { InetAddress.class, int.class }); - Object linkAddress = laConstructor.newInstance(InetAddress.getByName(ip), prefixLength); - // 设置地址ip地址 ipAddress - setValueField(staticIpConfigClass, linkAddress, "ipAddress"); - // 设置网关 gateway - setValueField(staticIpConfigClass, InetAddress.getByName(gateway), "gateway"); - if(dns != null){ // 判断是否需要设置域名 - // 设置DNS - ArrayList mDnses = (ArrayList) getDeclaredField(staticIpConfigClass, "dnsServers"); - mDnses.clear(); // or add a new dns address , here I just want to replace DNS1 - mDnses.add(InetAddress.getByName(dns)); - } - // 设置赋值 staticIpConfiguration 属性 - setValueField(obj, staticIpConfigClass, "staticIpConfiguration"); - } - - /** - * 通过反射获取public字段 - * @param obj - * @param name - * @return - * @throws Exception - */ - private Object getField(Object obj, String name) throws Exception { - Field f = obj.getClass().getField(name); - Object out = f.get(obj); - return out; - } - - /** - * 通过反射获取全部字段 - * @param obj - * @param name - * @return - * @throws Exception - */ - private Object getDeclaredField(Object obj, String name) throws Exception { - Field f = obj.getClass().getDeclaredField(name); - f.setAccessible(true); - Object out = f.get(obj); - return out; - } - - /** - * 通过反射枚举类,进行设置 - * @param obj 设置对象 - * @param value 设置参数值 - * @param name 变量名 - * @throws Exception 抛出异常 - */ - private void setEnumField(Object obj, String value, String name) throws Exception { - Field f = obj.getClass().getField(name); - f.set(obj, Enum.valueOf((Class) f.getType(), value)); - } - - /** - * 通过反射,进行设置 - * @param obj 设置对象 - * @param val 设置参数值 - * @param name 变量名 - * @throws Exception 抛出异常 - */ - private void setValueField(Object obj, Object val, String name) throws Exception { - Field f = obj.getClass().getField(name); - f.set(obj, val); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiVo.java b/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiVo.java deleted file mode 100644 index 1c37c7f259..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/app/wifi/WifiVo.java +++ /dev/null @@ -1,119 +0,0 @@ -package dev.utils.app.wifi; - -import android.net.wifi.ScanResult; -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.ArrayList; -import java.util.List; - -import dev.utils.LogPrintUtils; - -/** - * detail: Wifi信息实体类 - * Created by Ttt - */ -public class WifiVo implements Parcelable { - - // 日志TAG - private static final String TAG = WifiVo.class.getSimpleName(); - /** wifi SSID */ - public String wSSID = null; - /** wifi 密码 */ - public String wPwd = null; - /** wifi 加密类型 */ - public int wType = WifiUtils.NOPWD; - /** wifi 信号等级 */ - public int wLevel = 0; - - // -- - - public WifiVo(){ - } - - /** - * 获取Wifi信息 - * @param sResult 扫描的Wifi信息 - */ - public static WifiVo createWifiVo(ScanResult sResult){ - if (sResult != null){ - try { - // 防止wifi名长度为0 - if(sResult.SSID.length() == 0){ - return null; - } - // 初始化wifi信息实体类 - WifiVo wifiVo = new WifiVo(); - // 保存ssid - wifiVo.wSSID = WifiUtils.formatSSID(sResult.SSID, false); - // 保存加密类型 - wifiVo.wType = WifiUtils.getWifiType(sResult.capabilities); - // 保存wifi信号等级 - wifiVo.wLevel = sResult.level; - return wifiVo; - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "createWifiVo"); - } - } - return null; - } - - /** - * 扫描Wifi信息 - * @param listWifiVos 处理后数据源 - * @param listScanResults 扫描返回的数据 - */ - public static void scanWifiVos(ArrayList listWifiVos, List listScanResults){ - // 清空旧数据 - listWifiVos.clear(); - // 遍历wifi列表数据 - for(int i = 0, len = listScanResults.size();i < len;i++){ - // 如果出现异常,或者失败,则无视当前的索引wifi信息 - try { - // 获取当前索引的wifi信息 - ScanResult sResult = listScanResults.get(i); - // 防止wifi名长度为0 - if(sResult.SSID.length() == 0){ - continue; - } - // 保存wifi信息 - listWifiVos.add(createWifiVo(sResult)); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "scanWifiVos"); - } - } - } - - // =============== Parcelable =============== - - protected WifiVo(Parcel in) { - wSSID = in.readString(); - wPwd = in.readString(); - wType = in.readInt(); - wLevel = in.readInt(); - } - - public static final Creator CREATOR = new Creator() { - @Override - public WifiVo createFromParcel(Parcel in) { - return new WifiVo(in); - } - @Override - public WifiVo[] newArray(int size) { - return new WifiVo[size]; - } - }; - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(wSSID); - dest.writeString(wPwd); - dest.writeInt(wType); - dest.writeInt(wLevel); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/AssistUtils.java b/DevLibUtils/src/main/java/dev/utils/common/AssistUtils.java deleted file mode 100644 index 4eca7a3b20..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/AssistUtils.java +++ /dev/null @@ -1,108 +0,0 @@ -package dev.utils.common; - -import java.util.Random; -import java.util.UUID; - -import dev.utils.common.encrypt.MD5Utils; - -/** - * detail: 快捷辅助工具类 - * Created by Ttt - */ -public final class AssistUtils { - - private AssistUtils(){ - } - - /** - * 获取随机唯一数 - * @return - */ - public static UUID randomUUID(){ - return UUID.randomUUID(); - } - - /** - * 获取随机唯一数 HashCode - * @return - */ - public static int randomUUIDToHashCode(){ - return UUID.randomUUID().hashCode(); - } - - /** - * 获取随机唯一数 HashCode - * @param uuid - * @return - */ - public static int randomUUIDToHashCode(UUID uuid){ - if (uuid != null){ - return uuid.hashCode(); - } - return -1; - } - - /** - * 获取随机数 唯一id - * @return - */ - public static String getRandomUUID(){ - // 获取随机数 - String random1 = (900000 + new Random().nextInt(10000)) + ""; - // 获取随机数 - String random2 = (900000 + new Random().nextInt(10000)) + ""; - // 获取当前时间 - String cTime = Long.toString(System.currentTimeMillis()) + random1 + random2; - // 生成唯一随机uuid cTime.hashCode(), random1.hashCode() | random2.hashCode() - UUID randomUUID = new UUID(cTime.hashCode(), ((long) random1.hashCode() << 32) | random2.hashCode()); - // 获取uid - return randomUUID.toString(); - } - - // - - - /** - * 循环MD5 加密处理 - * @param data - * @param number - * @param isUppercase - * @param salts - * @return - */ - public static String whileMD5(String data, int number, boolean isUppercase, String... salts){ - if (data != null && number >= 1){ - int saltLen = (salts != null) ? salts.length : 0; - // 判断是否大写 - if (isUppercase){ - // 循环加密 - for (int i = 0; i < number; i++){ - if (saltLen >= i){ - String salt = salts[i]; - if (salt != null){ - data = MD5Utils.md5Upper(data + salt); - } else { - data = MD5Utils.md5Upper(data); - } - } else { - data = MD5Utils.md5Upper(data); - } - } - } else { - // 循环加密 - for (int i = 0; i < number; i++){ - if (saltLen >= i){ - String salt = salts[i]; - if (salt != null){ - data = MD5Utils.md5(data + salt); - } else { - data = MD5Utils.md5(data); - } - } else { - data = MD5Utils.md5(data); - } - } - } - } - return data; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/BigDecimalUtils.java b/DevLibUtils/src/main/java/dev/utils/common/BigDecimalUtils.java deleted file mode 100644 index 86c7c37bdd..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/BigDecimalUtils.java +++ /dev/null @@ -1,629 +0,0 @@ -package dev.utils.common; - -import java.math.BigDecimal; - -import dev.utils.JCLogUtils; - -/** - * detail: 资金运算工具类 - * Created by Ttt - */ -public final class BigDecimalUtils { - - private BigDecimalUtils(){ - } - - // 日志TAG - private static final String TAG = BigDecimalUtils.class.getSimpleName(); - - /** 默认保留位数 */ - private static final int DEF_DIV_SCALE = 10; - - // = - - /** - * 提供精确的加法运算 - * @param v1 被加数 - * @param v2 加数 - * @return 两个参数的和 - */ - public static double add(double v1, double v2) { - BigDecimal b1 = new BigDecimal(Double.toString(v1)); - BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return b1.add(b2).doubleValue(); - } - - /** - * 提供精确的加法运算 - * @param v1 被加数 - * @param v2 加数 - * @return 两个参数的和 - */ - public static BigDecimal add(String v1, String v2) { - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.add(b2); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "add"); - } - return new BigDecimal(0); - } - - /** - * 提供精确的加法运算 - * @param v1 被加数 - * @param v2 加数 - * @param scale 保留scale 位小数 - * @return 两个参数的和 - */ - public static String add(String v1, String v2, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "add"); - } - return "0"; - } - - /** - * 提供精确的减法运算 - * @param v1 被减数 - * @param v2 减数 - * @return 两个参数的差 - */ - public static double substract(double v1, double v2) { - BigDecimal b1 = new BigDecimal(Double.toString(v1)); - BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return b1.subtract(b2).doubleValue(); - } - - /** - * 提供精确的减法运算。 - * @param v1 被减数 - * @param v2 减数 - * @return 两个参数的差 - */ - public static BigDecimal substract(String v1, String v2) { - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.subtract(b2); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "substract"); - } - return new BigDecimal(0); - } - - /** - * 提供精确的减法运算 - * @param v1 被减数 - * @param v2 减数 - * @param scale 保留scale 位小数 - * @return 两个参数的差 - */ - public static String substract(String v1, String v2, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "substract"); - } - return "0"; - } - - /** - * 提供精确的乘法运算 - * @param v1 被乘数 - * @param v2 乘数 - * @return 两个参数的积 - */ - public static double multiply(double v1, double v2) { - BigDecimal b1 = new BigDecimal(Double.toString(v1)); - BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return b1.multiply(b2).doubleValue(); - } - - /** - * 提供精确的乘法运算 - * @param v1 被乘数 - * @param v2 乘数 - * @param scale 保留scale 位小数 - * @return 两个参数的积 - */ - public static double multiply(double v1, double v2, int scale) { - if (scale < 0) { - return 0d; - } - BigDecimal b1 = new BigDecimal(Double.toString(v1)); - BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return round(b1.multiply(b2).doubleValue(), scale); - } - - /** - * 提供精确的乘法运算 - * @param v1 被乘数 - * @param v2 乘数 - * @param scale 保留scale 位小数 - * @return 两个参数的积 - */ - public static String multiply(String v1, String v2, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "multiply"); - } - return "0"; - } - - /** - * 提供(相对)精确的除法运算,当发生除不尽的情况时, - * 精确到小数点以后10位,以后的数字四舍五入. - * @param v1 被除数 - * @param v2 除数 - * @return 两个参数的商 - */ - public static double divide(double v1, double v2) { - return divide(v1, v2, DEF_DIV_SCALE); - } - - - /** - * 提供(相对)精确的除法运算. - * 当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入. - * @param v1 被除数 - * @param v2 除数 - * @param scale 表示需要精确到小数点以后几位 - * @return 两个参数的商 - */ - public static double divide(double v1, double v2, int scale) { - if (scale < 0) { - return 0d; - } - BigDecimal b1 = new BigDecimal(Double.toString(v1)); - BigDecimal b2 = new BigDecimal(Double.toString(v2)); - return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); - } - - /** - * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 - * 定精度,以后的数字四舍五入 - * @param v1 被除数 - * @param v2 除数 - * @param scale 表示需要精确到小数点以后几位 - * @return 两个参数的商 - */ - public static String divide(String v1, String v2, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v1); - return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "divide"); - } - return "0"; - } - - /** - * 提供精确的小数位四舍五入处理 - * @param v 需要四舍五入的数字 - * @param scale 小数点后保留几位 - * @return 四舍五入后的结果 - */ - public static double round(double v, int scale) { - if (scale < 0) { - return 0d; - } - BigDecimal b = new BigDecimal(Double.toString(v)); - BigDecimal one = new BigDecimal("1"); - return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); - } - - /** - * 提供精确的小数位四舍五入处理 - * @param v 需要四舍五入的数字 - * @param scale 小数点后保留几位 - * @return 四舍五入后的结果 - */ - public static String round(String v, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b = new BigDecimal(v); - return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "round"); - } - return "0"; - } - - /** - * 提供精确的小数位获取 - * @param v 需要处理的数字 - * @param scale 小数点后保留几位 - * @return 最后的结果 - */ - public static double round(double v, int scale, int round) { - if (scale < 0) { - return 0d; - } - // https://www.cnblogs.com/liqforstudy/p/5652517.html - // 向下取 - // round = BigDecimal.ROUND_DOWN; - BigDecimal b = new BigDecimal(Double.toString(v)); - BigDecimal one = new BigDecimal("1"); - return b.divide(one, scale, round).doubleValue(); - } - - /** - * 取余数 - * @param v1 被除数 - * @param v2 除数 - * @param scale 小数点后保留几位 - * @return 余数 - */ - public static String remainder(String v1, String v2, int scale) { - if (scale < 0) { - return "0"; - } - try { - BigDecimal b1 = new BigDecimal(v1); - BigDecimal b2 = new BigDecimal(v2); - return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "remainder"); - } - return "0"; - } - - /** - * 取余数 BigDecimal - * @param v1 被除数 - * @param v2 除数 - * @param scale 小数点后保留几位 - * @return 余数 - */ - public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) { - if (scale < 0) { - return new BigDecimal(0); - } - try { - return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "remainder"); - } - return new BigDecimal(0); - } - - /** - * 金额分割,四舍五入金额 - * @param s - * @return - */ - public static String formatMoney(BigDecimal s) { - String retVal = ""; - String str = ""; - boolean is_positive_integer = false; - if (null == s) { - return "0.00"; - } - - if (0 == s.doubleValue()) { - return "0.00"; - } - // 判断是否正整数 - if (s.toString().indexOf("-") != -1) { - is_positive_integer = true; - } else { - is_positive_integer = false; - } - // 是负整数 - if (is_positive_integer) { - // 去掉 - 号 - s = new BigDecimal(s.toString().substring(1, s.toString().length())); - } - str = s.setScale(2, BigDecimal.ROUND_HALF_UP).toString(); - StringBuffer sb = new StringBuffer(); - String[] strs = str.split("\\."); - int j = 1; - for (int i = 0; i < strs[0].length(); i++) { - char a = strs[0].charAt(strs[0].length() - i - 1); - sb.append(a); - if (j % 3 == 0 && i != strs[0].length() - 1) { - sb.append(","); - } - j++; - } - String str1 = sb.toString(); - StringBuffer sb1 = new StringBuffer(); - for (int i = 0; i < str1.length(); i++) { - char a = str1.charAt(str1.length() - 1 - i); - sb1.append(a); - } - sb1.append("."); - sb1.append(strs[1]); - retVal = sb1.toString(); - - if (is_positive_integer) { - retVal = "-" + retVal; - } - return retVal; - } - - /** - * 四舍五入金额 - * @param s - * @return - */ - public static String formatMoney1(BigDecimal s) { - String retVal = ""; - String str = ""; - boolean is_positive_integer = false; - if (null == s) { - return "0.00"; - } - - if (0 == s.doubleValue()) { - return "0.00"; - } - // 判断是否正整数 - if (s.toString().indexOf("-") != -1) { - is_positive_integer = true; - } else { - is_positive_integer = false; - } - // 是负整数 - if (is_positive_integer) { - // 去掉 - 号 - s = new BigDecimal(s.toString().substring(1, s.toString().length())); - } - str = s.setScale(2, BigDecimal.ROUND_HALF_UP).toString(); - StringBuffer sb = new StringBuffer(); - String[] strs = str.split("\\."); - int j = 1; - for (int i = 0; i < strs[0].length(); i++) { - char a = strs[0].charAt(strs[0].length() - i - 1); - sb.append(a); - if (j % 3 == 0 && i != strs[0].length() - 1) { - sb.append(""); - } - j++; - } - String str1 = sb.toString(); - StringBuffer sb1 = new StringBuffer(); - for (int i = 0; i < str1.length(); i++) { - char a = str1.charAt(str1.length() - 1 - i); - sb1.append(a); - } - sb1.append("."); - sb1.append(strs[1]); - retVal = sb1.toString(); - - if (is_positive_integer) { - retVal = "-" + retVal; - } - return retVal; - } - - /** - * 比较大小 - * @param amount 输入的数值 - * @param compare 被比较的数字 - * @return true 大于被比较的数 - */ - public static Boolean compareBigDecimal(String amount, double compare) { - try { - BigDecimal lenth = new BigDecimal(amount); - if (lenth.compareTo(BigDecimal.valueOf(compare)) == -1) { - return false; - } - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "compareBigDecimal"); - } - return true; - } - - /** - * 获取自己想要的数据格式 - * @param s 需处理的数据 - * @param numOfIntPart 整数位数 - * @param numOfDecimalPart 小数位数 - * @return 处理过的数据 - */ - public static String adjustDouble(String s, int numOfIntPart, int numOfDecimalPart) { - if (s == null){ - return null; - } - // 按小数点的位置分割成整数部分和小数部分 - String[] array = s.split("\\."); - char[] tempA = new char[numOfIntPart]; - char[] tempB = new char[numOfDecimalPart]; - // 整数部分满足精度要求(情况1) - if (array[0].length() == numOfIntPart) { - // 直接获取整数部分长度字符 - for (int i = 0; i < array[0].length(); i++) { - tempA[i] = array[0].charAt(i); - } - // 小数部分精度大于或等于指定的精度 - if (numOfDecimalPart <= array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - tempB[i] = array[1].charAt(i); - } - } - // 小数部分精度小于指定的精度 - if (numOfDecimalPart > array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - if (i < array[1].length()) { - tempB[i] = array[1].charAt(i); - } else { - tempB[i] = '0'; - } - } - } - if (numOfDecimalPart == 0) { - return String.valueOf(tempA) + String.valueOf(tempB); - } - return String.valueOf(tempA) + "." + String.valueOf(tempB); - } - // 整数部分位数大于精度要求(情况2) - if (array[0].length() > numOfIntPart) { - // 先倒序获取指定位数的整数 - for (int i = array[0].length() - 1, j = 0; (i >= array[0].length() - numOfIntPart) && (j < numOfIntPart); i--, j++) { - tempA[j] = array[0].charAt(i); - } - char[] tempA1 = new char[numOfIntPart]; - // 调整顺序 - for (int j = 0, k = tempA.length - 1; j < numOfIntPart && (k >= 0); j++, k--) { - tempA1[j] = tempA[k]; - - } - // 小数部分精度大于或等于指定的精度 - if (numOfDecimalPart <= array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - tempB[i] = array[1].charAt(i); - } - } - // 小数部分精度小于指定的精度 - if (numOfDecimalPart > array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - if (i < array[1].length()) { - tempB[i] = array[1].charAt(i); - } else { - tempB[i] = '0'; - } - } - } - return String.valueOf(tempA1) + "." + String.valueOf(tempB); - } - // 整数部分满足精度要求(情况3) - if (array[0].length() == numOfIntPart) { - //直接获取整数部分长度字符 - for (int i = 0; i < array[0].length(); i++) { - tempA[i] = array[0].charAt(i); - } - // 小数部分精度小于指定的精度 - if (numOfDecimalPart > array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - if (i < array[1].length()) { - tempB[i] = array[1].charAt(i); - } else { - tempB[i] = '0'; - } - } - } - // 小数部分精度大于或等于指定的精度 - if (numOfDecimalPart <= array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - tempB[i] = array[1].charAt(i); - } - } - if (numOfDecimalPart == 0) { - return String.valueOf(tempA) + String.valueOf(tempB); - } - return String.valueOf(tempA) + "." + String.valueOf(tempB); - } - // 整数部分大于精度要求(情况4) - if (array[0].length() > numOfIntPart) { - // 先倒序获取指定位数的整数 - for (int i = array[0].length() - 1, j = 0; (i >= array[0].length() - numOfIntPart + 1) && (j < numOfIntPart); i--, j++) { - tempA[j] = array[0].charAt(i); - } - char[] tempA1 = new char[numOfIntPart]; - // 调整顺序 - for (int j = 0, k = tempA.length - 1; j < numOfIntPart && (k >= 0); j++) { - tempA1[j] = tempA[k]; - k--; - } - // 小数部分精度小于指定的精度 - if (numOfDecimalPart > array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - if (i >= array[1].length()) { - tempB[i] = '0'; - } else { - tempB[i] = array[1].charAt(i); - } - } - } - // 小数部分精度大于或等于指定的精度 - if (numOfDecimalPart <= array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - tempB[i] = array[1].charAt(i); - } - } - if (numOfDecimalPart == 0) { - return String.valueOf(tempA1) + String.valueOf(tempB); - } - return String.valueOf(tempA1) + "." + String.valueOf(tempB); - } - // 整数部分小于精度要求(情况5) - if (array[0].length() < numOfIntPart) { - // 先倒序获取指定位数的整数 - char[] tempA1 = new char[numOfIntPart]; - for (int i = array[0].length() - 1, j = 0; (i >= numOfIntPart - array[0].length() - (numOfIntPart - array[0].length())) && (j < numOfIntPart); i--, j++) { - tempA1[j] = array[0].charAt(i); - } - // 补0 - for (int i = array[0].length(); i < array[0].length() + numOfIntPart - array[0].length(); i++) { - tempA1[i] = '0'; - } - - char[] tempA2 = new char[numOfIntPart]; - // 调整顺序 - for (int j = 0, k = tempA1.length - 1; j < numOfIntPart && (k >= 0); j++) { - tempA2[j] = tempA1[k]; - k--; - } - // 小数部分精度小于指定的精度 - if (numOfDecimalPart > array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - if (i < array[1].length()) { - tempB[i] = array[1].charAt(i); - } else { - tempB[i] = '0'; - } - } - } - // 小数部分精度大于或等于指定的精度 - if (numOfDecimalPart <= array[1].length()) { - for (int i = 0; i < numOfDecimalPart; i++) { - tempB[i] = array[1].charAt(i); - - } - } - if (numOfDecimalPart == 0) { - return String.valueOf(tempA2) + String.valueOf(tempB); - } - return String.valueOf(tempA2) + "." + String.valueOf(tempB); - } - // 情况(6) - if ((array[0].length() < numOfIntPart) && (array[1].length() < numOfDecimalPart)) { - for (int i = 0; i < numOfIntPart - array[0].length(); i++) { - s = "0" + s; - } - - for (int i = 0; i < numOfDecimalPart - array[1].length(); i++) { - s = s + "0"; - } - return s; - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ByteUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ByteUtils.java deleted file mode 100644 index 6141a597a3..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ByteUtils.java +++ /dev/null @@ -1,233 +0,0 @@ -package dev.utils.common; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -import dev.utils.JCLogUtils; - -/** - * detail: 字节工具类,提供一些有关字节的便捷方法 - * (01)、位移加密:static void byteJiaMi(byte[] bytes) - * (02)、从bytes上截取一段:static byte[] cutOut(byte[] bytes, int off, int length) - * Created by Ttt - * http://www.runoob.com/java/java-operators.html - */ -public final class ByteUtils { - - private ByteUtils(){ - } - - // 日志TAG - private static final String TAG = ByteUtils.class.getSimpleName(); - - // 按位补运算符 例子: - // ByteUtils.byteToBit(new byte[] { 1 }) = 00000001 (二进制字符串) - // 进行反转 变成 11111110 - // 在通过二进制字符串转换 byte 数组 ByteUtils.bits2Bytes("11111110") => byte[] { -2 } - // ~1 => -2 结果是正常的 - - /** - * (01)、位移加密、解密,调同一个方法 - * @param bytes - */ - public static void byteJiaMi(byte[] bytes) { - if (bytes == null){ - return; - } - for (int w = 0; w < bytes.length; w++) { - int a = bytes[w]; - a = ~a; // 按位补运算符 => 翻转操作数的每一位,即0变成1,1变成0,再通过反转后的二进制初始化回16进制 - bytes[w] = (byte) a; - } - } - - /** - * 字符串转数组 - * @param str - * @return - */ - public static byte[] hexStrToByteArray(String str) { - if (str == null) { - return null; - } - if (str.length() == 0) { - return new byte[0]; - } - byte[] byteArray = new byte[str.length() / 2]; - for (int i = 0; i < byteArray.length; i++) { - String subStr = str.substring(2 * i, 2 * i + 2); - byteArray[i] = ((byte) Integer.parseInt(subStr, 16)); - } - return byteArray; - } - - /** - * (02)、从bytes上截取一段 - * @param bytes 母体 - * @param off 起始 - * @param length 个数 - * @return byte[] - */ - public static byte[] cutOut(byte[] bytes, int off, int length) { - byte[] bytess = new byte[length]; - System.arraycopy(bytes, off, bytess, 0, length); - return bytess; - } - - /** - * 将字节转换为二进制字符串 - * @param bytes 字节数组 - * @return 二进制字符串 - */ - public static String byteToBit(byte... bytes) { - StringBuffer sb = new StringBuffer(); - int z, len; - String str; - for (int w = 0; w < bytes.length; w++) { - z = bytes[w]; - z |= 256; - str = Integer.toBinaryString(z); - len = str.length(); - sb.append(str.substring(len - 8, len)); - } - return sb.toString(); - } - - /** - * 二进制字符串, 转换成byte数组 - * 例: "011000010111001101100100" 传入 bits2Bytes, 返回 byte[], 通过new String(byte()) 获取 asd => 配合 bytes2Bits 使用 - * @param bits The bits. - * @return bytes - */ - public static byte[] bits2Bytes(String bits) { - int lenMod = bits.length() % 8; - int byteLen = bits.length() / 8; - // add "0" until length to 8 times - if (lenMod != 0) { - for (int i = lenMod; i < 8; i++) { - bits = "0" + bits; - } - byteLen++; - } - byte[] bytes = new byte[byteLen]; - for (int i = 0; i < byteLen; ++i) { - for (int j = 0; j < 8; ++j) { - bytes[i] <<= 1; - bytes[i] |= bits.charAt(i * 8 + j) - '0'; - } - } - return bytes; - } - - /** - * 字节数组转换成16进制字符串 - * @param raw - * @return - */ - public static String getHex(byte[] raw) { - String HEXES = "0123456789ABCDEF"; - if (raw == null) { - return null; - } - final StringBuilder hex = new StringBuilder(2 * raw.length); - for (final byte b : raw) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)) - .append(HEXES.charAt((b & 0x0F))); - } - return hex.toString(); - } - - /** - * 将一个short转换成字节数组 - * @param sh short - * @return 字节数组 - */ - public static byte[] valueOf(short sh) { - byte[] shortBuf = new byte[2]; - for (int i = 0; i < 2; i++) { - int offset = (shortBuf.length - 1 - i) * 8; - shortBuf[i] = (byte) ((sh >>> offset) & 0xff); - } - return shortBuf; - } - - /** - * 将一个int转换成字节数组 - * @param in int - * @return 字节数组 - */ - public static byte[] valueOf(int in) { - byte[] b = new byte[4]; - for (int i = 0; i < 4; i++) { - int offset = (b.length - 1 - i) * 8; - b[i] = (byte) ((in >>> offset) & 0xFF); - } - return b; - } - - /** - * 从一个byte[]数组中截取一部分 - * @param src - * @param begin - * @param count - * @return - */ - public static byte[] subBytes(byte[] src, int begin, int count) { - byte[] bs = new byte[count]; - for (int i = begin; i < begin + count; i++) { - bs[i - begin] = src[i]; - } - return bs; - } - - - /** - * byte[] 转为 对象 - * @param bytes - * @return - */ - public static Object byteToObject(byte[] bytes) { - ObjectInputStream ois = null; - try { - ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); - return ois.readObject(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "byteToObject"); - } finally { - if (ois != null) { - try { - ois.close(); - } catch (Exception e) { - } - } - } - return null; - } - - /** - * 对象 转为 byte[] - * @param obj - * @return - */ - public static byte[] objectToByte(Object obj) { - ObjectOutputStream oos = null; - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - oos = new ObjectOutputStream(bos); - oos.writeObject(obj); - return bos.toByteArray(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "objectToByte"); - } finally { - if (oos != null) { - try { - oos.close(); - } catch (Exception e) { - } - } - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ClassUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ClassUtils.java deleted file mode 100644 index 16e8da28e9..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ClassUtils.java +++ /dev/null @@ -1,93 +0,0 @@ -package dev.utils.common; - -import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.Date; - -import dev.utils.JCLogUtils; - -/** - * detail: 类工具 - * @author mty - */ -public final class ClassUtils { - - private ClassUtils(){ - } - - // 日志TAG - private static final String TAG = ClassUtils.class.getSimpleName(); - - /** - * 判断类是否是基础数据类型 - * 目前支持11种 - * @param clazz - * @return - */ - public static boolean isBaseDataType(Class clazz) { - return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) - || clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Float.class) - || clazz.equals(Double.class) || clazz.equals(Byte.class) || clazz.equals(Character.class) - || clazz.equals(Short.class) || clazz.equals(Date.class) || clazz.equals(byte[].class) - || clazz.equals(Byte[].class); - } - - /** - * 根据类获取对象:不再必须一个无参构造 - * @param claxx - * @return - */ - public static T newInstance(Class claxx){ - try { - Constructor[] cons = claxx.getDeclaredConstructors(); - for (Constructor c : cons) { - Class[] cls = c.getParameterTypes(); - if (cls.length == 0) { - c.setAccessible(true); - return (T) c.newInstance(); - } else { - Object[] objs = new Object[cls.length]; - for (int i = 0; i < cls.length; i++) { - objs[i] = getDefaultPrimiticeValue(cls[i]); - } - c.setAccessible(true); - return (T) c.newInstance(objs); - } - } - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "newInstance"); - } - return null; - } - - /** - * 判断 Class 是否为原始类型(boolean、char、byte、short、int、long、float、double) - * @param clazz - * @return - */ - public static Object getDefaultPrimiticeValue(Class clazz) { - if (clazz.isPrimitive()) { - return clazz == boolean.class ? false : 0; - } - return null; - } - - /** - * 判断是否集合类型 - * @param claxx - * @return - */ - public static boolean isCollection(Class claxx) { - return Collection.class.isAssignableFrom(claxx); - } - - /** - * 是否数组类型 - * @param claxx - * @return - */ - public static boolean isArray(Class claxx) { - return claxx.isArray(); - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/CloneUtils.java b/DevLibUtils/src/main/java/dev/utils/common/CloneUtils.java deleted file mode 100644 index 956a67c602..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/CloneUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -package dev.utils.common; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; - -import dev.utils.JCLogUtils; - -/** - * detail: 克隆相关工具类 - * Created by Ttt - */ -public final class CloneUtils { - - private CloneUtils(){ - } - - // 日志TAG - private static final String TAG = CloneUtils.class.getSimpleName(); - - /** - * 进行克隆 - * @param data - * @param - * @return - */ - public static T deepClone(final Serializable data) { - if (data == null) return null; - return (T) bytes2Object(serializable2Bytes(data)); - } - - /** - * 通过序列化实体类, 获取对应的byte数组数据 - * @param serializable - * @return - */ - private static byte[] serializable2Bytes(final Serializable serializable) { - if (serializable == null) return null; - ByteArrayOutputStream baos; - ObjectOutputStream oos = null; - try { - oos = new ObjectOutputStream(baos = new ByteArrayOutputStream()); - oos.writeObject(serializable); - return baos.toByteArray(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "serializable2Bytes"); - return null; - } finally { - if (oos != null) { - try { - oos.close(); - } catch (IOException e) { - } - } - } - } - - /** - * 通过 byte数据, 生成Object对象 - * @param bytes - * @return - */ - private static Object bytes2Object(final byte[] bytes) { - if (bytes == null) return null; - ObjectInputStream ois = null; - try { - ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); - return ois.readObject(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "bytes2Object"); - return null; - } finally { - if (ois != null) { - try { - ois.close(); - } catch (IOException e) { - } - } - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/CloseUtils.java b/DevLibUtils/src/main/java/dev/utils/common/CloseUtils.java deleted file mode 100644 index 7d764f519e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/CloseUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.utils.common; - -import java.io.Closeable; - -import dev.utils.JCLogUtils; - -/** - * detail: 关闭工具类 - (关闭IO流等) - * Created by Blankj - * http://blankj.com - * Update to Ttt - */ -public final class CloseUtils { - - private CloseUtils() { - } - - // 日志TAG - private static final String TAG = CloseUtils.class.getSimpleName(); - - /** - * 关闭 IO - * @param closeables closeables - */ - public static void closeIO(final Closeable... closeables) { - if (closeables == null) return; - for (Closeable closeable : closeables) { - if (closeable != null) { - try { - closeable.close(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "closeIO"); - } - } - } - } - - /** - * 安静关闭 IO - * @param closeables closeables - */ - public static void closeIOQuietly(final Closeable... closeables) { - if (closeables == null) return; - for (Closeable closeable : closeables) { - if (closeable != null) { - try { - closeable.close(); - } catch (Exception ignore) { - JCLogUtils.eTag(TAG, ignore, "closeIO"); - } - } - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ColorsUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ColorsUtils.java deleted file mode 100644 index ec48afa5c9..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ColorsUtils.java +++ /dev/null @@ -1,240 +0,0 @@ -package dev.utils.common; - -/** - * detail: 颜色工具类 包括常用的色值 - * Created by Ttt - */ -public final class ColorsUtils { - - private ColorsUtils() { - } - - /** - 黑色 透明度10-90 - 19 - 33 - 4C - 66 - 7F - 99 - B2 - CC - E5 - */ - - // 0-255 十进值转换成十六进制,如255 就是 ff - // 百分之10 透明度 = 19 - // 百分之20 透明度 = 33 - // 255 * 0.x = 十进制 -> 十六进制 - // import android.graphics.Color; - // Color.argb() - // Color.rgb() - - /** 白色 */ - public static final int WHITE = 0xffffffff; - - /** 白色 - 半透明 */ - public static final int WHITE_TRANSLUCENT = 0x80ffffff; - - /** 黑色 */ - public static final int BLACK = 0xff000000; - - /** 黑色 - 半透明 */ - public static final int BLACK_TRANSLUCENT = 0x80000000; - - /** 透明 */ - public static final int TRANSPARENT = 0x00000000; - - /** 红色 */ - public static final int RED = 0xffff0000; - - /** 红色 - 半透明 */ - public static final int RED_TRANSLUCENT = 0x80ff0000; - - /** 红色 - 深的 */ - public static final int RED_DARK = 0xff8b0000; - - /** 红色 - 深的 - 半透明 */ - public static final int RED_DARK_TRANSLUCENT = 0x808b0000; - - /** 绿色 */ - public static final int GREEN = 0xff00ff00; - - /** 绿色 - 半透明 */ - public static final int GREEN_TRANSLUCENT = 0x8000ff00; - - /** 绿色 - 深的 */ - public static final int GREEN_DARK = 0xff003300; - - /** 绿色 - 深的 - 半透明 */ - public static final int GREEN_DARK_TRANSLUCENT = 0x80003300; - - /** 绿色 - 浅的 */ - public static final int GREEN_LIGHT = 0xffccffcc; - - /** 绿色 - 浅的 - 半透明 */ - public static final int GREEN_LIGHT_TRANSLUCENT = 0x80ccffcc; - - /** 蓝色 */ - public static final int BLUE = 0xff0000ff; - - /** 蓝色 - 半透明 */ - public static final int BLUE_TRANSLUCENT = 0x800000ff; - - /** 蓝色 - 深的 */ - public static final int BLUE_DARK = 0xff00008b; - - /** 蓝色 - 深的 - 半透明 */ - public static final int BLUE_DARK_TRANSLUCENT = 0x8000008b; - - /** 蓝色 - 浅的 */ - public static final int BLUE_LIGHT = 0xff36a5E3; - - /** 蓝色 - 浅的 - 半透明 */ - public static final int BLUE_LIGHT_TRANSLUCENT = 0x8036a5E3; - - /** 天蓝 */ - public static final int SKYBLUE = 0xff87ceeb; - - /** 天蓝 - 半透明 */ - public static final int SKYBLUE_TRANSLUCENT = 0x8087ceeb; - - /** 天蓝 - 深的 */ - public static final int SKYBLUE_DARK = 0xff00bfff; - - /** 天蓝 - 深的 - 半透明 */ - public static final int SKYBLUE_DARK_TRANSLUCENT = 0x8000bfff; - - /** 天蓝 - 浅的 */ - public static final int SKYBLUE_LIGHT = 0xff87cefa; - - /** 天蓝 - 浅的 - 半透明 */ - public static final int SKYBLUE_LIGHT_TRANSLUCENT = 0x8087cefa; - - /** 灰色 */ - public static final int GRAY = 0xff969696; - - /** 灰色 - 半透明 */ - public static final int GRAY_TRANSLUCENT = 0x80969696; - - /** 灰色 - 深的 */ - public static final int GRAY_DARK = 0xffa9a9a9; - - /** 灰色 - 深的 - 半透明 */ - public static final int GRAY_DARK_TRANSLUCENT = 0x80a9a9a9; - - /** 灰色 - 暗的 */ - public static final int GRAY_DIM = 0xff696969; - - /** 灰色 - 暗的 - 半透明 */ - public static final int GRAY_DIM_TRANSLUCENT = 0x80696969; - - /** 灰色 - 浅的 */ - public static final int GRAY_LIGHT = 0xffd3d3d3; - - /** 灰色 - 浅的 - 半透明 */ - public static final int GRAY_LIGHT_TRANSLUCENT = 0x80d3d3d3; - - /** 橙色 */ - public static final int ORANGE = 0xffffa500; - - /** 橙色 - 半透明 */ - public static final int ORANGE_TRANSLUCENT = 0x80ffa500; - - /** 橙色 - 深的 */ - public static final int ORANGE_DARK = 0xffff8800; - - /** 橙色 - 深的 - 半透明 */ - public static final int ORANGE_DARK_TRANSLUCENT = 0x80ff8800; - - /** 橙色 - 浅的 */ - public static final int ORANGE_LIGHT = 0xffffbb33; - - /** 橙色 - 浅的 - 半透明 */ - public static final int ORANGE_LIGHT_TRANSLUCENT = 0x80ffbb33; - - /** 金色 */ - public static final int GOLD = 0xffffd700; - - /** 金色 - 半透明 */ - public static final int GOLD_TRANSLUCENT = 0x80ffd700; - - /** 粉色 */ - public static final int PINK = 0xffffc0cb; - - /** 粉色 - 半透明 */ - public static final int PINK_TRANSLUCENT = 0x80ffc0cb; - - /** 紫红色 */ - public static final int FUCHSIA = 0xffff00ff; - - /** 紫红色 - 半透明 */ - public static final int FUCHSIA_TRANSLUCENT = 0x80ff00ff; - - /** 灰白色 */ - public static final int GRAYWHITE = 0xfff2f2f2; - - /** 灰白色 - 半透明 */ - public static final int GRAYWHITE_TRANSLUCENT = 0x80f2f2f2; - - /** 紫色 */ - public static final int PURPLE = 0xff800080; - - /** 紫色 - 半透明 */ - public static final int PURPLE_TRANSLUCENT = 0x80800080; - - /** 青色 */ - public static final int CYAN = 0xff00ffff; - - /** 青色 - 半透明 */ - public static final int CYAN_TRANSLUCENT = 0x8000ffff; - - /** 青色 - 深的 */ - public static final int CYAN_DARK = 0xff008b8b; - - /** 青色 - 深的 - 半透明 */ - public static final int CYAN_DARK_TRANSLUCENT = 0x80008b8b; - - /** 黄色 */ - public static final int YELLOW = 0xffffff00; - - /** 黄色 - 半透明 */ - public static final int YELLOW_TRANSLUCENT = 0x80ffff00; - - /** 黄色 - 浅的 */ - public static final int YELLOW_LIGHT = 0xffffffe0; - - /** 黄色 - 浅的 - 半透明 */ - public static final int YELLOW_LIGHT_TRANSLUCENT = 0x80ffffe0; - - /** 巧克力色 */ - public static final int CHOCOLATE = 0xffd2691e; - - /** 巧克力色 - 半透明 */ - public static final int CHOCOLATE_TRANSLUCENT = 0x80d2691e; - - /** 番茄色 */ - public static final int TOMATO = 0xffff6347; - - /** 番茄色 - 半透明 */ - public static final int TOMATO_TRANSLUCENT = 0x80ff6347; - - /** 橙红色 */ - public static final int ORANGERED = 0xffff4500; - - /** 橙红色 - 半透明 */ - public static final int ORANGERED_TRANSLUCENT = 0x80ff4500; - - /** 银白色 */ - public static final int SILVER = 0xffc0c0c0; - - /** 银白色 - 半透明 */ - public static final int SILVER_TRANSLUCENT = 0x80c0c0c0; - - /** 高光 */ - public static final int HIGHLIGHT = 0x33ffffff; - - /** 低光 */ - public static final int LOWLIGHT = 0x33000000; - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ConverUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ConverUtils.java deleted file mode 100644 index c0995772dc..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ConverUtils.java +++ /dev/null @@ -1,554 +0,0 @@ -package dev.utils.common; - -import java.util.Arrays; - -import dev.utils.JCLogUtils; - -/** - * detail: 转换工具类 - * Created by Ttt - */ -public final class ConverUtils { - - private ConverUtils(){ - } - - // 日志TAG - private static final String TAG = ConverUtils.class.getSimpleName(); - - // byte是字节数据类型、有符号型的、占1个字节、大小范围为-128——127 - // char是字符数据类型、无符号型的、占2个字节(unicode码)、大小范围为0-65535 - - // byte[] (-128) - 127 - // 当大于127时则开始缩进 127 = 127, 128 = -128 , 129 = -127 - - /** - * char 数组 转 String - * @param chars - * @param dfStr - * @return - */ - public static String toString(char[] chars, String dfStr){ - if (chars != null){ - try { - return new String(chars); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "toString"); - } - } - return dfStr; - } - - /** - * byte 数组 转 String - * @param bytes - * @param dfStr - * @return - */ - public static String toString(byte[] bytes, String dfStr){ - if (bytes != null){ - try { - return new String(bytes); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "toString"); - } - } - return dfStr; - } - - /** - * char 转 String - * @param val - * @return - * -- - * 97 - 122 = a-z, 48-57 = 0-9 - * toString((char) 97); = a - */ - public static String toString(char val){ - return Character.toString(val); - } - - /** - * Object 转 String - * @param obj - * @return - */ - public static String toString(Object obj, String dfStr) { - if (obj != null){ - // return (obj instanceof String ? (String) obj : obj.toString()); - try { - if (obj instanceof String){ - return (String) obj; - } else { - Class cla = obj.getClass(); - // 判断是否数组类型 - if (cla.isArray()){ - // == 基本数据类型 == - if (cla.isAssignableFrom(int[].class)){ - return Arrays.toString((int[]) obj); - } else if (cla.isAssignableFrom(boolean[].class)){ - return Arrays.toString((boolean[]) obj); - } else if (cla.isAssignableFrom(long[].class)){ - return Arrays.toString((long[]) obj); - } else if (cla.isAssignableFrom(double[].class)){ - return Arrays.toString((double[]) obj); - } else if (cla.isAssignableFrom(float[].class)){ - return Arrays.toString((float[]) obj); - } else if (cla.isAssignableFrom(byte[].class)){ - return Arrays.toString((byte[]) obj); - } else if (cla.isAssignableFrom(char[].class)){ - return Arrays.toString((char[]) obj); - } else if (cla.isAssignableFrom(short[].class)){ - return Arrays.toString((short[]) obj); - } - // == 基本类型封装 == - if (cla.isAssignableFrom(Integer[].class)){ - return Arrays.toString((Integer[]) obj); - } else if (cla.isAssignableFrom(Boolean[].class)){ - return Arrays.toString((Boolean[]) obj); - } else if (cla.isAssignableFrom(Long[].class)){ - return Arrays.toString((Long[]) obj); - } else if (cla.isAssignableFrom(Double[].class)){ - return Arrays.toString((Double[]) obj); - } else if (cla.isAssignableFrom(Float[].class)){ - return Arrays.toString((Float[]) obj); - } else if (cla.isAssignableFrom(Byte[].class)){ - return Arrays.toString((Byte[]) obj); - } else if (cla.isAssignableFrom(Character[].class)){ - return Arrays.toString((Character[]) obj); - } else if (cla.isAssignableFrom(Short[].class)){ - return Arrays.toString((Short[]) obj); - } - } - return obj.toString(); - } - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "toString"); - } - } - return dfStr; - } - - /** - * 字符串 转 int - * @param str - * @param dfValue - * @return - */ - public static int toInt(String str, int dfValue) { - try { - return Integer.parseInt(str); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toInt"); - } - return dfValue; - } - - /** - * 字符串 转 boolean - * @param str - * @param dfValue - * @return - */ - public static boolean toBoolean(String str, boolean dfValue){ - try { - // 判断是否0 - if (str.equalsIgnoreCase("true") || str.equalsIgnoreCase("1")){ - return true; - } else { - return false; - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toBoolean"); - } - return dfValue; - } - - /** - * 字符串 转 float - * @param str - * @param dfValue - * @return - */ - public static float toFloat(String str, float dfValue){ - try { - return Float.parseFloat(str); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toFloat"); - } - return dfValue; - } - - /** - * 字符串 转 double - * @param str - * @param dfValue - * @return - */ - public static double toDouble(String str, double dfValue){ - try { - return Double.parseDouble(str); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toDouble"); - } - return dfValue; - } - - /** - * 字符串 转 long - * @param str - * @param dfValue - * @return - */ - public static long toLong(String str, long dfValue){ - try { - return Long.parseLong(str); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toLong"); - } - return dfValue; - } - - // == 转换对象 == - - /** - * 基本类型对象 转 int - * @param val - * @param dfValue - * @return - */ - public static int toInt(Integer val, int dfValue) { - try { - return val; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toInt"); - } - return dfValue; - } - - /** - * 基本类型对象 转 boolean - * @param val - * @return - */ - public static boolean toBoolean(Boolean val){ - return (val != null && val); - } - - /** - * 基本类型对象 转 float - * @param val - * @param dfValue - * @return - */ - public static float toFloat(Float val, float dfValue){ - try { - return val; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toFloat"); - } - return dfValue; - } - - /** - * 基本类型对象 转 double - * @param val - * @param dfValue - * @return - */ - public static double toDouble(Double val, double dfValue){ - try { - return val; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toDouble"); - } - return dfValue; - } - - /** - * 基本类型对象 转 long - * @param val - * @param dfValue - * @return - */ - public static long toLong(Long val, long dfValue){ - try { - return val; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toLong"); - } - return dfValue; - } - - // == 平常其他 == - - /** - * char 转换 int - * @param val - * @return - */ - public static int toInt(char val){ - return (int) val; - } - - /** - * 字符串 获取 char(默认第一位) - * @param str - * @param dfValue - * @return - */ - public static char toChar(String str, char dfValue){ - return toChar(str, 0, dfValue); - } - - /** - * 字符串 获取 char - * @param str - * @param pos - * @param dfValue - * @return - */ - public static char toChar(String str, int pos, char dfValue){ - try { - return str.charAt(pos); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "toChar"); - } - return dfValue; - } - - /** - * char 转换 unicode 编码 - * @param val - * @return - * -- - * toCharInt('a') = 97 - */ - public static int toCharInt(char val){ - return (int) val; - } - - /** - * 字符串 获取 char数组 - * @param str - * @return - */ - public static char[] toCharArys(String str){ - try { - return str.toCharArray(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "toCharArys"); - } - return null; - } - - /** - * 字符串 获取 byte数组 - * @param str - * @return - */ - public static byte[] toByteArys(String str){ - if (str != null) { - try { - return str.getBytes(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toByteArys"); - } - } - return null; - } - - // ====================================================================== - - // 以0x开始的数据表示16进制 - - /** - * 一个整数参数的字符串表示形式在基数为16的无符号整数 - * @param val - * @return - * -- - * 例如 -> 传入 0x1f603 => toHexString(0x1f603); 返回: 1f603 - */ - public static String toHexString(int val){ - return Integer.toHexString(val); - } - - public static String toHexString(long val){ - return Long.toHexString(val); - } - - public static String toHexString(double val){ - return Double.toHexString(val); - } - - public static String toHexString(float val){ - return Float.toHexString(val); - } - - // -- - - /** - * 字符串转换对应的进制 - * @param s - * @param radix - * @return - * -- - * 如: parseInt("1f603", 16) = 128515 - */ - public static int parseInt(String s, int radix){ - return Integer.parseInt(s, radix); - } - - // == - - // toHexString(0x1f603) = 1f603 - // parseInt("1f603", 16) = 128515 - // toHexString(128515) = 1f603 - - // == - - // ====================================================================== - - // 小写 - public static final char HEX_DIGITS[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - // 大写 - public static final char HEX_DIGITS_UPPER[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; - - /** - * 进行转换 十六进制字符 - * @param bData - * @param hexDigits - * @return - */ - public static String toHexString(byte[] bData, char[] hexDigits) { - if (bData == null || hexDigits == null){ - return null; - } - StringBuilder sBuilder = new StringBuilder(bData.length * 2); - for (int i = 0, len = bData.length; i < len; i++) { - sBuilder.append(hexDigits[(bData[i] & 0xf0) >>> 4]); - sBuilder.append(hexDigits[bData[i] & 0x0f]); - } - return sBuilder.toString(); - } - - /** - * 十六进制字符串 转换byte数组 - * @param hexString - * @return - */ - public static byte[] hexString2Bytes(String hexString) { - if (isSpace(hexString)) return null; - int len = hexString.length(); - if (len % 2 != 0) { - hexString = "0" + hexString; - len = len + 1; - } - char[] hexBytes = hexString.toUpperCase().toCharArray(); - byte[] ret = new byte[len >> 1]; - for (int i = 0; i < len; i += 2) { - ret[i >> 1] = (byte) (hex2Int(hexBytes[i]) << 4 | hex2Int(hexBytes[i + 1])); - } - return ret; - } - - private static int hex2Int(final char hexChar) { - if (hexChar >= '0' && hexChar <= '9') { - return hexChar - '0'; - } else if (hexChar >= 'A' && hexChar <= 'F') { - return hexChar - 'A' + 10; - } else { - throw new IllegalArgumentException(); - } - } - - // == - - /** - * 把 bytes 数据, 转换成二进制数据 - * 例: "asd".getBytes() 传入 bytes2Bits 返回 011000010111001101100100 二进制的字符串数据 - * @param bytes The bytes. - * @return bits - */ - public static String bytes2Bits(final byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte aByte : bytes) { - for (int j = 7; j >= 0; --j) { - sb.append(((aByte >> j) & 0x01) == 0 ? '0' : '1'); - } - } - return sb.toString(); - } - - /** - * 二进制字符串, 转换成byte数组 - * 例: "011000010111001101100100" 传入 bits2Bytes, 返回 byte[], 通过new String(byte()) 获取 asd => 配合 bytes2Bits 使用 - * @param bits The bits. - * @return bytes - */ - public static byte[] bits2Bytes(String bits) { - int lenMod = bits.length() % 8; - int byteLen = bits.length() / 8; - // add "0" until length to 8 times - if (lenMod != 0) { - for (int i = lenMod; i < 8; i++) { - bits = "0" + bits; - } - byteLen++; - } - byte[] bytes = new byte[byteLen]; - for (int i = 0; i < byteLen; ++i) { - for (int j = 0; j < 8; ++j) { - bytes[i] <<= 1; - bytes[i] |= bits.charAt(i * 8 + j) - '0'; - } - } - return bytes; - } - - /** - * byte 数组 转换 char 数组, 并且进行补码 - * @param bytes The bytes. - * @return chars - */ - public static char[] bytes2Chars(final byte[] bytes) { - if (bytes == null) return null; - int len = bytes.length; - if (len <= 0) return null; - char[] chars = new char[len]; - for (int i = 0; i < len; i++) { - chars[i] = (char) (bytes[i] & 0xff); - } - return chars; - } - - /** - * char 数组 转换 byte 数组 - * @param chars The chars. - * @return bytes - */ - public static byte[] chars2Bytes(final char[] chars) { - if (chars == null || chars.length <= 0) return null; - int len = chars.length; - byte[] bytes = new byte[len]; - for (int i = 0; i < len; i++) { - bytes[i] = (byte) (chars[i]); - } - return bytes; - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/CoordinateUtils.java b/DevLibUtils/src/main/java/dev/utils/common/CoordinateUtils.java deleted file mode 100644 index a8bf0ce0c9..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/CoordinateUtils.java +++ /dev/null @@ -1,135 +0,0 @@ -package dev.utils.common; - -import static java.lang.Math.PI; - -/** - * detail: 坐标相关工具类 - * @author Blankj - */ -public final class CoordinateUtils { - - private CoordinateUtils(){ - } - - private final static double X_PI = 3.14159265358979324 * 3000.0 / 180.0; - private final static double A = 6378245.0; - private final static double EE = 0.00669342162296594323; - - /** - * BD09 坐标转 GCJ02 坐标 - * @param lng BD09 坐标纬度 - * @param lat BD09 坐标经度 - * @return GCJ02 坐标:[经度,纬度] - */ - public static double[] bd09ToGcj02(double lng, double lat) { - double x = lng - 0.0065; - double y = lat - 0.006; - double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); - double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); - double gg_lng = z * Math.cos(theta); - double gg_lat = z * Math.sin(theta); - return new double[]{gg_lng, gg_lat}; - } - - /** - * GCJ02 坐标转 BD09 坐标 - * @param lng GCJ02 坐标经度 - * @param lat GCJ02 坐标纬度 - * @return BD09 坐标:[经度,纬度] - */ - public static double[] gcj02ToBd09(double lng, double lat) { - double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); - double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); - double bd_lng = z * Math.cos(theta) + 0.0065; - double bd_lat = z * Math.sin(theta) + 0.006; - return new double[]{bd_lng, bd_lat}; - } - - /** - * GCJ02 坐标转 WGS84 坐标 - * @param lng GCJ02 坐标经度 - * @param lat GCJ02 坐标纬度 - * @return WGS84 坐标:[经度,纬度] - */ - public static double[] gcj02ToWGS84(double lng, double lat) { - if (outOfChina(lng, lat)) { - return new double[]{lng, lat}; - } - double dlat = transformLat(lng - 105.0, lat - 35.0); - double dlng = transformLng(lng - 105.0, lat - 35.0); - double radlat = lat / 180.0 * PI; - double magic = Math.sin(radlat); - magic = 1 - EE * magic * magic; - double sqrtmagic = Math.sqrt(magic); - dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); - dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); - double mglat = lat + dlat; - double mglng = lng + dlng; - return new double[]{lng * 2 - mglng, lat * 2 - mglat}; - } - - /** - * WGS84 坐标转 GCJ02 坐标 - * @param lng WGS84 坐标经度 - * @param lat WGS84 坐标纬度 - * @return GCJ02 坐标:[经度,纬度] - */ - public static double[] wgs84ToGcj02(double lng, double lat) { - if (outOfChina(lng, lat)) { - return new double[]{lng, lat}; - } - double dlat = transformLat(lng - 105.0, lat - 35.0); - double dlng = transformLng(lng - 105.0, lat - 35.0); - double radlat = lat / 180.0 * PI; - double magic = Math.sin(radlat); - magic = 1 - EE * magic * magic; - double sqrtmagic = Math.sqrt(magic); - dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); - dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); - double mglat = lat + dlat; - double mglng = lng + dlng; - return new double[]{mglng, mglat}; - } - - /** - * BD09 坐标转 WGS84 坐标 - * @param lng BD09 坐标经度 - * @param lat BD09 坐标纬度 - * @return WGS84 坐标:[经度,纬度] - */ - public static double[] bd09ToWGS84(double lng, double lat) { - double[] gcj = bd09ToGcj02(lng, lat); - return gcj02ToWGS84(gcj[0], gcj[1]); - } - - /** - * WGS84 坐标转 BD09 坐标 - * @param lng WGS84 坐标经度 - * @param lat WGS84 坐标纬度 - * @return BD09 坐标:[经度,纬度] - */ - public static double[] wgs84ToBd09(double lng, double lat) { - double[] gcj = wgs84ToGcj02(lng, lat); - return gcj02ToBd09(gcj[0], gcj[1]); - } - - private static double transformLat(double lng, double lat) { - double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); - ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; - ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; - return ret; - } - - private static double transformLng(double lng, double lat) { - double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); - ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; - ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; - return ret; - } - - private static boolean outOfChina(double lng, double lat) { - return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/DateUtils.java b/DevLibUtils/src/main/java/dev/utils/common/DateUtils.java deleted file mode 100644 index df2e8ca147..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/DateUtils.java +++ /dev/null @@ -1,613 +0,0 @@ -package dev.utils.common; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; - -import dev.utils.JCLogUtils; - -/** - * detail: 日期工具类 - * Created by Ttt - */ -public final class DateUtils { - - private DateUtils() { - } - - // 日志TAG - private static final String TAG = DateUtils.class.getSimpleName(); - - /** 日期格式类型 */ - public static final String yyyyMMdd = "yyyy-MM-dd"; - public static final String yyyyMMddHHmmss = "yyyy-MM-dd HH:mm:ss"; - public static final String yyyyMMddHHmmss_2 = "yyyy年M月d日 HH:mm:ss"; - public static final String HHmmss = "HH:mm:ss"; - public static final String hhmmMMDDyyyy = "hh:mm M月d日 yyyy"; - public static final String MMdd = "MM月dd日"; - // -- - /** 一分钟 60秒 */ - public static final int MINUTE_S = 60; - /** 一小时 60 * 60秒 */ - public static final int HOUR_S = 3600; - /** 一天 24 * 60 * 60*/ - public static final int DAY_S = 86400; - - /** 秒与毫秒的倍数 */ - public static final long SEC = 1000; - /** 分与毫秒的倍数 */ - public static final long MIN = SEC * 60; - /** 时与毫秒的倍数 */ - public static final long HOUR = MIN * 60; - /** 天与毫秒的倍数 */ - public static final long DAY = HOUR * 24; - /** 周与毫秒的倍数 */ - public static final long WEEK = DAY * 7; - /** 月与毫秒的倍数 */ - public static final long MONTH = DAY * 30; - /** 年与毫秒的倍数 */ - public static final long YEAR = DAY * 365; - - /** - * 获取当前日期的字符串 - "yyyy-MM-dd HH:mm:ss" - * @return 字符串 - */ - public static String getDateNow(){ - return getDateNow(yyyyMMddHHmmss); - } - - /** - * 获取当前日期的字符串 - * @param format 日期格式,譬如:"yyyy-MM-dd HH:mm:ss" - * @return 字符串 - */ - public static String getDateNow(String format) { - try { - if ((format == null) || (format != null && format.equals(""))) - format = yyyyMMddHHmmss; - - Calendar cld = Calendar.getInstance(); - DateFormat df = new SimpleDateFormat(format); - return df.format(cld.getTime()); - } catch (Exception e) { - } - return null; - } - - // == - - /** - * 将时间戳转换日期字符串 - * @param time 时间戳 - * @param format 日期格式 - * @return 按照需求格式的日期字符串 - */ - public static String formatTime(long time, String format){ - try { - return new SimpleDateFormat(format).format(time); - } catch (Exception e) { - } - return null; - } - - /** - * 将Date类型转换日期字符串 - * @param date 日期 - * @param format 日期格式 - * @return 按照需求格式的日期字符串 - */ - public static String formatDate(Date date, String format) { - try { - return new SimpleDateFormat(format).format(date); - } catch (Exception e) { - } - return null; - } - - // === - - /** - * 将时间戳转换成Date类型 - * @param time - * @return - */ - public static Date parseDate(long time){ - try { - return new Date(time); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "parseDate"); - } - return null; - } - - /** - * 将日期字符串转换为Date类型 - 默认表示time 属于 yyyy-MM-dd HH:mm:ss 格式 - * @param time - * @return - */ - public static Date parseDate(String time){ - return parseDate(time, yyyyMMddHHmmss); - } - - /** - * 将日期字符串转换为Date类型 - * @param time - * @param format - * @return - */ - public static Date parseDate(String time, String format) { - try { - if ((format == null) || (format != null && format.equals(""))) - format = yyyyMMddHHmmss; - - return new SimpleDateFormat(format).parse(time); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "parseDate"); - } - return null; - } - - // == - - /** - * 解析时间字符串转换为long毫秒 - 默认表示time 属于 yyyy-MM-dd HH:mm:ss 格式 - * @param time - * @return - */ - public static long parseLong(String time){ - return parseLong(time, yyyyMMddHHmmss); - } - - /** - * 解析时间字符串转换为long毫秒 - * @param time 时间 - * @param format 时间的格式 - * @return - */ - public static long parseLong(String time, String format) { - try { - if ((format == null) || (format != null && format.equals(""))) - format = yyyyMMddHHmmss; - - SimpleDateFormat sdf = new SimpleDateFormat(format); - // 按规定的时间格式,进行格式化时间,并且获取long时间毫秒 - long millionSeconds = sdf.parse(time).getTime(); - // 返回毫秒时间 - return millionSeconds; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "parseLong"); - } - return 0l; - } - - // == - - /** - * 获取时间差 - 分钟 - * @param time(毫秒) - * @return - */ - public static int getTimeDiffMinute(long time){ - return (int) (time / 60000); // 60 * 1000 - } - - /** - * 获取时间差 - 小时 - * @param time(毫秒) - * @return - */ - public static int getTimeDiffHour(long time){ - return (int) (time / 3600000); // 60 * 1000 * 60 - } - - /** - * 获取时间差 - 天 - * @param time(毫秒) - * @return - */ - public static int getTimeDiffDay(long time){ - return (int) (time / 86400000); // 60 * 1000 * 60 * 24 - } - - /** - * 获取时间差 - (传入时间 - 当前时间) - * @param time - * @return - */ - public static long getTimeDiff(long time){ - return time - System.currentTimeMillis(); - } - - /** - * 获取时间差 - * @param timeVal1 - * @param timeVal2 - * @return - */ - public static long getTimeDiff(String timeVal1, String timeVal2){ - long time1 = parseLong(timeVal1); - long time2 = parseLong(timeVal2); - if (time1 > 1l && time2 > 1l){ - return time1 - time2; - } - return -2l; - } - - /** - * 获取时间差 - * @param timeVal1 - * @param timeFormat1 - * @param timeVal2 - * @param timeFormat2 - * @return - */ - public static long getTimeDiff(String timeVal1, String timeFormat1, String timeVal2, String timeFormat2){ - long time1 = parseLong(timeVal1, timeFormat1); - long time2 = parseLong(timeVal2, timeFormat2); - if (time1 > 1l && time2 > 1l){ - return time1 - time2; - } - return -2l; - } - -// System.out.println("现在时间: " + getDateNow()); -// String startTimeStr = "2017-11-27 14:45:00"; -// System.out.println("开始时间: " + startTimeStr); -// long startTimeL = DateUtils.parseLong(startTimeStr); -// System.out.println("转换long: " + startTimeL); -// long timeDiff = startTimeL - System.currentTimeMillis(); -// System.out.println("时间差: " + timeDiff); - - // ======= 获取时间 ======= - - /** - * 获取年 - * @param date Date对象 - * @return 年 - */ - public static int getYear(Date date) { - try { - Calendar cld = Calendar.getInstance(); - cld.setTime(date); - return cld.get(Calendar.YEAR); - } catch (Exception e) { - } - return -1; - } - - /** - * 获取月 (0 - 11) + 1 - * @param date Date对象 - * @return 月 - */ - public static int getMonth(Date date) { - try { - Calendar cld = Calendar.getInstance(); - cld.setTime(date); - return cld.get(Calendar.MONTH) + 1; - } catch (Exception e) { - } - return -1; - } - - /** - * 获取日 - * @param date Date对象 - * @return 日 - */ - public static int getDay(Date date) { - try { - Calendar c = Calendar.getInstance(); - c.setTime(date); - return c.get(Calendar.DAY_OF_MONTH); - } catch (Exception e) { - } - return -1; - } - - /** - * 获取时 - 24 - * @param date Date对象 - * @return 时 - */ - public static int get24Hour(Date date) { - try { - Calendar c = Calendar.getInstance(); - c.setTime(date); - return c.get(Calendar.HOUR_OF_DAY); - } catch (Exception e) { - } - return -1; - } - - /** - * 获取时 - 12 - * @param date Date对象 - * @return 时 - */ - public static int get12Hour(Date date) { - try { - Calendar c = Calendar.getInstance(); - c.setTime(date); - return c.get(Calendar.HOUR); - } catch (Exception e) { - } - return -1; - } - - /** - * 获取分 - * @param date Date对象 - * @return 分 - */ - public static int getMinute(Date date) { - try { - Calendar c = Calendar.getInstance(); - c.setTime(date); - return c.get(Calendar.MINUTE); - } catch (Exception e) { - } - return -1; - } - - /** - * 获取秒 - * @param date Date对象 - * @return 秒 - */ - public static int getSecond(Date date) { - try { - Calendar c = Calendar.getInstance(); - c.setTime(date); - return c.get(Calendar.SECOND); - } catch (Exception e) { - } - return -1; - } - - public static String convertTime(int time, boolean isAppand){ - if (isAppand){ - return time >= 10 ? time + "" : "0" + time; - } - return time + ""; - } - - // == - - /** - * 获取年 - */ - public static int getYear() { - return Calendar.getInstance().get(Calendar.YEAR); - } - - /** - * 获取月 (0 - 11) + 1 - */ - public static int getMonth() { - int month = Calendar.getInstance().get(Calendar.MONTH) + 1; - return month; - } - - /** - * 获取日 - */ - public static int getDay() { - return Calendar.getInstance().get(Calendar.DATE); - } - - /** - * 获取时 - 24 - */ - public static int get24Hour() { - return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); - } - - /** - * 获取时 - 12 - */ - public static int get12Hour() { - return Calendar.getInstance().get(Calendar.HOUR); - } - - /** - * 获取分 - */ - public static int getMinute() { - return Calendar.getInstance().get(Calendar.MINUTE); - } - - /** - * 获取秒 - * @return - */ - public static int getSecond() { - return Calendar.getInstance().get(Calendar.SECOND); - } - - // == - - /** - * 判断是否闰年 - * @param year 年数 - * @return - */ - public static boolean isLeapYear(int year) { - // 判断是否闰年 - if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { - return true; - } - return false; - } - - /** - * 获取月份 - 对应天数 - * @param year 年数 - * @param month 月份 - * @return - */ - public static int getMonthDayNumber(int year, int month) { - int number = 31; - // 判断返回的标识数字 - switch(month) { - case 1: - case 3: - case 5: - case 7: - case 8: - case 10: - case 12: - number = 31; - break; - case 2: - if(isLeapYear(year)) { - number = 29; - } else { - number = 28; - } - break; - case 4: - case 6: - case 9: - case 11: - number = 30; - break; - } - return number; - } - - // ======================================================= - - /** - * 传入时间,获取时间(00:00:00 格式) - 不处理大于一天 - * @param time 时间(秒为单位) - * @return - */ - public static String secToTimeRetain(int time) { - return secToTimeRetain(time, false); - } - - /** - * 传入时间,获取时间(00:00:00 格式) - * @param time 时间(秒为单位) - * @param isHandlerMDay 是否处理大于一天的时间 - * @return - */ - public static String secToTimeRetain(int time, boolean isHandlerMDay) { - try { - if(time <= 0) { - return "00:00:00"; - } else { - // 取模 - int rSecond = 0; - int rMinute = 0; - // 差数 - int dSecond = 0; - int dMinute = 0; - int dHour = 0; - // 转换时间格式 - if(time < MINUTE_S) { // 小于1分钟 - return "00:00:" + ((time >=10)?time:("0" + time)); - } else if(time >= MINUTE_S && time < HOUR_S) { // 小于1小时 - dSecond = time % MINUTE_S; // 取模分钟,获取多出的秒数 - dMinute = (time - dSecond) / MINUTE_S; - return "00:" + ((dMinute >=10)?dMinute:("0" + dMinute)) + ":" + ((dSecond >=10)?dSecond:("0" + dSecond)); - } else if(time >= HOUR_S && time < DAY_S) { // 小于等于一天 - rMinute = time % HOUR_S; // 取模小时,获取多出的分钟 - dHour = (time - rMinute) / HOUR_S; // 获取小时 - dSecond = (time - dHour * HOUR_S); // 获取多出的秒数 - dMinute = dSecond / MINUTE_S; // 获取多出的分钟 - rSecond = dSecond % MINUTE_S; // 取模分钟,获取多余的秒速 - return ((dHour >= 10) ? dHour : ("0" + dHour)) + ":" + ((dMinute >= 10) ? dMinute:("0" + dMinute)) + ":" + ((rSecond >= 10) ? rSecond:"0" + rSecond); - } else { // 多余的时间,直接格式化 - // 大于一天的情况 - if(isHandlerMDay) { - rMinute = time % HOUR_S; // 取模小时,获取多出的分钟 - dHour = (time - rMinute) / HOUR_S; // 获取小时 - dSecond = (time - dHour * HOUR_S); // 获取多出的秒数 - dMinute = dSecond / MINUTE_S; // 获取多出的分钟 - rSecond = dSecond % MINUTE_S; // 取模分钟,获取多余的秒速 - return ((dHour >= 10) ? dHour : ("0" + dHour)) + ":" + ((dMinute >= 10) ? dMinute:("0" + dMinute)) + ":" + ((rSecond >= 10) ? rSecond:"0" + rSecond); - } - } - } - } catch (Exception e) { - } - return null; - } - - /** - * 传入时间,时间参数(小时、分钟、秒) - * @param time 时间(秒为单位) - * @return - */ - public static int[] convertTimeArys(int time) { - try { - if(time <= 0) { - return new int[] { 0, 0, 0 }; - } else { - // 取模 - int rSecond = 0; - int rMinute = 0; - // 差数 - int dSecond = 0; - int dMinute = 0; - int dHour = 0; - // 转换时间格式 - if(time < MINUTE_S) { // 小于1分钟 - return new int[]{ 0, 0, time }; - } else if(time >= MINUTE_S && time < HOUR_S) { // 小于1小时 - dSecond = time % MINUTE_S; // 取模分钟,获取多出的秒数 - dMinute = (time - dSecond) / MINUTE_S; - return new int[]{ 0, dMinute, dSecond }; - } else if(time >= HOUR_S && time < DAY_S) { // 小于等于一天 - rMinute = time % HOUR_S; // 取模小时,获取多出的分钟 - dHour = (time - rMinute) / HOUR_S; // 获取小时 - dSecond = (time - dHour * HOUR_S); // 获取多出的秒数 - dMinute = dSecond / MINUTE_S; // 获取多出的分钟 - rSecond = dSecond % MINUTE_S; // 取模分钟,获取多余的秒速 - return new int[]{ dHour, dMinute, rSecond }; - } else { // 多余的时间,直接格式化 - // 大于一天的情况 - rMinute = time % HOUR_S; // 取模小时,获取多出的分钟 - dHour = (time - rMinute) / HOUR_S; // 获取小时 - dSecond = (time - dHour * HOUR_S); // 获取多出的秒数 - dMinute = dSecond / MINUTE_S; // 获取多出的分钟 - rSecond = dSecond % MINUTE_S; // 取模分钟,获取多余的秒速 - return new int[]{ dHour, dMinute, rSecond }; - } - } - } catch (Exception e) { - } - return null; - } - - /** - * 转换时间 - * @param millis - * @param precision - * precision = 0, return null - * precision = 1, return 天 - * precision = 2, return 天, 小时 - * precision = 3, return 天, 小时, 分钟 - * precision = 4, return 天, 小时, 分钟, 秒 - * precision = 5,return 天, 小时, 分钟, 秒, 毫秒 - * @return fit time span - */ - public static String millis2FitTimeSpan(long millis, int precision) { - if (millis <= 0 || precision <= 0) return null; - StringBuilder sb = new StringBuilder(); - String[] units = {"天", "小时", "分钟", "秒", "毫秒"}; - int[] unitLen = {86400000, 3600000, 60000, 1000, 1}; - precision = Math.min(precision, 5); - for (int i = 0; i < precision; i++) { - if (millis >= unitLen[i]) { - long mode = millis / unitLen[i]; - millis -= mode * unitLen[i]; - sb.append(mode).append(units[i]); - } - } - return sb.toString(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/DevCommonUtils.java b/DevLibUtils/src/main/java/dev/utils/common/DevCommonUtils.java deleted file mode 100644 index 58d67fd827..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/DevCommonUtils.java +++ /dev/null @@ -1,1255 +0,0 @@ -package dev.utils.common; - -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; - -import dev.utils.JCLogUtils; - -/** - * detail: 开发常用方法 - 工具类 - * Created by Ttt - */ -public final class DevCommonUtils { - - private DevCommonUtils() { - } - - // 日志TAG - private static final String TAG = DevCommonUtils.class.getSimpleName(); - /** 换行字符串 */ - public static final String NEW_LINE_STR = System.getProperty("line.separator"); - /** 换行字符串 - 两行 */ - public static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - /** - * 判断是否网络资源 - * @param resPath 资源地址 - * @return - */ - public static boolean isHttpRes(String resPath){ - if (!isEmpty(resPath)){ - // 属于第一位开始, 才是属于网络资源 - if (resPath.toLowerCase().startsWith("http:") || - resPath.toLowerCase().startsWith("https:")){ - return true; - } - } - return false; - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - public static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } - - /** - * 获取空格 - * @param number 空格数量 - * @return - */ - private static String getSpace(int number){ - StringBuffer buffer = new StringBuffer(); - // 循环空格 - for (int i = 0; i < number; i++){ - buffer.append(" "); - } - return buffer.toString(); - } - - /** - * 获取 Tab - * @param number tab 键数量 - * @return - */ - private static String getTab(int number){ - StringBuffer buffer = new StringBuffer(); - // 循环空格 - for (int i = 0; i < number; i++){ - buffer.append("\t"); - } - return buffer.toString(); - } - - // ======================== - // == 判断数据是否为null == - // ======================== - - /** - * 判断是否为null - * @param str - * @return - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - /** - * 判断字符串是否为 null 或全为空格 - * @param str - * @return - */ - public static boolean isTrimEmpty(final String str) { - return (str == null || str.trim().length() == 0); - } - - /** - * 判断多个字符串是否为null - * @param strs - * @return - */ - public static boolean isEmpty(String... strs){ - if (strs != null && strs.length != 0){ - for (int i = 0, len = strs.length; i < len; i++){ - if (isEmpty(strs[i])){ - return true; - } - } - return false; - } - // 默认表示属于null - return true; - } - - // ==================== - // 单独需要判断是否为null,而不需要判断长度,只需要调用length方法 - // length(list, -1) == -1 => true 表示为null, false 表示不为null,存在数据(可能返回0) - // ==================== - - /** - * 判断是否为null to Object - * @param obj - * @return - */ - public static boolean isEmpty(Object obj){ - if (obj != null){ - // 判断是否属于基本类型数组 - if (obj.getClass().isArray()){ - try { - Class cla = obj.getClass(); - // == 基本数据类型 == - if (cla.isAssignableFrom(int[].class)){ - return (((int[]) obj).length == 0); - } else if (cla.isAssignableFrom(boolean[].class)){ - return (((boolean[]) obj).length == 0); - } else if (cla.isAssignableFrom(long[].class)){ - return (((long[]) obj).length == 0); - } else if (cla.isAssignableFrom(double[].class)){ - return (((double[]) obj).length == 0); - } else if (cla.isAssignableFrom(float[].class)){ - return (((float[]) obj).length == 0); - } else if (cla.isAssignableFrom(byte[].class)){ - return (((byte[]) obj).length == 0); - } else if (cla.isAssignableFrom(char[].class)){ - return (((char[]) obj).length == 0); - } else if (cla.isAssignableFrom(short[].class)){ - return (((short[]) obj).length == 0); - } - } catch (Exception e){ - } - } - return false; - } - return true; - } - - /** - * 判断是否为null to 数组 - * @param objs - * @return - */ - public static boolean isEmpty(Object[] objs){ - if (objs != null){ - return (objs.length == 0); - } - return true; - } - - /** - * 判读是否为null to List - * @param list - * @return - */ - public static boolean isEmpty(List list){ - if (list != null){ - return (list.size() == 0); - } - return true; - } - - /** - * 判读是否为null to Map - * @param map - * @return - */ - public static boolean isEmpty(Map map){ - if (map != null){ - return (map.size() == 0); - } - return true; - } - - /** - * 判读是否为null to Set - * @param set - * @return - */ - public static boolean isEmpty(Set set){ - if (set != null){ - return (set.size() == 0); - } - return true; - } - - /** - * 判读是否为null to Queue - * @param queue - * @return - */ - public static boolean isEmpty(Queue queue){ - if (queue != null){ - return (queue.size() == 0); - } - return true; - } - - /** - * 判读是否为null to 可变数组 - * @param args - * @return - */ - public static boolean isEmptyObjs(Object... args){ - if (args != null){ - return (args.length == 0); - } - return true; - } - - // ================== - // == 判断数据长度 == - // ================== - - /** - * 获取长度,如果字符串为null,则返回0 - * @param str - * @return - */ - public static int length(String str) { - return str == null ? 0 : str.length(); - } - - /** - * 获取数组长度 - * @param objs - * @return - */ - public static int length(Object[] objs){ - return length(objs, 0); - } - - /** - * 获取长度 to List - * @param list - * @return - */ - public static int length(List list){ - return length(list, 0); - } - - /** - * 获取长度 to Map - * @param map - * @return - */ - public static int length(Map map){ - return length(map, 0); - } - - /** - * 获取长度 to Set - * @param set - * @return - */ - public static int length(Set set){ - return length(set, 0); - } - - /** - * 获取长度 to Queue - * @param queue - * @return - */ - public static int length(Queue queue){ - return length(queue, 0); - } - - // = - - /** - * 获取字符串长度 - * @param str - * @param dfLength - * @return - */ - public static int length(String str, int dfLength) { - if (str != null){ - return str.length(); - } - return dfLength; - } - - /** - * 获取数组长度 - * @param objs - * @param dfLength - * @return - */ - public static int length(Object[] objs, int dfLength){ - if (objs != null){ - return objs.length; - } - return dfLength; - } - - /** - * 获取长度 to List - * @param list - * @param dfLength - * @return - */ - public static int length(List list, int dfLength){ - if (list != null){ - return list.size(); - } - return dfLength; - } - - /** - * 获取长度 to Map - * @param map - * @param dfLength - * @return - */ - public static int length(Map map, int dfLength){ - if (map != null){ - return map.size(); - } - return dfLength; - } - - /** - * 获取长度 to Set - * @param set - * @param dfLength - * @return - */ - public static int length(Set set, int dfLength){ - if (set != null){ - return set.size(); - } - return dfLength; - } - - /** - * 获取长度 to Queue - * @param queue - * @param dfLength - * @return - */ - public static int length(Queue queue, int dfLength){ - if (queue != null){ - return queue.size(); - } - return dfLength; - } - - // == - - /** - * 获取可变数组长度 - * @param args - * @return - */ - public static int lengthObjs(Object... args){ - return lengthObjsDf(0, args); - } - - /** - * 获取可变数组长度 - * @param args - * @param dfLength - * @return - */ - public static int lengthObjsDf(int dfLength, Object... args){ - if (args != null){ - return args.length; - } - return dfLength; - } - - // === - - /** - * 字符串长度匹配 - * @param str 源字符串 - * @param length 期望长度 - * @return - */ - public static boolean isLength(String str, int length) { - return str != null && str.length() == length; - } - - /** - * 获取数组长度 是否等于 期望长度 - * @param objs - * @param length - * @return - */ - public static boolean isLength(Object[] objs, int length){ - return objs != null && objs.length == length; - } - - /** - * 获取长度 to List 是否等于 期望长度 - * @param list - * @param length - * @return - */ - public static boolean isLength(List list, int length){ - return list != null && list.size() == length; - } - - /** - * 获取长度 to Map 是否等于 期望长度 - * @param map - * @param length - * @return - */ - public static boolean isLength(Map map, int length){ - return map != null && map.size() == length; - } - - /** - * 获取长度 to Set 是否等于 期望长度 - * @param set - * @param length - * @return - */ - public static boolean isLength(Set set, int length){ - return set != null && set.size() == length; - } - - /** - * 获取长度 to Queue 是否等于 期望长度 - * @param queue - * @param length - * @return - */ - public static boolean isLength(Queue queue, int length){ - return queue != null && queue.size() == length; - } - - // === - - /** - * 判断两字符串是否相等 - * @param a 待校验字符串 a - * @param b 待校验字符串 b - * @return true : 相等, false : 不相等 - */ - public static boolean equals(final CharSequence a, final CharSequence b) { - if (a == b) return true; - int length; - if (a != null && b != null && (length = a.length()) == b.length()) { - if (a instanceof String && b instanceof String) { - return a.equals(b); - } else { - for (int i = 0; i < length; i++) { - if (a.charAt(i) != b.charAt(i)) return false; - } - return true; - } - } - return false; - } - - /** - * 判断多个字符串是否相等, 只有全相等才返回true - 对比大小写 - * @param args - * @return - */ - public static boolean isEquals(String... args) { - return isEquals(false, args); - } - - /** - * 判断多个字符串是否相等, 只有全相等才返回true - * @param isIgnore 是否忽略大小写 - * @param args - * @return - */ - public static boolean isEquals(boolean isIgnore, String... args) { - if (args != null) { - String last = null; - // 获取数据长度 - int len = args.length; - // 如果最多只有一个数据判断,则直接跳过 - if (len <= 1){ - return false; - } - // 遍历判断 - for (int i = 0; i < len; i++) { - // 获取临时变量 - String val = args[i]; - // 如果等于null,则跳过 - if (val == null) { - return false; - } - if (last != null) { - if (isIgnore) { - if (!val.equalsIgnoreCase(last)) { - return false; - } - } else { - if (!val.equals(last)) { - return false; - } - } - } - last = val; - } - return true; - } - return false; - } - - /** - * 判断多个字符串,只要有一个符合条件,则通过 - * @param content - * @param args - * @return - */ - public static boolean isOrEquals(String content, String... args){ - return isOrEquals(false, content, args); - } - - /** - * 判断多个字符串,只要有一个符合条件,则通过 - * @param isIgnore 是否忽略大小写 - * @param content - * @param args - * @return - */ - public static boolean isOrEquals(boolean isIgnore, String content, String... args){ - if (content != null && args != null && args.length != 0) { - // 获取数据长度 - int len = args.length; - // 如果最多只有一个数据判断,则直接跳过 - if (len <= 1){ - return false; - } - // 遍历判断 - for (int i = 0; i < len; i++) { - // 获取临时变量 - String val = args[i]; - // 如果等于null,则跳过 - if (val == null) { - continue; - } else { - if (isIgnore) { - if (val.equalsIgnoreCase(content)) { - return true; - } - } else { - if (val.equals(content)) { - return true; - } - } - } - } - } - return false; - } - - /** - * 判断一堆值中,是否存在符合该条件的(包含) - * @param content - * @param args - * @return - */ - public static boolean isContains(String content, String... args){ - return isContains(false, content, args); - } - - /** - * 判断一堆值中,是否存在符合该条件的(包含) - * @param isIgnore - * @param content - * @param args - * @return - */ - public static boolean isContains(boolean isIgnore, String content, String... args){ - if (content != null && args != null && args.length != 0){ - // 判断是否需要忽略大小写 - if (isIgnore){ - content = content.toLowerCase(); - } - // 获取内容长度 - int cLength = content.length(); - // 遍历判断 - for (int i = 0, len = args.length; i < len; i++){ - // 获取参数 - String val = args[i]; - // 判断是否为null,或者长度为0 - if (!isEmpty(val) && cLength != 0){ - if (isIgnore) { - // 转换小写 - String valIgnore = val.toLowerCase(); - // 判断是否包含 - if (valIgnore.indexOf(content) != -1) { - return true; - } - } else { - // 判断是否包含 - if (val.indexOf(content) != -1) { - return true; - } - } - } else { - // 下面这一串可以不要,因为判断字符串是否包含 - // 已经处理了值不为null,并且需要判断的值长度不能为0,下面则不需要加上 - if (content.equals(val)){ - return true; - } - } - } - } - return false; - } - - /** - * 判断内容, 是否属于特定字符串数组开头 - 对比大小写 - * @param content - * @param args - * @return - */ - public static boolean isStartsWith(String content, String... args) { - return isStartsWith(false, content, args); - } - - /** - * 判断内容, 是否属于特定字符串数组开头 - * @param isIgnore 是否忽略大小写 - * @param content - * @param args - * @return - */ - public static boolean isStartsWith(boolean isIgnore, String content, String... args) { - if (!isEmpty(content) && args != null && args.length != 0){ - // 判断是否需要忽略大小写 - if (isIgnore){ - content = content.toLowerCase(); - } - // 获取数据长度 - int len = args.length; - // 遍历判断 - for (int i = 0; i < len; i++) { - // 获取临时变量 - String val = args[i]; - // 判断是否为null,或者长度为0 - if (!isEmpty(val)){ - if (isIgnore) { - // 转换小写 - String valIgnore = val.toLowerCase(); - // 判断是否属于 val 开头 - if (content.startsWith(valIgnore)) { - return true; - } - } else { - // 判断是否属于 val 开头 - if (content.startsWith(val)) { - return true; - } - } - } - } - } - return false; - } - - /** - * 判断内容, 是否属于特定字符串数组结尾 - 对比大小写 - * @param content - * @param args - * @return - */ - public static boolean isEndsWith(String content, String... args) { - return isEndsWith(false, content, args); - } - - /** - * 判断内容, 是否属于特定字符串数组结尾 - * @param isIgnore 是否忽略大小写 - * @param content - * @param args - * @return - */ - public static boolean isEndsWith(boolean isIgnore, String content, String... args) { - if (!isEmpty(content) && args != null && args.length != 0){ - // 判断是否需要忽略大小写 - if (isIgnore){ - content = content.toLowerCase(); - } - // 获取数据长度 - int len = args.length; - // 遍历判断 - for (int i = 0; i < len; i++) { - // 获取临时变量 - String val = args[i]; - // 判断是否为null,或者长度为0 - if (!isEmpty(val)){ - if (isIgnore) { - // 转换小写 - String valIgnore = val.toLowerCase(); - // 判断是否属于 val 结尾 - if (content.endsWith(valIgnore)) { - return true; - } - } else { - // 判断是否属于 val 结尾 - if (content.endsWith(val)) { - return true; - } - } - } - } - } - return false; - } - - // -- - - /** - * 清空全部空格,并返回处理后的字符串 - * @param str - * @return - */ - public static String toClearSpace(String str) { - if (isEmpty(str)) { - return str; - } - return str.replaceAll(" ", ""); - } - - /** - * 清空前后空格,并返回处理后的字符串 - * @param str - * @return - */ - public static String toClearSpaceTrim(String str) { - if (isEmpty(str)) { - return str; - } - // 如果前面或者后面都是 空格开头,就一直进行处理 - while(str.startsWith(" ") || str.endsWith(" ")) { - str = str.trim(); - } - return str; - } - - // == - - /** - * 检查字符串,如果为null,返回 "" - * @param str - * @return - */ - public static String toCheckValue(String str) { - return toCheckValue("", str); - } - - /** - * 检查字符串,如果为null,返回 默认字符串 - * @param dfStr - * @param str - * @return - */ - public static String toCheckValue(String dfStr, String str) { - return isEmpty(str) ? dfStr : str; - } - - /** - * 单独检查两个值,减少循环,不直接调用toCheckValues - * @param dfStr - * @param value1 - * @param value2 - * @return - */ - public static String toCheckValue(String dfStr, String value1, String value2){ - if (isEmpty(value1)){ - if (isEmpty(value2)){ - return dfStr; - } else { - return value2; - } - } else { - return value1; - } - } - - /** - * 检查多个值,并返回第一个非null and "" 的字符串,如果都不符合条件,则返回默认值 - * @param dfStr - * @param params - * @return - */ - public static String toCheckValues(String dfStr, String... params){ - if (params != null && params.length != 0){ - for (int i = 0, len = params.length; i < len; i++){ - String param = params[i]; - if (isEmpty(param)){ - if (i == len - 1){ - return dfStr; // 属于最后一个,则返回默认值 - } else { - continue; // 不属于最后一个则跳过 - } - } else { - return param; - } - } - } - return dfStr; - } - - /** - * 检查多个值,并返回第一个非null and "" and 全部不是属于空格 的字符串,如果都不符合条件,则返回默认值 - * @param dfStr - * @param params - * @return - */ - public static String toCheckValuesSpace(String dfStr, String... params){ - if (params != null && params.length != 0){ - for (int i = 0, len = params.length; i < len; i++){ - // 处理后,进行返回 => 删除前后空格 - String param = toClearSpaceTrim(params[i]); - if (isEmpty(param)){ - if (i == len - 1){ - return dfStr; // 属于最后一个,则返回默认值 - } else { - continue; // 不属于最后一个则跳过 - } - } else { - return param; - } - } - } - return dfStr; - } - - /** - * 裁剪符号处理 - * @param start 开始位置 - * @param symbolNumber 转换数量 - * @param content - * @param symbol - * @return - */ - public static String subSymbolHide(int start, int symbolNumber, String content, String symbol){ - if (!isEmpty(content)){ - if (start <= 0 || symbolNumber <= 0){ - return content; - } - // 获取数据长度 - int length = content.length(); - // 如果数据小于 start 位则直接返回 - if (length <= start){ - return content; - } else { // 大于 start 位 - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append(content.substring(0, start)); - int len = length - start - symbolNumber; - // 如果超出总长度, 则进行控制 - if (len <= 0){ // 表示后面的全部转换 - len = length - start; - } else { // 需要裁剪的数量 - len = symbolNumber; - } - // 进行遍历保存 - for (int i = 0; i < len; i++) { - stringBuffer.append(symbol); - } - stringBuffer.append(content.substring(start + len, length)); - return stringBuffer.toString(); - } - } - return ""; - } - - /** - * 转换符号处理 - * @param start 开始位置 - * @param content - * @param symbol - * @return - */ - public static String converSymbolHide(int start, String content, String symbol){ - if (!isEmpty(content)){ - if (start <= 0){ - return content; - } - // 获取数据长度 - int length = content.length(); - // 如果数据小于 start 位则直接返回 - if (length <= start){ - return content; - } else { // 大于 start 位 - StringBuffer stringBuffer = new StringBuffer(); - stringBuffer.append(content.substring(0, start)); - int len = length - start; - // 进行平分 - len /= 2; - // 进行遍历保存 - for (int i = 0; i < len; i++){ - stringBuffer.append(symbol); - } - stringBuffer.append(content.substring(start + len, length)); - return stringBuffer.toString(); - } - } - return ""; - } - - // == 替换截取操作 == - - /** - * 替换(删除 - 替换成"") 字符串中符合 特定标记字符的 startsWith - endsWith - * * 如 _____a_a_a_a________ 传入 _ 等于 ____a_a_a_a____ - * @param str - * @param key 需要判断的标记 - * @return - */ - public static String toReplaceSEWith(String str, String key){ - return toReplaceSEWith(str, key, ""); - } - - /** - * 替换字符串中符合 特定标记字符的 startsWith - endsWith - * 如 _____a_a_a_a________ 传入 _ , c 等于 c____a_a_a_a____c - * @param str - * @param key 需要判断的标记 - * @param value 需要替换的内容 - * @return - */ - public static String toReplaceSEWith(String str, String key, String value){ - if (!(!isEmpty(str) && !isEmpty(key) && !isEmpty(value) && !key.equals(value))) { - return str; - } - try { - // 获取编辑内容长度 - int kLength = key.length(); - // 保存新的Buffer中,减少内存开销 - StringBuffer sBuffer = new StringBuffer(str); - // 判断是否在最头部 - if (sBuffer.indexOf(key) == 0) { - sBuffer.delete(0, kLength); - // 追加内容 - sBuffer.insert(0, value); - } - // 获取尾部的位置 - int lastIndexOf = -1; - // 数据长度 - int bufLength = -1; - // 判断是否在最尾部 - if ((lastIndexOf = sBuffer.lastIndexOf(key)) == ((bufLength = sBuffer.length()) - kLength)) { - sBuffer.delete(lastIndexOf, bufLength); - // 追加内容 - sBuffer.insert(lastIndexOf, value); - } - return sBuffer.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toReplaceSEWith"); - } - return str; - } - - /** - * (这个方法功能主要把字符符合标记的 头部和尾部都替换成 "") - * 如 _____a_a_a_a________ 传入 _ 等于 a_a_a_a - * 替换字符串中符合 特定标记字符的 startsWith(indexOf) - endsWith(lastIndexOf) ,while - * @param str - * @param key 需要判断的标记 - * @return - */ - public static String toClearSEWiths(String str, String key){ - if (!(!isEmpty(str) && !isEmpty(key))) { - return str; - } - try { - // 获取编辑内容长度 - int kLength = key.length(); - // 保存新的Buffer中,减少内存开销 - StringBuffer sBuffer = new StringBuffer(str); - // 进行循环判断 - 属于最前面的,才进行处理 - while (sBuffer.indexOf(key) == 0) { - sBuffer.delete(0, kLength); - } - // 获取尾部的位置 - int lastIndexOf = -1; - // 数据长度 - int bufLength = -1; - // 进行循环判断 - 属于最后面的,才进行处理 - while ((lastIndexOf = sBuffer.lastIndexOf(key)) == ((bufLength = sBuffer.length()) - kLength)) { - sBuffer.delete(lastIndexOf, bufLength); - } - return sBuffer.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toClearSEWiths"); - } - return str; - } - - /** - * 裁剪字符串 - * @param content 需要裁剪的字符串 - * @param endIndex 结束裁剪的位置 - * @return - */ - public static String substring(String content, int endIndex){ - return substring(content, 0, endIndex, true); - } - - /** - * 裁剪字符串 - * @param content 需要裁剪的字符串 - * @param endIndex 结束裁剪的位置 - * @param isReturn 开始位置超过限制是否返回内容 - * @return - */ - public static String substring(String content, int endIndex, boolean isReturn){ - return substring(content, 0, endIndex, isReturn); - } - - /** - * 裁剪字符串 - * @param content 需要裁剪的字符串 - * @param beginIndex 开始裁剪的位置 - * @param endIndex 结束裁剪的位置 - * @param isReturn 开始位置超过限制是否返回内容 - * @return - */ - public static String substring(String content, int beginIndex, int endIndex, boolean isReturn){ - if (!isEmpty(content) && beginIndex >= 0 && endIndex >= 0 && endIndex >= beginIndex){ - // 获取数据长度 - int len = length(content); - // 防止超过限制 - if (beginIndex > len){ - return isReturn ? content : ""; - } - // 防止超过限制 - if (endIndex >= len){ - endIndex = len; - } - return content.substring(beginIndex, endIndex); - } - return isReturn ? content : ""; - } - - // == - - /** - * 替换开头字符串 - * @param str 内容 - * @param key 需要判断的kye - * @return - */ - public static String toReplaceStartsWith(String str, String key){ - return toReplaceStartsWith(str, key, ""); - } - - /** - * 替换开头字符串 - * @param str 内容 - * @param key 需要判断的kye - * @param value 需要替换的内容 - * @return - */ - public static String toReplaceStartsWith(String str, String key, String value){ - if (!isEmpty(str) && !isEmpty(key)) { - try { - if (str.startsWith(key)){ - return value + str.substring(key.length(), str.length()); - } - } catch (Exception e) { - } - } - return str; - } - - /** - * 清空属于特定字符串开头的字段 - * 如 _____a_a_a_a________ 传入 _ 等于 a_a_a_a_____ - * 替换字符串中符合 特定标记字符的 endsWith(lastIndexOf) ,while - * @param str - * @param key 需要判断的标记 - * @return - */ - public static String toClearStartsWith(String str, String key){ - if (!(!isEmpty(str) && !isEmpty(key))) { - return str; - } - try { - // 获取编辑内容长度 - int kLength = key.length(); - // 保存新的Buffer中,减少内存开销 - StringBuffer sBuffer = new StringBuffer(str); - // 进行循环判断 - 属于最前面的,才进行处理 - while (sBuffer.indexOf(key) == 0) { - sBuffer.delete(0, kLength); - } - return sBuffer.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toClearStartsWith"); - } - return str; - } - - // == - - /** - * 替换结尾字符串 - * @param content 内容 - * @param key 需要判断的kye - * @return - */ - public static String toReplaceEndsWith(String content, String key){ - return toReplaceEndsWith(content, key, ""); - } - - /** - * 替换结尾字符串 - * @param content 内容 - * @param key 需要判断的kye - * @param value 需要替换的内容 - * @return - */ - public static String toReplaceEndsWith(String content, String key, String value){ - if (!isEmpty(content) && !isEmpty(key)) { - try { - if (content.endsWith(key)){ - return content.substring(0, content.length() - key.length()) + value; - } - } catch (Exception e) { - } - } - return content; - } - - /** - * 清空属于特定字符串结尾的字段 - * 如 _____a_a_a_a________ 传入 _ 等于 _____a_a_a_a - * 替换字符串中符合 特定标记字符的 endsWith(lastIndexOf) ,while - * @param str - * @param key 需要判断的标记 - * @return - */ - public static String toClearEndsWith(String str, String key){ - if (!(!isEmpty(str) && !isEmpty(key))) { - return str; - } - try { - // 获取编辑内容长度 - int kLength = key.length(); - // 保存新的Buffer中,减少内存开销 - StringBuffer sBuffer = new StringBuffer(str); - // 获取最后一位位置 - int sLength = 0; - // 进行循环判断 - 属于最前面的,才进行处理 - while (sBuffer.lastIndexOf(key) == ((sLength = sBuffer.length()) - kLength)) { - sBuffer.delete(sLength - kLength, sLength); - } - return sBuffer.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toClearEndsWith"); - } - return str; - } - - // === - - /** - * 替换字符串 - * @param content 内容 - * @param - * @return - */ - public static String replaceStrs(String content, String[] spArys, String[] reArys){ - // 防止数据为null - if (content != null && spArys != null && reArys != null){ - String cStr = content; - // 替换的特殊字符串长度 - int spCount = spArys.length; - // 替换的内容长度 - int reCount = reArys.length; - // 相同才进行处理 - if (spCount == reCount){ - // 遍历进行判断 - for (int i = 0; i < spCount; i++){ - // 进行替换字符串 - cStr = replaceStr(cStr, spArys[i], reArys[i]); - } - // 最终不为null,则进行返回 - return cStr; - } - } - return null; - } - - /** - * 替换字符串 - * @param content 需要替换的内容 - * @param spStr 特殊的字符串 - * @param reStr 替换的内容 - * @return - */ - public static String replaceStr(String content, String spStr, String reStr){ - // 如果替换的内容或者判断的字符串为null,则直接跳过 - if (!isEmpty(content) && !isEmpty(spStr) && reStr != null){ - try { - return content.replaceAll(spStr, reStr); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "replaceStr"); - } - } - return content; - } - - /** - * 替换字符串 - * @param content 需要替换的内容 - * @param spStr 特殊的字符串 - * @param reStr 替换的内容 - * @return 如果异常则直接返回null - */ - public static String replaceStrToNull(String content, String spStr, String reStr){ - // 如果替换的内容或者判断的字符串为null,则直接跳过 - if (!isEmpty(content) && !isEmpty(spStr) && reStr != null){ - try { - return content.replaceAll(spStr, reStr); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "replaceStrToNull"); - } - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/FieldUtils.java b/DevLibUtils/src/main/java/dev/utils/common/FieldUtils.java deleted file mode 100644 index d259d8bf11..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/FieldUtils.java +++ /dev/null @@ -1,133 +0,0 @@ -package dev.utils.common; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.LinkedList; -import java.util.List; - -import dev.utils.JCLogUtils; - -/** - * detail: 域工具 - * @author mty - */ -public final class FieldUtils { - - private FieldUtils(){ - } - - // 日志TAG - private static final String TAG = FieldUtils.class.getSimpleName(); - - /** - * 判断是否序列化 - * @param f - * @return - */ - public static boolean isSerializable(Field f) { - Class[] cls = f.getType().getInterfaces(); - for (Class c : cls) { - if (Serializable.class == c) { - return true; - } - } - return false; - } - - /** - * 设置域的值 - * @param f - * @param obj - * @return - */ - public static Object set(Field f, Object obj, Object value) { - try { - f.setAccessible(true); - f.set(obj, value); - return f.get(obj); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "set"); - } - return null; - } - - /** - * 获取域的值 - * @param f - * @param obj - * @return - */ - public static Object get(Field f, Object obj) { - try { - f.setAccessible(true); - return f.get(obj); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "get"); - } - return null; - } - - public static boolean isLong(Field field) { - return field.getType() == long.class || field.getType() == Long.class; - } - - public static boolean isInteger(Field field) { - return field.getType() == int.class || field.getType() != Integer.class; - } - - /** - * 获取域的泛型类型,如果不带泛型返回null - * @param f - * @return - */ - public static Class getGenericType(Field f) { - Type type = f.getGenericType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (type instanceof Class) return (Class) type; - } else if (type instanceof Class) return (Class) type; - return null; - } - - /** - * 获取数组的类型 - * @param f - * @return - */ - public static Class getComponentType(Field f) { - return f.getType().getComponentType(); - } - - /** - * 获取全部Field,包括父类 - * @param claxx - * @return - */ - public static List getAllDeclaredFields(Class claxx) { - // find all field. - LinkedList fieldList = new LinkedList(); - while (claxx != null && claxx != Object.class) { - Field[] fs = claxx.getDeclaredFields(); - for (int i = 0; i < fs.length; i++) { - Field f = fs[i]; - if (!isInvalid(f)) { - fieldList.addLast(f); - } - } - claxx = claxx.getSuperclass(); - } - return fieldList; - } - - /** - * 是静态常量或者内部结构属性 - * @param f - * @return - */ - public static boolean isInvalid(Field f) { - return (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) || f.isSynthetic(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/FileIOUtils.java b/DevLibUtils/src/main/java/dev/utils/common/FileIOUtils.java deleted file mode 100644 index 57bd51669f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/FileIOUtils.java +++ /dev/null @@ -1,674 +0,0 @@ -package dev.utils.common; - -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.List; - -import dev.utils.JCLogUtils; - -/** - * detail: 文件IO流工具类 - * Created by Ttt - */ -public final class FileIOUtils { - - private FileIOUtils() { - } - - // 日志TAG - private static final String TAG = FileIOUtils.class.getSimpleName(); - - // 换行符 - private static final String LINE_SEP = System.getProperty("line.separator"); - // 缓存大小 - private static int sBufferSize = 8192; - - /** - * Set the buffer's size. - Default size equals 8192 bytes. - * @param bufferSize The buffer's size. - */ - public static void setBufferSize(final int bufferSize) { - sBufferSize = bufferSize; - } - - /** - * Write file from input stream. - * @param filePath The path of file. - * @param is The input stream. - * @return true : success, false : fail - */ - public static boolean writeFileFromIS(final String filePath, final InputStream is) { - return writeFileFromIS(getFileByPath(filePath), is, false); - } - - /** - * Write file from input stream. - * @param filePath The path of file. - * @param is The input stream. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromIS(final String filePath, final InputStream is, final boolean append) { - return writeFileFromIS(getFileByPath(filePath), is, append); - } - - /** - * Write file from input stream. - * @param file The file. - * @param is The input stream. - * @return true : success, false : fail - */ - public static boolean writeFileFromIS(final File file, final InputStream is) { - return writeFileFromIS(file, is, false); - } - - /** - * Write file from input stream. - * @param file The file. - * @param is The input stream. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromIS(final File file, final InputStream is, final boolean append) { - if (!createOrExistsFile(file) || is == null) return false; - OutputStream os = null; - try { - os = new BufferedOutputStream(new FileOutputStream(file, append)); - byte data[] = new byte[sBufferSize]; - int len; - while ((len = is.read(data, 0, sBufferSize)) != -1) { - os.write(data, 0, len); - } - return true; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "writeFileFromIS"); - return false; - } finally { - CloseUtils.closeIO(is, os); - } - } - - /** - * Write file from bytes by stream. - * @param filePath The path of file. - * @param bytes The bytes. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes) { - return writeFileFromBytesByStream(getFileByPath(filePath), bytes, false); - } - - /** - * Write file from bytes by stream. - * @param filePath The path of file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByStream(final String filePath, final byte[] bytes, final boolean append) { - return writeFileFromBytesByStream(getFileByPath(filePath), bytes, append); - } - - /** - * Write file from bytes by stream. - * @param file The file. - * @param bytes The bytes. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes) { - return writeFileFromBytesByStream(file, bytes, false); - } - - /** - * Write file from bytes by stream. - * @param file The file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByStream(final File file, final byte[] bytes, final boolean append) { - if (bytes == null || !createOrExistsFile(file)) return false; - BufferedOutputStream bos = null; - try { - bos = new BufferedOutputStream(new FileOutputStream(file, append)); - bos.write(bytes); - return true; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "writeFileFromBytesByStream"); - return false; - } finally { - CloseUtils.closeIO(bos); - } - } - - /** - * Write file from bytes by channel. - * @param filePath The path of file. - * @param bytes The bytes. - * @param isForce 是否写入文件 - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByChannel(final String filePath, final byte[] bytes, final boolean isForce) { - return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, false, isForce); - } - - /** - * Write file from bytes by channel. - * @param filePath The path of file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByChannel(final String filePath, final byte[] bytes, final boolean append, final boolean isForce) { - return writeFileFromBytesByChannel(getFileByPath(filePath), bytes, append, isForce); - } - - /** - * Write file from bytes by channel. - * @param file The file. - * @param bytes The bytes. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByChannel(final File file, final byte[] bytes, final boolean isForce) { - return writeFileFromBytesByChannel(file, bytes, false, isForce); - } - - /** - * Write file from bytes by channel. - * @param file The file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByChannel(final File file, final byte[] bytes, final boolean append, final boolean isForce) { - if (bytes == null) return false; - FileChannel fc = null; - try { - fc = new FileOutputStream(file, append).getChannel(); - fc.position(fc.size()); - fc.write(ByteBuffer.wrap(bytes)); - if (isForce) fc.force(true); - return true; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "writeFileFromBytesByChannel"); - return false; - } finally { - CloseUtils.closeIO(fc); - } - } - - /** - * Write file from bytes by map. - * @param filePath The path of file. - * @param bytes The bytes. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByMap(final String filePath, final byte[] bytes, final boolean isForce) { - return writeFileFromBytesByMap(filePath, bytes, false, isForce); - } - - /** - * Write file from bytes by map. - * @param filePath The path of file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByMap(final String filePath, final byte[] bytes, final boolean append, final boolean isForce) { - return writeFileFromBytesByMap(getFileByPath(filePath), bytes, append, isForce); - } - - /** - * Write file from bytes by map. - * @param file The file. - * @param bytes The bytes. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByMap(final File file, final byte[] bytes, final boolean isForce) { - return writeFileFromBytesByMap(file, bytes, false, isForce); - } - - /** - * Write file from bytes by map. - * @param file The file. - * @param bytes The bytes. - * @param append True to append, false otherwise. - * @param isForce True to force write file, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromBytesByMap(final File file, final byte[] bytes, final boolean append, final boolean isForce) { - if (bytes == null || !createOrExistsFile(file)) return false; - FileChannel fc = null; - try { - fc = new FileOutputStream(file, append).getChannel(); - MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length); - mbb.put(bytes); - if (isForce) mbb.force(); - return true; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "writeFileFromBytesByMap"); - return false; - } finally { - CloseUtils.closeIO(fc); - } - } - - /** - * Write file from string. - * @param filePath The path of file. - * @param content The string of content. - * @return true : success, false : fail - */ - public static boolean writeFileFromString(final String filePath, final String content) { - return writeFileFromString(getFileByPath(filePath), content, false); - } - - /** - * Write file from string. - * @param filePath The path of file. - * @param content The string of content. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromString(final String filePath, final String content, final boolean append) { - return writeFileFromString(getFileByPath(filePath), content, append); - } - - /** - * Write file from string. - * @param file The file. - * @param content The string of content. - * @return true : success, false : fail - */ - public static boolean writeFileFromString(final File file, final String content) { - return writeFileFromString(file, content, false); - } - - /** - * Write file from string. - * @param file The file. - * @param content The string of content. - * @param append True to append, false otherwise. - * @return true : success, false : fail - */ - public static boolean writeFileFromString(final File file, final String content, final boolean append) { - if (file == null || content == null) return false; - if (!createOrExistsFile(file)) return false; - BufferedWriter bw = null; - try { - bw = new BufferedWriter(new FileWriter(file, append)); - bw.write(content); - return true; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "writeFileFromString"); - return false; - } finally { - CloseUtils.closeIO(bw); - } - } - - // ================================= - // the divide line of write and read - // ================================= - - /** - * Return the lines in file. - * @param filePath The path of file. - * @return the lines in file - */ - public static List readFile2List(final String filePath) { - return readFile2List(getFileByPath(filePath), null); - } - - /** - * Return the lines in file. - * @param filePath The path of file. - * @param charsetName The name of charset. - * @return the lines in file - */ - public static List readFile2List(final String filePath, final String charsetName) { - return readFile2List(getFileByPath(filePath), charsetName); - } - - /** - * Return the lines in file. - * @param file The file. - * @return the lines in file - */ - public static List readFile2List(final File file) { - return readFile2List(file, 0, 0x7FFFFFFF, null); - } - - /** - * Return the lines in file. - * @param file The file. - * @param charsetName The name of charset. - * @return the lines in file - */ - public static List readFile2List(final File file, final String charsetName) { - return readFile2List(file, 0, 0x7FFFFFFF, charsetName); - } - - /** - * Return the lines in file. - * @param filePath The path of file. - * @param st The line's index of start. - * @param end The line's index of end. - * @return the lines in file - */ - public static List readFile2List(final String filePath, final int st, final int end) { - return readFile2List(getFileByPath(filePath), st, end, null); - } - - /** - * Return the lines in file. - * @param filePath The path of file. - * @param st The line's index of start. - * @param end The line's index of end. - * @param charsetName The name of charset. - * @return the lines in file - */ - public static List readFile2List(final String filePath, final int st, final int end, final String charsetName) { - return readFile2List(getFileByPath(filePath), st, end, charsetName); - } - - /** - * Return the lines in file. - * @param file The file. - * @param st The line's index of start. - * @param end The line's index of end. - * @return the lines in file - */ - public static List readFile2List(final File file, final int st, final int end) { - return readFile2List(file, st, end, null); - } - - /** - * Return the lines in file. - * @param file The file. - * @param st The line's index of start. - * @param end The line's index of end. - * @param charsetName The name of charset. - * @return the lines in file - */ - public static List readFile2List(final File file, final int st, final int end, final String charsetName) { - if (!isFileExists(file)) return null; - if (st > end) return null; - BufferedReader reader = null; - try { - String line; - int curLine = 1; - List list = new ArrayList<>(); - if (isSpace(charsetName)) { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); - } else { - reader = new BufferedReader( new InputStreamReader(new FileInputStream(file), charsetName)); - } - while ((line = reader.readLine()) != null) { - if (curLine > end) break; - if (st <= curLine && curLine <= end) list.add(line); - ++curLine; - } - return list; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "readFile2List"); - return null; - } finally { - CloseUtils.closeIO(reader); - } - } - - /** - * Return the string in file. - * @param filePath The path of file. - * @return the string in file - */ - public static String readFile2String(final String filePath) { - return readFile2String(getFileByPath(filePath), null); - } - - /** - * Return the string in file. - * @param filePath The path of file. - * @param charsetName The name of charset. - * @return the string in file - */ - public static String readFile2String(final String filePath, final String charsetName) { - return readFile2String(getFileByPath(filePath), charsetName); - } - - /** - * Return the string in file. - * @param file The file. - * @return the string in file - */ - public static String readFile2String(final File file) { - return readFile2String(file, null); - } - - /** - * Return the string in file. - * @param file The file. - * @param charsetName The name of charset. - * @return the string in file - */ - public static String readFile2String(final File file, final String charsetName) { - if (!isFileExists(file)) return null; - BufferedReader reader = null; - try { - StringBuilder sb = new StringBuilder(); - if (isSpace(charsetName)) { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); - } else { - reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName)); - } - String line; - if ((line = reader.readLine()) != null) { - sb.append(line); - while ((line = reader.readLine()) != null) { - sb.append(LINE_SEP).append(line); - } - } - return sb.toString(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "readFile2String"); - return null; - } finally { - CloseUtils.closeIO(reader); - } - } - - /** - * Return the bytes in file by stream. - * @param filePath The path of file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByStream(final String filePath) { - return readFile2BytesByStream(getFileByPath(filePath)); - } - - /** - * Return the bytes in file by stream. - * @param file The file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByStream(final File file) { - if (!isFileExists(file)) return null; - FileInputStream fis = null; - ByteArrayOutputStream os = null; - try { - fis = new FileInputStream(file); - os = new ByteArrayOutputStream(); - byte[] b = new byte[sBufferSize]; - int len; - while ((len = fis.read(b, 0, sBufferSize)) != -1) { - os.write(b, 0, len); - } - return os.toByteArray(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "readFile2BytesByStream"); - return null; - } finally { - CloseUtils.closeIO(fis, os); - } - } - - /** - * Return the bytes in file by channel. - * @param filePath The path of file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByChannel(final String filePath) { - return readFile2BytesByChannel(getFileByPath(filePath)); - } - - /** - * Return the bytes in file by channel. - * @param file The file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByChannel(final File file) { - if (!isFileExists(file)) return null; - FileChannel fc = null; - try { - fc = new RandomAccessFile(file, "r").getChannel(); - ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size()); - while (true) { - if (!((fc.read(byteBuffer)) > 0)) break; - } - return byteBuffer.array(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "readFile2BytesByChannel"); - return null; - } finally { - CloseUtils.closeIO(fc); - } - } - - /** - * Return the bytes in file by map. - * @param filePath The path of file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByMap(final String filePath) { - return readFile2BytesByMap(getFileByPath(filePath)); - } - - /** - * Return the bytes in file by map. - * @param file The file. - * @return the bytes in file - */ - public static byte[] readFile2BytesByMap(final File file) { - if (!isFileExists(file)) return null; - FileChannel fc = null; - try { - fc = new RandomAccessFile(file, "r").getChannel(); - int size = (int) fc.size(); - MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); - byte[] result = new byte[size]; - mbb.get(result, 0, size); - return result; - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "readFile2BytesByMap"); - return null; - } finally { - CloseUtils.closeIO(fc); - } - } - - // = - - /** - * 获取文件 - * @param filePath - * @return - */ - private static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 判断文件是否存在,不存在则判断是否创建成功 - * @param filePath 文件路径 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsFile(final String filePath) { - return createOrExistsFile(getFileByPath(filePath)); - } - - /** - * 判断文件是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsFile(final File file) { - if (file == null) return false; - // 如果存在,是文件则返回 true,是目录则返回 false - if (file.exists()) return file.isFile(); - // 判断文件是否存在, 不存在则直接返回 - if (!createOrExistsDir(file.getParentFile())) return false; - try { - // 存在, 则返回新的路径 - return file.createNewFile(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "createOrExistsFile"); - return false; - } - } - - /** - * 判断目录是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsDir(final File file) { - // 如果存在,是目录则返回 true,是文件则返回 false,不存在则返回是否创建成功 - return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); - } - - /** - * 检查是否存在某个文件 - * @param file 文件路径 - * @return 是否存在文件 - */ - private static boolean isFileExists(final File file){ - return file != null && file.exists(); - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/FileUtils.java b/DevLibUtils/src/main/java/dev/utils/common/FileUtils.java deleted file mode 100644 index 3d8991e5da..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/FileUtils.java +++ /dev/null @@ -1,2020 +0,0 @@ -package dev.utils.common; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileFilter; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.DigestInputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; - -import dev.utils.JCLogUtils; - -/** - * detail: 文件操作工具类 - * Created by Ttt - */ -public final class FileUtils { - - private FileUtils() { - } - - // 日志TAG - private static final String TAG = FileUtils.class.getSimpleName(); - /** 换行字符串 */ - public static final String NEW_LINE_STR = System.getProperty("line.separator"); - /** 换行字符串 - 两行 */ - public static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - /** - * 判断是否为null - * @param str - * @return - */ - private static boolean isEmpty(final String str) { - return (str == null || str.length() == 0); - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } - - // = - - /** - * 获取文件 - to getFileByPath - * @param filePath - * @return - */ - public static File getFile(final String filePath){ - return getFileByPath(filePath); - } - - /** - * 获取文件 - * @param filePath 文件路径 - * @param fName 文件名 - * @return - */ - public static File getFile(final String filePath, final String fName){ - return (filePath != null && fName != null) ? new File(filePath, fName) : null; - } - - /** - * 获取文件 - * @param filePath - * @return - */ - public static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 获取路径, 并且进行创建目录 - * @param filePath 保存目录 - * @param fName 文件名 - * @return - */ - public static File getFileCreateFolder(final String filePath, final String fName) { - // 防止不存在目录文件,自动创建 - createFolder(filePath); - // 返回处理过后的File - return getFile(filePath, fName); - } - - /** - * 判断某个文件夹是否创建,未创建则创建(纯路径 - 无文件名) - * @param dirPath 文件夹路径 (无文件名字.后缀) - */ - public static boolean createFolder(final String dirPath) { - return createFolder(getFileByPath(dirPath)); - } - - /** - * 判断某个文件夹是否创建,未创建则创建(纯路径 - 无文件名) - * @param file 文件夹路径 (无文件名字.后缀) - */ - public static boolean createFolder(final File file) { - if (file != null) { - try { - // 当这个文件夹不存在的时候则创建文件夹 - if (!file.exists()) { - // 允许创建多级目录 - return file.mkdirs(); - // 这个无法创建多级目录 - // rootFile.mkdir(); - } - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "createFolder"); - } - } - return false; - } - - /** - * 创建文件夹目录 - 可以传入文件名 - * @param filePath 文件路径 + 文件名 - * @return - */ - public static boolean createFolderByPath(final String filePath) { - return createFolderByPath(getFileByPath(filePath)); - } - - /** - * 创建文件夹目录 - 可以传入文件名 - * @param file - * @return - */ - public static boolean createFolderByPath(final File file){ - // 创建文件夹 - 如果失败才创建 - if (file != null) { - if (file.exists()){ - return true; - } else if (!file.getParentFile().mkdirs()) { - return createFolder(file.getParent()); - } - } - return false; - } - - /** - * 创建多个文件夹, 如果不存在则创建 - * @param filePaths - */ - public static void createFolderByPaths(final String... filePaths){ - if (filePaths != null && filePaths.length != 0){ - for (int i = 0, len = filePaths.length; i < len; i++){ - createFolder(filePaths[i]); - } - } - } - - /** - * 创建多个文件夹, 如果不存在则创建 - * @param files - */ - public static void createFolderByPaths(final File... files){ - if (files != null && files.length != 0){ - for (int i = 0, len = files.length; i < len; i++){ - createFolder(files[i]); - } - } - } - - // == - - /** - * 判断目录是否存在,不存在则判断是否创建成功 - * @param dirPath 目录路径 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - public static boolean createOrExistsDir(final String dirPath) { - return createOrExistsDir(getFileByPath(dirPath)); - } - - /** - * 判断目录是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - public static boolean createOrExistsDir(final File file) { - // 如果存在,是目录则返回 true,是文件则返回 false,不存在则返回是否创建成功 - return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); - } - - /** - * 判断文件是否存在,不存在则判断是否创建成功 - * @param filePath 文件路径 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - public static boolean createOrExistsFile(final String filePath) { - return createOrExistsFile(getFileByPath(filePath)); - } - - /** - * 判断文件是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - public static boolean createOrExistsFile(final File file) { - if (file == null) return false; - // 如果存在,是文件则返回 true,是目录则返回 false - if (file.exists()) return file.isFile(); - // 判断文件是否存在, 不存在则直接返回 - if (!createOrExistsDir(file.getParentFile())) return false; - try { - // 存在, 则返回新的路径 - return file.createNewFile(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "createOrExistsFile"); - return false; - } - } - - /** - * 判断文件是否存在,存在则在创建之前删除 - * @param filePath - * @return true : 创建成功, false : 创建失败 - */ - public static boolean createFileByDeleteOldFile(final String filePath) { - return createFileByDeleteOldFile(getFileByPath(filePath)); - } - - /** - * 判断文件是否存在,存在则在创建之前删除 - * @param file - * @return true : 创建成功, false : 创建失败 - */ - public static boolean createFileByDeleteOldFile(final File file) { - if (file == null) return false; - // 文件存在并且删除失败返回 false - if (file.exists() && !file.delete()) return false; - // 创建目录失败返回 false - if (!createOrExistsDir(file.getParentFile())) return false; - try { - return file.createNewFile(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "createFileByDeleteOldFile"); - return false; - } - } - - /** - * 获取文件路径 - * @param file - * @return - */ - public static String getPath(final File file){ - return file != null ? file.getPath() : null; - } - - /** - * 获取文件绝对路径 - * @param file - * @return - */ - public static String getAbsolutePath(final File file){ - return file != null ? file.getAbsolutePath() : null; - } - - // == - - /** - * 获取文件名 - * @param file - * @return - */ - public static String getName(final File file){ - return file != null ? file.getName() : null; - } - - /** - * 获取文件名 - * @param filePath 文件路径 - * @return - */ - public static String getName(final String filePath){ - return getName(filePath, ""); - } - - /** - * 获取文件名 - * @param filePath 文件路径 - * @param dfStr - * @return - */ - public static String getName(final String filePath, final String dfStr){ - if (!isSpace(filePath)) { - return new File(filePath).getName(); - } - return dfStr; - } - - /** - * 获得文件后缀名(无.,单独后缀) - * @param file - * @return - */ - public static String getFileSuffix(final File file){ - return getFileSuffix(getAbsolutePath(file)); - } - - /** - * 获得文件后缀名(无.,单独后缀) - * @param filePath 文件地址、文件名都行 - * @return - */ - public static String getFileSuffix(final String filePath) { - // 获取最后的索引 - int lastIndexOf = -1; - // 判断是否存在 - if (filePath != null && (lastIndexOf = filePath.lastIndexOf('.')) != -1) { - String result = filePath.substring(lastIndexOf); - if (result.startsWith(".")) { - return result.substring(1); - } - return result; - } - return null; - } - - /** - * 获得文件名(无后缀) - * @param file - * @return - */ - public static String getFileNotSuffix(final File file) { - return getFileNotSuffix(getName(file)); - } - - /** - * 获得文件名(无后缀) - * @param filePath - * @return - */ - public static String getFileNotSuffixToPath(final String filePath) { - return getFileNotSuffix(getName(filePath)); - } - - /** - * 获得文件名(无后缀) - * @param fileName 文件名 - * @return - */ - public static String getFileNotSuffix(final String fileName) { - if (fileName != null){ - if (fileName.lastIndexOf('.') != -1) { - return fileName.substring(0, fileName.lastIndexOf('.')); - } else { - return fileName; - } - } - return null; - } - - /** - * 获取全路径中的不带拓展名的文件名 - * @param file The file. - * @return 不带拓展名的文件名 - */ - public static String getFileNameNoExtension(final File file) { - if (file == null) return null; - return getFileNameNoExtension(file.getPath()); - } - - /** - * 获取全路径中的不带拓展名的文件名 - * @param filePath - * @return 不带拓展名的文件名 - */ - public static String getFileNameNoExtension(final String filePath) { - if (isSpace(filePath)) return filePath; - int lastPoi = filePath.lastIndexOf('.'); - int lastSep = filePath.lastIndexOf(File.separator); - if (lastSep == -1) { - return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); - } - if (lastPoi == -1 || lastSep > lastPoi) { - return filePath.substring(lastSep + 1); - } - return filePath.substring(lastSep + 1, lastPoi); - } - - /** - * 获取全路径中的文件拓展名 - * @param file The file. - * @return 文件拓展名 - */ - public static String getFileExtension(final File file) { - if (file == null) return null; - return getFileExtension(file.getPath()); - } - - /** - * 获取全路径中的文件拓展名 - * @param filePath - * @return 文件拓展名 - */ - public static String getFileExtension(final String filePath) { - if (isSpace(filePath)) return filePath; - int lastPoi = filePath.lastIndexOf('.'); - int lastSep = filePath.lastIndexOf(File.separator); - if (lastPoi == -1 || lastSep >= lastPoi) return ""; - return filePath.substring(lastPoi + 1); - } - - // == - - /** - * 检查是否存在某个文件 - * @param file 文件路径 - * @return 是否存在文件 - */ - public static boolean isFileExists(final File file){ - return file != null && file.exists(); - } - - /** - * 检查是否存在某个文件 - * @param filePath 文件路径 - * @return 是否存在文件 - */ - public static boolean isFileExists(final String filePath) { - return isFileExists(getFileByPath(filePath)); - } - - /** - * 检查是否存在某个文件 - * @param filePath 文件路径 - * @param fName 文件名 - * @return 是否存在文件 - */ - public static boolean isFileExists(final String filePath, final String fName) { - return filePath != null && fName != null && new File(filePath, fName).exists(); - } - - /** - * 判断是否文件 - * @param filePath - * @return - */ - public static boolean isFile(final String filePath){ - return isFile(getFileByPath(filePath)); - } - - /** - * 判断是否文件 - * @param file - * @return - */ - public static boolean isFile(final File file){ - return file != null && file.exists() && file.isFile(); - } - - /** - * 判断是否文件夹 - * @param filePath - * @return - */ - public static boolean isDir(final String filePath){ - return isDir(getFileByPath(filePath)); - } - - /** - * 判断是否文件夹 - * @param file - * @return - */ - public static boolean isDir(final File file){ - return file != null && file.exists() && file.isDirectory(); - } - - /** - * 判断是否隐藏文件 - * @param filePath - * @return - */ - public static boolean isHide(final String filePath){ - return isHide(getFileByPath(filePath)); - } - - /** - * 判断是否隐藏文件 - * @param file - * @return - */ - public static boolean isHide(final File file){ - return file != null && file.exists() && file.isHidden(); - } - - // == - - /** - * 获取文件最后修改的毫秒时间戳 - * @param filePath 文件路径 - * @return 文件最后修改的毫秒时间戳 - */ - - public static long getFileLastModified(final String filePath) { - return getFileLastModified(getFileByPath(filePath)); - } - - /** - * 获取文件最后修改的毫秒时间戳 - * @param file 文件 - * @return 文件最后修改的毫秒时间戳 - */ - public static long getFileLastModified(final File file) { - if (file == null) return -1; - return file.lastModified(); - } - - /** - * 简单获取文件编码格式 - * @param filePath 文件路径 - * @return 文件编码 - */ - public static String getFileCharsetSimple(final String filePath) { - return getFileCharsetSimple(getFileByPath(filePath)); - } - - /** - * 简单获取文件编码格式 - * @param file 文件 - * @return 文件编码 - */ - public static String getFileCharsetSimple(final File file) { - int p = 0; - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(file)); - p = (is.read() << 8) + is.read(); - } catch (IOException e) { - JCLogUtils.eTag(TAG, e, "getFileCharsetSimple"); - } finally { - CloseUtils.closeIO(is); - } - switch (p) { - case 0xefbb: - return "UTF-8"; - case 0xfffe: - return "Unicode"; - case 0xfeff: - return "UTF-16BE"; - default: - return "GBK"; - } - } - - /** - * 获取文件行数 - * @param filePath - * @return 文件行数 - */ - public static int getFileLines(final String filePath) { - return getFileLines(getFileByPath(filePath)); - } - - /** - * 获取文件行数 => 比 readLine 要快很多 - * @param file The file. - * @return 文件行数 - */ - public static int getFileLines(final File file) { - int count = 1; - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(file)); - byte[] buffer = new byte[1024]; - int readChars; - if (NEW_LINE_STR.endsWith("\n")) { - while ((readChars = is.read(buffer, 0, 1024)) != -1) { - for (int i = 0; i < readChars; ++i) { - if (buffer[i] == '\n') ++count; - } - } - } else { - while ((readChars = is.read(buffer, 0, 1024)) != -1) { - for (int i = 0; i < readChars; ++i) { - if (buffer[i] == '\r') ++count; - } - } - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileLines"); - } finally { - CloseUtils.closeIO(is); - } - return count; - } - - // == - - /** - * 获取文件大小 - * @param filePath - * @return 文件大小 - */ - public static String getFileSize(final String filePath) { - return getFileSize(getFileByPath(filePath)); - } - - /** - * 获取文件大小 - * @param file The file. - * @return 文件大小 - */ - public static String getFileSize(final File file) { - return formatByteMemorySize(getFileLength(file)); - } - - /** - * 获取目录大小 - * @param dirPath 目录路径 - * @return 文件大小 - */ - public static String getDirSize(final String dirPath) { - return getDirSize(getFileByPath(dirPath)); - } - - /** - * 获取目录大小 - * @param dir 目录 - * @return 文件大小 - */ - public static String getDirSize(final File dir) { - return formatByteMemorySize(getDirLength(dir)); - } - - /** - * 获取文件大小 - * @param filePath - * @return - */ - public static long getFileLength(final String filePath){ - return getFileLength(getFileByPath(filePath)); - } - - /** - * 获取文件大小 - * @param file - * @return - */ - public static long getFileLength(final File file){ - return file != null ? file.length() : 0l; - } - - /** - * 获取目录长度 - * @param dirPath 目录路径 - * @return 目录长度 - */ - public static long getDirLength(final String dirPath) { - return getDirLength(getFileByPath(dirPath)); - } - - /** - * 获取目录长度 - * @param dir 目录 - * @return 目录长度 - */ - public static long getDirLength(final File dir) { - if (!isDir(dir)) return 0; - long len = 0; - File[] files = dir.listFiles(); - if (files != null && files.length != 0) { - for (File file : files) { - if (file.isDirectory()) { - len += getDirLength(file); - } else { - len += file.length(); - } - } - } - return len; - } - - /** - * 获取文件长度 - 网络资源 - * @param httpUri - * @return 文件长度 - */ - public static long getFileLengthNetwork(final String httpUri) { - boolean isURL = httpUri.matches("[a-zA-z]+://[^\\s]*"); - if (isURL) { - try { - HttpURLConnection conn = (HttpURLConnection) new URL(httpUri).openConnection(); - conn.setRequestProperty("Accept-Encoding", "identity"); - conn.connect(); - if (conn.getResponseCode() == 200) { - return conn.getContentLength(); - } - return 0l; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileLengthNetwork"); - } - } - return 0l; - } - - /** - * 获取全路径中的文件名 - * @param file The file. - * @return 文件名 - */ - public static String getFileName(final File file) { - if (file == null) return null; - return getFileName(file.getPath()); - } - - /** - * 获取全路径中的文件名 - * @param filePath - * @return 文件名 - */ - public static String getFileName(final String filePath) { - if (isSpace(filePath)) return filePath; - int lastSep = filePath.lastIndexOf(File.separator); - return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); - } - - /** - * 获取全路径中的最长目录 - * @param file The file. - * @return filePath 最长目录 - */ - public static String getDirName(final File file) { - if (file == null) return null; - return getDirName(file.getPath()); - } - - /** - * 获取全路径中的最长目录 - * @param filePath - * @return filePath 最长目录 - */ - public static String getDirName(final String filePath) { - if (isSpace(filePath)) return filePath; - int lastSep = filePath.lastIndexOf(File.separator); - return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); - } - - // == - - /** - * 重命名文件 - 同个目录下, 修改文件名 - * @param filePath 文件路径 - * @param newName 新名称 - * @return - */ - public static boolean rename(final String filePath, final String newName) { - return rename(getFileByPath(filePath), newName); - } - - /** - * 重命名文件 - 同个目录下, 修改文件名 - * @param file 文件 - * @param newName 新名称 - * @return - */ - public static boolean rename(final File file, final String newName) { - // 文件为空返回 false - if (file == null) return false; - // 文件不存在返回 false - if (!file.exists()) return false; - // 如果文件名没有改变返回 true - if (newName.equals(file.getName())) return true; - // 拼接新的文件路径 - File newFile = new File(file.getParent() + File.separator + newName); - // 如果重命名的文件已存在返回 false - return !newFile.exists() && file.renameTo(newFile); - } - - // == 文件大小处理 == - - /** - * 传入文件路径, 返回对应的文件大小 - * @param filePath - * @return - */ - public static String formatFileSize(final String filePath){ - File file = getFileByPath(filePath); - return formatFileSize(file != null ? file.length() : 0); - } - - /** - * 传入文件路径, 返回对应的文件大小 - * @param file - * @return - */ - public static String formatFileSize(final File file){ - return formatFileSize(file != null ? file.length() : 0); - } - - /** - * 传入对应的文件大小double,返回转换后文件大小 - * @param fileS 返回String文件大小 - * @return - */ - public static String formatFileSize(final double fileS) { - // 转换文件大小 - DecimalFormat df = new DecimalFormat("#.00"); - String fileSizeString = ""; - if (fileS <= 0) { - fileSizeString = "0B"; - } else if (fileS < 1024) { - fileSizeString = df.format(fileS) + "B"; - } else if (fileS < 1048576) { - fileSizeString = df.format(fileS / 1024) + "KB"; - } else if (fileS < 1073741824) { - fileSizeString = df.format(fileS / 1048576) + "MB"; - } else if (fileS < 1099511627776d){ - fileSizeString = df.format(fileS / 1073741824) + "GB"; - } else { - fileSizeString = df.format(fileS / 1099511627776d) + "TB"; - } - return fileSizeString; - } - - /** - * 字节数转合适内存大小 保留 3 位小数 (%.位数f) - * @param byteNum 字节数 - * @return 合适内存大小 - */ - public static String formatByteMemorySize(final double byteNum) { - if (byteNum < 0) { - return "0B"; - } else if (byteNum < 1024) { - return String.format("%.3fB", (double) byteNum); - } else if (byteNum < 1048576) { - return String.format("%.3fKB", (double) byteNum / 1024); - } else if (byteNum < 1073741824) { - return String.format("%.3fMB", (double) byteNum / 1048576); - } else if (byteNum < 1099511627776d){ - return String.format("%.3fGB", (double) byteNum / 1073741824); - } else { - return String.format("%.3fTB", (double) byteNum / 1099511627776d); - } - } - - /** - * 字节数转合适内存大小 保留 number 位小数 (%.位数f) - * @param number 字节数 - * @param byteNum 字节数 - * @return 合适内存大小 - */ - public static String formatByteMemorySize(final int number, final double byteNum) { - if (byteNum < 0) { - return "0B"; - } else if (byteNum < 1024) { - return String.format("%." + number + "fB", (double) byteNum); - } else if (byteNum < 1048576) { - return String.format("%." + number + "fKB", (double) byteNum / 1024); - } else if (byteNum < 1073741824) { - return String.format("%." + number + "fMB", (double) byteNum / 1048576); - } else if (byteNum < 1099511627776d){ - return String.format("%." + number + "fGB", (double) byteNum / 1073741824); - } else { - return String.format("%." + number + "fTB", (double) byteNum / 1099511627776d); - } - } - - // == 获取文件MD5值 == - - /** - * 获取文件的 MD5 校验码 - * @param filePath 文件路径 - * @return 文件的 MD5 校验码 - */ - public static String getFileMD5ToString(final String filePath) { - return getFileMD5ToString(getFileByPath(filePath)); - } - - /** - * 获取文件的 MD5 校验码 - * @param file 文件 - * @return 文件的 MD5 校验码 - */ - public static String getFileMD5ToString(final File file) { - try { - return toHexString(getFileMD5(file), HEX_DIGITS); - } catch (Exception e){ - } - return null; - } - - /** - * 获取文件的 MD5 校验码 - * @param filePath 文件路径 - * @return 文件的 MD5 校验码 - */ - public static byte[] getFileMD5(final String filePath) { - return getFileMD5(getFileByPath(filePath)); - } - - /** - * 获取文件的 MD5 校验码 - * @param file 文件 - * @return 文件的 MD5 校验码 - */ - public static byte[] getFileMD5(final File file) { - if (file == null) return null; - DigestInputStream dis = null; - try { - FileInputStream fis = new FileInputStream(file); - MessageDigest md = MessageDigest.getInstance("MD5"); - dis = new DigestInputStream(fis, md); - byte[] buffer = new byte[1024 * 256]; - while (true) { - if (!(dis.read(buffer) > 0)) break; - } - md = dis.getMessageDigest(); - return md.digest(); - } catch (NoSuchAlgorithmException | IOException e) { - JCLogUtils.eTag(TAG, e, "getFileMD5"); - } finally { - CloseUtils.closeIO(dis); - } - return null; - } - - // == - - /** - * 获取文件MD5值 - 小写 - * @param filePath 文件地址 - * @return - */ - public static String getFileMD5ToString2(final String filePath) { - return getFileMD5ToString2(getFileByPath(filePath)); - } - - /** - * 获取文件MD5值 - 小写 - * @param file 文件地址 - * @return - */ - public static String getFileMD5ToString2(final File file) { - try { - InputStream fis = new FileInputStream(file); - byte[] buffer = new byte[1024]; - MessageDigest md5 = MessageDigest.getInstance("MD5"); - int numRead = 0; - while ((numRead = fis.read(buffer)) > 0) { - md5.update(buffer, 0, numRead); - } - fis.close(); - return toHexString(md5.digest(), HEX_DIGITS); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileMD5ToString2"); - } - return null; - } - - // 小写 - private static final char HEX_DIGITS[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; -// // 大写 -// private static final char HEX_DIGITS_UPPER[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; - - /** - * 进行转换 - * @param bData - * @param hexDigits - * @return - */ - private static String toHexString(final byte[] bData, final char[] hexDigits) { - if (bData == null || hexDigits == null){ - return null; - } - StringBuilder sBuilder = new StringBuilder(bData.length * 2); - for (int i = 0, len = bData.length; i < len; i++) { - sBuilder.append(hexDigits[(bData[i] & 0xf0) >>> 4]); - sBuilder.append(hexDigits[bData[i] & 0x0f]); - } - return sBuilder.toString(); - } - - // ============== - // == 文件操作 == - // ============== - - /** - * 删除文件 - * @param filePath - * @return - */ - public static boolean deleteFile(final String filePath) { - return deleteFile(getFileByPath(filePath)); - } - - /** - * 删除文件 - * @param file - * @return - */ - public static boolean deleteFile(final File file) { - // 文件存在,并且不是目录文件,则直接删除 - if (file != null && file.exists() && !file.isDirectory()) { - return file.delete(); - } - return false; - } - - /** - * 删除多个文件 - * @param filePaths - * @return - */ - public static void deleteFiles(final String... filePaths) { - if (filePaths != null && filePaths.length != 0){ - for (int i = 0, len = filePaths.length; i < len; i++){ - deleteFile(filePaths[i]); - } - } - } - - /** - * 删除多个文件 - * @param files - * @return - */ - public static void deleteFiles(final File... files) { - if (files != null && files.length != 0){ - for (int i = 0, len = files.length; i < len; i++){ - deleteFile(files[i]); - } - } - } - - // = - - /** - * 删除文件夹 - * @param filePath - * @return - */ - public static boolean deleteFolder(final String filePath) { - return deleteFolder(getFileByPath(filePath)); - } - - /** - * 删除文件夹 - * @param file - * @return - */ - public static boolean deleteFolder(final File file) { - if (file != null) { - try { - // 文件存在,并且不是目录文件,则直接删除 - if (file.exists()) { - if (file.isDirectory()) { // 属于文件目录 - File[] files = file.listFiles(); - if (null == files || files.length == 0) { - file.delete(); - } - for (File f : files) { - deleteFolder(f.getPath()); - } - return file.delete(); - } else { // 属于文件 - return deleteFile(file); - } - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "deleteFolder"); - } - } - return false; - } - - /** - * 保存文件 - * @param file - * @param bytes 保存内容 - * @return 是否保存成功 - */ - public static boolean saveFile(final File file, final byte[] bytes) { - if (bytes != null && file != null) { - try { - // 防止文件没创建 - createFolder(getDirName(file)); - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(file); - BufferedOutputStream bos = new BufferedOutputStream(fos); - bos.write(bytes); - bos.close(); - fos.close(); - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "saveFile"); - } - } - return false; - } - - /** - * 保存文件 - * @param filePath 保存路径 - * @param fName 文件名.后缀 - * @param bytes 保存内容 - * @return 是否保存成功 - */ - public static boolean saveFile(final String filePath, final String fName, final byte[] bytes) { - if (bytes != null && filePath != null && fName != null) { - try { - // 防止文件没创建 - createFolder(filePath); - // 保存路径 - File sFile = new File(filePath, fName); - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(sFile); - BufferedOutputStream bos = new BufferedOutputStream(fos); - bos.write(bytes); - bos.close(); - fos.close(); - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "saveFile"); - } - } - return false; - } - - /** - * 保存文件 - * @param filePath 保存路径 - * @param fName 文件名.后缀 - * @param content 保存内容 - * @return 是否保存成功 - */ - public static boolean saveFile(final String filePath, final String fName, final String content) { - if (content != null && filePath != null && fName != null){ - try { - // 防止文件没创建 - createFolder(filePath); - // 保存路径 - File sFile = new File(filePath, fName); - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(sFile); - fos.write(content.getBytes()); - fos.close(); - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "saveFile"); - } - } - return false; - } - - /** - * 保存文件 - * @param filePath 保存路径 - * @param fName 文件名.后缀 - * @param content 保存内容 - * @param coding 编码 - * @return 是否保存成功 - */ - public static boolean saveFile(final String filePath, final String fName, final String content, final String coding) { - if (content != null && filePath != null && fName != null){ - try { - // 防止文件没创建 - createFolder(filePath); - // 保存路径 - File sFile = new File(filePath, fName); - // 保存内容到一个文件 - FileOutputStream fos = new FileOutputStream(sFile); - Writer out; - if (coding != null) { - out = new OutputStreamWriter(fos, coding); - } else { - out = new OutputStreamWriter(fos); - } - out.write(content); - out.close(); - fos.close(); - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "saveFile"); - } - } - return false; - } - - /** - * 追加文件:使用FileWriter - * @param filePath 文件地址 - * @param content 追加内容 - */ - public static void appendFile(final String filePath, final String content) { - if(filePath == null || content == null) { - return; - } - File file = new File(filePath); - if(!file.exists()) { // 如果文件不存在,则跳过 - return; - } - FileWriter writer = null; - try { - // 打开一个写文件器,构造函数中的第二个参数true表示以追加形式写文件 - writer = new FileWriter(file, true); - writer.write(content); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "appendFile"); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (IOException e) { - } - } - } - } - - // == - - /** - * 读取文件 - * @param filePath - * @return - */ - public static byte[] readFileBytes(final String filePath) { - return readFileBytes(getFileByPath(filePath)); - } - - /** - * 读取文件 - * @param file - * @return - */ - public static byte[] readFileBytes(final File file) { - if (file != null && file.exists()){ - try { - FileInputStream fin = new FileInputStream(file); - int length = fin.available(); - byte[] buffer = new byte[length]; - fin.read(buffer); - fin.close(); - return buffer; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "readFileBytes"); - } - } - return null; - } - - /** - * 读取文件 - * @return - */ - public static byte[] readFileBytes(final InputStream iStream) { - if (iStream != null){ - try { - int length = iStream.available(); - byte[] buffer = new byte[length]; - iStream.read(buffer); - iStream.close(); - return buffer; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "readFileBytes"); - } - } - return null; - } - - /** - * 读取文件 - * @param filePath - * @return - */ - public static String readFile(final String filePath) { - return readFile(getFileByPath(filePath)); - } - - /** - * 读取文件 - * @param file - * @return - */ - public static String readFile(final File file) { - if (file != null && file.exists()){ - try { - return readFile(new FileInputStream(file)); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "readFile"); - } - } - return null; - } - - /** - * 读取文件 - * @param iStream -> new FileInputStream(path) - * @return - */ - public static String readFile(final InputStream iStream) { - if (iStream != null){ - try { - InputStreamReader isR = new InputStreamReader(iStream); - BufferedReader br = new BufferedReader(isR); - StringBuilder sBuilder = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - sBuilder.append(line); - } - isR.close(); - br.close(); - return sBuilder.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "readFile"); - } - } - return null; - } - - /** - * 读取文件 - * @param iStream -> new FileInputStream(path) - * @param encode 编码 - * @return - */ - public static String readFile(final InputStream iStream, final String encode) { - if (iStream != null){ - try { - InputStreamReader isR = null; - if (encode != null){ - new InputStreamReader(iStream, encode); - } else { - new InputStreamReader(iStream); - } - BufferedReader br = new BufferedReader(isR); - StringBuilder sBuilder = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - sBuilder.append(line); - } - isR.close(); - br.close(); - return sBuilder.toString(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "readFile"); - } - } - return null; - } - - // == - - /** - * 复制单个文件 - * @param inputStream 文件流(被复制) - * @param destFilePath 目标文件地址 - * @param overlay 如果目标文件存在,是否覆盖 - * @return 如果复制成功返回true,否则返回false - */ - public static boolean copyFile(final InputStream inputStream, final String destFilePath, final boolean overlay){ - if (inputStream == null || destFilePath == null){ - return false; - } - // 判断目标文件是否存在 - File destFile = new File(destFilePath); - // 如果属于文件夹则跳过 - if (destFile.isDirectory()){ - return false; - } - if (destFile.exists()) { - // 如果目标文件存在并允许覆盖 - if (overlay) { - // 删除已经存在的目标文件,无论目标文件是目录还是单个文件 - destFile.delete(); - } - } else { - // 如果目标文件所在目录不存在,则创建目录 - if (!destFile.getParentFile().exists()) { - // 目标文件所在目录不存在 - if (!destFile.getParentFile().mkdirs()) { - // 复制文件失败:创建目标文件所在目录失败 - return false; - } - } - } - // 复制文件 - int byteread = 0; // 读取的字节数 - InputStream in = inputStream; - OutputStream out = null; - try { - out = new FileOutputStream(destFile); - byte[] buffer = new byte[1024]; - while ((byteread = in.read(buffer)) != -1) { - out.write(buffer, 0, byteread); - } - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "copyFile"); - return false; - } finally { - try { - if (out != null) - out.close(); - if (in != null) - in.close(); - } catch (IOException e) { - } - } - } - - /** - * 复制单个文件 - * @param srcFilePath 待复制的文件地址 - * @param destFilePath 目标文件地址 - * @param overlay 如果目标文件存在,是否覆盖 - * @return 如果复制成功返回true,否则返回false - */ - public static boolean copyFile(final String srcFilePath, final String destFilePath, final boolean overlay) { - if (srcFilePath == null || destFilePath == null){ - return false; - } - File srcFile = new File(srcFilePath); - // 判断源文件是否存在 - if (!srcFile.exists()) { - return false; - } else if (!srcFile.isFile()) { // srcFile.isDirectory(); - return false; - } - // 判断目标文件是否存在 - File destFile = new File(destFilePath); - // 如果属于文件夹则跳过 - if (destFile.isDirectory()){ - return false; - } - if (destFile.exists()) { - // 如果目标文件存在并允许覆盖 - if (overlay) { - // 删除已经存在的目标文件,无论目标文件是目录还是单个文件 - new File(destFilePath).delete(); - } - } else { - // 如果目标文件所在目录不存在,则创建目录 - if (!destFile.getParentFile().exists()) { - // 目标文件所在目录不存在 - if (!destFile.getParentFile().mkdirs()) { - // 复制文件失败:创建目标文件所在目录失败 - return false; - } - } - } - // 复制文件 - int byteread = 0; // 读取的字节数 - InputStream in = null; - OutputStream out = null; - try { - in = new FileInputStream(srcFile); - out = new FileOutputStream(destFile); - byte[] buffer = new byte[1024]; - while ((byteread = in.read(buffer)) != -1) { - out.write(buffer, 0, byteread); - } - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "copyFile"); - return false; - } finally { - try { - if (out != null) - out.close(); - if (in != null) - in.close(); - } catch (IOException e) { - } - } - } - - /** - * 复制文件夹 - * @param srcFolderPath 待复制的文件夹地址 - * @param destFolderPath 目标文件夹地址 - * @param overlay 如果目标文件存在,是否覆盖 - * @return 如果复制成功返回true,否则返回false - */ - public static boolean copyFolder(final String srcFolderPath, final String destFolderPath, final boolean overlay) { - return copyFolder(srcFolderPath, destFolderPath, srcFolderPath, overlay); - } - - /** - * 复制文件夹 - * @param srcFolderPath 待复制的文件夹地址 - * @param destFolderPath 目标文件夹地址 - * @param sourcePath 源文件地址 - * @param overlay 如果目标文件存在,是否覆盖 - * @return 如果复制成功返回true,否则返回false - */ - private static boolean copyFolder(final String srcFolderPath, final String destFolderPath, final String sourcePath, boolean overlay) { - if (srcFolderPath == null || destFolderPath == null) { - return false; - } - File srcFile = new File(srcFolderPath); - // 判断源文件是否存在 - if (!srcFile.exists()) { - return false; - } else if (!srcFile.isDirectory()) { // 不属于文件夹则跳过 - return false; - } - // 判断目标文件是否存在 - File destFile = new File(destFolderPath); - // 如果文件夹没创建,则创建 - if (!destFile.exists()) { - // 允许创建多级目录 - destFile.mkdirs(); - } - // 判断是否属于文件夹 - if (!destFile.isDirectory()) { // 不属于文件夹则跳过 - return false; - } - // 判断是否存在 - if (srcFile.exists()) { - // 获取文件路径 - File[] files = srcFile.listFiles(); - // 防止不存在文件 - if (files != null && files.length != 0) { - // 进行遍历 - for (File file : files) { - // 文件存在,才进行处理 - if (file.exists()) { - // 属于文件夹 - if (file.isDirectory()) { - copyFolder(file.getAbsolutePath(), destFolderPath, sourcePath, overlay); - } else { // 属于文件 - // 复制的文件地址 - String filePath = file.getAbsolutePath(); - // 获取源文件地址 - 并且进行判断 - String dealSource = new File(sourcePath).getAbsolutePath(); - // 属于最前才进行处理 - if (filePath.indexOf(dealSource) == 0){ - // 获取处理后的地址 - dealSource = filePath.substring(dealSource.length(), filePath.length()); - // 获得需要复制保存的地址 - String savePath = new File(destFolderPath, dealSource).getAbsolutePath(); - // 进行复制文件 - boolean isResult = copyFile(filePath, savePath, overlay); - } - } - } - } - } - } - return true; - } - - // -- - - /** - * 移动(剪切)文件 - * @param srcFilePath - * @param destFilePath - * @param overlay - * @return - */ - public static boolean moveFile(final String srcFilePath, final String destFilePath, final boolean overlay){ - // 复制文件 - if (copyFile(srcFilePath, destFilePath, overlay)){ - // 删除文件 - return deleteFile(srcFilePath); - } - return false; - } - - /** - * 移动(剪切)文件夹 - * @param srcFilePath - * @param destFilePath - * @param overlay - * @return - */ - public static boolean moveFolder(final String srcFilePath, final String destFilePath, final boolean overlay){ - // 复制文件夹 - if (copyFolder(srcFilePath, destFilePath, overlay)){ - // 删除文件夹 - return deleteFolder(srcFilePath); - } - return false; - } - - // =============== - // == FileUtils == - // =============== - - /** - * 复制或移动目录 - * @param srcDirPath 源目录路径 - * @param destDirPath 目标目录路径 - * @param listener 是否覆盖监听器 - * @param isMove 是否移动 - * @return true : 复制或移动成功, false :复制或移动失败 - */ - private static boolean copyOrMoveDir(final String srcDirPath, final String destDirPath, final OnReplaceListener listener, final boolean isMove) { - return copyOrMoveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener, isMove); - } - - /** - * 复制或移动目录 - * @param srcDir 源目录 - * @param destDir 目标目录 - * @param listener 是否覆盖监听器 - * @param isMove 是否移动 - * @return true : 复制或移动成功, false :复制或移动失败 - */ - private static boolean copyOrMoveDir(final File srcDir, final File destDir, final OnReplaceListener listener, final boolean isMove) { - if (srcDir == null || destDir == null) return false; - // 如果目标目录在源目录中则返回 false,看不懂的话好好想想递归怎么结束 - // srcPath : F:\\MyGithub\\AndroidUtilCode\\utilcode\\src\\test\\res - // destPath: F:\\MyGithub\\AndroidUtilCode\\utilcode\\src\\test\\res1 - // 为防止以上这种情况出现出现误判,须分别在后面加个路径分隔符 - String srcPath = srcDir.getPath() + File.separator; - String destPath = destDir.getPath() + File.separator; - if (destPath.contains(srcPath)) return false; - // 源文件不存在或者不是目录则返回 false - if (!srcDir.exists() || !srcDir.isDirectory()) return false; - if (destDir.exists()) { - if (listener.onReplace()) { // 需要覆盖则删除旧目录 - if (!deleteAllInDir(destDir)) { // 删除文件失败的话返回 false - return false; - } - } else { // 不需要覆盖直接返回即可 true - return true; - } - } - // 目标目录不存在返回 false - if (!createOrExistsDir(destDir)) return false; - File[] files = srcDir.listFiles(); - for (File file : files) { - File oneDestFile = new File(destPath + file.getName()); - if (file.isFile()) { - // 如果操作失败返回 false - if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; - } else if (file.isDirectory()) { - // 如果操作失败返回 false - if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; - } - } - return !isMove || deleteDir(srcDir); - } - - /** - * 复制或移动文件 - * @param srcFilePath 源文件路径 - * @param destFilePath 目标文件路径 - * @param listener 是否覆盖监听器 - * @param isMove 是否移动 - * @return true : 复制或移动成功, false :复制或移动失败 - */ - private static boolean copyOrMoveFile(final String srcFilePath, final String destFilePath, final OnReplaceListener listener, final boolean isMove) { - return copyOrMoveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener, isMove); - } - - /** - * 复制或移动文件 - * @param srcFile 源文件 - * @param destFile 目标文件 - * @param listener 是否覆盖监听器 - * @param isMove 是否移动 - * @return true : 复制或移动成功, false :复制或移动失败 - */ - private static boolean copyOrMoveFile(final File srcFile, final File destFile, final OnReplaceListener listener, final boolean isMove) { - if (srcFile == null || destFile == null) return false; - // 如果源文件和目标文件相同则返回 false - if (srcFile.equals(destFile)) return false; - // 源文件不存在或者不是文件则返回 false - if (!srcFile.exists() || !srcFile.isFile()) return false; - if (destFile.exists()) { // 目标文件存在 - if (listener.onReplace()) { // 需要覆盖则删除旧文件 - if (!destFile.delete()) { // 删除文件失败的话返回 false - return false; - } - } else { // 不需要覆盖直接返回即可 true - return true; - } - } - // 目标目录不存在返回 false - if (!createOrExistsDir(destFile.getParentFile())) return false; - try { - return FileIOUtils.writeFileFromIS(destFile, new FileInputStream(srcFile), false) && !(isMove && !deleteFile(srcFile)); - } catch (FileNotFoundException e) { - JCLogUtils.eTag(TAG, e, "copyOrMoveFile"); - return false; - } - } - - /** - * 复制目录 - * @param srcDirPath 源目录路径 - * @param destDirPath 目标目录路径 - * @param listener 是否覆盖监听器 - * @return true : 复制成功, false :复制失败 - */ - public static boolean copyDir(final String srcDirPath, final String destDirPath, final OnReplaceListener listener) { - return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); - } - - /** - * 复制目录 - * @param srcDir 源目录 - * @param destDir 目标目录 - * @param listener 是否覆盖监听器 - * @return true : 复制成功, false :复制失败 - */ - public static boolean copyDir(final File srcDir, final File destDir, final OnReplaceListener listener) { - return copyOrMoveDir(srcDir, destDir, listener, false); - } - - /** - * 复制文件 - * @param srcFilePath 源文件路径 - * @param destFilePath 目标文件路径 - * @param listener 是否覆盖监听器 - * @return true : 复制成功, false :复制失败 - */ - public static boolean copyFile(final String srcFilePath, final String destFilePath, final OnReplaceListener listener) { - return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); - } - - /** - * 复制文件 - * @param srcFile 源文件 - * @param destFile 目标文件 - * @param listener 是否覆盖监听器 - * @return true : 复制成功, false :复制失败 - */ - public static boolean copyFile(final File srcFile, final File destFile, final OnReplaceListener listener) { - return copyOrMoveFile(srcFile, destFile, listener, false); - } - - /** - * 移动目录 - * @param srcDirPath 源目录路径 - * @param destDirPath 目标目录路径 - * @param listener 是否覆盖监听器 - * @return true : 移动成功, false :移动失败 - */ - public static boolean moveDir(final String srcDirPath, final String destDirPath, final OnReplaceListener listener) { - return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); - } - - /** - * 移动目录 - * @param srcDir 源目录 - * @param destDir 目标目录 - * @param listener 是否覆盖监听器 - * @return true : 移动成功, false :移动失败 - */ - public static boolean moveDir(final File srcDir, final File destDir, final OnReplaceListener listener) { - return copyOrMoveDir(srcDir, destDir, listener, true); - } - - /** - * 移动文件 - * @param srcFilePath 源文件路径 - * @param destFilePath 目标文件路径 - * @param listener 是否覆盖监听器 - * @return true : 移动成功, false :移动失败 - */ - public static boolean moveFile(final String srcFilePath, final String destFilePath, final OnReplaceListener listener) { - return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); - } - - /** - * 移动文件 - * @param srcFile 源文件 - * @param destFile 目标文件 - * @param listener 是否覆盖监听器 - * @return true : 移动成功, false :移动失败 - */ - public static boolean moveFile(final File srcFile, final File destFile, final OnReplaceListener listener) { - return copyOrMoveFile(srcFile, destFile, listener, true); - } - - /** - * 删除目录 - * @param dirPath 目录路径 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteDir(final String dirPath) { - return deleteDir(getFileByPath(dirPath)); - } - - /** - * 删除目录 - * @param dir 目录 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteDir(final File dir) { - if (dir == null) return false; - // dir doesn't exist then return true - if (!dir.exists()) return true; - // dir isn't a directory then return false - if (!dir.isDirectory()) return false; - File[] files = dir.listFiles(); - if (files != null && files.length != 0) { - for (File file : files) { - if (file.isFile()) { - if (!file.delete()) return false; - } else if (file.isDirectory()) { - if (!deleteDir(file)) return false; - } - } - } - return dir.delete(); - } - - /** - * 删除目录下所有东西 - * @param dirPath 目录路径 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteAllInDir(final String dirPath) { - return deleteAllInDir(getFileByPath(dirPath)); - } - - /** - * 删除目录下所有东西 - * @param dir 目录 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteAllInDir(final File dir) { - return deleteFilesInDirWithFilter(dir, new FileFilter() { - @Override - public boolean accept(File pathname) { - return true; - } - }); - } - - /** - * 删除目录下所有文件 - * @param dirPath 目录路径 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteFilesInDir(final String dirPath) { - return deleteFilesInDir(getFileByPath(dirPath)); - } - - /** - * 删除目录下所有文件 - * @param dir 目录 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteFilesInDir(final File dir) { - return deleteFilesInDirWithFilter(dir, new FileFilter() { - @Override - public boolean accept(File pathname) { - return pathname.isFile(); - } - }); - } - - /** - * 删除目录下所有过滤的文件 - * @param dirPath 目录路径 - * @param filter 过滤器 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteFilesInDirWithFilter(final String dirPath, final FileFilter filter) { - return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); - } - - /** - * 删除目录下所有过滤的文件 - * @param dir 目录 - * @param filter 过滤器 - * @return true : 删除成功, false :删除失败 - */ - public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) { - if (dir == null) return false; - // dir doesn't exist then return true - if (!dir.exists()) return true; - // dir isn't a directory then return false - if (!dir.isDirectory()) return false; - File[] files = dir.listFiles(); - if (files != null && files.length != 0) { - for (File file : files) { - if (filter.accept(file)) { - if (file.isFile()) { - if (!file.delete()) return false; - } else if (file.isDirectory()) { - if (!deleteDir(file)) return false; - } - } - } - } - return true; - } - - /** - * 获取目录下所有文件 - 不递归进子目录 - * @param dirPath 目录路径 - * @return 文件链表 - */ - public static List listFilesInDir(final String dirPath) { - return listFilesInDir(dirPath, false); - } - - /** - * 获取目录下所有文件 - 不递归进子目录 - * @param dir 目录 - * @return 文件链表 - */ - public static List listFilesInDir(final File dir) { - return listFilesInDir(dir, false); - } - - /** - * 获取目录下所有文件 - * @param dirPath 目录路径 - * @param isRecursive 是否递归进子目录 - * @return 文件链表 - */ - public static List listFilesInDir(final String dirPath, final boolean isRecursive) { - return listFilesInDir(getFileByPath(dirPath), isRecursive); - } - - /** - * 获取目录下所有文件 - * @param dir 目录 - * @param isRecursive 是否递归进子目录 - * @return 文件链表 - */ - public static List listFilesInDir(final File dir, final boolean isRecursive) { - return listFilesInDirWithFilter(dir, new FileFilter() { - @Override - public boolean accept(File pathname) { - return true; - } - }, isRecursive); - } - - /** - * 获取目录下所有过滤的文件 - 不递归进子目录 - * @param dirPath 目录路径 - * @param filter 过滤器 - * @return 文件链表 - */ - public static List listFilesInDirWithFilter(final String dirPath, final FileFilter filter) { - return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); - } - - /** - * 获取目录下所有过滤的文件 - 不递归进子目录 - * @param dir 目录 - * @param filter 过滤器 - * @return 文件链表 - */ - public static List listFilesInDirWithFilter(final File dir, final FileFilter filter) { - return listFilesInDirWithFilter(dir, filter, false); - } - - /** - * 获取目录下所有过滤的文件 - * @param dirPath 目录路径 - * @param filter 过滤器 - * @param isRecursive 是否递归进子目录 - * @return 文件链表 - */ - public static List listFilesInDirWithFilter(final String dirPath, final FileFilter filter, final boolean isRecursive) { - return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); - } - - /** - * 获取目录下所有过滤的文件 - * @param dir 目录 - * @param filter 过滤器 - * @param isRecursive 是否递归进子目录 - * @return 文件链表 - */ - public static List listFilesInDirWithFilter(final File dir, final FileFilter filter, final boolean isRecursive) { - if (!isDir(dir)) return null; - List list = new ArrayList<>(); - File[] files = dir.listFiles(); - if (files != null && files.length != 0) { - for (File file : files) { - if (filter.accept(file)) { - list.add(file); - } - if (isRecursive && file.isDirectory()) { - // noinspection ConstantConditions - list.addAll(listFilesInDirWithFilter(file, filter, true)); - } - } - } - return list; - } - - public interface OnReplaceListener { - boolean onReplace(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/HexUtils.java b/DevLibUtils/src/main/java/dev/utils/common/HexUtils.java deleted file mode 100644 index ecd95c966a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/HexUtils.java +++ /dev/null @@ -1,150 +0,0 @@ -package dev.utils.common; - -import dev.utils.JCLogUtils; - -/** - * detail: 十六进制处理 - * Created by Ttt - */ -public final class HexUtils { - - private HexUtils(){ - } - - // 日志TAG - private static final String TAG = HexUtils.class.getSimpleName(); - - /** 用于建立十六进制字符的输出的小写字符数组 */ - private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - - /** 用于建立十六进制字符的输出的大写字符数组 */ - private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', - '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - /** - * 将字节数组转换为十六进制字符数组 - * @param data byte[] - * @return 十六进制char[] - */ - public static char[] encodeHex(byte[] data) { - return encodeHex(data, true); - } - - /** - * 将字节数组转换为十六进制字符数组 - * @param data byte[] - * @param toLowerCase true : 传换成小写格式 , false : 传换成大写格式 - * @return 十六进制char[] - */ - public static char[] encodeHex(byte[] data, boolean toLowerCase) { - return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); - } - - /** - * 将字节数组转换为十六进制字符数组 - * @param data byte[] - * @param toDigits 用于控制输出的char[] - * @return 十六进制char[] - */ - protected static char[] encodeHex(byte[] data, char[] toDigits) { - if (data == null || toDigits == null) return null; - try { - int len = data.length; - char[] out = new char[len << 1]; - // two characters form the hex value. - for (int i = 0, j = 0; i < len; i++) { - out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; - out[j++] = toDigits[0x0F & data[i]]; - } - return out; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "encodeHex"); - } - return null; - } - - /** - * 将字节数组转换为十六进制字符串 - * @param data byte[] - * @return 十六进制String - */ - public static String encodeHexStr(byte[] data) { - return encodeHexStr(data, true); - } - - /** - * 将字节数组转换为十六进制字符串 - * @param data byte[] - * @param toLowerCase true : 传换成小写格式 , false : 传换成大写格式 - * @return 十六进制String - */ - public static String encodeHexStr(byte[] data, boolean toLowerCase) { - return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); - } - - /** - * 将字节数组转换为十六进制字符串 - * @param data byte[] - * @param toDigits 用于控制输出的char[] - * @return 十六进制String - */ - protected static String encodeHexStr(byte[] data, char[] toDigits) { - try { - return new String(encodeHex(data, toDigits)); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "encodeHexStr"); - } - return null; - } - - /** - * 将十六进制字符数组转换为字节数组 - * @param data 十六进制char[] - * @return byte[] - * @throws RuntimeException 如果源十六进制字符数组是一个奇怪的长度,将抛出运行时异常 - */ - public static byte[] decodeHex(char[] data) { - if (data == null) return null; - try { - int len = data.length; - byte[] out = new byte[len >> 1]; - // two characters form the hex value. - for (int i = 0, j = 0; j < len; i++) { - int f = toDigit(data[j], j) << 4; - j++; - f = f | toDigit(data[j], j); - j++; - out[i] = (byte) (f & 0xFF); - } - return out; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "decodeHex"); - } - return null; - } - - /** - * 将十六进制字符转换成一个整数 - * @param ch 十六进制char - * @param index 十六进制字符在字符数组中的位置 - * @return 一个整数 - * @throws RuntimeException 当ch不是一个合法的十六进制字符时,抛出运行时异常 - */ - protected static int toDigit(char ch, int index) { - int digit = Character.digit(ch, 16); - if (digit == -1) { - throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index); - } - return digit; - } - -// public static void main(String[] args) { -// String srcStr = "待转换字符串"; -// String encodeStr = encodeHexStr(srcStr.getBytes()); -// String decodeStr = new String(decodeHex(encodeStr.toCharArray())); -// System.out.println("转换前:" + srcStr); -// System.out.println("转换后:" + encodeStr); -// System.out.println("还原后:" + decodeStr); -// } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/common/HttpParamsUtils.java b/DevLibUtils/src/main/java/dev/utils/common/HttpParamsUtils.java deleted file mode 100644 index 4e5077863e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/HttpParamsUtils.java +++ /dev/null @@ -1,260 +0,0 @@ -package dev.utils.common; - -import java.net.URLEncoder; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import dev.utils.JCLogUtils; -import dev.utils.LogPrintUtils; - -/** - * detail: Http 参数工具类 - * Created by Ttt - */ -public final class HttpParamsUtils { - - private HttpParamsUtils() { - } - - // 日志TAG - private static final String TAG = HttpParamsUtils.class.getSimpleName(); - - /** - * 拆分参数 - * @param params - * @return - */ - public static HashMap splitParams(final String params) { - return splitParams(params, false); - } - - /** - * 拆分参数 - * @param params - * @param urlEncode 是否需要编码 - * @return - */ - public static HashMap splitParams(final String params, boolean urlEncode) { - HashMap mapParams = new HashMap<>(); - if (params != null) { - // 拆分数据 - String[] keyValues = params.split("&"); - // 数据长度 - int valLength = 0; - // 进行循环遍历 - for (String val : keyValues){ - // 数据不为null - if (val != null && (valLength = val.length()) != 0){ - // 获取首位 = 索引 - int indexOf = val.indexOf('='); - // 不存在则不处理 - if (indexOf != -1){ - // 获取key - String key = val.substring(0, indexOf); - // 获取value - String value = null; - // 防止资源浪费 - if (indexOf + 1 == valLength){ - value = ""; - } else { - value = val.substring(indexOf + 1, valLength); - } - // 判断是否编码 - if (urlEncode){ - mapParams.put(key, urlEncode(value)); - } else { - mapParams.put(key, value); - } - } - } - } - } - return mapParams; - } - - // - - - /** - * 拼接请求参数 - value => String - * @param mapParams - * @return - */ - public static String joinReqParams(final HashMap mapParams) { - return joinReqParams(mapParams, false); - } - - /** - * 拼接请求参数 - value => String - * @param mapParams - * @param urlEncode 是否需要编码 - * @return - */ - public static String joinReqParams(final HashMap mapParams, boolean urlEncode) { - if (mapParams != null) { - int index = 0; - // -- - StringBuilder sBuilder = new StringBuilder(); - Iterator> iterator = mapParams.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - // -- - if (index > 0) sBuilder.append('&'); - sBuilder.append(entry.getKey()); - sBuilder.append('='); - sBuilder.append(urlEncode ? urlEncode(entry.getValue()) : entry.getValue()); - index++; - } - return sBuilder.toString(); - } - return null; - } - - // - - - /** - * 拼接请求参数 - value => Object - * @param mapParams - * @return - */ - public static String joinReqParamsObj(final HashMap mapParams) { - return joinReqParamsObj(mapParams, false); - } - - /** - * 拼接请求参数 - value => Object - * @param mapParams - * @param urlEncode 是否需要编码 - * @return - */ - public static String joinReqParamsObj(final HashMap mapParams, boolean urlEncode) { - if (mapParams != null) { - int index = 0; - // -- - StringBuilder sBuilder = new StringBuilder(); - Iterator> iterator = mapParams.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - // -- - if (index > 0) sBuilder.append('&'); - sBuilder.append(entry.getKey()); - sBuilder.append('='); - if (urlEncode){ - if (entry.getValue() instanceof String){ - sBuilder.append(urlEncode((String) entry.getValue())); - } - } else { - sBuilder.append(entry.getValue()); - } - index++; - } - return sBuilder.toString(); - } - return null; - } - - // - - - /** - * toString 快捷方法, 拼接打印 String - * @param mapParams - * @return - */ - public static String toStringMap(final HashMap mapParams) { - return toStringMap(mapParams, false); - } - - /** - * toString 快捷方法, 拼接打印 String - * @param mapParams - * @param urlEncode 是否需要编码 - * @return - */ - public static String toStringMap(final HashMap mapParams, boolean urlEncode) { - if (mapParams != null) { - StringBuilder sBuilder = new StringBuilder(); - // -- - Iterator> iterator = mapParams.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - sBuilder.append(entry.getKey()); - sBuilder.append(" => "); - sBuilder.append(urlEncode ? urlEncode(entry.getValue()) : entry.getValue()); - sBuilder.append("\n"); - } - return sBuilder.toString(); - } - return null; - } - - // == 拼接成, 模拟 JavaScript 传递对象数组格式 == - - // 正常数据 - // objStr[key] => sex=男&name=Ttt - // 方法会进行 url编码 - // objStr[key] => sex%3D%E7%94%B7%26name%3DTtt - - /** - * 进行转换对象处理(请求发送对象) - * @param mapParams - * @param objStr - * @param key - * @param value - */ - public static void toConvertObjToMS(HashMap mapParams, String objStr, String key, String value){ - if (mapParams != null) { - String data = null; - try { - data = URLEncoder.encode(value, "UTF-8"); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toConvertObjToMS"); - } - mapParams.put(objStr + "[" + key + "]", data); - } - } - - /** - * 进行转换对象处理(请求发送对象) - * @param mapParams - * @param objStr - * @param key - * @param value - */ - public static void toConvertObjToMO(HashMap mapParams, String objStr, String key, Object value){ - if (mapParams != null) { - Object data = null; - try { - data = URLEncoder.encode(value.toString(), "UTF-8"); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toConvertObjToMO"); - } - mapParams.put(objStr + "[" + key + "]", data); - } - } - - // = - - /** - * url编码 - utf-8 - * @param input The input. - * @return the urlencoded string - */ - public static String urlEncode(final String input) { - return urlEncode(input, "UTF-8"); - } - - /** - * url编码 - * @param input The input. - * @param charsetName The name of charset. - * @return the urlencoded string - */ - public static String urlEncode(final String input, final String charsetName) { - try { - return URLEncoder.encode(input, charsetName); - } catch (Exception e) { - LogPrintUtils.eTag(TAG, e, "urlEncode"); - return input; - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/HttpURLConnectionUtils.java b/DevLibUtils/src/main/java/dev/utils/common/HttpURLConnectionUtils.java deleted file mode 100644 index 270ee578df..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/HttpURLConnectionUtils.java +++ /dev/null @@ -1,265 +0,0 @@ -package dev.utils.common; - -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - -import dev.utils.JCLogUtils; - -/** - * detail: HttpURLConnection 网络工具类 - * Created by Ttt - */ -public final class HttpURLConnectionUtils { - - private HttpURLConnectionUtils(){ - } - - // 日志TAG - private static final String TAG = HttpURLConnectionUtils.class.getSimpleName(); - - // 请求超时时间 - private static final int TIMEOUT_IN_MILLIONS = 5000; - - // 请求回调 - public interface CallBack { - - /** - * 请求响应回调 - * @param result - */ - void onResponse(String result); - - /** - * 请求失败 - * @param e - */ - void onFail(Exception e); - } - - /** - * 异步的Get请求 - * @param urlStr - * @param callBack - */ - public static void doGetAsyn(final String urlStr, final CallBack callBack) { - new Thread() { - public void run() { - try { - request("GET", urlStr, null, null, callBack); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "doGetAsyn"); - } - } - }.start(); - } - - /** - * 异步的Post请求 - * @param urlStr - * @param params - * @param callBack - */ - public static void doPostAsyn(final String urlStr, final String params, final CallBack callBack) { - new Thread() { - public void run() { - try { - request("POST", urlStr, null, params, callBack); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "doPostAsyn"); - } - } - }.start(); - } - - /** - * 发送请求 - * @param method - * @param urlStr - * @param headers - * @param params - * @param callBack - * @return - */ - public static void request(final String method, final String urlStr, final HashMap headers, final String params, final CallBack callBack) { - // 获取连接对象 - HttpURLConnection connection = null; - InputStream inputStream = null; - ByteArrayOutputStream bout = null; - try { - // 请求路径 - URL url = new URL(urlStr); - // 获取连接对象 - connection = (HttpURLConnection) url.openConnection(); - // 设置请求方法 - connection.setRequestMethod(method); - // 设置请求头信息 - if (headers != null){ - Iterator> iterator = headers.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - connection.setRequestProperty(entry.getKey(), entry.getValue()); - } - } - // 获取请求时间 - JCLogUtils.dTag(TAG, "response time: " + connection.getDate()); - // 判断是否需要写入数据 - if(params != null && params.length() != 0) { - // 允许写入 - connection.setDoInput(true); - // 设置是否向connection输出,如果是post请求,参数要放在http正文内,因此需要设为true - connection.setDoOutput(true); - // Post 请求不能使用缓存 - connection.setUseCaches(false); - // 写入数据 - OutputStream ot = connection.getOutputStream(); - ot.write(params.getBytes()); - ot.flush(); - ot.close(); - } - // 单位是毫秒 - connection.setConnectTimeout(TIMEOUT_IN_MILLIONS); // 设置连接超时 - connection.setReadTimeout(TIMEOUT_IN_MILLIONS); // 设置读取超时 - // 获取请求状态码 - int responseCode = connection.getResponseCode(); - // 判断请求码是否是200 - if (responseCode >= 200 && responseCode < 300) { - // 输入流 - inputStream = connection.getInputStream(); - bout = new ByteArrayOutputStream(); - // 设置缓存流大小 - byte[] buffer = new byte[1024]; - int len = 0; - while (((len = inputStream.read(buffer)) != -1)) { - bout.write(buffer, 0, len); - } - // 获取请求结果 - String result = new String(bout.toByteArray()); - // 判断是否回调 - if (callBack != null){ - // 请求成功, 触发回调 - callBack.onResponse(result); - } - } else { - // 响应成功,非200直接返回null - if (callBack != null){ - callBack.onFail(new Exception("responseCode not >= 200 or < 300 , code: " + responseCode)); - } - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "request"); - if (callBack != null){ - callBack.onFail(e); - } - } finally { - if (bout != null) { - try { - bout.close(); - } catch(Exception ignore){ - } - } - if (inputStream != null) { - try { - inputStream.close(); - } catch(Exception ignore){ - } - } - if (connection != null) { - try { - // 关闭底层连接Socket - connection.disconnect(); - } catch(Exception ignore){ - } - } - } - } - - // == 获取网络时间处理 == - - public static final String BAIDU_URL = "https://www.baidu.com"; - - /** - * 时间回调 - */ - public interface TimeCallBack { - - /** - * 请求相应回调 - * @param time 毫秒 - */ - void onResponse(long time); - - /** - * 请求失败 - * @param e - */ - void onFail(Exception e); - } - - /** - * 获取网络时间 - 默认使用百度链接 - * @param timeCallBack - */ - public static void getNetTime(TimeCallBack timeCallBack){ - getNetTime(BAIDU_URL, timeCallBack); - } - - /** - * 获取网络时间 - * @param urlStr - * @param timeCallBack - */ - public static void getNetTime(final String urlStr, final TimeCallBack timeCallBack){ - new Thread(new Runnable() { - @Override - public void run() { - reqNetTime(urlStr, timeCallBack); - } - }).start(); - } - - /** - * 请求网络时间(内部私有) - * @param urlStr - * @param timeCallBack - */ - private static void reqNetTime(String urlStr, TimeCallBack timeCallBack){ - // 获取连接对象 - HttpURLConnection connection = null; - try { - // 请求路径 - URL url = new URL(urlStr); - // 获取连接对象 - connection = (HttpURLConnection) url.openConnection(); - // 获取时间 - long date = connection.getDate(); - // 获取失败, 则进行修改 - if (date <= 0){ - date = -1l; - } - // 触发回调 - if (timeCallBack != null){ - timeCallBack.onResponse(date); - } - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "getNetTime"); - // 触发回调 - if (timeCallBack != null){ - timeCallBack.onFail(e); - } - } finally { - if (connection != null) { - try { - // 关闭底层连接Socket - connection.disconnect(); - } catch(Exception ignore){ - } - } - } - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/common/ObjectUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ObjectUtils.java deleted file mode 100644 index 10da8a01a8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ObjectUtils.java +++ /dev/null @@ -1,115 +0,0 @@ -package dev.utils.common; - -import java.lang.reflect.Array; -import java.util.Collection; -import java.util.Map; - -import dev.utils.JCLogUtils; - -/** - * detail: 对象相关工具类 - * Created by Ttt - */ -public final class ObjectUtils { - - private ObjectUtils() { - } - - // 日志TAG - private static final String TAG = ObjectUtils.class.getSimpleName(); - - /** - * 判断对象是否为空 - * @param obj 对象 - * @return true : 为空, false : 不为空 - */ - public static boolean isEmpty(final Object obj) { - if (obj == null) { - return true; - } - if (obj instanceof CharSequence && obj.toString().length() == 0) { - return true; - } - if (obj.getClass().isArray() && Array.getLength(obj) == 0) { - return true; - } - if (obj instanceof Collection && ((Collection) obj).isEmpty()) { - return true; - } - if (obj instanceof Map && ((Map) obj).isEmpty()) { - return true; - } - return false; - } - - /** - * 判断对象是否非空 - * @param obj 对象 - * @return true : 非空, false : 空 - */ - public static boolean isNotEmpty(final Object obj) { - return !isEmpty(obj); - } - - /** - * 判断对象是否相等 - * @param o1 对象1 - * @param o2 对象2 - * @return true : 相等, false : 不相等 - */ - public static boolean equals(Object o1, Object o2) { - return o1 == o2 || (o1 != null && o1.equals(o2)); - } - - /** - * 检查对象非空 - * @param object 对象 - * @param message 报错 - * @param 范型 - * @return 非空对象 - */ - public static T requireNonNull(T object, String message) { - if (object == null) { - throw new NullPointerException(message); - } - return object; - } - - /** - * 获取非空或默认对象 - * @param object 对象 - * @param defaultObject 默认值 - * @param 范型 - * @return 非空或默认对象 - */ - public static T getOrDefault(T object, T defaultObject) { - if (object == null) { - return defaultObject; - } - return object; - } - - /** - * 获取对象哈希值 - * @param o 对象 - * @return 哈希值 - */ - public static int hashCode(Object o) { - return o != null ? o.hashCode() : 0; - } - - /** - * 获取转换对象 - * @param obj 对象 - * @param 范型 - * @return 非空或默认对象 - */ - public static T converObj(Object obj) { - try { - return (T) obj; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "converObj"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/QuickCommonUtils.java b/DevLibUtils/src/main/java/dev/utils/common/QuickCommonUtils.java deleted file mode 100644 index c44bcfeea5..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/QuickCommonUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -package dev.utils.common; - -/** - * detail: 快捷通用 - * Created by Ttt - */ -public final class QuickCommonUtils { - - private QuickCommonUtils(){ - } - - /** - * 转换手机号 - * @param phone - */ - public static String converHideMobile(String phone){ - return converHideMobile(phone, "*"); - } - - /** - * 转换手机号 - * @param phone - * @param symbol 符号 - */ - public static String converHideMobile(String phone, String symbol){ - return DevCommonUtils.converSymbolHide(3, phone, symbol); - } - - /** - * 耗时时间记录 - * @param buffer - * @param title 标题 - * @param sTime 开始时间 - * @param eTime 结束时间 - */ - public static void timeRecord(StringBuffer buffer, String title, long sTime, long eTime) { - // 使用时间 - long uTime = eTime - sTime; - // 计算时间 - buffer.append("\n" + title); - buffer.append("\n开始时间:" + sTime); - buffer.append("\n结束时间:" + eTime); - buffer.append("\n所用时间:" + uTime); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/RandomUtils.java b/DevLibUtils/src/main/java/dev/utils/common/RandomUtils.java deleted file mode 100644 index 8dec43e263..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/RandomUtils.java +++ /dev/null @@ -1,274 +0,0 @@ -package dev.utils.common; - -import java.util.Random; - -/** - * detail: 随机生成工具类 - * Created by Ttt - */ -public final class RandomUtils { - - private RandomUtils() { - } - - // 0123456789 - public static final char[] NUMBERS = new char[] { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 }; - - // abcdefghijklmnopqrstuvwxyz - public static final char[] LOWER_CASE_LETTERS = new char[] { 97, 98, 99, - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, - 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 }; - - // ABCDEFGHIJKLMNOPQRSTUVWXYZ - public static final char[] CAPITAL_LETTERS = new char[] { 65, 66, 67, 68, - 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, - 86, 87, 88, 89, 90 }; - - // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - public static final char[] LETTERS = new char[] { 97, 98, 99, 100, 101, - 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, - 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, - 89, 90 }; - - // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ - public static final char[] NUMBERS_AND_LETTERS = new char[] { 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, - 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 }; - - // = - - public static boolean nextBoolean(Random random) { - return random.nextBoolean(); - } - - public static byte[] nextBytes(Random random, byte[] buf) { - try { - random.nextBytes(buf); - } catch (Exception e) { - } - return buf; - } - - public static double nextDouble(Random random) { - return random.nextDouble(); - } - - public synchronized double nextGaussian(Random random) { - return random.nextGaussian(); - } - - public static float nextFloat(Random random) { - return random.nextFloat(); - } - - public static int nextInt(Random random) { - return random.nextInt(); - } - - public static int nextInt(Random random, int n) { - return random.nextInt(n); - } - - public static long nextLong(Random random) { - return random.nextLong(); - } - - // == ----------------------------------------- == - - public static boolean nextBoolean() { - return new Random().nextBoolean(); - } - - public static byte[] nextBytes(byte[] buf) { - return nextBytes(new Random(), buf); - } - - public static double nextDouble() { - return Math.random(); - } - - public synchronized double nextGaussian() { - return new Random().nextGaussian(); - } - - public static float nextFloat() { - return new Random().nextFloat(); - } - - public static int nextInt() { - return new Random().nextInt(); - } - - public static int nextInt(int n) { - return new Random().nextInt(n); - } - - public static long nextLong() { - return new Random().nextLong(); - } - - // == ----------------------------------------- == - - /** - * 获取数字自定义长度的随机数 - * @param length 长度 - * @return 随机数字符串 - */ - public static String getRandomNumbers(int length) { - return getRandom(NUMBERS, length); - } - - /** - * 获取小写字母自定义长度的随机数 - * @param length 长度 - * @return 随机字符串 - */ - public static String getRandomLowerCaseLetters(int length) { - return getRandom(LOWER_CASE_LETTERS, length); - } - - /** - * 获取大写字母自定义长度的随机数 - * @param length 长度 - * @return 随机字符串 - */ - public static String getRandomCapitalLetters(int length) { - return getRandom(CAPITAL_LETTERS, length); - } - - /** - * 获取大小写字母自定义长度的随机数 - * @param length 长度 - * @return 随机字符串 - */ - public static String getRandomLetters(int length) { - return getRandom(LETTERS, length); - } - - /** - * 获取数字、大小写字母自定义长度的随机数 - * @param length 长度 - * @return 随机字符串 - */ - public static String getRandomNumbersAndLetters(int length) { - return getRandom(NUMBERS_AND_LETTERS, length); - } - - /** - * 获取自定义数据自定义长度的随机数 - * @param length 长度 - * @return 随机字符串 - */ - public static String getRandom(String source, int length) { - return getRandom(source.toCharArray(), length); - } - - /** - * 获取char[]内的随机数 - * @param chars 随机的数据源 - * @param length 需要最终长度 - * @return - */ - public static String getRandom(char[] chars, int length) { - if(length > 0 && chars != null && chars.length != 0) { - StringBuilder str = new StringBuilder(length); - Random random = new Random(); - for (int i = 0; i < length; i++) { - str.append(chars[random.nextInt(chars.length)]); - } - return str.toString(); - } - return null; - } - - /** - * 获取 0 - 最大随机数之间的随机数 - * @param max 最大随机数 - */ - public static int getRandom(int max) { - return getRandom(0, max); - } - - /** - * 获取两个数之间的随机数(不含最大随机数,需要 + 1) - * @param min 最小随机数 - * @param max 最大随机数 - */ - public static int getRandom(int min, int max) { - if (min > max) { - return 0; - } else if (min == max) { - return min; - } - return min + new Random().nextInt(max - min); - } - - /** - * 洗牌算法,随机置换指定的数组使用的默认源的随机性 - * @param objArray - * @return - */ - public static boolean shuffle(Object[] objArray) { - if (objArray == null) { - return false; - } - return shuffle(objArray, getRandom(1,objArray.length)); - } - - /** - * 洗牌算法,随机置换指定的数组 - * @param objArray - * @param shuffleCount - * @return - */ - public static boolean shuffle(Object[] objArray, int shuffleCount) { - int length; - if(shuffleCount > 0 && objArray != null && (length = objArray.length) >= shuffleCount) { - for (int i = 1; i <= shuffleCount; i++) { - int random = getRandom(0,length - i); - Object temp = objArray[length - i]; - objArray[length - i] = objArray[random]; - objArray[random] = temp; - } - return true; - } - return false; - } - - /** - * 洗牌算法,随机置换指定数组的使用随机的默认源 - * @param intArray - * @return - */ - public static int[] shuffle(int[] intArray) { - if (intArray == null) { - return null; - } - return shuffle(intArray, getRandom(1,intArray.length)); - } - - /** - * 洗牌算法,随机置换指定的数组 - * @param intArray - * @param shuffleCount - * @return - */ - public static int[] shuffle(int[] intArray, int shuffleCount) { - int length; - if(shuffleCount > 0 && intArray != null && (length = intArray.length) >= shuffleCount) { - int[] out = new int[shuffleCount]; - for (int i = 1; i <= shuffleCount; i++) { - int random = getRandom(0,length - i); - out[i - 1] = intArray[random]; - int temp = intArray[length - i]; - intArray[length - i] = intArray[random]; - intArray[random] = temp; - } - return out; - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/Reflect2Utils.java b/DevLibUtils/src/main/java/dev/utils/common/Reflect2Utils.java deleted file mode 100644 index 4f4553f2e0..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/Reflect2Utils.java +++ /dev/null @@ -1,249 +0,0 @@ -package dev.utils.common; - -import java.lang.reflect.Array; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -import dev.utils.JCLogUtils; - -/** - * detail: 反射相关工具类 - * Created by Ttt - */ -public final class Reflect2Utils { - - private Reflect2Utils(){ - } - - // 日志TAG - private static final String TAG = Reflect2Utils.class.getSimpleName(); - - /** - * 获取某个对象的公共属性 - * @param owner, fieldName - * @return 该属性对象 - */ - public static Object getProperty(Object owner, String fieldName) { - try { - Class ownerClass = owner.getClass(); - Field field = ownerClass.getField(fieldName); - Object property = field.get(owner); - return property; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "getProperty"); - } - return null; - } - - /** - * 获取某类的静态公共属性 - * @param className 类名 - * @param fieldName 属性名 - * @return 该属性对象 - */ - public static Object getStaticProperty(String className, String fieldName) { - try { - Class ownerClass = Class.forName(className); - Field field = ownerClass.getField(fieldName); - Object property = field.get(ownerClass); - return property; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "getStaticProperty"); - } - return null; - } - - - /** - * 执行某对象方法 - * @param owner 对象 - * @param methodName 方法名 - * @param args 参数 - * @return 方法返回值 - */ - public static Object invokeMethod(Object owner, String methodName, Object[] args) { - try { - Class ownerClass = owner.getClass(); - Class[] argsClass = new Class[args.length]; - for (int i = 0, j = args.length; i < j; i++) { - argsClass[i] = args[i].getClass(); - } - Method method = ownerClass.getMethod(methodName, argsClass); - return method.invoke(owner, args); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "invokeMethod"); - } - return null; - } - - - /** - * 执行某类的静态方法 - * @param className 类名 - * @param methodName 方法名 - * @param args 参数数组 - * @return 执行方法返回的结果 - */ - public static Object invokeStaticMethod(String className, String methodName, Object[] args) { - try { - Class ownerClass = Class.forName(className); - Class[] argsClass = new Class[args.length]; - for (int i = 0, j = args.length; i < j; i++) { - argsClass[i] = args[i].getClass(); - } - Method method = ownerClass.getMethod(methodName, argsClass); - return method.invoke(null, args); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "invokeStaticMethod"); - } - return null; - } - - - /** - * 新建实例 - * @param className 类名 - * @param args 构造函数的参数 如果无构造参数,args 填写为 null - * @return 新建的实例 - */ - public static Object newInstance(String className, Object[] args, Class[] argsType) { - try { - Class newoneClass = Class.forName(className); - if (args == null) { - return newoneClass.newInstance(); - } else { - // Class[] argsClass = new Class[args.length]; - // for (int i = 0, j = args.length; i < j; i++) { - // argsClass[i] = args[i].getClass(); - // } - Constructor cons = newoneClass.getConstructor(argsType); - return cons.newInstance(args); - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "newInstance"); - } - return null; - } - - /** - * 是不是某个类的实例 - * @param obj 实例 - * @param cls 类 - * @return 如果 obj 是此类的实例,则返回 true - */ - public static boolean isInstance(Object obj, Class cls) { - return cls.isInstance(obj); - } - - /** - * 获取数组中的某个元素 - * @param array 数组 - * @param index 索引 - * @return 返回指定数组对象中索引组件的值 - */ - public static Object getByArray(Object array, int index) { - return Array.get(array, index); - } - - public static Class GetClassListByPackage(String pPackage) { - Package _Package = Package.getPackage(pPackage); - Class _List = _Package.getClass(); - return _List; - } - - // = - - /** - * 通过反射获取全部字段 - * 如: (ListView) getDeclaredField(对象, "私有属性") - * @param obj - * @param name - * @return - */ - public static Object getDeclaredField(Object obj, String name) throws Exception { - Field f = obj.getClass().getDeclaredField(name); - f.setAccessible(true); - Object out = f.get(obj); - return out; - } - - /** - * 获取父类中的变量对象 - * @param object : 子类对象 - * @param fieldName : 父类中的属性名 - * @return 父类中的变量对象 - */ - public static Object getDeclaredFieldParentObj(Object object, String fieldName){ - try { - Field field = getDeclaredFieldParent(object, fieldName); - field.setAccessible(true); - return field.get(object); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "getDeclaredFieldParentObj"); - } - return null; - } - - /** - * 循环向上转型, 获取对象的 DeclaredField - * @param object : 子类对象 - * @param fieldName : 父类中的属性名 - * @return 父类中的变量对象 - */ - public static Field getDeclaredFieldParent(Object object, String fieldName){ - Field field = null ; - Class clazz = object.getClass() ; - for(; clazz != Object.class ; clazz = clazz.getSuperclass()) { - try { - field = clazz.getDeclaredField(fieldName) ; - return field ; - } catch (Exception e) { - //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。 - //如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了 - } - } - return null; - } - - /** - * 设置反射的方法 - * @param object - * @param name 方法名 - * @param args 方法需要的参数 - */ - public static boolean setFieldMethod(Object object, final String name, final Object... args){ - try { - Method method = object.getClass().getDeclaredMethod(name); - method.setAccessible(true); - // 如果不为null, 则不放参数 - if (args != null && args.length != 0){ - method.invoke(object, args); - } else { - method.invoke(object); - } - return true; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "setFieldMethod"); - } - return false; - } - - /** - * 设置反射的字段 - * @param obj - * @param name 字段名 - * @param value 字段值 - */ - public static boolean setFieldValue(Object obj, String name, Object value) { - try { - Field field = obj.getClass().getDeclaredField(name); - field.setAccessible(true); - field.set(obj, value); - return true; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "setFieldValue"); - } - return false; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ReflectUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ReflectUtils.java deleted file mode 100644 index 1340621631..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ReflectUtils.java +++ /dev/null @@ -1,553 +0,0 @@ -package dev.utils.common; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * detail: 反射相关工具类 - * Created by Ttt - * == - * 有两个方法:getMethod,getDeclaredMethod。看了下说明大概的意思就是getMethod只能调用public声明的方法,而getDeclaredMethod基本可以调用任何类型声明的方法 - * 反射多用getDeclaredMethod,尽量少用getMethod。 - */ -public final class ReflectUtils { - - private final Class type; - - private final Object object; - - private ReflectUtils(final Class type) { - this(type, type); - } - - private ReflectUtils(final Class type, Object object) { - this.type = type; - this.object = object; - } - - // ================= - // ==== reflect ==== - // ================= - - /** - * 设置要反射的类 - * @param className 完整类名 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public static ReflectUtils reflect(final String className) throws ReflectException { - return reflect(forName(className)); - } - - /** - * 设置要反射的类 - * @param className 完整类名 - * @param classLoader 类加载器 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public static ReflectUtils reflect(final String className, final ClassLoader classLoader) throws ReflectException { - return reflect(forName(className, classLoader)); - } - - /** - * 设置要反射的类 - * @param clazz 类的类型 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public static ReflectUtils reflect(final Class clazz) throws ReflectException { - return new ReflectUtils(clazz); - } - - /** - * 设置要反射的类 - * @param object 类对象 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public static ReflectUtils reflect(final Object object) throws ReflectException { - return new ReflectUtils(object == null ? Object.class : object.getClass(), object); - } - - private static Class forName(String className) { - try { - return Class.forName(className); - } catch (ClassNotFoundException e) { - throw new ReflectException(e); - } - } - - private static Class forName(String name, ClassLoader classLoader) { - try { - return Class.forName(name, true, classLoader); - } catch (ClassNotFoundException e) { - throw new ReflectException(e); - } - } - - // ===================== - // ==== newInstance ==== - // ===================== - - /** - * 实例化反射对象 - * @return {@link ReflectUtils} - */ - public ReflectUtils newInstance() { - return newInstance(new Object[0]); - } - - /** - * 实例化反射对象 - * @param args 实例化需要的参数 - * @return {@link ReflectUtils} - */ - public ReflectUtils newInstance(Object... args) { - Class[] types = getArgsType(args); - try { - Constructor constructor = type().getDeclaredConstructor(types); - return newInstance(constructor, args); - } catch (NoSuchMethodException e) { - List> list = new ArrayList<>(); - for (Constructor constructor : type().getDeclaredConstructors()) { - if (match(constructor.getParameterTypes(), types)) { - list.add(constructor); - } - } - if (list.isEmpty()) { - throw new ReflectException(e); - } else { - sortConstructors(list); - return newInstance(list.get(0), args); - } - } - } - - private Class[] getArgsType(final Object... args) { - if (args == null) return new Class[0]; - Class[] result = new Class[args.length]; - for (int i = 0; i < args.length; i++) { - Object value = args[i]; - result[i] = value == null ? NULL.class : value.getClass(); - } - return result; - } - - private void sortConstructors(List> list) { - Collections.sort(list, new Comparator>() { - @Override - public int compare(Constructor o1, Constructor o2) { - Class[] types1 = o1.getParameterTypes(); - Class[] types2 = o2.getParameterTypes(); - int len = types1.length; - for (int i = 0; i < len; i++) { - if (!types1[i].equals(types2[i])) { - if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { - return 1; - } else { - return -1; - } - } - } - return 0; - } - }); - } - - private ReflectUtils newInstance(final Constructor constructor, final Object... args) { - try { - return new ReflectUtils(constructor.getDeclaringClass(), accessible(constructor).newInstance(args)); - } catch (Exception e) { - throw new ReflectException(e); - } - } - - // =============== - // ==== field ==== - // =============== - - /** - * 设置反射的字段 - * @param name 字段名 - * @return {@link ReflectUtils} - */ - public ReflectUtils field(final String name) { - try { - Field field = getField(name); - return new ReflectUtils(field.getType(), field.get(object)); - } catch (IllegalAccessException e) { - throw new ReflectException(e); - } - } - - /** - * 设置反射的字段 - * @param name 字段名 - * @param value 字段值 - * @return {@link ReflectUtils} - */ - public ReflectUtils field(String name, Object value) { - try { - Field field = getField(name); - field.set(object, unwrap(value)); - return this; - } catch (Exception e) { - throw new ReflectException(e); - } - } - - private Field getField(String name) throws IllegalAccessException { - Field field = getAccessibleField(name); - if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { - try { - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - } catch (NoSuchFieldException ignore) { - // runs in android will happen - } - } - return field; - } - - private Field getAccessibleField(String name) { - Class type = type(); - try { - return accessible(type.getField(name)); - } catch (NoSuchFieldException e) { - do { - try { - return accessible(type.getDeclaredField(name)); - } catch (NoSuchFieldException ignore) { - } - type = type.getSuperclass(); - } while (type != null); - throw new ReflectException(e); - } - } - - private Object unwrap(Object object) { - if (object instanceof ReflectUtils) { - return ((ReflectUtils) object).get(); - } - return object; - } - - // == - - /** - * 获取Object 对象 - * 例: 获取父类中的变量 - * Object obj = 对象; - * getObject(getDeclaredFieldBase(obj, "父类中变量名"), obj); - * @param field - * @param obj - * @return - */ - public static Object getObject(Field field, Object obj){ - try { - field.setAccessible(true); - Object out = field.get(obj); - return out; - } catch (Exception e){ - } - return null; - } - - /** - * 设置枚举值 - * @param clas 类型 - * @param name - * @param val - */ - public ReflectUtils setEnumVal(Class clas, String name, String val){ - try { - return field(name, Enum.valueOf((Class) clas, val)); - } catch (Exception e) { - } - return this; - } - - /** - * 通过反射获取全部字段 - * @param obj - * @param name - * @return - * @throws Exception - */ - public static Object getDeclaredField(Object obj, String name) throws Exception { - Field f = obj.getClass().getDeclaredField(name); - f.setAccessible(true); - Object out = f.get(obj); - return out; - } - - /** - * 循环向上转型, 获取对象的 DeclaredField - * @param object - * @param fieldName - * @return - */ - public static Field getDeclaredFieldBase(Object object, String fieldName) { - return getDeclaredFieldBase(object, fieldName, false); - } - - /** - * 循环向上转型, 获取对象的 DeclaredField - * @param object : 子类对象 - * @param fieldName : 父类中的属性名 - * @param isSuper 是否一直跟到最后, 如果父类还有父类,并且有相同变量名, 则设置isSuper = true,一直会跟到最后的变量 - * @return 父类中的属性对象 - */ - public static Field getDeclaredFieldBase(Object object, String fieldName, boolean isSuper){ - Field field = null ; - Class clazz = object.getClass() ; - for(; clazz != Object.class ; clazz = clazz.getSuperclass()) { - try { - field = clazz.getDeclaredField(fieldName); - if (!isSuper){ - return field; - } - } catch (Exception e) { - //这里甚么都不要做!并且这里的异常必须这样写,不能抛出去。 - //如果这里的异常打印或者往外抛,则就不会执行clazz = clazz.getSuperclass(),最后就不会进入到父类中了 - } - } - return field; - } - - // ================ - // ==== method ==== - // ================ - - /** - * 设置反射的方法 - * @param name 方法名 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public ReflectUtils method(final String name) throws ReflectException { - return method(name, new Object[0]); - } - - /** - * 设置反射的方法 - * @param name 方法名 - * @param args 方法需要的参数 - * @return {@link ReflectUtils} - * @throws ReflectException 反射异常 - */ - public ReflectUtils method(final String name, final Object... args) throws ReflectException { - Class[] types = getArgsType(args); - try { - Method method = exactMethod(name, types); - return method(method, object, args); - } catch (NoSuchMethodException e) { - try { - Method method = similarMethod(name, types); - return method(method, object, args); - } catch (NoSuchMethodException e1) { - throw new ReflectException(e1); - } - } - } - - private ReflectUtils method(final Method method, final Object obj, final Object... args) { - try { - accessible(method); - if (method.getReturnType() == void.class) { - method.invoke(obj, args); - return reflect(obj); - } else { - return reflect(method.invoke(obj, args)); - } - } catch (Exception e) { - throw new ReflectException(e); - } - } - - private Method exactMethod(final String name, final Class[] types) throws NoSuchMethodException { - Class type = type(); - try { - return type.getMethod(name, types); - } catch (NoSuchMethodException e) { - do { - try { - return type.getDeclaredMethod(name, types); - } catch (NoSuchMethodException ignore) { - } - type = type.getSuperclass(); - } while (type != null); - throw new NoSuchMethodException(); - } - } - - private Method similarMethod(final String name, final Class[] types) throws NoSuchMethodException { - Class type = type(); - List methods = new ArrayList<>(); - for (Method method : type.getMethods()) { - if (isSimilarSignature(method, name, types)) { - methods.add(method); - } - } - if (!methods.isEmpty()) { - sortMethods(methods); - return methods.get(0); - } - do { - for (Method method : type.getDeclaredMethods()) { - if (isSimilarSignature(method, name, types)) { - methods.add(method); - } - } - if (!methods.isEmpty()) { - sortMethods(methods); - return methods.get(0); - } - type = type.getSuperclass(); - } while (type != null); - - throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types) + " could be found on type " + type() + "."); - } - - private void sortMethods(final List methods) { - Collections.sort(methods, new Comparator() { - @Override - public int compare(Method o1, Method o2) { - Class[] types1 = o1.getParameterTypes(); - Class[] types2 = o2.getParameterTypes(); - int len = types1.length; - for (int i = 0; i < len; i++) { - if (!types1[i].equals(types2[i])) { - if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { - return 1; - } else { - return -1; - } - } - } - return 0; - } - }); - } - - private boolean isSimilarSignature(final Method possiblyMatchingMethod, final String desiredMethodName, final Class[] desiredParamTypes) { - return possiblyMatchingMethod.getName().equals(desiredMethodName) && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes); - } - - private boolean match(final Class[] declaredTypes, final Class[] actualTypes) { - if (declaredTypes.length == actualTypes.length) { - for (int i = 0; i < actualTypes.length; i++) { - if (actualTypes[i] == NULL.class || wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) { - continue; - } - return false; - } - return true; - } else { - return false; - } - } - - private T accessible(T accessible) { - if (accessible == null) return null; - if (accessible instanceof Member) { - Member member = (Member) accessible; - if (Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(member.getDeclaringClass().getModifiers())) { - return accessible; - } - } - if (!accessible.isAccessible()) accessible.setAccessible(true); - return accessible; - } - - public Class type() { - return type; - } - - private Class wrapper(final Class type) { - if (type == null) { - return null; - } else if (type.isPrimitive()) { - if (boolean.class == type) { - return Boolean.class; - } else if (int.class == type) { - return Integer.class; - } else if (long.class == type) { - return Long.class; - } else if (short.class == type) { - return Short.class; - } else if (byte.class == type) { - return Byte.class; - } else if (double.class == type) { - return Double.class; - } else if (float.class == type) { - return Float.class; - } else if (char.class == type) { - return Character.class; - } else if (void.class == type) { - return Void.class; - } - } - return type; - } - - /** - * 获取反射想要获取的 - * @param 返回的范型 - * @return 反射想要获取的 - */ - @SuppressWarnings("unchecked") - public T get() { - return (T) object; - } - - @Override - public int hashCode() { - return object.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj instanceof ReflectUtils && object.equals(((ReflectUtils) obj).get()); - } - - @Override - public String toString() { - return object.toString(); - } - - // == - - /** 内部标记 null */ - private static class NULL { - } - - /** 定义反射异常类 */ - public static class ReflectException extends RuntimeException { - - private static final long serialVersionUID = 858774075258496016L; - - public ReflectException(String message) { - super(message); - } - - public ReflectException(String message, Throwable cause) { - super(message, cause); - } - - public ReflectException(Throwable cause) { - super(cause); - } - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ScaleUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ScaleUtils.java deleted file mode 100644 index 1c81a5781f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ScaleUtils.java +++ /dev/null @@ -1,138 +0,0 @@ -package dev.utils.common; - -import dev.utils.JCLogUtils; - -/** - * detail: 计算比例方法 - * Created by Ttt - */ -public final class ScaleUtils { - - private ScaleUtils() { - } - - // 日志TAG - private static final String TAG = ScaleUtils.class.getSimpleName(); - - /** - * 计算缩放比例 - 根据宽度比例转换高度 - * @param width 需要的最终宽度 - * @param cWidth 当前宽度 - * @param cHeight 当前高度 - * @return [0] = 宽度, [1] = 高度 - */ - public static int[] calcScaleToWidth(int width, int cWidth, int cHeight){ - try { - if (cWidth == 0){ - return new int[] { 0, 0 }; - } - // 计算比例 - float scale = ((float) width) / ((float) cWidth); - // 计算缩放后的高度 - int sHeight = (int) (scale * (float) cHeight); - // 返回对应的数据 - return new int[] { width, sHeight }; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "calcScaleToWidth"); - } - return null; - } - - /** - * 计算缩放比例 - 根据高度比例转换宽度 - * @param height 需要的最终高度 - * @param cWidth 当前宽度 - * @param cHeight 当前高度 - * @return [0] = 宽度, [1] = 高度 - */ - public static int[] calcScaleToHeight(int height, int cWidth, int cHeight){ - try { - if (cHeight == 0){ - return new int[] { 0, 0 }; - } - // 计算比例 - float scale = ((float) height) / ((float) cHeight); - // 计算缩放后的宽度 - int sWidth = (int) (scale * (float) cWidth); - // 返回对应的数据 - return new int[] { sWidth, height }; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "calcScaleToHeight"); - } - return null; - } - - /** - * 通过宽度,高度,根据对应的比例 -> 转换成对应的比例宽度高度 - 智能转换 - * @param width - * @param height - * @param wScale - * @param hScale - * @return - */ - public static int[] calcWidthHeightToScale(int width, int height, float wScale, float hScale){ - try { - // 如果宽度的比例,大于等于高度比例 - if (wScale >= hScale){ // 以宽度为基准 - // 设置宽度 -> 以宽度为基准 - int sWidth = width; - // 计算宽度 - int sHeight = (int) (((float) sWidth) * (hScale / wScale)); - // 返回对应的比例 - return new int[] { sWidth , sHeight }; - } else { // 以高度为基准 - // 设置高度 - int sHeight = height; - // 同步缩放比例 - int sWidth = (int) (((float) sHeight) * (wScale / hScale)); - // 返回对应的比例 - return new int[] { sWidth , sHeight }; - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "calcWidthHeightToScale"); - } - return null; - } - - /** - * 以宽度为基准 -> 转换对应比例的高度 - * @param width - * @param wScale - * @param hScale - * @return - */ - public static int[] calcWidthToScale(int width, float wScale, float hScale){ - try { - // 设置宽度 - int sWidth = width; - // 计算高度 - int sHeight = (int) (((float) sWidth) * (hScale / wScale)); - // 返回对应的比例 - return new int[] { sWidth , sHeight }; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "calcWidthToScale"); - } - return null; - } - - /** - * 以高度为基准 -> 转换对应比例的宽度 - * @param height - * @param wScale - * @param hScale - * @return - */ - public static int[] calcHeightToScale(int height, float wScale, float hScale){ - try { - // 设置高度 - int sHeight = height; - // 计算宽度 - int sWidth = (int) (((float) sHeight) * (wScale / hScale)); - // 返回对应的比例 - return new int[] { sWidth , sHeight }; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "calcHeightToScale"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/SingletonUtils.java b/DevLibUtils/src/main/java/dev/utils/common/SingletonUtils.java deleted file mode 100644 index 9f1b25852a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/SingletonUtils.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.utils.common; - -/** - * detail: 单例工具类 - * Created by Ttt - */ -public abstract class SingletonUtils { - - private T instance; - - protected abstract T newInstance(); - - public final T getInstance() { - if (instance == null) { - synchronized (SingletonUtils.class) { - if (instance == null) { - instance = newInstance(); - } - } - } - return instance; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/StreamUtils.java b/DevLibUtils/src/main/java/dev/utils/common/StreamUtils.java deleted file mode 100644 index 10c3eb2564..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/StreamUtils.java +++ /dev/null @@ -1,185 +0,0 @@ -package dev.utils.common; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; - -import dev.utils.JCLogUtils; - -/** - * detail: 流操作工具类 - * Created by Ttt - */ -public final class StreamUtils { - - private StreamUtils(){ - } - - // 日志TAG - private static final String TAG = StreamUtils.class.getSimpleName(); - - /** - * Input stream to output stream. - * @param is The input stream. - * @return output stream - */ - public static ByteArrayOutputStream input2OutputStream(final InputStream is) { - if (is == null) return null; - try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] b = new byte[1024]; - int len; - while ((len = is.read(b, 0, 1024)) != -1) { - os.write(b, 0, len); - } - return os; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "input2OutputStream"); - return null; - } finally { - CloseUtils.closeIO(is); - } - } - - /** - * Output stream to input stream. - * @param out The output stream. - * @return input stream - */ - public ByteArrayInputStream output2InputStream(final OutputStream out) { - if (out == null) return null; - return new ByteArrayInputStream(((ByteArrayOutputStream) out).toByteArray()); - } - - /** - * Input stream to bytes. - * @param is The input stream. - * @return bytes - */ - public static byte[] inputStream2Bytes(final InputStream is) { - if (is == null) return null; - return input2OutputStream(is).toByteArray(); - } - - /** - * Bytes to input stream. - * @param bytes The bytes. - * @return input stream - */ - public static InputStream bytes2InputStream(final byte[] bytes) { - if (bytes == null || bytes.length <= 0) return null; - return new ByteArrayInputStream(bytes); - } - - /** - * Output stream to bytes. - * @param out The output stream. - * @return bytes - */ - public static byte[] outputStream2Bytes(final OutputStream out) { - if (out == null) return null; - return ((ByteArrayOutputStream) out).toByteArray(); - } - - /** - * Bytes to output stream. - * @param bytes The bytes. - * @return output stream - */ - public static OutputStream bytes2OutputStream(final byte[] bytes) { - if (bytes == null || bytes.length <= 0) return null; - ByteArrayOutputStream os = null; - try { - os = new ByteArrayOutputStream(); - os.write(bytes); - return os; - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "bytes2OutputStream"); - return null; - } finally { - CloseUtils.closeIO(os); - } - } - - /** - * Input stream to string. - * @param is The input stream. - * @param charsetName The name of charset. - * @return string - */ - public static String inputStream2String(final InputStream is, final String charsetName) { - if (is == null || isSpace(charsetName)) return null; - try { - return new String(inputStream2Bytes(is), charsetName); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "inputStream2String"); - return null; - } - } - - /** - * String to input stream. - * @param string The string. - * @param charsetName The name of charset. - * @return input stream - */ - public static InputStream string2InputStream(final String string, final String charsetName) { - if (string == null || isSpace(charsetName)) return null; - try { - return new ByteArrayInputStream(string.getBytes(charsetName)); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "string2InputStream"); - return null; - } - } - - /** - * Output stream to string. - * @param out The output stream. - * @param charsetName The name of charset. - * @return string - */ - public static String outputStream2String(final OutputStream out, final String charsetName) { - if (out == null || isSpace(charsetName)) return null; - try { - return new String(outputStream2Bytes(out), charsetName); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "outputStream2String"); - return null; - } - } - - /** - * String to output stream. - * @param string The string. - * @param charsetName The name of charset. - * @return output stream - */ - public static OutputStream string2OutputStream(final String string, final String charsetName) { - if (string == null || isSpace(charsetName)) return null; - try { - return bytes2OutputStream(string.getBytes(charsetName)); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "string2OutputStream"); - return null; - } - } - - // = - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/StringUtils.java b/DevLibUtils/src/main/java/dev/utils/common/StringUtils.java deleted file mode 100644 index f4c8d14112..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/StringUtils.java +++ /dev/null @@ -1,471 +0,0 @@ -package dev.utils.common; - -import java.net.URLEncoder; -import java.util.Locale; - -import dev.utils.JCLogUtils; - -/** - * detail: 字符串工具类 - * Created by Ttt - */ -public final class StringUtils { - - private StringUtils() { - } - - // 日志TAG - private static final String TAG = StringUtils.class.getSimpleName(); - /** 换行字符串 */ - public static final String NEW_LINE_STR = System.getProperty("line.separator"); - /** 换行字符串 - 两行 */ - public static final String NEW_LINE_STR_X2 = NEW_LINE_STR + NEW_LINE_STR; - - /** - * 获取长度,如果字符串为null,则返回0 - * @param str - * @return - */ - public static int length(String str) { - return str == null ? 0 : str.length(); - } - - /** - * 判断是否为null - * @param str - * @return - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } - - /** - * 清空全部空格,并返回处理后的字符串 - * @param str - * @return - */ - public static String toClearSpace(String str) { - if (isEmpty(str)) { - return str; - } - return str.replaceAll(" ", ""); - } - - /** - * 清空前后空格,并返回处理后的字符串 - * @param str - * @return - */ - public static String toClearSpaceTrim(String str) { - if (isEmpty(str)) return str; - // 如果前面或者后面都是 空格开头,就一直进行处理 - while(str.startsWith(" ") || str.endsWith(" ")) { - str = str.trim(); - } - return str; - } - - // == - - /** - * 字符串进行 GBK 编码 - * @param str - * @return - */ - public static String toGBKEncode(String str){ - return toStrEncode(str, "GBK"); - } - - /** - * 字符串进行 GBK2312 编码 - * @param str - * @return - */ - public static String toGBK2312Encode(String str){ - return toStrEncode(str, "GBK-2312"); - } - - /** - * 字符串进行 UTF-8 编码 - * @param str - * @return - */ - public static String toUTF8Encode(String str){ - return toStrEncode(str, "UTF-8"); - } - - /** - * 进行字符串编码 - * @param str - * @param enc - * @return - */ - public static String toStrEncode(String str, String enc){ - try { - return new String(str.getBytes(), enc); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toStrEncode"); - } - return str; - } - - // == - - /** - * 进行 URL 编码,默认UTF-8 - * @param str - * @return - */ - public static String toUrlEncode(String str){ - return toUrlEncode(str, "UTF-8"); - } - - /** - * 进行 URL 编码 - * @param str - * @param enc - * @return - */ - public static String toUrlEncode(String str, String enc){ - try { - return URLEncoder.encode(str, enc); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "toUrlEncode"); - // 如果URL编码失败,则直接进行字符串编码 - return toStrEncode(str, enc); - } - } - - // = - - /** - * 将字符串转移为ASCII码 - * @param str 字符串 - * @return 字符串ASCII码 - */ - public static String toASCII(String str) { - if (isEmpty(str)) return str; - StringBuffer strBuf = new StringBuffer(); - byte[] bytes = str.getBytes(); - for (int i = 0, len = bytes.length; i < len; i++) { - strBuf.append(Integer.toHexString(bytes[i] & 0xff)); - } - return strBuf.toString(); - } - - /** - * 将字符串转移为Unicode码 - * @param str 字符串 - * @return - */ - public static String toUnicode(String str) { - if (isEmpty(str)) return str; - StringBuffer strBuf = new StringBuffer(); - char[] chars = str.toCharArray(); - for (int i = 0, len = chars.length; i < len; i++) { - strBuf.append("\\u").append(Integer.toHexString(chars[i])); - } - return strBuf.toString(); - } - - /** - * 将字符串转移为Unicode码 - * @param chars 字符数组 - * @return - */ - public static String toUnicodeString(char[] chars) { - if (chars == null) return null; - StringBuffer strBuf = new StringBuffer(); - for (int i = 0, len = chars.length; i < len; i++) { - strBuf.append("\\u").append(Integer.toHexString(chars[i])); - } - return strBuf.toString(); - } - - /** - * 转化为半角字符 - * @param str 待转字符串 - * @return 半角字符串 - */ - public static String toDBC(final String str) { - if (isEmpty(str)) return str; - char[] chars = str.toCharArray(); - for (int i = 0, len = chars.length; i < len; i++) { - if (chars[i] == 12288) { - chars[i] = ' '; - } else if (65281 <= chars[i] && chars[i] <= 65374) { - chars[i] = (char) (chars[i] - 65248); - } else { - chars[i] = chars[i]; - } - } - return new String(chars); - } - - /** - * 转化为全角字符 如: a => a A => A - * @param str 待转字符串 - * @return 全角字符串 - */ - public static String toSBC(final String str) { - if (isEmpty(str)) return str; - char[] chars = str.toCharArray(); - for (int i = 0, len = chars.length; i < len; i++) { - if (chars[i] == ' ') { - chars[i] = (char) 12288; - } else if (33 <= chars[i] && chars[i] <= 126) { - chars[i] = (char) (chars[i] + 65248); - } else { - chars[i] = chars[i]; - } - } - return new String(chars); - } - - // == - - /** - * byte[]数组转换为16进制的字符串 - * @param data 要转换的字节数组 - * @return 转换后的结果 - */ - public static final String byteArrayToHexString(byte[] data) { - if (data == null) return null; - StringBuilder sBuilder = new StringBuilder(data.length * 2); - for (byte b : data) { - int v = b & 0xff; - if (v < 16) { - sBuilder.append('0'); - } - sBuilder.append(Integer.toHexString(v)); - } - return sBuilder.toString().toUpperCase(Locale.getDefault()); - } - - /** - * 进行转换 - * @param bData - * @param hexDigits - * @return - */ - public static String toHexString(byte[] bData, char[] hexDigits) { - if (bData == null || hexDigits == null) return null; - StringBuilder sBuilder = new StringBuilder(bData.length * 2); - for (int i = 0; i < bData.length; i++) { - sBuilder.append(hexDigits[(bData[i] & 0xf0) >>> 4]); - sBuilder.append(hexDigits[bData[i] & 0x0f]); - } - return sBuilder.toString(); - } - - /** - * 16进制表示的字符串转换为字节数组 - * @param str 16进制表示的字符串 - * @return byte[] 字节数组 - */ - public static byte[] hexStringToByteArray(String str) { - if (isEmpty(str)) return null; - int len = str.length(); - byte[] d = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - // 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个进制字节 - d[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4) + Character.digit(str.charAt(i + 1), 16)); - } - return d; - } - - /** - * 检测String是否全是中文 - * @param str - * @return - */ - public static boolean checkCheseToString(String str) { - if (isEmpty(str)) return false; - boolean result = true; - char[] chars = str.toCharArray(); - for (int i = 0, len = chars.length; i < len; i++) { - if (!isChinese(chars[i])) { - result = false; - break; - } - } - return result; - } - - /** - * 判定输入汉字 - * @param c - * @return - */ - public static boolean isChinese(char c) { - Character.UnicodeBlock ub = Character.UnicodeBlock.of(c); - if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS - || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS - || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A - || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION - || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION - || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { - return true; - } - return false; - } - - // == 字符串处理方法 == - - /** - * 首字母大写 - * @param str 待转字符串 - * @return 首字母大写字符串 - */ - public static String upperFirstLetter(final String str) { - if (isEmpty(str) || !Character.isLowerCase(str.charAt(0))) return str; - try { - return String.valueOf((char) (str.charAt(0) - 32)) + str.substring(1); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "upperFirstLetter"); - return str; - } - } - - /** - * 首字母小写 - * @param str 待转字符串 - * @return 首字母小写字符串 - */ - public static String lowerFirstLetter(final String str) { - if (isEmpty(str) || !Character.isUpperCase(str.charAt(0))) return str; - try { - return String.valueOf((char) (str.charAt(0) + 32)) + str.substring(1); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "lowerFirstLetter"); - return str; - } - } - - /** - * 反转字符串 - * @param str 待反转字符串 - * @return 反转字符串 - */ - public static String reverse(final String str) { - int len = length(str); - if (len <= 1) return str; - int mid = len >> 1; - char[] chars = str.toCharArray(); - char c; - for (int i = 0; i < mid; ++i) { - c = chars[i]; - chars[i] = chars[len - i - 1]; - chars[len - i - 1] = c; - } - return new String(chars); - } - - /** - * 字符串连接,将参数列表拼接为一个字符串 - * @param more 追加 - * @return 返回拼接后的字符串 - */ - public static String concat(Object... more) { - return concatSpiltWith("", more); - } - - /** - * 字符串连接,将参数列表拼接为一个字符串 - * @param split - * @param more - * @return 回拼接后的字符串 - */ - public static String concatSpiltWith(String split, Object... more) { - if (more == null) return null; - StringBuilder buf = new StringBuilder(); - for (int i = 0, len = more.length; i < len; i++) { - if (i != 0) buf.append(split); - buf.append(more[i]); - } - return buf.toString(); - } - - /** - * 下划线命名转为驼峰命名 - * @param str 下划线命名格式 - * @return 驼峰命名格式 - */ - public static final String underScoreCase2CamelCase(String str) { - if (isEmpty(str)) return str; - if (!str.contains("_")) return str; - StringBuilder sb = new StringBuilder(); - char[] chars = str.toCharArray(); - boolean hitUnderScore = false; - sb.append(chars[0]); - for (int i = 1, len = chars.length; i < len; i++) { - char c = chars[i]; - if (c == '_') { - hitUnderScore = true; - } else { - if (hitUnderScore) { - sb.append(Character.toUpperCase(c)); - hitUnderScore = false; - } else { - sb.append(c); - } - } - } - return sb.toString(); - } - - /** - * 驼峰命名法转为下划线命名 - * @param str 驼峰命名格式 - * @return 下划线命名格式 - */ - public static final String camelCase2UnderScoreCase(String str) { - if (isEmpty(str)) return str; - StringBuilder sb = new StringBuilder(); - char[] chars = str.toCharArray(); - for (int i = 0, len = chars.length; i < len; i++) { - char c = chars[i]; - if (Character.isUpperCase(c)) { - sb.append("_").append(Character.toLowerCase(c)); - } else { - sb.append(c); - } - } - return sb.toString(); - } - - /** - * 数据库字符转义 - * @param keyWord - * @return - */ - public static String sqliteEscape(String keyWord){ - if (isEmpty(keyWord)) return keyWord; - // 替换关键字 - keyWord = keyWord.replace("/", "//"); - keyWord = keyWord.replace("'", "''"); - keyWord = keyWord.replace("[", "/["); - keyWord = keyWord.replace("]", "/]"); - keyWord = keyWord.replace("%", "/%"); - keyWord = keyWord.replace("&","/&"); - keyWord = keyWord.replace("_", "/_"); - keyWord = keyWord.replace("(", "/("); - keyWord = keyWord.replace(")", "/)"); - return keyWord; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/ZipUtils.java b/DevLibUtils/src/main/java/dev/utils/common/ZipUtils.java deleted file mode 100644 index d8e8065f6b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/ZipUtils.java +++ /dev/null @@ -1,411 +0,0 @@ -package dev.utils.common; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Enumeration; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; - -/** - * detail: 压缩相关工具类 - * Created by Ttt - */ -public final class ZipUtils { - - private ZipUtils() { - } - - // 缓存大小 - private static final int BUFFER_LEN = 8192; - - /** - * 批量压缩文件 - * @param resFiles 待压缩文件路径集合 - * @param zipFilePath 压缩文件路径 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO错误时抛出 - */ - public static boolean zipFiles(final Collection resFiles, final String zipFilePath) throws IOException { - return zipFiles(resFiles, zipFilePath, null); - } - - /** - * 批量压缩文件 - * @param resFilePaths 待压缩文件路径集合 - * @param zipFilePath 压缩文件路径 - * @param comment 压缩文件的注释 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO错误时抛出 - */ - public static boolean zipFiles(final Collection resFilePaths, final String zipFilePath, final String comment) throws IOException { - if (resFilePaths == null || zipFilePath == null) return false; - ZipOutputStream zos = null; - try { - zos = new ZipOutputStream(new FileOutputStream(zipFilePath)); - for (String resFile : resFilePaths) { - if (!zipFile(getFileByPath(resFile), "", zos, comment)) return false; - } - return true; - } finally { - if (zos != null) { - zos.finish(); - CloseUtils.closeIO(zos); - } - } - } - - /** - * 批量压缩文件 - * @param resFiles 待压缩文件集合 - * @param zipFile 压缩文件 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO错误时抛出 - */ - public static boolean zipFiles(final Collection resFiles, final File zipFile) throws IOException { - return zipFiles(resFiles, zipFile, null); - } - - /** - * 批量压缩文件 - * @param resFiles 待压缩文件集合 - * @param zipFile 压缩文件 - * @param comment 压缩文件的注释 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO错误时抛出 - */ - public static boolean zipFiles(final Collection resFiles, final File zipFile, final String comment) throws IOException { - if (resFiles == null || zipFile == null) return false; - ZipOutputStream zos = null; - try { - zos = new ZipOutputStream(new FileOutputStream(zipFile)); - for (File resFile : resFiles) { - if (!zipFile(resFile, "", zos, comment)) return false; - } - return true; - } finally { - if (zos != null) { - zos.finish(); - CloseUtils.closeIO(zos); - } - } - } - - /** - * 压缩文件 - * @param resFilePath 待压缩文件路径 - * @param zipFilePath 压缩文件路径 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO 错误时抛出 - */ - public static boolean zipFile(final String resFilePath, final String zipFilePath) throws IOException { - return zipFile(getFileByPath(resFilePath), getFileByPath(zipFilePath), null); - } - - /** - * 压缩文件 - * @param resFilePath 待压缩文件路径 - * @param zipFilePath 压缩文件路径 - * @param comment 压缩文件的注释 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO 错误时抛出 - */ - public static boolean zipFile(final String resFilePath, final String zipFilePath, final String comment) throws IOException { - return zipFile(getFileByPath(resFilePath), getFileByPath(zipFilePath), comment); - } - - /** - * 压缩文件 - * @param resFile 待压缩文件 - * @param zipFile 压缩文件 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO 错误时抛出 - */ - public static boolean zipFile(final File resFile, final File zipFile) throws IOException { - return zipFile(resFile, zipFile, null); - } - - /** - * 压缩文件 - * @param resFile 待压缩文件 - * @param zipFile 压缩文件 - * @param comment 压缩文件的注释 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO 错误时抛出 - */ - public static boolean zipFile(final File resFile, final File zipFile, final String comment) throws IOException { - if (resFile == null || zipFile == null) return false; - ZipOutputStream zos = null; - try { - zos = new ZipOutputStream(new FileOutputStream(zipFile)); - return zipFile(resFile, "", zos, comment); - } finally { - if (zos != null) { - CloseUtils.closeIO(zos); - } - } - } - - /** - * 压缩文件 - * @param resFile 待压缩文件 - * @param rootPath 相对于压缩文件的路径 - * @param zos 压缩文件输出流 - * @param comment 压缩文件的注释 - * @return true : 压缩成功, false : 压缩失败 - * @throws IOException IO 错误时抛出 - */ - private static boolean zipFile(final File resFile, String rootPath, final ZipOutputStream zos, final String comment) throws IOException { - rootPath = rootPath + (isSpace(rootPath) ? "" : File.separator) + resFile.getName(); - if (resFile.isDirectory()) { - File[] fileList = resFile.listFiles(); - // 如果是空文件夹那么创建它,我把'/'换为File.separator测试就不成功,eggPain - if (fileList == null || fileList.length <= 0) { - ZipEntry entry = new ZipEntry(rootPath + '/'); - entry.setComment(comment); - zos.putNextEntry(entry); - zos.closeEntry(); - } else { - for (File file : fileList) { - // 如果递归返回 false 则返回 false - if (!zipFile(file, rootPath, zos, comment)) return false; - } - } - } else { - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(resFile)); - ZipEntry entry = new ZipEntry(rootPath); - entry.setComment(comment); - zos.putNextEntry(entry); - byte buffer[] = new byte[BUFFER_LEN]; - int len; - while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) { - zos.write(buffer, 0, len); - } - zos.closeEntry(); - } finally { - CloseUtils.closeIO(is); - } - } - return true; - } - - /** - * 解压文件 - * @param zipFilePath 待解压文件路径 - * @param destDirPath 目标目录路径 - * @return 文件链表 - * @throws IOException IO 错误时抛出 - */ - public static List unzipFile(final String zipFilePath, final String destDirPath) throws IOException { - return unzipFileByKeyword(zipFilePath, destDirPath, null); - } - - /** - * 解压文件 - * @param zipFile 待解压文件 - * @param destDir 目标目录 - * @return 文件链表 - * @throws IOException IO 错误时抛出 - */ - public static List unzipFile(final File zipFile, final File destDir) throws IOException { - return unzipFileByKeyword(zipFile, destDir, null); - } - - /** - * 解压带有关键字的文件 - * @param zipFilePath 待解压文件路径 - * @param destDirPath 目标目录路径 - * @param keyword 关键字 - * @return 返回带有关键字的文件链表 - * @throws IOException IO 错误时抛出 - */ - public static List unzipFileByKeyword(final String zipFilePath, final String destDirPath, final String keyword) throws IOException { - return unzipFileByKeyword(getFileByPath(zipFilePath), getFileByPath(destDirPath), keyword); - } - - /** - * 解压带有关键字的文件 - * @param zipFile 待解压文件 - * @param destDir 目标目录 - * @param keyword 关键字 - * @return 返回带有关键字的文件链表 - * @throws IOException IO 错误时抛出 - */ - public static List unzipFileByKeyword(final File zipFile, final File destDir, final String keyword) throws IOException { - if (zipFile == null || destDir == null) return null; - List files = new ArrayList<>(); - ZipFile zf = new ZipFile(zipFile); - Enumeration entries = zf.entries(); - if (isSpace(keyword)) { - while (entries.hasMoreElements()) { - ZipEntry entry = ((ZipEntry) entries.nextElement()); - String entryName = entry.getName(); - if (!unzipChildFile(destDir, files, zf, entry, entryName)) return files; - } - } else { - while (entries.hasMoreElements()) { - ZipEntry entry = ((ZipEntry) entries.nextElement()); - String entryName = entry.getName(); - if (entryName.contains(keyword)) { - if (!unzipChildFile(destDir, files, zf, entry, entryName)) return files; - } - } - } - return files; - } - - /** - * 解压文件 - * @param destDir - * @param files - * @param zf - * @param entry - * @param entryName - * @return - * @throws IOException - */ - private static boolean unzipChildFile(final File destDir, final List files, final ZipFile zf, final ZipEntry entry, final String entryName) throws IOException { - String filePath = destDir + File.separator + entryName; - File file = new File(filePath); - files.add(file); - if (entry.isDirectory()) { - if (!createOrExistsDir(file)) return false; - } else { - if (!createOrExistsFile(file)) return false; - InputStream in = null; - OutputStream out = null; - try { - in = new BufferedInputStream(zf.getInputStream(entry)); - out = new BufferedOutputStream(new FileOutputStream(file)); - byte buffer[] = new byte[BUFFER_LEN]; - int len; - while ((len = in.read(buffer)) != -1) { - out.write(buffer, 0, len); - } - } finally { - CloseUtils.closeIO(in, out); - } - } - return true; - } - - /** - * 获取压缩文件中的文件路径链表 - * @param zipFilePath 压缩文件路径 - * @return 压缩文件中的文件路径链表 - * @throws IOException IO 错误时抛出 - */ - public static List getFilesPath(final String zipFilePath) throws IOException { - return getFilesPath(getFileByPath(zipFilePath)); - } - - /** - * 获取压缩文件中的文件路径链表 - * @param zipFile 压缩文件 - * @return 压缩文件中的文件路径链表 - * @throws IOException IO 错误时抛出 - */ - public static List getFilesPath(final File zipFile) throws IOException { - if (zipFile == null) return null; - List paths = new ArrayList<>(); - Enumeration entries = new ZipFile(zipFile).entries(); - while (entries.hasMoreElements()) { - paths.add(((ZipEntry) entries.nextElement()).getName()); - } - return paths; - } - - /** - * 获取压缩文件中的注释链表 - * @param zipFilePath 压缩文件路径 - * @return 压缩文件中的注释链表 - * @throws IOException IO 错误时抛出 - */ - public static List getComments(final String zipFilePath) throws IOException { - return getComments(getFileByPath(zipFilePath)); - } - - /** - * 获取压缩文件中的注释链表 - * @param zipFile 压缩文件 - * @return 压缩文件中的注释链表 - * @throws IOException IO 错误时抛出 - */ - public static List getComments(final File zipFile) throws IOException { - if (zipFile == null) return null; - List comments = new ArrayList<>(); - Enumeration entries = new ZipFile(zipFile).entries(); - while (entries.hasMoreElements()) { - ZipEntry entry = ((ZipEntry) entries.nextElement()); - comments.add(entry.getComment()); - } - return comments; - } - - // = - - /** - * 判断目录是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsDir(final File file) { - // 如果存在,是目录则返回 true,是文件则返回 false,不存在则返回是否创建成功 - return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); - } - - /** - * 判断文件是否存在,不存在则判断是否创建成功 - * @param file 文件 - * @return true : 存在或创建成功, false : 不存在或创建失败 - */ - private static boolean createOrExistsFile(final File file) { - if (file == null) return false; - // 如果存在,是文件则返回 true,是目录则返回 false - if (file.exists()) return file.isFile(); - // 判断文件是否存在, 不存在则直接返回 - if (!createOrExistsDir(file.getParentFile())) return false; - try { - // 存在, 则返回新的路径 - return file.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - /** - * 获取文件 - * @param filePath The path of file. - * @return - */ - private static File getFileByPath(final String filePath){ - return filePath != null ? new File(filePath) : null; - } - - /** - * 判断字符串是否为 null 或全为空白字符 - * @param str 待校验字符串 - * @return - */ - private static boolean isSpace(final String str) { - if (str == null) return true; - for (int i = 0, len = str.length(); i < len; ++i) { - if (!Character.isWhitespace(str.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/assist/Averager.java b/DevLibUtils/src/main/java/dev/utils/common/assist/Averager.java deleted file mode 100644 index 1c8d3cfa62..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/assist/Averager.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.utils.common.assist; - -import java.util.ArrayList; - -/** - * detail: 用以统计平均数 - * @author MaTianyu - */ -public class Averager { - - private ArrayList numList = new ArrayList(); - - /** - * 添加一个数字 - * @param num - */ - public synchronized void add(Number num) { - numList.add(num); - } - - /** - * 清除全部 - */ - public void clear() { - numList.clear(); - } - - /** - * 返回参与均值计算的数字个数 - * @return - */ - public Number size() { - return numList.size(); - } - - /** - * 获取平均数 - * @return - */ - public Number getAverage() { - if (numList.size() == 0) { - return 0; - } else { - Float sum = 0f; - for (int i = 0, size = numList.size(); i < size; i++) { - sum = sum.floatValue() + numList.get(i).floatValue(); - } - return sum / numList.size(); - } - } - - /** - * 打印数字列 - * @return - */ - public String print() { - return "PrintList(" + size() + "): " + numList; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/assist/Base64.java b/DevLibUtils/src/main/java/dev/utils/common/assist/Base64.java deleted file mode 100644 index 2761cd6002..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/assist/Base64.java +++ /dev/null @@ -1,741 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.utils.common.assist; - -import java.io.UnsupportedEncodingException; - -/** - * detail: Base64 工具类 - * Utilities for encoding and decoding the Base64 representation of - * binary data. See RFCs 2045 and 3548. - */ -public class Base64 { - /** - * Default values for encoder/decoder flags. - */ - public static final int DEFAULT = 0; - - /** - * Encoder flag bit to omit the padding '=' characters at the end - * of the output (if any). - */ - public static final int NO_PADDING = 1; - - /** - * Encoder flag bit to omit all line terminators (i.e., the output - * will be on one long line). - */ - public static final int NO_WRAP = 2; - - /** - * Encoder flag bit to indicate lines should be terminated with a - * CRLF pair instead of just an LF. Has no effect if {@code - * NO_WRAP} is specified as well. - */ - public static final int CRLF = 4; - - /** - * Encoder/decoder flag bit to indicate using the "URL and - * filename safe" variant of Base64 (see RFC 3548 section 4) where - * {@code -} and {@code _} are used in place of {@code +} and - * {@code /}. - */ - public static final int URL_SAFE = 8; - - /** - * Flag to pass to {@link Base64OutputStream} to indicate that it - * should not close the output stream it is wrapping when it - * itself is closed. - */ - public static final int NO_CLOSE = 16; - - // -------------------------------------------------------- - // shared code - // -------------------------------------------------------- - - /* package */ static abstract class Coder { - public byte[] output; - public int op; - - /** - * Encode/decode another block of input data. this.output is - * provided by the caller, and must be big enough to hold all - * the coded data. On exit, this.opwill be set to the length - * of the coded data. - * - * @param finish true if this is the final call to process for - * this object. Will finalize the coder state and - * include any final bytes in the output. - * - * @return true if the input so far is good; false if some - * error has been detected in the input stream.. - */ - public abstract boolean process(byte[] input, int offset, int len, boolean finish); - - /** - * @return the maximum number of bytes a call to process() - * could produce for the given number of input bytes. This may - * be an overestimate. - */ - public abstract int maxOutputSize(int len); - } - - // -------------------------------------------------------- - // decoding - // -------------------------------------------------------- - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - * The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param str the input String to decode, which is converted to - * bytes using the default charset - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(String str, int flags) { - return decode(str.getBytes(), flags); - } - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - * The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the input array to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(byte[] input, int flags) { - return decode(input, 0, input.length, flags); - } - - /** - * Decode the Base64-encoded data in input and return the data in - * a new byte array. - * - * The padding '=' characters at the end are considered optional, but - * if any are present, there must be the correct number of them. - * - * @param input the data to decode - * @param offset the position within the input array at which to start - * @param len the number of bytes of input to decode - * @param flags controls certain features of the decoded output. - * Pass {@code DEFAULT} to decode standard Base64. - * - * @throws IllegalArgumentException if the input contains - * incorrect padding - */ - public static byte[] decode(byte[] input, int offset, int len, int flags) { - // Allocate space for the most data the input could represent. - // (It could contain less if it contains whitespace, etc.) - Decoder decoder = new Decoder(flags, new byte[len*3/4]); - - if (!decoder.process(input, offset, len, true)) { - throw new IllegalArgumentException("bad base-64"); - } - - // Maybe we got lucky and allocated exactly enough output space. - if (decoder.op == decoder.output.length) { - return decoder.output; - } - - // Need to shorten the array, so allocate a new one of the - // right size and copy. - byte[] temp = new byte[decoder.op]; - System.arraycopy(decoder.output, 0, temp, 0, decoder.op); - return temp; - } - - /* package */ static class Decoder extends Coder { - /** - * Lookup table for turning bytes into their position in the - * Base64 alphabet. - */ - private static final int DECODE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - - /** - * Decode lookup table for the "web safe" variant (RFC 3548 - * sec. 4) where - and _ replace + and /. - */ - private static final int DECODE_WEBSAFE[] = { - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - }; - - /** Non-data values in the DECODE arrays. */ - private static final int SKIP = -1; - private static final int EQUALS = -2; - - /** - * States 0-3 are reading through the next input tuple. - * State 4 is having read one '=' and expecting exactly - * one more. - * State 5 is expecting no more data or padding characters - * in the input. - * State 6 is the error state; an error has been detected - * in the input and no future input can "fix" it. - */ - private int state; // state number (0 to 6) - private int value; - - final private int[] alphabet; - - public Decoder(int flags, byte[] output) { - this.output = output; - - alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; - state = 0; - value = 0; - } - - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could decode to. - */ - public int maxOutputSize(int len) { - return len * 3/4 + 10; - } - - /** - * Decode another block of input data. - * - * @return true if the state machine is still healthy. false if - * bad base-64 data has been detected in the input stream. - */ - public boolean process(byte[] input, int offset, int len, boolean finish) { - if (this.state == 6) return false; - - int p = offset; - len += offset; - - // Using local variables makes the decoder about 12% - // faster than if we manipulate the member variables in - // the loop. (Even alphabet makes a measurable - // difference, which is somewhat surprising to me since - // the member variable is final.) - int state = this.state; - int value = this.value; - int op = 0; - final byte[] output = this.output; - final int[] alphabet = this.alphabet; - - while (p < len) { - // Try the fast path: we're starting a new tuple and the - // next four bytes of the input stream are all data - // bytes. This corresponds to going through states - // 0-1-2-3-0. We expect to use this method for most of - // the data. - // - // If any of the next four bytes of input are non-data - // (whitespace, etc.), value will end up negative. (All - // the non-data values in decode are small negative - // numbers, so shifting any of them up and or'ing them - // together will result in a value with its top bit set.) - // - // You can remove this whole block and the output should - // be the same, just slower. - if (state == 0) { - while (p+4 <= len && - (value = ((alphabet[input[p] & 0xff] << 18) | - (alphabet[input[p+1] & 0xff] << 12) | - (alphabet[input[p+2] & 0xff] << 6) | - (alphabet[input[p+3] & 0xff]))) >= 0) { - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - p += 4; - } - if (p >= len) break; - } - - // The fast path isn't available -- either we've read a - // partial tuple, or the next four input bytes aren't all - // data, or whatever. Fall back to the slower state - // machine implementation. - - int d = alphabet[input[p++] & 0xff]; - - switch (state) { - case 0: - if (d >= 0) { - value = d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 1: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 2: - if (d >= 0) { - value = (value << 6) | d; - ++state; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect exactly one more padding character. - output[op++] = (byte) (value >> 4); - state = 4; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 3: - if (d >= 0) { - // Emit the output triple and return to state 0. - value = (value << 6) | d; - output[op+2] = (byte) value; - output[op+1] = (byte) (value >> 8); - output[op] = (byte) (value >> 16); - op += 3; - state = 0; - } else if (d == EQUALS) { - // Emit the last (partial) output tuple; - // expect no further data or padding characters. - output[op+1] = (byte) (value >> 2); - output[op] = (byte) (value >> 10); - op += 2; - state = 5; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 4: - if (d == EQUALS) { - ++state; - } else if (d != SKIP) { - this.state = 6; - return false; - } - break; - - case 5: - if (d != SKIP) { - this.state = 6; - return false; - } - break; - } - } - - if (!finish) { - // We're out of input, but a future call could provide - // more. - this.state = state; - this.value = value; - this.op = op; - return true; - } - - // Done reading input. Now figure out where we are left in - // the state machine and finish up. - - switch (state) { - case 0: - // Output length is a multiple of three. Fine. - break; - case 1: - // Read one extra input byte, which isn't enough to - // make another output byte. Illegal. - this.state = 6; - return false; - case 2: - // Read two extra input bytes, enough to emit 1 more - // output byte. Fine. - output[op++] = (byte) (value >> 4); - break; - case 3: - // Read three extra input bytes, enough to emit 2 more - // output bytes. Fine. - output[op++] = (byte) (value >> 10); - output[op++] = (byte) (value >> 2); - break; - case 4: - // Read one padding '=' when we expected 2. Illegal. - this.state = 6; - return false; - case 5: - // Read all the padding '='s we expected and no more. - // Fine. - break; - } - - this.state = state; - this.op = op; - return true; - } - } - - // -------------------------------------------------------- - // encoding - // -------------------------------------------------------- - - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static String encodeToString(byte[] input, int flags) { - try { - return new String(encode(input, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - - /** - * Base64-encode the given data and return a newly allocated - * String with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static String encodeToString(byte[] input, int offset, int len, int flags) { - try { - return new String(encode(input, offset, len, flags), "US-ASCII"); - } catch (UnsupportedEncodingException e) { - // US-ASCII is guaranteed to be available. - throw new AssertionError(e); - } - } - - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static byte[] encode(byte[] input, int flags) { - return encode(input, 0, input.length, flags); - } - - /** - * Base64-encode the given data and return a newly allocated - * byte[] with the result. - * - * @param input the data to encode - * @param offset the position within the input array at which to - * start - * @param len the number of bytes of input to encode - * @param flags controls certain features of the encoded output. - * Passing {@code DEFAULT} results in output that - * adheres to RFC 2045. - */ - public static byte[] encode(byte[] input, int offset, int len, int flags) { - Encoder encoder = new Encoder(flags, null); - - // Compute the exact length of the array we will produce. - int output_len = len / 3 * 4; - - // Account for the tail of the data and the padding bytes, if any. - if (encoder.do_padding) { - if (len % 3 > 0) { - output_len += 4; - } - } else { - switch (len % 3) { - case 0: break; - case 1: output_len += 2; break; - case 2: output_len += 3; break; - } - } - - // Account for the newlines, if any. - if (encoder.do_newline && len > 0) { - output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * - (encoder.do_cr ? 2 : 1); - } - - encoder.output = new byte[output_len]; - encoder.process(input, offset, len, true); - - assert encoder.op == output_len; - - return encoder.output; - } - - /* package */ static class Encoder extends Coder { - /** - * Emit a new line every this many output tuples. Corresponds to - * a 76-character line length (the maximum allowable according to - * RFC 2045). - */ - public static final int LINE_GROUPS = 19; - - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', - }; - - /** - * Lookup table for turning Base64 alphabet positions (6 bits) - * into output bytes. - */ - private static final byte ENCODE_WEBSAFE[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', - }; - - final private byte[] tail; - /* package */ int tailLen; - private int count; - - final public boolean do_padding; - final public boolean do_newline; - final public boolean do_cr; - final private byte[] alphabet; - - public Encoder(int flags, byte[] output) { - this.output = output; - - do_padding = (flags & NO_PADDING) == 0; - do_newline = (flags & NO_WRAP) == 0; - do_cr = (flags & CRLF) != 0; - alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; - - tail = new byte[2]; - tailLen = 0; - - count = do_newline ? LINE_GROUPS : -1; - } - - /** - * @return an overestimate for the number of bytes {@code - * len} bytes could encode to. - */ - public int maxOutputSize(int len) { - return len * 8/5 + 10; - } - - public boolean process(byte[] input, int offset, int len, boolean finish) { - // Using local variables makes the encoder about 9% faster. - final byte[] alphabet = this.alphabet; - final byte[] output = this.output; - int op = 0; - int count = this.count; - - int p = offset; - len += offset; - int v = -1; - - // First we need to concatenate the tail of the previous call - // with any input bytes available now and see if we can empty - // the tail. - - switch (tailLen) { - case 0: - // There was no tail. - break; - - case 1: - if (p+2 <= len) { - // A 1-byte tail with at least 2 bytes of - // input available now. - v = ((tail[0] & 0xff) << 16) | - ((input[p++] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - }; - break; - - case 2: - if (p+1 <= len) { - // A 2-byte tail with at least 1 byte of input. - v = ((tail[0] & 0xff) << 16) | - ((tail[1] & 0xff) << 8) | - (input[p++] & 0xff); - tailLen = 0; - } - break; - } - - if (v != -1) { - output[op++] = alphabet[(v >> 18) & 0x3f]; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (--count == 0) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - - // At this point either there is no tail, or there are fewer - // than 3 bytes of input available. - - // The main loop, turning 3 input bytes into 4 output bytes on - // each iteration. - while (p+3 <= len) { - v = ((input[p] & 0xff) << 16) | - ((input[p+1] & 0xff) << 8) | - (input[p+2] & 0xff); - output[op] = alphabet[(v >> 18) & 0x3f]; - output[op+1] = alphabet[(v >> 12) & 0x3f]; - output[op+2] = alphabet[(v >> 6) & 0x3f]; - output[op+3] = alphabet[v & 0x3f]; - p += 3; - op += 4; - if (--count == 0) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - count = LINE_GROUPS; - } - } - - if (finish) { - // Finish up the tail of the input. Note that we need to - // consume any bytes in tail before any bytes - // remaining in input; there should be at most two bytes - // total. - - if (p-tailLen == len-1) { - int t = 0; - v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; - tailLen -= t; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - output[op++] = '='; - } - if (do_newline) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - } else if (p-tailLen == len-2) { - int t = 0; - v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | - (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); - tailLen -= t; - output[op++] = alphabet[(v >> 12) & 0x3f]; - output[op++] = alphabet[(v >> 6) & 0x3f]; - output[op++] = alphabet[v & 0x3f]; - if (do_padding) { - output[op++] = '='; - } - if (do_newline) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - } else if (do_newline && op > 0 && count != LINE_GROUPS) { - if (do_cr) output[op++] = '\r'; - output[op++] = '\n'; - } - - assert tailLen == 0; - assert p == len; - } else { - // Save the leftovers in tail to be consumed on the next - // call to encodeInternal. - - if (p == len-1) { - tail[tailLen++] = input[p]; - } else if (p == len-2) { - tail[tailLen++] = input[p]; - tail[tailLen++] = input[p+1]; - } - } - - this.op = op; - this.count = count; - - return true; - } - } - - private Base64() { } // don't instantiate -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeAverager.java b/DevLibUtils/src/main/java/dev/utils/common/assist/TimeAverager.java deleted file mode 100644 index 54d55ee6c5..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeAverager.java +++ /dev/null @@ -1,60 +0,0 @@ -package dev.utils.common.assist; - -/** - * detail: 时间均值计算器, 只能用于单线程计时。 - * @author MaTianyu - */ -public class TimeAverager { - - /** 计时器 */ - private TimeCounter tc = new TimeCounter(); - - /** 均值器 */ - private Averager av = new Averager(); - - /** - * 一个计时开始 - */ - public long start() { - return tc.start(); - } - - /** - * 一个计时结束 - */ - public long end() { - long time = tc.duration(); - av.add(time); - return time; - } - - /** - * 一个计时结束,并且启动下次计时。 - */ - public long endAndRestart() { - long time = tc.durationRestart(); - av.add(time); - return time; - } - - /** - * 求全部计时均值 - */ - public Number average() { - return av.getAverage(); - } - - /** - * 打印全部时间值 - */ - public void print() { - av.print(); - } - - /** - * 清除数据 - */ - public void clear() { - av.clear(); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeCounter.java b/DevLibUtils/src/main/java/dev/utils/common/assist/TimeCounter.java deleted file mode 100644 index 0bbf7fd11b..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeCounter.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.utils.common.assist; - -/** - * detail: 时间计时器 - * @author MaTianyu - */ -public class TimeCounter { - - private long t; - - public TimeCounter() { - start(); - } - - /** - * Count start. - */ - public long start() { - t = System.currentTimeMillis(); - return t; - } - - /** - * Get duration and restart. - */ - public long durationRestart() { - long now = System.currentTimeMillis(); - long d = now - t; - t = now; - return d; - } - - /** - * Get duration. - */ - public long duration() { - return System.currentTimeMillis() - t; - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeKeeper.java b/DevLibUtils/src/main/java/dev/utils/common/assist/TimeKeeper.java deleted file mode 100644 index 7549c4e62d..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/assist/TimeKeeper.java +++ /dev/null @@ -1,75 +0,0 @@ -package dev.utils.common.assist; - -import android.os.SystemClock; - -/** - * detail: 时间堵塞保留 - * @author 氢一 - */ -public class TimeKeeper { - - // 预计堵塞时间 - private long keepTimeMillis; - // 开始计时时间 - private long startMillis; - - /** - * 构造函数 - * @param keepTimeMillis - */ - public TimeKeeper(long keepTimeMillis) { - this.keepTimeMillis = keepTimeMillis; - } - - /** - * 获取预计堵塞时间 - * @return - */ - public long getKeepTimeMillis() { - return keepTimeMillis; - } - - /** - * 设置预计堵塞时间 - * @param keepTimeMillis - * @return - */ - public TimeKeeper setKeepTimeMillis(long keepTimeMillis) { - this.keepTimeMillis = keepTimeMillis; - return this; - } - - /** - * 开始计时 - * @return - */ - public TimeKeeper startNow() { - startMillis = SystemClock.elapsedRealtime(); - return this; - } - - public TimeKeeper waitForEnd(OnEndCallback endCallback) { - long costMillis = SystemClock.elapsedRealtime() - startMillis; - long leftMillis = keepTimeMillis - costMillis; - if (leftMillis > 0) { - SystemClock.sleep(leftMillis); - endCallback.onEnd(costMillis, leftMillis); - } else { - endCallback.onEnd(costMillis, leftMillis); - } - return this; - } - - /** - * 结束通知回调 - */ - public interface OnEndCallback { - - /** - * 结束触发通知方法 - * @param costTime 使用 -> 花费时间 - * @param leftTime 堵塞时间 - */ - void onEnd(long costTime, long leftTime); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/cipher/Base64Cipher.java b/DevLibUtils/src/main/java/dev/utils/common/cipher/Base64Cipher.java deleted file mode 100644 index dd454cb488..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/cipher/Base64Cipher.java +++ /dev/null @@ -1,47 +0,0 @@ -package dev.utils.common.cipher; - -import dev.utils.common.assist.Base64; - -/** - * detail: Baes64 编解码, 进行加密 - * @author MaTianyu - */ -public class Base64Cipher implements Cipher { - - // 中间加密层 - private Cipher cipher; - - public Base64Cipher() { - } - - public Base64Cipher(Cipher cipher) { - this.cipher = cipher; - } - - /** - * 解码 - * @param res - * @return - */ - @Override - public byte[] decrypt(byte[] res) { - res = Base64.decode(res, Base64.DEFAULT); - if (cipher != null) { - res = cipher.decrypt(res); - } - return res; - } - - /** - * 编码 - * @param res - * @return - */ - @Override - public byte[] encrypt(byte[] res) { - if (cipher != null) { - res = cipher.encrypt(res); - } - return Base64.encode(res, Base64.DEFAULT); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/cipher/Cipher.java b/DevLibUtils/src/main/java/dev/utils/common/cipher/Cipher.java deleted file mode 100644 index 2039149ed8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/cipher/Cipher.java +++ /dev/null @@ -1,9 +0,0 @@ -package dev.utils.common.cipher; - -/** - * detail: 通用加密/解密抽象实现类 - * @author MaTianyu - */ -public interface Cipher extends Decrypt, Encrypt { - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/cipher/CipherUtils.java b/DevLibUtils/src/main/java/dev/utils/common/cipher/CipherUtils.java deleted file mode 100644 index 6ebe62a684..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/cipher/CipherUtils.java +++ /dev/null @@ -1,78 +0,0 @@ -package dev.utils.common.cipher; - -import dev.utils.common.ByteUtils; -import dev.utils.common.HexUtils; - -/** - * detail: 加密工具类 - * Created by Ttt - */ -public class CipherUtils { - - private CipherUtils(){ - } - - // 保存加密: 如SP - // 保存到SP,可以通过 SP.put(key, CipherUtils.encrypt(obj, cipher)); - // 获取 Object obj = CipherUtils.decrypt(SP.get(key), cipher); - - /** - * 加密工具类 - * @param obj - * @return - */ - public static String encrypt(Object obj){ - return encrypt(obj, null); - } - - /** - * 加密工具类 - * @param obj - * @param cipher - * @return - */ - public static String encrypt(Object obj, Cipher cipher){ - if (obj == null) return null; - byte[] bytes = ByteUtils.objectToByte(obj); - if (cipher != null) bytes = cipher.encrypt(bytes); - return HexUtils.encodeHexStr(bytes); - } - - // - - - /** - * 解密方法 - * @param hex - * @return - */ - public static Object decrypt (String hex){ - return decrypt(hex, null); - } - - /** - * 解密方法 - * @param hex - * @param cipher - * @return - */ - public static Object decrypt (String hex, Cipher cipher){ - if (hex == null) return null; - byte[] bytes = HexUtils.decodeHex(hex.toCharArray()); - if (cipher != null) bytes = cipher.decrypt(bytes); - Object obj = ByteUtils.byteToObject(bytes); - return obj; - } - - -// private static Cipher cipher = new Cipher() { -// @Override -// public byte[] decrypt(byte[] res) { -// return res; -// } -// -// @Override -// public byte[] encrypt(byte[] res) { -// return res; -// } -// }; -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/cipher/Decrypt.java b/DevLibUtils/src/main/java/dev/utils/common/cipher/Decrypt.java deleted file mode 100644 index e56917739e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/cipher/Decrypt.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.utils.common.cipher; - -/** - * detail: 解密/解码接口 - * @author MaTianyu - */ -public interface Decrypt { - - byte[] decrypt(byte[] res); - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/cipher/Encrypt.java b/DevLibUtils/src/main/java/dev/utils/common/cipher/Encrypt.java deleted file mode 100644 index c95b3f7a62..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/cipher/Encrypt.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.utils.common.cipher; - -/** - * detail: 加密/编码接口 - * @author MaTianyu - */ -public interface Encrypt { - - byte[] encrypt(byte[] res); - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/AESUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/AESUtils.java deleted file mode 100644 index da5c1a1a6a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/AESUtils.java +++ /dev/null @@ -1,75 +0,0 @@ -package dev.utils.common.encrypt; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import dev.utils.JCLogUtils; - -/** - * detail: AES对称加密(Advanced Encryption Standard,高级数据加密标准,AES算法可以有效抵制针对DES的攻击算法,对称加密算法) - * Created by Ttt - */ -public final class AESUtils { - - private AESUtils() { - } - - // 日志TAG - private static final String TAG = AESUtils.class.getSimpleName(); - - /** - * 生成密钥 - * @return - */ - public static byte[] initKey(){ - try { - KeyGenerator keyGen = KeyGenerator.getInstance("AES"); - keyGen.init(256); //192 256 - SecretKey secretKey = keyGen.generateKey(); - return secretKey.getEncoded(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "initKey"); - } - return null; - } - - /** - * AES 加密 - * @param data - * @param key - * @return - */ - public static byte[] encrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] cipherBytes = cipher.doFinal(data); - return cipherBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "encrypt"); - } - return null; - } - - /** - * AES 解密 - * @param data - * @param key - * @return - */ - public static byte[] decrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "AES"); - Cipher cipher = Cipher.getInstance("AES"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] plainBytes = cipher.doFinal(data); - return plainBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "decrypt"); - } - return null; - } -} \ No newline at end of file diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/CRCUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/CRCUtils.java deleted file mode 100644 index ecc931776a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/CRCUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package dev.utils.common.encrypt; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.zip.CRC32; - -import dev.utils.JCLogUtils; - -/** - * detail: CRC 工具类 - * Created by Ttt - */ -public final class CRCUtils { - - private CRCUtils() { - } - - // 日志TAG - private static final String TAG = CRCUtils.class.getSimpleName(); - - /** - * 获取 CRC32 值(返回Long,一定几率上唯一) - * @param str - * @return - */ - public static long getCRC32(String str){ - try { - CRC32 crc32 = new CRC32(); - crc32.update(str.getBytes()); - return crc32.getValue(); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getCRC32"); - } - return -1l; - } - - /** - * 获取 CRC32 值(做了处理,返回String) - * @param str - * @return - */ - public static String getCRC32Str(String str){ - try { - CRC32 crc32 = new CRC32(); - crc32.update(str.getBytes()); - return Long.toHexString(crc32.getValue()); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getCRC32Str"); - } - return null; - } - - /** - * 获取文件CRC32 值 - * @return - */ - public static String getFileCrc32(String fPath){ - try { - InputStream fis = new FileInputStream(fPath); - byte[] buffer = new byte[1024]; - CRC32 crc32 = new CRC32(); - int numRead = 0; - while ((numRead = fis.read(buffer)) > 0) { - crc32.update(buffer, 0, numRead); - } - fis.close(); - return Long.toHexString(crc32.getValue()); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileCrc32"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/DESUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/DESUtils.java deleted file mode 100644 index e13c20f094..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/DESUtils.java +++ /dev/null @@ -1,82 +0,0 @@ -package dev.utils.common.encrypt; - -import java.security.Key; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.SecretKeySpec; - -import dev.utils.JCLogUtils; - -/** - * detail: DES对称加密(Data Encryption Standard,数据加密标准,对称加密算法) - * Created by Ttt - */ -public final class DESUtils { - - private DESUtils() { - } - - // 日志TAG - private static final String TAG = CRCUtils.class.getSimpleName(); - - /** - * 返回可逆算法DES的密钥 - * @param key 前8字节将被用来生成密钥。 - * @return 生成的密钥 - * @throws Exception - */ - public static Key getDESKey(byte[] key){ - try { - DESKeySpec des = new DESKeySpec(key); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - return keyFactory.generateSecret(des); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "getDESKey"); - } - return null; - } - - - /** - * DES 加密 - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] encrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "DES"); - Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] cipherBytes = cipher.doFinal(data); - return cipherBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "encrypt"); - } - return null; - } - - /** - * DES 解密 - * @param data - * @param key - * @return - * @throws Exception - */ - public static byte[] decrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "DES"); - Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] plainBytes = cipher.doFinal(data); - return plainBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "decrypt"); - } - return null; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/EncryptUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/EncryptUtils.java deleted file mode 100644 index b47a4bba4a..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/EncryptUtils.java +++ /dev/null @@ -1,985 +0,0 @@ -package dev.utils.common.encrypt; - -import android.util.Base64; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.security.DigestInputStream; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.spec.AlgorithmParameterSpec; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import dev.utils.JCLogUtils; -import dev.utils.common.CloseUtils; - -/** - * detail: 加密工具类 - * Created by Ttt - */ -public final class EncryptUtils { - - private EncryptUtils() { - } - - // 日志TAG - private static final String TAG = EncryptUtils.class.getSimpleName(); - - /////////////////////////////////////////////////////////////////////////// - // hash encryption - /////////////////////////////////////////////////////////////////////////// - - /** - * Return the hex string of MD2 encryption. - * @param data The data. - * @return the hex string of MD2 encryption - */ - public static String encryptMD2ToString(final String data) { - return encryptMD2ToString(data.getBytes()); - } - - /** - * Return the hex string of MD2 encryption. - * - * @param data The data. - * @return the hex string of MD2 encryption - */ - public static String encryptMD2ToString(final byte[] data) { - return bytes2HexString(encryptMD2(data)); - } - - /** - * Return the bytes of MD2 encryption. - * - * @param data The data. - * @return the bytes of MD2 encryption - */ - public static byte[] encryptMD2(final byte[] data) { - return hashTemplate(data, "MD2"); - } - - /** - * Return the hex string of MD5 encryption. - * - * @param data The data. - * @return the hex string of MD5 encryption - */ - public static String encryptMD5ToString(final String data) { - return encryptMD5ToString(data.getBytes()); - } - - /** - * Return the hex string of MD5 encryption. - * - * @param data The data. - * @param salt The salt. - * @return the hex string of MD5 encryption - */ - public static String encryptMD5ToString(final String data, final String salt) { - return bytes2HexString(encryptMD5((data + salt).getBytes())); - } - - /** - * Return the hex string of MD5 encryption. - * - * @param data The data. - * @return the hex string of MD5 encryption - */ - public static String encryptMD5ToString(final byte[] data) { - return bytes2HexString(encryptMD5(data)); - } - - /** - * Return the hex string of MD5 encryption. - * - * @param data The data. - * @param salt The salt. - * @return the hex string of MD5 encryption - */ - public static String encryptMD5ToString(final byte[] data, final byte[] salt) { - if (data == null || salt == null) return null; - byte[] dataSalt = new byte[data.length + salt.length]; - System.arraycopy(data, 0, dataSalt, 0, data.length); - System.arraycopy(salt, 0, dataSalt, data.length, salt.length); - return bytes2HexString(encryptMD5(dataSalt)); - } - - /** - * Return the bytes of MD5 encryption. - * - * @param data The data. - * @return the bytes of MD5 encryption - */ - public static byte[] encryptMD5(final byte[] data) { - return hashTemplate(data, "MD5"); - } - - /** - * Return the hex string of file's MD5 encryption. - * - * @param filePath The path of file. - * @return the hex string of file's MD5 encryption - */ - public static String encryptMD5File2String(final String filePath) { - File file = isSpace(filePath) ? null : new File(filePath); - return encryptMD5File2String(file); - } - - /** - * Return the bytes of file's MD5 encryption. - * - * @param filePath The path of file. - * @return the bytes of file's MD5 encryption - */ - public static byte[] encryptMD5File(final String filePath) { - File file = isSpace(filePath) ? null : new File(filePath); - return encryptMD5File(file); - } - - /** - * Return the hex string of file's MD5 encryption. - * - * @param file The file. - * @return the hex string of file's MD5 encryption - */ - public static String encryptMD5File2String(final File file) { - return bytes2HexString(encryptMD5File(file)); - } - - /** - * Return the bytes of file's MD5 encryption. - * - * @param file The file. - * @return the bytes of file's MD5 encryption - */ - public static byte[] encryptMD5File(final File file) { - if (file == null) return null; - FileInputStream fis = null; - DigestInputStream digestInputStream; - try { - fis = new FileInputStream(file); - MessageDigest md = MessageDigest.getInstance("MD5"); - digestInputStream = new DigestInputStream(fis, md); - byte[] buffer = new byte[256 * 1024]; - while (true) { - if (!(digestInputStream.read(buffer) > 0)) break; - } - md = digestInputStream.getMessageDigest(); - return md.digest(); - } catch (NoSuchAlgorithmException | IOException e) { - JCLogUtils.eTag(TAG, e, "encryptMD5File"); - return null; - } finally { - CloseUtils.closeIO(fis); - } - } - - /** - * Return the hex string of SHA1 encryption. - * - * @param data The data. - * @return the hex string of SHA1 encryption - */ - public static String encryptSHA1ToString(final String data) { - return encryptSHA1ToString(data.getBytes()); - } - - /** - * Return the hex string of SHA1 encryption. - * - * @param data The data. - * @return the hex string of SHA1 encryption - */ - public static String encryptSHA1ToString(final byte[] data) { - return bytes2HexString(encryptSHA1(data)); - } - - /** - * Return the bytes of SHA1 encryption. - * - * @param data The data. - * @return the bytes of SHA1 encryption - */ - public static byte[] encryptSHA1(final byte[] data) { - return hashTemplate(data, "SHA1"); - } - - /** - * Return the hex string of SHA224 encryption. - * - * @param data The data. - * @return the hex string of SHA224 encryption - */ - public static String encryptSHA224ToString(final String data) { - return encryptSHA224ToString(data.getBytes()); - } - - /** - * Return the hex string of SHA224 encryption. - * - * @param data The data. - * @return the hex string of SHA224 encryption - */ - public static String encryptSHA224ToString(final byte[] data) { - return bytes2HexString(encryptSHA224(data)); - } - - /** - * Return the bytes of SHA224 encryption. - * - * @param data The data. - * @return the bytes of SHA224 encryption - */ - public static byte[] encryptSHA224(final byte[] data) { - return hashTemplate(data, "SHA224"); - } - - /** - * Return the hex string of SHA256 encryption. - * - * @param data The data. - * @return the hex string of SHA256 encryption - */ - public static String encryptSHA256ToString(final String data) { - return encryptSHA256ToString(data.getBytes()); - } - - /** - * Return the hex string of SHA256 encryption. - * - * @param data The data. - * @return the hex string of SHA256 encryption - */ - public static String encryptSHA256ToString(final byte[] data) { - return bytes2HexString(encryptSHA256(data)); - } - - /** - * Return the bytes of SHA256 encryption. - * - * @param data The data. - * @return the bytes of SHA256 encryption - */ - public static byte[] encryptSHA256(final byte[] data) { - return hashTemplate(data, "SHA256"); - } - - /** - * Return the hex string of SHA384 encryption. - * - * @param data The data. - * @return the hex string of SHA384 encryption - */ - public static String encryptSHA384ToString(final String data) { - return encryptSHA384ToString(data.getBytes()); - } - - /** - * Return the hex string of SHA384 encryption. - * - * @param data The data. - * @return the hex string of SHA384 encryption - */ - public static String encryptSHA384ToString(final byte[] data) { - return bytes2HexString(encryptSHA384(data)); - } - - /** - * Return the bytes of SHA384 encryption. - * - * @param data The data. - * @return the bytes of SHA384 encryption - */ - public static byte[] encryptSHA384(final byte[] data) { - return hashTemplate(data, "SHA384"); - } - - /** - * Return the hex string of SHA512 encryption. - * - * @param data The data. - * @return the hex string of SHA512 encryption - */ - public static String encryptSHA512ToString(final String data) { - return encryptSHA512ToString(data.getBytes()); - } - - /** - * Return the hex string of SHA512 encryption. - * - * @param data The data. - * @return the hex string of SHA512 encryption - */ - public static String encryptSHA512ToString(final byte[] data) { - return bytes2HexString(encryptSHA512(data)); - } - - /** - * Return the bytes of SHA512 encryption. - * - * @param data The data. - * @return the bytes of SHA512 encryption - */ - public static byte[] encryptSHA512(final byte[] data) { - return hashTemplate(data, "SHA512"); - } - - /** - * Return the bytes of hash encryption. - * - * @param data The data. - * @param algorithm The name of hash encryption. - * @return the bytes of hash encryption - */ - private static byte[] hashTemplate(final byte[] data, final String algorithm) { - if (data == null || data.length <= 0) return null; - try { - MessageDigest md = MessageDigest.getInstance(algorithm); - md.update(data); - return md.digest(); - } catch (NoSuchAlgorithmException e) { - JCLogUtils.eTag(TAG, e, "hashTemplate"); - return null; - } - } - - /////////////////////////////////////////////////////////////////////////// - // hmac encryption - /////////////////////////////////////////////////////////////////////////// - - /** - * Return the hex string of HmacMD5 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacMD5 encryption - */ - public static String encryptHmacMD5ToString(final String data, final String key) { - return encryptHmacMD5ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacMD5 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacMD5 encryption - */ - public static String encryptHmacMD5ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacMD5(data, key)); - } - - /** - * Return the bytes of HmacMD5 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacMD5 encryption - */ - public static byte[] encryptHmacMD5(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacMD5"); - } - - /** - * Return the hex string of HmacSHA1 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA1 encryption - */ - public static String encryptHmacSHA1ToString(final String data, final String key) { - return encryptHmacSHA1ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacSHA1 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA1 encryption - */ - public static String encryptHmacSHA1ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacSHA1(data, key)); - } - - /** - * Return the bytes of HmacSHA1 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacSHA1 encryption - */ - public static byte[] encryptHmacSHA1(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacSHA1"); - } - - /** - * Return the hex string of HmacSHA224 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA224 encryption - */ - public static String encryptHmacSHA224ToString(final String data, final String key) { - return encryptHmacSHA224ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacSHA224 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA224 encryption - */ - public static String encryptHmacSHA224ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacSHA224(data, key)); - } - - /** - * Return the bytes of HmacSHA224 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacSHA224 encryption - */ - public static byte[] encryptHmacSHA224(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacSHA224"); - } - - /** - * Return the hex string of HmacSHA256 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA256 encryption - */ - public static String encryptHmacSHA256ToString(final String data, final String key) { - return encryptHmacSHA256ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacSHA256 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA256 encryption - */ - public static String encryptHmacSHA256ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacSHA256(data, key)); - } - - /** - * Return the bytes of HmacSHA256 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacSHA256 encryption - */ - public static byte[] encryptHmacSHA256(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacSHA256"); - } - - /** - * Return the hex string of HmacSHA384 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA384 encryption - */ - public static String encryptHmacSHA384ToString(final String data, final String key) { - return encryptHmacSHA384ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacSHA384 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA384 encryption - */ - public static String encryptHmacSHA384ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacSHA384(data, key)); - } - - /** - * Return the bytes of HmacSHA384 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacSHA384 encryption - */ - public static byte[] encryptHmacSHA384(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacSHA384"); - } - - /** - * Return the hex string of HmacSHA512 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA512 encryption - */ - public static String encryptHmacSHA512ToString(final String data, final String key) { - return encryptHmacSHA512ToString(data.getBytes(), key.getBytes()); - } - - /** - * Return the hex string of HmacSHA512 encryption. - * - * @param data The data. - * @param key The key. - * @return the hex string of HmacSHA512 encryption - */ - public static String encryptHmacSHA512ToString(final byte[] data, final byte[] key) { - return bytes2HexString(encryptHmacSHA512(data, key)); - } - - /** - * Return the bytes of HmacSHA512 encryption. - * - * @param data The data. - * @param key The key. - * @return the bytes of HmacSHA512 encryption - */ - public static byte[] encryptHmacSHA512(final byte[] data, final byte[] key) { - return hmacTemplate(data, key, "HmacSHA512"); - } - - /** - * Return the bytes of hmac encryption. - * - * @param data The data. - * @param key The key. - * @param algorithm The name of hmac encryption. - * @return the bytes of hmac encryption - */ - private static byte[] hmacTemplate(final byte[] data, - final byte[] key, - final String algorithm) { - if (data == null || data.length == 0 || key == null || key.length == 0) return null; - try { - SecretKeySpec secretKey = new SecretKeySpec(key, algorithm); - Mac mac = Mac.getInstance(algorithm); - mac.init(secretKey); - return mac.doFinal(data); - } catch (InvalidKeyException | NoSuchAlgorithmException e) { - JCLogUtils.eTag(TAG, e, "hmacTemplate"); - return null; - } - } - - /////////////////////////////////////////////////////////////////////////// - // DES encryption - /////////////////////////////////////////////////////////////////////////// - - /** - * Return the Base64-encode bytes of DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the Base64-encode bytes of DES encryption - */ - public static byte[] encryptDES2Base64(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return base64Encode(encryptDES(data, key, transformation, iv)); - } - - /** - * Return the hex string of DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the hex string of DES encryption - */ - public static String encryptDES2HexString(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return bytes2HexString(encryptDES(data, key, transformation, iv)); - } - - /** - * Return the bytes of DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of DES encryption - */ - public static byte[] encryptDES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "DES", transformation, iv, true); - } - - /** - * Return the bytes of DES decryption for Base64-encode bytes. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of DES decryption for Base64-encode bytes - */ - public static byte[] decryptBase64DES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decryptDES(base64Decode(data), key, transformation, iv); - } - - /** - * Return the bytes of DES decryption for hex string. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of DES decryption for hex string - */ - public static byte[] decryptHexStringDES(final String data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decryptDES(hexString2Bytes(data), key, transformation, iv); - } - - /** - * Return the bytes of DES decryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of DES decryption - */ - public static byte[] decryptDES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "DES", transformation, iv, false); - } - - /////////////////////////////////////////////////////////////////////////// - // 3DES encryption - /////////////////////////////////////////////////////////////////////////// - - /** - * Return the Base64-encode bytes of 3DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the Base64-encode bytes of 3DES encryption - */ - public static byte[] encrypt3DES2Base64(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return base64Encode(encrypt3DES(data, key, transformation, iv)); - } - - /** - * Return the hex string of 3DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the hex string of 3DES encryption - */ - public static String encrypt3DES2HexString(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return bytes2HexString(encrypt3DES(data, key, transformation, iv)); - } - - /** - * Return the bytes of 3DES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of 3DES encryption - */ - public static byte[] encrypt3DES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "DESede", transformation, iv, true); - } - - /** - * Return the bytes of 3DES decryption for Base64-encode bytes. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of 3DES decryption for Base64-encode bytes - */ - public static byte[] decryptBase64_3DES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decrypt3DES(base64Decode(data), key, transformation, iv); - } - - /** - * Return the bytes of 3DES decryption for hex string. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of 3DES decryption for hex string - */ - public static byte[] decryptHexString3DES(final String data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decrypt3DES(hexString2Bytes(data), key, transformation, iv); - } - - /** - * Return the bytes of 3DES decryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of 3DES decryption - */ - public static byte[] decrypt3DES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "DESede", transformation, iv, false); - } - - /////////////////////////////////////////////////////////////////////////// - // AES encryption - /////////////////////////////////////////////////////////////////////////// - - /** - * Return the Base64-encode bytes of AES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the Base64-encode bytes of AES encryption - */ - public static byte[] encryptAES2Base64(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return base64Encode(encryptAES(data, key, transformation, iv)); - } - - /** - * Return the hex string of AES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the hex string of AES encryption - */ - public static String encryptAES2HexString(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return bytes2HexString(encryptAES(data, key, transformation, iv)); - } - - /** - * Return the bytes of AES encryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of AES encryption - */ - public static byte[] encryptAES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "AES", transformation, iv, true); - } - - /** - * Return the bytes of AES decryption for Base64-encode bytes. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of AES decryption for Base64-encode bytes - */ - public static byte[] decryptBase64AES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decryptAES(base64Decode(data), key, transformation, iv); - } - - /** - * Return the bytes of AES decryption for hex string. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of AES decryption for hex string - */ - public static byte[] decryptHexStringAES(final String data, - final byte[] key, - final String transformation, - final byte[] iv) { - return decryptAES(hexString2Bytes(data), key, transformation, iv); - } - - /** - * Return the bytes of AES decryption. - * - * @param data The data. - * @param key The key. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param iv The buffer with the IV. The contents of the - * buffer are copied to protect against subsequent modification. - * @return the bytes of AES decryption - */ - public static byte[] decryptAES(final byte[] data, - final byte[] key, - final String transformation, - final byte[] iv) { - return symmetricTemplate(data, key, "AES", transformation, iv, false); - } - - /** - * Return the bytes of symmetric encryption or decryption. - * - * @param data The data. - * @param key The key. - * @param algorithm The name of algorithm. - * @param transformation The name of the transformation, e.g., DES/CBC/PKCS5Padding. - * @param isEncrypt True to encrypt, false otherwise. - * @return the bytes of symmetric encryption or decryption - */ - private static byte[] symmetricTemplate(final byte[] data, - final byte[] key, - final String algorithm, - final String transformation, - final byte[] iv, - final boolean isEncrypt) { - if (data == null || data.length == 0 || key == null || key.length == 0) return null; - try { - SecretKeySpec keySpec = new SecretKeySpec(key, algorithm); - Cipher cipher = Cipher.getInstance(transformation); - if (iv == null || iv.length == 0) { - cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec); - } else { - AlgorithmParameterSpec params = new IvParameterSpec(iv); - cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, params); - } - return cipher.doFinal(data); - } catch (Throwable e) { - JCLogUtils.eTag(TAG, e, "symmetricTemplate"); - return null; - } - } - - private static final char HEX_DIGITS[] = - {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - private static String bytes2HexString(final byte[] bytes) { - if (bytes == null) return null; - int len = bytes.length; - if (len <= 0) return null; - char[] ret = new char[len << 1]; - for (int i = 0, j = 0; i < len; i++) { - ret[j++] = HEX_DIGITS[bytes[i] >>> 4 & 0x0f]; - ret[j++] = HEX_DIGITS[bytes[i] & 0x0f]; - } - return new String(ret); - } - - private static byte[] hexString2Bytes(String hexString) { - if (isSpace(hexString)) return null; - int len = hexString.length(); - if (len % 2 != 0) { - hexString = "0" + hexString; - len = len + 1; - } - char[] hexBytes = hexString.toUpperCase().toCharArray(); - byte[] ret = new byte[len >> 1]; - for (int i = 0; i < len; i += 2) { - ret[i >> 1] = (byte) (hex2Dec(hexBytes[i]) << 4 | hex2Dec(hexBytes[i + 1])); - } - return ret; - } - - private static int hex2Dec(final char hexChar) { - if (hexChar >= '0' && hexChar <= '9') { - return hexChar - '0'; - } else if (hexChar >= 'A' && hexChar <= 'F') { - return hexChar - 'A' + 10; - } else { - throw new IllegalArgumentException(); - } - } - - private static byte[] base64Encode(final byte[] input) { - return Base64.encode(input, Base64.NO_WRAP); - } - - private static byte[] base64Decode(final byte[] input) { - return Base64.decode(input, Base64.NO_WRAP); - } - - private static boolean isSpace(final String s) { - if (s == null) return true; - for (int i = 0, len = s.length(); i < len; ++i) { - if (!Character.isWhitespace(s.charAt(i))) { - return false; - } - } - return true; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/EscapeUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/EscapeUtils.java deleted file mode 100644 index 76a6995e3f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/EscapeUtils.java +++ /dev/null @@ -1,153 +0,0 @@ -package dev.utils.common.encrypt; - -/** - * detail: 解码,编码 - * Created by Ttt - */ -public final class EscapeUtils { - - private EscapeUtils(){ - } - - /** 十六进制 - 0-255 */ - private final static String[] hex = { "00", "01", "02", "03", "04", "05", - "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10", - "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", - "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26", - "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", - "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", - "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", - "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", - "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", - "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", - "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", - "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", - "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", - "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", - "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", - "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", - "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5", - "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0", - "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", - "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6", - "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1", - "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", - "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", - "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" }; - - private final static byte[] val = { 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x01, - 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, - 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F }; - - /** - * 编码 - * @param str - * @return - */ - public static String escape(String str) { - if (str == null){ - return null; - } - StringBuffer sbuf = new StringBuffer(); - for (int i = 0, len = str.length(); i < len; i++) { - int ch = str.charAt(i); - if ('A' <= ch && ch <= 'Z') { - sbuf.append((char) ch); - } else if ('a' <= ch && ch <= 'z') { - sbuf.append((char) ch); - } else if ('0' <= ch && ch <= '9') { - sbuf.append((char) ch); - } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' - || ch == '~' || ch == '*' || ch == '\'' || ch == '(' - || ch == ')') { - sbuf.append((char) ch); - } else if (ch <= 0x007F) { - sbuf.append('%'); - sbuf.append(hex[ch]); - } else { - sbuf.append('%'); - sbuf.append('u'); - sbuf.append(hex[(ch >>> 8)]); - sbuf.append(hex[(0x00FF & ch)]); - } - } - return sbuf.toString(); - } - - /** - * 解码 说明:本方法保证 不论参数s是否经过escape()编码,均能获取正确的“解码”结果 - * @param str - * @return - */ - public static String unescape(String str) { - if (str == null){ - return null; - } - StringBuffer sbuf = new StringBuffer(); - int i = 0; - int len = str.length(); - while (i < len) { - int ch = str.charAt(i); - if ('A' <= ch && ch <= 'Z') { - sbuf.append((char) ch); - } else if ('a' <= ch && ch <= 'z') { - sbuf.append((char) ch); - } else if ('0' <= ch && ch <= '9') { - sbuf.append((char) ch); - } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' - || ch == '~' || ch == '*' || ch == '\'' || ch == '(' - || ch == ')') { - sbuf.append((char) ch); - } else if (ch == '%') { - int cint = 0; - if ('u' != str.charAt(i + 1)) { - cint = (cint << 4) | val[str.charAt(i + 1)]; - cint = (cint << 4) | val[str.charAt(i + 2)]; - i += 2; - } else { - cint = (cint << 4) | val[str.charAt(i + 2)]; - cint = (cint << 4) | val[str.charAt(i + 3)]; - cint = (cint << 4) | val[str.charAt(i + 4)]; - cint = (cint << 4) | val[str.charAt(i + 5)]; - i += 5; - } - sbuf.append((char) cint); - } else { - sbuf.append((char) ch); - } - i++; - } - return sbuf.toString(); - } - -// public static void main(String[] args) { -// // 需要编码的数据 -// String stest = "需要编码的数据"; -// System.out.println(stest); -// // 编码 -// System.out.println(escape(stest)); -// // 解码 -// System.out.println(unescape(escape(stest))); -// } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/MD5Utils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/MD5Utils.java deleted file mode 100644 index f876109bf3..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/MD5Utils.java +++ /dev/null @@ -1,163 +0,0 @@ -package dev.utils.common.encrypt; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.MessageDigest; - -import dev.utils.JCLogUtils; - -/** - * detail: MD5加密 不可逆(Message Digest,消息摘要算法) - * Created by Ttt - */ -public final class MD5Utils { - - private MD5Utils() { - } - - // 日志TAG - private static final String TAG = MD5Utils.class.getSimpleName(); - - /** 小写 */ - public static final char HEX_DIGITS[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - /** 大写 */ - public static final char HEX_DIGITS_UPPER[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; - - /** - * 加密内容 - 32位大小MD5 - 小写 - * @param str 加密内容 - * @return - */ - public final static String md5(String str) { - try { - return md5(str.getBytes()); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "MD5"); - } - return null; - } - - /** - * 加密内容 - 32位大小MD5 - 小写 - * @param bytes - * @return - */ - public final static String md5(byte[] bytes) { - try { - // 获得MD5摘要算法的 MessageDigest 对象 - MessageDigest mdInst = MessageDigest.getInstance("MD5"); - // 使用指定的字节更新摘要 - mdInst.update(bytes); - // 获得密文 - byte[] md = mdInst.digest(); - return toHexString(md, HEX_DIGITS); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "MD5"); - } - return null; - } - - /** - * 加密内容 - 32位大小MD5 - 大写 - * @param str 加密内容 - * @return - */ - public final static String md5Upper(String str) { - try { - return md5Upper(str.getBytes()); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "MD5Upper"); - } - return null; - } - - /** - * 加密内容 - 32位大小MD5 - 大写 - * @param bytes - * @return - */ - public final static String md5Upper(byte[] bytes) { - try { - // 获得MD5摘要算法的 MessageDigest 对象 - MessageDigest mdInst = MessageDigest.getInstance("MD5"); - // 使用指定的字节更新摘要 - mdInst.update(bytes); - // 获得密文 - byte[] md = mdInst.digest(); - return toHexString(md, HEX_DIGITS_UPPER); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "MD5Upper"); - } - return null; - } - - /** - * 进行转换 - * @param bData - * @return - */ - public static String toHexString(byte[] bData) { - return toHexString(bData, HEX_DIGITS); - } - - /** - * 进行转换 - * @param bData - * @param hexDigits - * @return - */ - public static String toHexString(byte[] bData, char[] hexDigits) { - if (bData == null || hexDigits == null){ - return null; - } - StringBuilder sBuilder = new StringBuilder(bData.length * 2); - for (int i = 0, len = bData.length; i < len; i++) { - sBuilder.append(hexDigits[(bData[i] & 0xf0) >>> 4]); - sBuilder.append(hexDigits[bData[i] & 0x0f]); - } - return sBuilder.toString(); - } - - /** - * 获取文件MD5值 - 小写 - * @param fPath 文件地址 - * @return - */ - public static String getFileMD5(String fPath) { - try { - InputStream fis = new FileInputStream(fPath); - byte[] buffer = new byte[1024]; - MessageDigest md5 = MessageDigest.getInstance("MD5"); - int numRead = 0; - while ((numRead = fis.read(buffer)) > 0) { - md5.update(buffer, 0, numRead); - } - fis.close(); - return toHexString(md5.digest(), HEX_DIGITS); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileMD5"); - } - return null; - } - -// /** -// * MD5加密(小写) -// * @param str 源字符串 -// * @return 加密后的字符串 -// */ -// public static String md5(String str) { -// try { -// byte[] hash = MessageDigest.getInstance("MD5").digest(str.getBytes("UTF-8")); -// StringBuilder hex = new StringBuilder(hash.length * 2); -// for (byte b : hash) { -// if ((b & 0xFF) < 0x10) -// hex.append("0"); -// hex.append(Integer.toHexString(b & 0xFF)); -// } -// return hex.toString(); -// } catch (Exception e) { -// JCLogUtils.eTag(TAG, e, "md5"); -// } -// return null; -// } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/SHAUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/SHAUtils.java deleted file mode 100644 index 6853eda43f..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/SHAUtils.java +++ /dev/null @@ -1,149 +0,0 @@ -package dev.utils.common.encrypt; - -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.MessageDigest; - -import dev.utils.JCLogUtils; - -/** - * detail: SHA 加密工具类 - * Created by Ttt - */ -public final class SHAUtils { - - private SHAUtils() { - } - - // 日志TAG - private static final String TAG = SHAUtils.class.getSimpleName(); - - /** 小写 */ - private static final char HEX_DIGITS[]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; - - /** - * 加密内容 SHA1 - * @param str 加密内容 - * @return - */ - public final static String sha1(String str) { - return shaHex(str, "SHA-1"); - } - - /** - * 加密内容 SHA224 - * @param str 加密内容 - * @return - */ - public final static String sha224(String str) { - return shaHex(str, "SHA-224"); - } - - /** - * 加密内容 SHA256 - * @param str 加密内容 - * @return - */ - public final static String sha256(String str) { - return shaHex(str, "SHA-256"); - } - - /** - * 加密内容 SHA384 - * @param str 加密内容 - * @return - */ - public final static String sha384(String str) { - return shaHex(str, "SHA-384"); - } - - /** - * 加密内容 SHA512 - * @param str 加密内容 - * @return - */ - public final static String sha512(String str) { - return shaHex(str, "SHA-512"); - } - - // ======= - - /** - * 加密内容 SHA - * @param str 加密内容 - * @param sha 加密算法 - * @return - */ - final static String shaHex(String str, String sha) { - try { - byte[] btInput = str.getBytes(); - // 获得 SHA-1 摘要算法的 MessageDigest 对象 - MessageDigest mdInst = MessageDigest.getInstance(sha); - // 使用指定的字节更新摘要 - mdInst.update(btInput); - // 获得密文 - byte[] md = mdInst.digest(); - return toHexString(md, HEX_DIGITS); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "shaHex"); - } - return null; - } - - /** - * 获取文件 Sha 值 - * @param fPath 文件地址 - * @return - */ - final static String getFileSHA(String fPath, String sha) { - try { - InputStream fis = new FileInputStream(fPath); - byte[] buffer = new byte[1024]; - MessageDigest md5 = MessageDigest.getInstance(sha); - int numRead = 0; - while ((numRead = fis.read(buffer)) > 0) { - md5.update(buffer, 0, numRead); - } - fis.close(); - return toHexString(md5.digest(), HEX_DIGITS); - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "getFileSHA"); - } - return null; - } - - // ======= - - /** - * 进行转换 - * @param bData - * @param hexDigits - * @return - */ - static String toHexString(byte[] bData, char[] hexDigits) { - StringBuilder sBuilder = new StringBuilder(bData.length * 2); - for (int i = 0, len = bData.length; i < len; i++) { - sBuilder.append(hexDigits[(bData[i] & 0xf0) >>> 4]); - sBuilder.append(hexDigits[bData[i] & 0x0f]); - } - return sBuilder.toString(); - } - - /** - * 获取文件 Sha1 值 - * @param fPath 文件地址 - * @return - */ - public static String getFileSHA1(String fPath) { - return getFileSHA(fPath, "SHA-1"); - } - - /** - * 获取文件 Sha256 值 - * @param fPath 文件地址 - * @return - */ - public static String getFileSHA256(String fPath) { - return getFileSHA(fPath, "SHA-256"); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java b/DevLibUtils/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java deleted file mode 100644 index faf4188ef8..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -package dev.utils.common.encrypt; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import dev.utils.JCLogUtils; - -/** - * detail: 3DES对称加密(Triple DES、DESede,进行了三重DES加密的算法,对称加密算法) - * Created by Ttt - */ -public final class TripleDESUtils { - - private TripleDESUtils() { - } - - // 日志TAG - private static final String TAG = TripleDESUtils.class.getSimpleName(); - - /** - * 生成密钥 - * @return - */ - public static byte[] initKey(){ - try { - KeyGenerator keyGen = KeyGenerator.getInstance("DESede"); - keyGen.init(168); // 112 168 - SecretKey secretKey = keyGen.generateKey(); - return secretKey.getEncoded(); - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "initKey"); - } - return null; - } - - /** - * 3DES 加密 - * @param data - * @param key - * @return - */ - public static byte[] encrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "DESede"); - Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, secretKey); - byte[] cipherBytes = cipher.doFinal(data); - return cipherBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "encrypt"); - } - return null; - } - - /** - * 3DES 解密 - * @param data - * @param key - * @return - */ - public static byte[] decrypt(byte[] data, byte[] key){ - try { - SecretKey secretKey = new SecretKeySpec(key, "DESede"); - Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] plainBytes = cipher.doFinal(data); - return plainBytes; - } catch (Exception e){ - JCLogUtils.eTag(TAG, e, "decrypt"); - } - return null; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadManager.java b/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadManager.java deleted file mode 100644 index 9a430696a6..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadManager.java +++ /dev/null @@ -1,102 +0,0 @@ -package dev.utils.common.thread; - -import java.util.HashMap; -import java.util.LinkedHashMap; - -/** - * detail: 线程池管理 - 开发类 - * Created by Ttt - */ -public final class DevThreadManager { - - /** 默认通用线程池 = 通过CPU自动处理 */ - private static final DevThreadPool sDevThreadPool = new DevThreadPool(DevThreadPool.DevThreadPoolType.CALC_CPU); - /** 线程池数据 */ - private static final LinkedHashMap mapThreads = new LinkedHashMap<>(); - /** 配置数据 */ - private static final HashMap mapConfig = new HashMap<>(); - - /** 禁止构造对象 */ - private DevThreadManager() { - } - - /** - * 获取 DevThreadManager 实例 ,单例模式 - * @param nThreads - * @return - */ - public static synchronized DevThreadPool getInstance(int nThreads) { - // 初始化key - String key = "n_" + nThreads; - // 如果不为null, 则直接返回 - DevThreadPool devThreadPool = mapThreads.get(key); - if (devThreadPool != null){ - return devThreadPool; - } - devThreadPool = new DevThreadPool(nThreads); - mapThreads.put(key, devThreadPool); - return devThreadPool; - } - - /** - * 获取 DevThreadManager 实例 ,单例模式 - * @param key - * @return - */ - public static synchronized DevThreadPool getInstance(String key) { - // 如果不为null, 则直接返回 - DevThreadPool devThreadPool = mapThreads.get(key); - if (devThreadPool != null){ - return devThreadPool; - } - Object obj = mapConfig.get(key); - if (obj != null){ - try { - // 判断是否属于线程池类型 - if (obj instanceof DevThreadPool.DevThreadPoolType){ - devThreadPool = new DevThreadPool((DevThreadPool.DevThreadPoolType) obj); - } else if (obj instanceof Integer){ - devThreadPool = new DevThreadPool((Integer) obj); - } else { // 其他类型, 统一转换 Integer - devThreadPool = new DevThreadPool(Integer.parseInt((String) obj)); - } - if (devThreadPool != null) { - mapThreads.put(key, devThreadPool); - return devThreadPool; - } - } catch (Exception e){ - return sDevThreadPool; - } - } - return sDevThreadPool; - } - - // === - - /** - * 初始化配置信息 - * @param mapConfig - */ - public static void initConfig(HashMap mapConfig){ - if (mapConfig != null){ - mapConfig.putAll(mapConfig); - } - } - - /** - * 添加配置信息 - * @param key - * @param val - */ - public static void putConfig(String key, Object val){ - mapConfig.put(key, val); - } - - /** - * 移除配置信息 - * @param key - */ - public static void removeConfig(String key){ - mapConfig.remove(key); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadPool.java b/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadPool.java deleted file mode 100644 index b995c02a2e..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/thread/DevThreadPool.java +++ /dev/null @@ -1,415 +0,0 @@ -package dev.utils.common.thread; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * detail: 线程池 - 开发类 - * Created by Ttt - */ -public final class DevThreadPool { - -// // https://www.jianshu.com/p/4d4634c92253 -// // 创建线程池 -// 1. 线程池里面管理多少个线程 -// 2. 如果排队满了, 额外的开的线程数 -// 3. 如果线程池没有要执行的任务 存活多久 -// 4. 时间的单位 -// 5. 如果 线程池里管理的线程都已经用了,剩下的任务 临时存到LinkedBlockingQueue对象中 排队 -// public ThreadPoolExecutor(int corePoolSize, -// int maximumPoolSize, -// long keepAliveTime, -// TimeUnit unit, -// BlockingQueue workQueue) { -// this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, -// Executors.defaultThreadFactory(), defaultHandler); -// } - - // ====== - - /** 线程池对象 */ - private final ExecutorService threadPool; - /** 定时任务线程池 */ - private ScheduledExecutorService scheduleExec; - - /** - * 构造函数 - * @param nThreads - */ - public DevThreadPool(int nThreads){ - // 如果小于等于 0, 则默认使用 1 - if (nThreads <= 0){ - nThreads = 1; - } - this.threadPool = Executors.newFixedThreadPool(nThreads); - // 初始化定时器任务 - this.scheduleExec = Executors.newScheduledThreadPool(nThreads); - } - - /** - * 构造函数 - * @param threadPool - */ - public DevThreadPool(ExecutorService threadPool){ - this.threadPool = threadPool; - } - - public DevThreadPool(DevThreadPoolType devThreadPoolType){ - // 初始化定时器任务 - this.scheduleExec = Executors.newScheduledThreadPool(getThreads()); - // = - if (devThreadPoolType != null){ - // = - switch (devThreadPoolType) { - case SINGLE: - threadPool = Executors.newSingleThreadExecutor(); - // 初始化定时器任务 - this.scheduleExec = Executors.newScheduledThreadPool(1); - break; -// case AUTO_CPU: -// //threadPool = Executors.newWorkStealingPool(); -// break; - case CALC_CPU: - threadPool = Executors.newFixedThreadPool(getThreads()); - break; - case CACHE: - threadPool = Executors.newCachedThreadPool(); - break; - default: - threadPool = Executors.newFixedThreadPool(getThreads()); - break; - } - } else { - threadPool = Executors.newFixedThreadPool(getThreads()); - } - } - - public enum DevThreadPoolType { - - // http://blog.csdn.net/a369414641/article/details/48342253 - - // http://blog.csdn.net/vking_wang/article/details/9619137 - - // http://ifeve.com/java8-concurrency-tutorial-thread-executor-examples/ - - // http://blog.csdn.net/sadfishsc/article/details/16980213 - - // 如果当前线程意外终止,会创建一个新线程继续执行任务,这和我们直接创建线程不同,也和newFixedThreadPool(1)不同。 - SINGLE, // newSingleThreadExecutor 获取的是一个单个的线程,这个线程会保证你的任务执行完成。 - - AUTO_CPU, // 根据CPU来创建(自定义创建) - - CALC_CPU, // 手动计算CPU来创建 - - CACHE, // 可缓存线程池 - -// 1 newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 -// 2 newFixedThreadPool:创建一个固定数目的、可重用的线程池。 -// 3 newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。 -// 4 newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。 -// 5 newSingleThreadScheduledExcutor:创建一个单例线程池,定期或延时执行任务。 -// 6 newWorkStealingPool:创建持有足够线程的线程池来支持给定的并行级别,并通过使用多个队列,减少竞争,它需要穿一个并行级别的参数,如果不传,则被设定为默认的CPU数量。 -// 7 ForkJoinPool:支持大任务分解成小任务的线程池,这是Java8新增线程池,通常配合ForkJoinTask接口的子类RecursiveAction或RecursiveTask使用。 - } - - // == - - /** - * 获取线程数 - * @return - */ - private final int getThreads() { - // 使用计算过后的 - return getCaclThreads(); - } - - /** - * 获取线程数 - * @return - */ - private final int getCaclThreads() { - // 获取CPU核心数 - int cNumber = Runtime.getRuntime().availableProcessors(); - // 如果小于等于5,则返回5 - if (cNumber <= 5) { - return 5; - } else { // 大于5的情况 - if (cNumber * 2 + 1 >= 10) { // 防止线程数量过大,当大于10 的时候,返回 10 - return 10; - } else { // 不大于10的时候,默认返回 支持的数量 * 2 + 1 - return cNumber * 2 + 1; - } - } - } - - // == - - /** - * 加入到线程池任务队列 - * @param runnable - */ - public void execute(Runnable runnable) { - if (threadPool != null) { - threadPool.execute(runnable); - } - } - - /** - * 加入到线程池任务队列 - * @param runnables - */ - public void execute(final List runnables) { - if (threadPool == null || runnables == null){ - return; - } - for (Runnable command : runnables) { - if (command != null) { - threadPool.execute(command); - } - } - } - - - /** - * 通过反射,调用某个类的方法 - * @param method - * @param _class - */ - public void execute(final Method method, final Object _class) { - if (threadPool != null){ - threadPool.execute(new Runnable() { - @Override - public void run() { - try { - method.invoke(_class); - } catch (Exception ignore) { - } - } - }); - } - } - - // == - - /** shutdown 会等待所有提交的任务执行完成,不管是正在执行还是保存在任务队列中的已提交任务 - * 待以前提交的任务执行完毕后关闭线程池 - * 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 - * 如果已经关闭,则调用没有作用。 - */ - public void shutdown() { - if (threadPool != null){ - threadPool.shutdown(); - } - } - - /** - * shutdownNow会尝试中断正在执行的任务(其主要是中断一些指定方法如sleep方法),并且停止执行等待队列中提交的任务。 - * 试图停止所有正在执行的活动任务 - * 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 - * 无法保证能够停止正在处理的活动执行任务,但是会尽力尝试。 - * @return - */ - public List shutdownNow() { - if (threadPool != null){ - return threadPool.shutdownNow(); - } - return null; - } - - /** - * 判断线程池是否已关闭 = isShutDown当调用shutdown()方法后返回为true。 - * @return - */ - public boolean isShutdown() { - if (threadPool != null){ - return threadPool.isShutdown(); - } - return false; - } - - /** - * 若关闭后所有任务都已完成,则返回true. - * 注意除非首先调用shutdown或shutdownNow, 否则isTerminated 永不为true. - * // -- - * isTerminated当调用shutdown()方法后,并且所有提交的任务完成后返回为true - * @return - */ - public boolean isTerminated() { - if (threadPool != null){ - return threadPool.isTerminated(); - } - return false; - } - - /** - * 请求关闭、发生超时或者当前线程中断 - * 无论哪一个首先发生之后,都将导致阻塞,直到所有任务完成执行。 - * @param timeout 最长等待时间 - * @param unit 时间单位 - * @return true : 请求成功, false : 请求超时 - * @throws InterruptedException 终端异常 - */ - public boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException { - return threadPool.awaitTermination(timeout, unit); - } - - /** - * 提交一个Callable任务用于执行 - * 如果想立即阻塞任务的等待,则可以使用{@code result = threadPool.submit(aCallable).get();}形式的构造。 - * @param task 任务 - * @param 泛型 - * @return 表示任务等待完成的Future, 该Future的{@code get}方法在成功完成时将会返回该任务的结果。 - */ - public Future submit(final Callable task) { - return threadPool.submit(task); - } - - /** - * 提交一个Runnable任务用于执行 - * @param task 任务 - * @param result 返回的结果 - * @param 泛型 - * @return 表示任务等待完成的Future, 该Future的{@code get}方法在成功完成时将会返回该任务的结果。 - */ - public Future submit(final Runnable task, final T result) { - return threadPool.submit(task, result); - } - - /** - * 提交一个Runnable任务用于执行 - * @param task 任务 - * @return 表示任务等待完成的Future, 该Future的{@code get}方法在成功完成时将会返回null结果。 - */ - public Future submit(final Runnable task) { - return threadPool.submit(task); - } - - /** - * 执行给定的任务 - * 当所有任务完成时,返回保持任务状态和结果的Future列表。 - * 返回列表的所有元素的{@link Future#isDone}为{@code true}。 - * 注意,可以正常地或通过抛出异常来终止已完成任务。 - * 如果正在进行此操作时修改了给定的 collection,则此方法的结果是不确定的。 - * @param tasks 任务集合 - * @param 泛型 - * @return 表示任务的 Future 列表,列表顺序与给定任务列表的迭代器所生成的顺序相同,每个任务都已完成。 - * @throws InterruptedException 如果等待时发生中断,在这种情况下取消尚未完成的任务。 - */ - public List> invokeAll(final Collection> tasks) throws InterruptedException { - return threadPool.invokeAll(tasks); - } - - /** - * 执行给定的任务 - * 当所有任务完成或超时期满时(无论哪个首先发生),返回保持任务状态和结果的Future列表。 - * 返回列表的所有元素的{@link Future#isDone}为{@code true}。 - * 一旦返回后,即取消尚未完成的任务。 - * 注意,可以正常地或通过抛出异常来终止已完成任务。 - * 如果此操作正在进行时修改了给定的 collection,则此方法的结果是不确定的。 - * @param tasks 任务集合 - * @param timeout 最长等待时间 - * @param unit 时间单位 - * @param 泛型 - * @return 表示任务的 Future 列表,列表顺序与给定任务列表的迭代器所生成的顺序相同。 - * 如果操作未超时,则已完成所有任务。如果确实超时了,则某些任务尚未完成。 - * @throws InterruptedException 如果等待时发生中断,在这种情况下取消尚未完成的任务 - */ - public List> invokeAll(final Collection> tasks, final long timeout, final TimeUnit unit) throws InterruptedException { - return threadPool.invokeAll(tasks, timeout, unit); - } - - /** - * 执行给定的任务 - * 如果某个任务已成功完成(也就是未抛出异常),则返回其结果。 - * 一旦正常或异常返回后,则取消尚未完成的任务。 - * 如果此操作正在进行时修改了给定的collection,则此方法的结果是不确定的。 - * @param tasks 任务集合 - * @param 泛型 - * @return 某个任务返回的结果 - * @throws InterruptedException 如果等待时发生中断 - * @throws ExecutionException 如果没有任务成功完成 - */ - public T invokeAny(final Collection> tasks) throws InterruptedException, ExecutionException { - return threadPool.invokeAny(tasks); - } - - /** - * 执行给定的任务 - * 如果在给定的超时期满前某个任务已成功完成(也就是未抛出异常),则返回其结果。 - * 一旦正常或异常返回后,则取消尚未完成的任务。 - * 如果此操作正在进行时修改了给定的collection,则此方法的结果是不确定的。 - * @param tasks 任务集合 - * @param timeout 最长等待时间 - * @param unit 时间单位 - * @param 泛型 - * @return 某个任务返回的结果 - * @throws InterruptedException 如果等待时发生中断 - * @throws ExecutionException 如果没有任务成功完成 - * @throws TimeoutException 如果在所有任务成功完成之前给定的超时期满 - */ - public T invokeAny(final Collection> tasks, final long timeout, final TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return threadPool.invokeAny(tasks, timeout, unit); - } - - // == - - - - /** - * 延迟执行Runnable命令 - * @param command 命令 - * @param delay 延迟时间 - * @param unit 单位 - * @return 表示挂起任务完成的ScheduledFuture,并且其{@code get()}方法在完成后将返回{@code null} - */ - public ScheduledFuture schedule(final Runnable command, final long delay, final TimeUnit unit) { - return scheduleExec.schedule(command, delay, unit); - } - - /** - * 延迟执行Callable命令 - * @param callable 命令 - * @param delay 延迟时间 - * @param unit 时间单位 - * @param 泛型 - * @return 可用于提取结果或取消的ScheduledFuture - */ - public ScheduledFuture schedule(final Callable callable, final long delay, final TimeUnit unit) { - return scheduleExec.schedule(callable, delay, unit); - } - - /** - * 延迟并循环执行命令 - * @param command 命令 - * @param initialDelay 首次执行的延迟时间 - * @param period 连续执行之间的周期 - * @param unit 时间单位 - * @return 表示挂起任务完成的ScheduledFuture,并且其{@code get()}方法在取消后将抛出异常 - */ - public ScheduledFuture scheduleWithFixedRate(final Runnable command, final long initialDelay, final long period, final TimeUnit unit) { - return scheduleExec.scheduleAtFixedRate(command, initialDelay, period, unit); - } - - /** - * 延迟并以固定休息时间循环执行命令 - * @param command 命令 - * @param initialDelay 首次执行的延迟时间 - * @param delay 每一次执行终止和下一次执行开始之间的延迟 - * @param unit 时间单位 - * @return 表示挂起任务完成的ScheduledFuture,并且其{@code get()}方法在取消后将抛出异常 - */ - public ScheduledFuture scheduleWithFixedDelay(final Runnable command, final long initialDelay, final long delay, final TimeUnit unit) { - return scheduleExec.scheduleWithFixedDelay(command, initialDelay, delay, unit); - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/validator/BankCheckUtils.java b/DevLibUtils/src/main/java/dev/utils/common/validator/BankCheckUtils.java deleted file mode 100644 index 792cc2d256..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/validator/BankCheckUtils.java +++ /dev/null @@ -1,718 +0,0 @@ -package dev.utils.common.validator; - -/** - * detail: 银行卡管理 - * @Description:主要功能:银行卡管理 - * @Prject: CommonUtilLibrary - * @Package: com.jingewenku.abrahamcaijin.commonutil - * @author: AbrahamCaiJin - * @date: 2017年05月15日 11:14 - * @Copyright: 个人版权所有 - * @Company: - * @version: 1.0.0 - */ -/* - 当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?其实可以不必这么担心, - 因为并不是一个随便的信用卡号码都是合法的,它必须通过Luhn算法来验证通过。 - 该校验的过程: - 1、从卡号最后一位数字开始,逆向将奇数位(1、3、5等等)相加。 - 2、从卡号最后一位数字开始,逆向将偶数位数字,先乘以2(如果乘积为两位数,则将其减去9),再求和。 - 3、将奇数位总和加上偶数位总和,结果应该可以被10整除。 - 例如,卡号是:5432123456788881 - 则奇数、偶数位(用红色标出)分布:5432123456788881 - 奇数位和=35 - 偶数位乘以2(有些要减去9)的结果:1 6 2 6 1 5 7 7,求和=35。 - 最后35+35=70 可以被10整除,认定校验通过。 - */ -public final class BankCheckUtils { - /* - * 银行卡是由”发卡行标识代码 + 自定义 + 校验码 “等部分组成的 BIN号 银联标准卡与以往发行的银行卡最直接的区别就是其卡号前6位数字的不同。 - * 银行卡卡号的前6位是用来表示发卡银行或机构的,称为“发卡行识别码”(Bank Identification Number,缩写为“BIN”)。 - * 银联标准卡是由国内各家商业银行 - * (含邮储、信用社)共同发行、符合银联业务规范和技术标准、卡正面右下角带有“银联”标识(目前,新发行的银联标准卡一定带有国际化的银联新标识 - * ,新发的非银联标准卡使用旧的联网通用银联标识)、 卡号前6位为622126至622925之一的银行卡,是中国银行卡产业共有的民族品牌。 - */ - // BIN号 - private final static String[] BANKBIN = { "621098", "622150", "622151", - "622181", "622188", "955100", "621095", "620062", "621285", - "621798", "621799", "621797", "620529", "622199", "621096", - "621622", "623219", "621674", "623218", "621599", "370246", - "370248", "370249", "427010", "427018", "427019", "427020", - "427029", "427030", "427039", "370247", "438125", "438126", - "451804", "451810", "451811", "458071", "489734", "489735", - "489736", "510529", "427062", "524091", "427064", "530970", - "530990", "558360", "620200", "620302", "620402", "620403", - "620404", "524047", "620406", "620407", "525498", "620409", - "620410", "620411", "620412", "620502", "620503", "620405", - "620408", "620512", "620602", "620604", "620607", "620611", - "620612", "620704", "620706", "620707", "620708", "620709", - "620710", "620609", "620712", "620713", "620714", "620802", - "620711", "620904", "620905", "621001", "620902", "621103", - "621105", "621106", "621107", "621102", "621203", "621204", - "621205", "621206", "621207", "621208", "621209", "621210", - "621302", "621303", "621202", "621305", "621306", "621307", - "621309", "621311", "621313", "621211", "621315", "621304", - "621402", "621404", "621405", "621406", "621407", "621408", - "621409", "621410", "621502", "621317", "621511", "621602", - "621603", "621604", "621605", "621608", "621609", "621610", - "621611", "621612", "621613", "621614", "621615", "621616", - "621617", "621607", "621606", "621804", "621807", "621813", - "621814", "621817", "621901", "621904", "621905", "621906", - "621907", "621908", "621909", "621910", "621911", "621912", - "621913", "621915", "622002", "621903", "622004", "622005", - "622006", "622007", "622008", "622010", "622011", "622012", - "621914", "622015", "622016", "622003", "622018", "622019", - "622020", "622102", "622103", "622104", "622105", "622013", - "622111", "622114", "622200", "622017", "622202", "622203", - "622208", "622210", "622211", "622212", "622213", "622214", - "622110", "622220", "622223", "622225", "622229", "622230", - "622231", "622232", "622233", "622234", "622235", "622237", - "622215", "622239", "622240", "622245", "622224", "622303", - "622304", "622305", "622306", "622307", "622308", "622309", - "622238", "622314", "622315", "622317", "622302", "622402", - "622403", "622404", "622313", "622504", "622505", "622509", - "622513", "622517", "622502", "622604", "622605", "622606", - "622510", "622703", "622715", "622806", "622902", "622903", - "622706", "623002", "623006", "623008", "623011", "623012", - "622904", "623015", "623100", "623202", "623301", "623400", - "623500", "623602", "623803", "623901", "623014", "624100", - "624200", "624301", "624402", "62451804", "62451810", "62451811", - "62458071", "623700", "628288", "624000", "628286", "622206", - "621225", "526836", "513685", "543098", "458441", "620058", - "621281", "622246", "900000", "544210", "548943", "370267", - "621558", "621559", "621722", "621723", "620086", "621226", - "402791", "427028", "427038", "548259", "356879", "356880", - "356881", "356882", "528856", "621618", "620516", "621227", - "621721", "900010", "625330", "625331", "625332", "623062", - "622236", "621670", "524374", "550213", "374738", "374739", - "621288", "625708", "625709", "622597", "622599", "360883", - "360884", "625865", "625866", "625899", "621376", "620054", - "620142", "621428", "625939", "621434", "625987", "621761", - "621749", "620184", "621300", "621378", "625114", "622159", - "621720", "625021", "625022", "621379", "620114", "620146", - "621724", "625918", "621371", "620143", "620149", "621414", - "625914", "621375", "620187", "621433", "625986", "621370", - "625925", "622926", "622927", "622928", "622929", "622930", - "622931", "620124", "620183", "620561", "625116", "622227", - "621372", "621464", "625942", "622158", "625917", "621765", - "620094", "620186", "621719", "621719", "621750", "621377", - "620148", "620185", "621374", "621731", "621781", "552599", - "623206", "621671", "620059", "403361", "404117", "404118", - "404119", "404120", "404121", "463758", "514027", "519412", - "519413", "520082", "520083", "558730", "621282", "621336", - "621619", "622821", "622822", "622823", "622824", "622825", - "622826", "622827", "622828", "622836", "622837", "622840", - "622841", "622843", "622844", "622845", "622846", "622847", - "622848", "622849", "623018", "625996", "625997", "625998", - "628268", "625826", "625827", "548478", "544243", "622820", - "622830", "622838", "625336", "628269", "620501", "621660", - "621661", "621662", "621663", "621665", "621667", "621668", - "621669", "621666", "625908", "625910", "625909", "356833", - "356835", "409665", "409666", "409668", "409669", "409670", - "409671", "409672", "456351", "512315", "512316", "512411", - "512412", "514957", "409667", "518378", "518379", "518474", - "518475", "518476", "438088", "524865", "525745", "525746", - "547766", "552742", "553131", "558868", "514958", "622752", - "622753", "622755", "524864", "622757", "622758", "622759", - "622760", "622761", "622762", "622763", "601382", "622756", - "628388", "621256", "621212", "620514", "622754", "622764", - "518377", "622765", "622788", "621283", "620061", "621725", - "620040", "558869", "621330", "621331", "621332", "621333", - "621297", "377677", "621568", "621569", "625905", "625906", - "625907", "628313", "625333", "628312", "623208", "621620", - "621756", "621757", "621758", "621759", "621785", "621786", - "621787", "621788", "621789", "621790", "621672", "625337", - "625338", "625568", "621648", "621248", "621249", "622750", - "622751", "622771", "622772", "622770", "625145", "620531", - "620210", "620211", "622479", "622480", "622273", "622274", - "621231", "621638", "621334", "625140", "621395", "622725", - "622728", "621284", "421349", "434061", "434062", "436728", - "436742", "453242", "491031", "524094", "526410", "544033", - "552245", "589970", "620060", "621080", "621081", "621466", - "621467", "621488", "621499", "621598", "621621", "621700", - "622280", "622700", "622707", "622966", "622988", "625955", - "625956", "553242", "621082", "621673", "623211", "356896", - "356899", "356895", "436718", "436738", "436745", "436748", - "489592", "531693", "532450", "532458", "544887", "552801", - "557080", "558895", "559051", "622166", "622168", "622708", - "625964", "625965", "625966", "628266", "628366", "625362", - "625363", "628316", "628317", "620021", "620521", "405512", - "601428", "405512", "434910", "458123", "458124", "520169", - "522964", "552853", "601428", "622250", "622251", "521899", - "622254", "622255", "622256", "622257", "622258", "622259", - "622253", "622261", "622284", "622656", "628216", "622252", - "66405512", "622260", "66601428", "955590", "955591", "955592", - "955593", "628218", "622262", "621069", "620013", "625028", - "625029", "621436", "621002", "621335", "433670", "433680", - "442729", "442730", "620082", "622690", "622691", "622692", - "622696", "622698", "622998", "622999", "433671", "968807", - "968808", "968809", "621771", "621767", "621768", "621770", - "621772", "621773", "620527", "356837", "356838", "486497", - "622660", "622662", "622663", "622664", "622665", "622666", - "622667", "622669", "622670", "622671", "622672", "622668", - "622661", "622674", "622673", "620518", "621489", "621492", - "620535", "623156", "621490", "621491", "620085", "623155", - "623157", "623158", "623159", "999999", "621222", "623020", - "623021", "623022", "623023", "622630", "622631", "622632", - "622633", "622615", "622616", "622618", "622622", "622617", - "622619", "415599", "421393", "421865", "427570", "427571", - "472067", "472068", "622620", "621691", "545392", "545393", - "545431", "545447", "356859", "356857", "407405", "421869", - "421870", "421871", "512466", "356856", "528948", "552288", - "622600", "622601", "622602", "517636", "622621", "628258", - "556610", "622603", "464580", "464581", "523952", "545217", - "553161", "356858", "622623", "625911", "377152", "377153", - "377158", "377155", "625912", "625913", "356885", "356886", - "356887", "356888", "356890", "402658", "410062", "439188", - "439227", "468203", "479228", "479229", "512425", "521302", - "524011", "356889", "545620", "545621", "545947", "545948", - "552534", "552587", "622575", "622576", "622577", "622579", - "622580", "545619", "622581", "622582", "622588", "622598", - "622609", "690755", "690755", "545623", "621286", "620520", - "621483", "621485", "621486", "628290", "622578", "370285", - "370286", "370287", "370289", "439225", "518710", "518718", - "628362", "439226", "628262", "625802", "625803", "621299", - "966666", "622909", "622908", "438588", "438589", "461982", - "486493", "486494", "486861", "523036", "451289", "527414", - "528057", "622901", "622902", "622922", "628212", "451290", - "524070", "625084", "625085", "625086", "625087", "548738", - "549633", "552398", "625082", "625083", "625960", "625961", - "625962", "625963", "356851", "356852", "404738", "404739", - "456418", "498451", "515672", "356850", "517650", "525998", - "622177", "622277", "622516", "622517", "622518", "622520", - "622521", "622522", "622523", "628222", "628221", "984301", - "984303", "622176", "622276", "622228", "621352", "621351", - "621390", "621792", "625957", "625958", "621791", "620530", - "625993", "622519", "621793", "621795", "621796", "622500", - "623078", "622384", "940034", "940015", "622886", "622391", - "940072", "622359", "940066", "622857", "940065", "621019", - "622309", "621268", "622884", "621453", "622684", "621016", - "621015", "622950", "622951", "621072", "623183", "623185", - "621005", "622172", "622985", "622987", "622267", "622278", - "622279", "622468", "622892", "940021", "621050", "620522", - "356827", "356828", "356830", "402673", "402674", "438600", - "486466", "519498", "520131", "524031", "548838", "622148", - "622149", "622268", "356829", "622300", "628230", "622269", - "625099", "625953", "625350", "625351", "625352", "519961", - "625839", "421317", "602969", "621030", "621420", "621468", - "623111", "422160", "422161", "622865", "940012", "623131", - "622178", "622179", "628358", "622394", "940025", "621279", - "622281", "622316", "940022", "621418", "512431", "520194", - "621626", "623058", "602907", "622986", "622989", "622298", - "622338", "940032", "623205", "621977", "990027", "622325", - "623029", "623105", "621244", "623081", "623108", "566666", - "622455", "940039", "622466", "628285", "622420", "940041", - "623118", "603708", "622993", "623070", "623069", "623172", - "623173", "622383", "622385", "628299", "603506", "603367", - "622878", "623061", "623209", "628242", "622595", "622303", - "622305", "621259", "622596", "622333", "940050", "621439", - "623010", "621751", "628278", "625502", "625503", "625135", - "622476", "621754", "622143", "940001", "623026", "623086", - "628291", "621532", "621482", "622135", "622152", "622153", - "622154", "622996", "622997", "940027", "623099", "623007", - "940055", "622397", "622398", "940054", "622331", "622426", - "625995", "621452", "628205", "628214", "625529", "622428", - "621529", "622429", "621417", "623089", "623200", "940057", - "622311", "623119", "622877", "622879", "621775", "623203", - "603601", "622137", "622327", "622340", "622366", "622134", - "940018", "623016", "623096", "940049", "622425", "622425", - "621577", "622485", "623098", "628329", "621538", "940006", - "621269", "622275", "621216", "622465", "940031", "621252", - "622146", "940061", "621419", "623170", "622440", "940047", - "940017", "622418", "623077", "622413", "940002", "623188", - "622310", "940068", "622321", "625001", "622427", "940069", - "623039", "628273", "622370", "683970", "940074", "621437", - "628319", "990871", "622308", "621415", "623166", "622132", - "621340", "621341", "622140", "623073", "622147", "621633", - "622301", "623171", "621422", "622335", "622336", "622165", - "622315", "628295", "625950", "621760", "622337", "622411", - "623102", "622342", "623048", "622367", "622392", "623085", - "622395", "622441", "622448", "621413", "622856", "621037", - "621097", "621588", "623032", "622644", "623518", "622870", - "622866", "623072", "622897", "628279", "622864", "621403", - "622561", "622562", "622563", "622167", "622777", "621497", - "622868", "622899", "628255", "625988", "622566", "622567", - "622625", "622626", "625946", "628200", "621076", "504923", - "622173", "622422", "622447", "622131", "940076", "621579", - "622876", "622873", "622962", "622936", "623060", "622937", - "623101", "621460", "622939", "622960", "623523", "621591", - "622961", "628210", "622283", "625902", "621010", "622980", - "623135", "621726", "621088", "620517", "622740", "625036", - "621014", "621004", "622972", "623196", "621028", "623083", - "628250", "623121", "621070", "628253", "622979", "621035", - "621038", "621086", "621498", "621296", "621448", "622945", - "621755", "622940", "623120", "628355", "621089", "623161", - "628339", "621074", "621515", "623030", "621345", "621090", - "623178", "621091", "623168", "621057", "623199", "621075", - "623037", "628303", "621233", "621235", "621223", "621780", - "621221", "623138", "628389", "621239", "623068", "621271", - "628315", "621272", "621738", "621273", "623079", "621263", - "621325", "623084", "621327", "621753", "628331", "623160", - "621366", "621388", "621348", "621359", "621360", "621217", - "622959", "621270", "622396", "622511", "623076", "621391", - "621339", "621469", "621625", "623688", "623113", "621601", - "621655", "621636", "623182", "623087", "621696", "622955", - "622478", "940013", "621495", "621688", "623162", "622462", - "628272", "625101", "622323", "623071", "603694", "622128", - "622129", "623035", "623186", "621522", "622271", "940037", - "940038", "985262", "622322", "628381", "622481", "622341", - "940058", "623115", "621258", "621465", "621528", "622328", - "940062", "625288", "623038", "625888", "622332", "940063", - "623123", "622138", "621066", "621560", "621068", "620088", - "621067", "622531", "622329", "623103", "622339", "620500", - "621024", "622289", "622389", "628300", "625516", "621516", - "622859", "622869", "623075", "622895", "623125", "622947", - "621561", "623095", "621073", "623109", "621361", "623033", - "623207", "622891", "621363", "623189", "623510", "622995", - "621053", "621230", "621229", "622218", "628267", "621392", - "621481", "621310", "621396", "623251", "628351" }; - // "发卡行.卡种名称", - private static final String[] BANKNAME = { "邮储银行·绿卡通", "邮储银行·绿卡银联标准卡", - "邮储银行·绿卡银联标准卡", "邮储银行·绿卡专用卡", "邮储银行·绿卡银联标准卡", "邮储银行·绿卡(银联卡)", - "邮储银行·绿卡VIP卡", "邮储银行·银联标准卡", "邮储银行·中职学生资助卡", "邮政储蓄银行·IC绿卡通VIP卡", - "邮政储蓄银行·IC绿卡通", "邮政储蓄银行·IC联名卡", "邮政储蓄银行·IC预付费卡", "邮储银行·绿卡银联标准卡", - "邮储银行·绿卡通", "邮政储蓄银行·武警军人保障卡", "邮政储蓄银行·中国旅游卡(金卡)", - "邮政储蓄银行·普通高中学生资助卡", "邮政储蓄银行·中国旅游卡(普卡)", "邮政储蓄银行·福农卡", - "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", - "工商银行·牡丹VISA卡(单位卡)", "工商银行·牡丹VISA信用卡", "工商银行·牡丹VISA卡(单位卡)", - "工商银行·牡丹VISA信用卡", "工商银行·牡丹VISA信用卡", "工商银行·牡丹VISA信用卡", - "工商银行·牡丹VISA信用卡", "工商银行·牡丹运通卡普通卡", "工商银行·牡丹VISA信用卡", - "工商银行·牡丹VISA白金卡", "工商银行·牡丹贷记卡(银联卡)", "工商银行·牡丹贷记卡(银联卡)", - "工商银行·牡丹贷记卡(银联卡)", "工商银行·牡丹贷记卡(银联卡)", "工商银行·牡丹欧元卡", "工商银行·牡丹欧元卡", - "工商银行·牡丹欧元卡", "工商银行·牡丹万事达国际借记卡", "工商银行·牡丹VISA信用卡", "工商银行·海航信用卡", - "工商银行·牡丹VISA信用卡", "工商银行·牡丹万事达信用卡", "工商银行·牡丹万事达信用卡", - "工商银行·牡丹万事达信用卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹万事达白金卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·海航信用卡个人普卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·灵通卡", "工商银行·牡丹灵通卡", "工商银行·E时代卡", "工商银行·E时代卡", - "工商银行·理财金卡", "工商银行·准贷记卡(个普)", "工商银行·准贷记卡(个普)", "工商银行·准贷记卡(个普)", - "工商银行·准贷记卡(个普)", "工商银行·准贷记卡(个普)", "工商银行·牡丹灵通卡", "工商银行·准贷记卡(商普)", - "工商银行·牡丹卡(商务卡)", "工商银行·准贷记卡(商金)", "工商银行·牡丹卡(商务卡)", "工商银行·贷记卡(个普)", - "工商银行·牡丹卡(个人卡)", "工商银行·牡丹卡(个人卡)", "工商银行·牡丹卡(个人卡)", "工商银行·牡丹卡(个人卡)", - "工商银行·贷记卡(个金)", "工商银行·牡丹交通卡", "工商银行·准贷记卡(个金)", "工商银行·牡丹交通卡", - "工商银行·贷记卡(商普)", "工商银行·贷记卡(商金)", "工商银行·牡丹卡(商务卡)", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹交通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", - "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹贷记卡", - "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹灵通卡", - "工商银行·中央预算单位公务卡", "工商银行·牡丹灵通卡", "工商银行·财政预算单位公务卡", "工商银行·牡丹卡白金卡", - "工商银行·牡丹卡普卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", - "工商银行·国航知音牡丹信用卡", "工商银行·银联标准卡", "工商银行·中职学生资助卡", "工商银行·专用信用消费卡", - "工商银行·牡丹社会保障卡", "中国工商银行·牡丹东航联名卡", "中国工商银行·牡丹东航联名卡", - "中国工商银行·牡丹运通白金卡", "中国工商银行·福农灵通卡", "中国工商银行·福农灵通卡", "工商银行·灵通卡", - "工商银行·灵通卡", "中国工商银行·中国旅行卡", "工商银行·牡丹卡普卡", "工商银行·国际借记卡", - "工商银行·国际借记卡", "工商银行·国际借记卡", "工商银行·国际借记卡", "中国工商银行·牡丹JCB信用卡", - "中国工商银行·牡丹JCB信用卡", "中国工商银行·牡丹JCB信用卡", "中国工商银行·牡丹JCB信用卡", - "中国工商银行·牡丹多币种卡", "中国工商银行·武警军人保障卡", "工商银行·预付芯片卡", "工商银行·理财金账户金卡", - "工商银行·灵通卡", "工商银行·牡丹宁波市民卡", "中国工商银行·中国旅游卡", "中国工商银行·中国旅游卡", - "中国工商银行·中国旅游卡", "中国工商银行·借记卡", "中国工商银行·借贷合一卡", "中国工商银行·普通高中学生资助卡", - "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹百夫长信用卡", - "中国工商银行·牡丹百夫长信用卡", "工商银行·工银财富卡", "中国工商银行·中小商户采购卡", - "中国工商银行·中小商户采购卡", "中国工商银行·环球旅行金卡", "中国工商银行·环球旅行白金卡", - "中国工商银行·牡丹工银大来卡", "中国工商银行·牡丹工银大莱卡", "中国工商银行·IC金卡", "中国工商银行·IC白金卡", - "中国工商银行·工行IC卡(红卡)", "中国工商银行布鲁塞尔分行·借记卡", "中国工商银行布鲁塞尔分行·预付卡", - "中国工商银行布鲁塞尔分行·预付卡", "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", - "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", "中国工商银行加拿大分行·借记卡", - "中国工商银行加拿大分行·借记卡", "中国工商银行加拿大分行·预付卡", "中国工商银行巴黎分行·借记卡", - "中国工商银行巴黎分行·借记卡", "中国工商银行巴黎分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", - "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", - "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·预付卡", "中国工商银行法兰克福分行·预付卡", - "中国工商银行印尼分行·借记卡", "中国工商银行印尼分行·信用卡", "中国工商银行米兰分行·借记卡", - "中国工商银行米兰分行·预付卡", "中国工商银行米兰分行·预付卡", "中国工商银行阿拉木图子行·借记卡", - "中国工商银行阿拉木图子行·贷记卡", "中国工商银行阿拉木图子行·借记卡", "中国工商银行阿拉木图子行·预付卡", - "中国工商银行万象分行·借记卡", "中国工商银行万象分行·贷记卡", "中国工商银行卢森堡分行·借记卡", - "中国工商银行卢森堡分行·贷记卡", "中国工商银行澳门分行·E时代卡", "中国工商银行澳门分行·E时代卡", - "中国工商银行澳门分行·E时代卡", "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·理财金账户", - "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·预付卡", "中国工商银行澳门分行·预付卡", - "中国工商银行澳门分行·工银闪付预付卡", "中国工商银行澳门分行·工银银联公司卡", "中国工商银行澳门分行·Diamond", - "中国工商银行阿姆斯特丹·借记卡", "中国工商银行卡拉奇分行·借记卡", "中国工商银行卡拉奇分行·贷记卡", - "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·借记卡", - "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·借记卡", - "中国工商银行新加坡分行·借记卡", "中国工商银行马德里分行·借记卡", "中国工商银行马德里分行·借记卡", - "中国工商银行马德里分行·预付卡", "中国工商银行马德里分行·预付卡", "中国工商银行伦敦子行·借记卡", - "中国工商银行伦敦子行·工银伦敦借记卡", "中国工商银行伦敦子行·借记卡", "农业银行·金穗贷记卡", "农业银行·中国旅游卡", - "农业银行·普通高中学生资助卡", "农业银行·银联标准卡", "农业银行·金穗贷记卡(银联卡)", - "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡(银联卡)", - "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡(银联卡)", "农业银行·VISA白金卡", - "农业银行·万事达白金卡", "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡(银联卡)", - "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡(银联卡)", "农业银行·金穗贷记卡", "农业银行·中职学生资助卡", - "农业银行·专用惠农卡", "农业银行·武警军人保障卡", "农业银行·金穗校园卡(银联卡)", "农业银行·金穗星座卡(银联卡)", - "农业银行·金穗社保卡(银联卡)", "农业银行·金穗旅游卡(银联卡)", "农业银行·金穗青年卡(银联卡)", - "农业银行·复合介质金穗通宝卡", "农业银行·金穗海通卡", "农业银行·退役金卡", "农业银行·金穗贷记卡", - "农业银行·金穗贷记卡", "农业银行·金穗通宝卡(银联卡)", "农业银行·金穗惠农卡", "农业银行·金穗通宝银卡", - "农业银行·金穗通宝卡(银联卡)", "农业银行·金穗通宝卡(银联卡)", "农业银行·金穗通宝卡", - "农业银行·金穗通宝卡(银联卡)", "农业银行·金穗通宝卡(银联卡)", "农业银行·金穗通宝钻石卡", "农业银行·掌尚钱包", - "农业银行·银联IC卡金卡", "农业银行·银联预算单位公务卡金卡", "农业银行·银联IC卡白金卡", "农业银行·金穗公务卡", - "中国农业银行贷记卡·IC普卡", "中国农业银行贷记卡·IC金卡", "中国农业银行贷记卡·澳元卡", - "中国农业银行贷记卡·欧元卡", "中国农业银行贷记卡·金穗通商卡", "中国农业银行贷记卡·金穗通商卡", - "中国农业银行贷记卡·银联白金卡", "中国农业银行贷记卡·中国旅游卡", "中国农业银行贷记卡·银联IC公务卡", - "宁波市农业银行·市民卡B卡", "中国银行·联名卡", "中国银行·个人普卡", "中国银行·个人金卡", "中国银行·员工普卡", - "中国银行·员工金卡", "中国银行·理财普卡", "中国银行·理财金卡", "中国银行·理财银卡", "中国银行·理财白金卡", - "中国银行·中行金融IC卡白金卡", "中国银行·中行金融IC卡普卡", "中国银行·中行金融IC卡金卡", - "中国银行·中银JCB卡金卡", "中国银行·中银JCB卡普卡", "中国银行·员工普卡", "中国银行·个人普卡", - "中国银行·中银威士信用卡员", "中国银行·中银威士信用卡员", "中国银行·个人白金卡", "中国银行·中银威士信用卡", - "中国银行·长城公务卡", "中国银行·长城电子借记卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", - "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银威士信用卡员", - "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", - "中国银行·长城万事达信用卡", "中国银行·中银奥运信用卡", "中国银行·长城信用卡", "中国银行·长城信用卡", - "中国银行·长城信用卡", "中国银行·长城万事达信用卡", "中国银行·长城公务卡", "中国银行·长城公务卡", - "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", - "中国银行·长城人民币信用卡", "中国银行·长城信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", - "中国银行·长城信用卡", "中国银行·银联单币贷记卡", "中国银行·长城信用卡", "中国银行·长城信用卡", - "中国银行·长城信用卡", "中国银行·长城电子借记卡", "中国银行·长城人民币信用卡", "中国银行·银联标准公务卡", - "中国银行·一卡双账户普卡", "中国银行·财互通卡", "中国银行·电子现金卡", "中国银行·长城人民币信用卡", - "中国银行·长城单位信用卡普卡", "中国银行·中银女性主题信用卡", "中国银行·长城单位信用卡金卡", "中国银行·白金卡", - "中国银行·中职学生资助卡", "中国银行·银联标准卡", "中国银行·金融IC卡", "中国银行·长城社会保障卡", - "中国银行·世界卡", "中国银行·社保联名卡", "中国银行·社保联名卡", "中国银行·医保联名卡", "中国银行·医保联名卡", - "中国银行·公司借记卡", "中国银行·银联美运顶级卡", "中国银行·长城福农借记卡金卡", "中国银行·长城福农借记卡普卡", - "中国银行·中行金融IC卡普卡", "中国银行·中行金融IC卡金卡", "中国银行·中行金融IC卡白金卡", - "中国银行·长城银联公务IC卡白金卡", "中国银行·中银旅游信用卡", "中国银行·长城银联公务IC卡金卡", - "中国银行·中国旅游卡", "中国银行·武警军人保障卡", "中国银行·社保联名借记IC卡", "中国银行·社保联名借记IC卡", - "中国银行·医保联名借记IC卡", "中国银行·医保联名借记IC卡", "中国银行·借记IC个人普卡", - "中国银行·借记IC个人金卡", "中国银行·借记IC个人普卡", "中国银行·借记IC白金卡", "中国银行·借记IC钻石卡", - "中国银行·借记IC联名卡", "中国银行·普通高中学生资助卡", "中国银行·长城环球通港澳台旅游金卡", - "中国银行·长城环球通港澳台旅游白金卡", "中国银行·中银福农信用卡", "中国银行金边分行·借记卡", - "中国银行雅加达分行·借记卡", "中国银行首尔分行·借记卡", "中国银行澳门分行·人民币信用卡", - "中国银行澳门分行·人民币信用卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", - "中国银行澳门分行·中银银联双币商务卡", "中国银行澳门分行·预付卡", "中国银行澳门分行·澳门中国银行银联预付卡", - "中国银行澳门分行·澳门中国银行银联预付卡", "中国银行澳门分行·熊猫卡", "中国银行澳门分行·财富卡", - "中国银行澳门分行·银联港币卡", "中国银行澳门分行·银联澳门币卡", "中国银行马尼拉分行·双币种借记卡", - "中国银行胡志明分行·借记卡", "中国银行曼谷分行·借记卡", "中国银行曼谷分行·长城信用卡环球通", - "中国银行曼谷分行·借记卡", "建设银行·龙卡准贷记卡", "建设银行·龙卡准贷记卡金卡", "建设银行·中职学生资助卡", - "建设银行·乐当家银卡VISA", "建设银行·乐当家金卡VISA", "建设银行·乐当家白金卡", - "建设银行·龙卡普通卡VISA", "建设银行·龙卡储蓄卡", "建设银行·VISA准贷记卡(银联卡)", - "建设银行·VISA准贷记金卡", "建设银行·乐当家", "建设银行·乐当家", "建设银行·准贷记金卡", - "建设银行·乐当家白金卡", "建设银行·金融复合IC卡", "建设银行·银联标准卡", "建设银行·银联理财钻石卡", - "建设银行·金融IC卡", "建设银行·理财白金卡", "建设银行·社保IC卡", "建设银行·财富卡私人银行卡", - "建设银行·理财金卡", "建设银行·福农卡", "建设银行·武警军人保障卡", "建设银行·龙卡通", "建设银行·银联储蓄卡", - "建设银行·龙卡储蓄卡(银联卡)", "建设银行·准贷记卡", "建设银行·理财白金卡", "建设银行·理财金卡", - "建设银行·准贷记卡普卡", "建设银行·准贷记卡金卡", "建设银行·龙卡信用卡", "建设银行·建行陆港通龙卡", - "中国建设银行·普通高中学生资助卡", "中国建设银行·中国旅游卡", "中国建设银行·龙卡JCB金卡", - "中国建设银行·龙卡JCB白金卡", "中国建设银行·龙卡JCB普卡", "中国建设银行·龙卡贷记卡公司卡", - "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡国际普通卡VISA", "中国建设银行·龙卡国际金卡VISA", - "中国建设银行·VISA白金信用卡", "中国建设银行·龙卡国际白金卡", "中国建设银行·龙卡国际普通卡MASTER", - "中国建设银行·龙卡国际金卡MASTER", "中国建设银行·龙卡万事达金卡", "中国建设银行·龙卡贷记卡", - "中国建设银行·龙卡万事达白金卡", "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡万事达信用卡", - "中国建设银行·龙卡人民币信用卡", "中国建设银行·龙卡人民币信用金卡", "中国建设银行·龙卡人民币白金卡", - "中国建设银行·龙卡IC信用卡普卡", "中国建设银行·龙卡IC信用卡金卡", "中国建设银行·龙卡IC信用卡白金卡", - "中国建设银行·龙卡银联公务卡普卡", "中国建设银行·龙卡银联公务卡金卡", "中国建设银行·中国旅游卡", - "中国建设银行·中国旅游卡", "中国建设银行·龙卡IC公务卡", "中国建设银行·龙卡IC公务卡", "交通银行·交行预付卡", - "交通银行·世博预付IC卡", "交通银行·太平洋互连卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋互连卡(银联卡)", - "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", - "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋人民币贷记卡", - "交通银行·太平洋人民币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", - "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋借记卡", - "交通银行·太平洋人民币贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋MORE卡", "交通银行·白金卡", - "交通银行·交通银行公务卡普卡", "交通银行·太平洋人民币贷记卡", "交通银行·太平洋互连卡", "交通银行·太平洋借记卡", - "交通银行·太平洋万事顺卡", "交通银行·太平洋贷记卡(银联卡)", "交通银行·太平洋贷记卡(银联卡)", - "交通银行·太平洋贷记卡(银联卡)", "交通银行·太平洋贷记卡(银联卡)", "交通银行·交通银行公务卡金卡", - "交通银行·交银IC卡", "交通银行香港分行·交通银行港币借记卡", "交通银行香港分行·港币礼物卡", - "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币卡", - "交通银行香港分行·银联人民币卡", "交通银行澳门分行·银联借记卡", "中信银行·中信借记卡", "中信银行·中信借记卡", - "中信银行·中信国际借记卡", "中信银行·中信国际借记卡", "中信银行·中国旅行卡", "中信银行·中信借记卡(银联卡)", - "中信银行·中信借记卡(银联卡)", "中信银行·中信贵宾卡(银联卡)", "中信银行·中信理财宝金卡", - "中信银行·中信理财宝白金卡", "中信银行·中信钻石卡", "中信银行·中信钻石卡", "中信银行·中信借记卡", - "中信银行·中信理财宝(银联卡)", "中信银行·中信理财宝(银联卡)", "中信银行·中信理财宝(银联卡)", - "中信银行·借记卡", "中信银行·理财宝IC卡", "中信银行·理财宝IC卡", "中信银行·理财宝IC卡", - "中信银行·理财宝IC卡", "中信银行·理财宝IC卡", "中信银行·主账户复合电子现金卡", "光大银行·阳光商旅信用卡", - "光大银行·阳光商旅信用卡", "光大银行·阳光商旅信用卡", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", - "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", - "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", - "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", "光大银行·阳光卡(银联卡)", - "光大银行·阳光卡(银联卡)", "光大银行·借记卡普卡", "光大银行·社会保障IC卡", "光大银行·IC借记卡普卡", - "光大银行·手机支付卡", "光大银行·联名IC卡普卡", "光大银行·借记IC卡白金卡", "光大银行·借记IC卡金卡", - "光大银行·阳光旅行卡", "光大银行·借记IC卡钻石卡", "光大银行·联名IC卡金卡", "光大银行·联名IC卡白金卡", - "光大银行·联名IC卡钻石卡", "华夏银行·华夏卡(银联卡)", "华夏银行·华夏白金卡", "华夏银行·华夏普卡", - "华夏银行·华夏金卡", "华夏银行·华夏白金卡", "华夏银行·华夏钻石卡", "华夏银行·华夏卡(银联卡)", - "华夏银行·华夏至尊金卡(银联卡)", "华夏银行·华夏丽人卡(银联卡)", "华夏银行·华夏万通卡", - "民生银行·民生借记卡(银联卡)", "民生银行·民生银联借记卡-金卡", "民生银行·钻石卡", - "民生银行·民生借记卡(银联卡)", "民生银行·民生借记卡(银联卡)", "民生银行·民生借记卡(银联卡)", - "民生银行·民生借记卡", "民生银行·民生国际卡", "民生银行·民生国际卡(银卡)", "民生银行·民生国际卡(欧元卡)", - "民生银行·民生国际卡(澳元卡)", "民生银行·民生国际卡", "民生银行·民生国际卡", "民生银行·薪资理财卡", - "民生银行·借记卡普卡", "民生银行·民生MasterCard", "民生银行·民生MasterCard", - "民生银行·民生MasterCard", "民生银行·民生MasterCard", "民生银行·民生JCB信用卡", - "民生银行·民生JCB金卡", "民生银行·民生贷记卡(银联卡)", "民生银行·民生贷记卡(银联卡)", - "民生银行·民生贷记卡(银联卡)", "民生银行·民生贷记卡(银联卡)", "民生银行·民生贷记卡(银联卡)", - "民生银行·民生JCB普卡", "民生银行·民生贷记卡(银联卡)", "民生银行·民生贷记卡(银联卡)", - "民生银行·民生信用卡(银联卡)", "民生银行·民生信用卡(银联卡)", "民生银行·民生银联白金信用卡", - "民生银行·民生贷记卡(银联卡)", "民生银行·民生银联个人白金卡", "民生银行·公务卡金卡", - "民生银行·民生贷记卡(银联卡)", "民生银行·民生银联商务信用卡", "民生银行·民VISA无限卡", - "民生银行·民生VISA商务白金卡", "民生银行·民生万事达钛金卡", "民生银行·民生万事达世界卡", - "民生银行·民生万事达白金公务卡", "民生银行·民生JCB白金卡", "民生银行·银联标准金卡", "民生银行·银联芯片普卡", - "民生银行·民生运通双币信用卡普卡", "民生银行·民生运通双币信用卡金卡", "民生银行·民生运通双币信用卡钻石卡", - "民生银行·民生运通双币标准信用卡白金卡", "民生银行·银联芯片金卡", "民生银行·银联芯片白金卡", - "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", - "招商银行·招商银行信用卡", "招商银行·两地一卡通", "招商银行·招行国际卡(银联卡)", "招商银行·招商银行信用卡", - "招商银行·VISA商务信用卡", "招商银行·招行国际卡(银联卡)", "招商银行·招商银行信用卡", - "招商银行·招商银行信用卡", "招商银行·招行国际卡(银联卡)", "招商银行·世纪金花联名信用卡", - "招商银行·招行国际卡(银联卡)", "招商银行·招商银行信用卡", "招商银行·万事达信用卡", "招商银行·万事达信用卡", - "招商银行·万事达信用卡", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", - "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", - "招商银行·一卡通(银联卡)", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", - "招商银行·一卡通(银联卡)", "招商银行·公司卡(银联卡)", "招商银行·金卡", "招商银行·招行一卡通", - "招商银行·招行一卡通", "招商银行·万事达信用卡", "招商银行·金葵花卡", "招商银行·电子现金卡", - "招商银行·银联IC普卡", "招商银行·银联IC金卡", "招商银行·银联金葵花IC卡", "招商银行·IC公务卡", - "招商银行·招商银行信用卡", "招商银行信用卡中心·美国运通绿卡", "招商银行信用卡中心·美国运通金卡", - "招商银行信用卡中心·美国运通商务绿卡", "招商银行信用卡中心·美国运通商务金卡", "招商银行信用卡中心·VISA信用卡", - "招商银行信用卡中心·MASTER信用卡", "招商银行信用卡中心·MASTER信用金卡", - "招商银行信用卡中心·银联标准公务卡(金卡)", "招商银行信用卡中心·VISA信用卡", - "招商银行信用卡中心·银联标准财政公务卡", "招商银行信用卡中心·芯片IC信用卡", "招商银行信用卡中心·芯片IC信用卡", - "招商银行香港分行·香港一卡通", "兴业银行·兴业卡(银联卡)", "兴业银行·兴业卡(银联标准卡)", - "兴业银行·兴业自然人生理财卡", "兴业银行·兴业智能卡(银联卡)", "兴业银行·兴业智能卡", - "兴业银行·visa标准双币个人普卡", "兴业银行·VISA商务普卡", "兴业银行·VISA商务金卡", - "兴业银行·VISA运动白金信用卡", "兴业银行·万事达信用卡(银联卡)", "兴业银行·VISA信用卡(银联卡)", - "兴业银行·加菲猫信用卡", "兴业银行·个人白金卡", "兴业银行·银联信用卡(银联卡)", "兴业银行·银联信用卡(银联卡)", - "兴业银行·银联白金信用卡", "兴业银行·银联标准公务卡", "兴业银行·VISA信用卡(银联卡)", - "兴业银行·万事达信用卡(银联卡)", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记金卡", - "兴业银行·银联标准贷记金卡", "兴业银行·银联标准贷记金卡", "兴业银行·兴业信用卡", "兴业银行·兴业信用卡", - "兴业银行·兴业信用卡", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记普卡", "兴业银行·兴业芯片普卡", - "兴业银行·兴业芯片金卡", "兴业银行·兴业芯片白金卡", "兴业银行·兴业芯片钻石卡", "浦东发展银行·浦发JCB金卡", - "浦东发展银行·浦发JCB白金卡", "浦东发展银行·信用卡VISA普通", "浦东发展银行·信用卡VISA金卡", - "浦东发展银行·浦发银行VISA年青卡", "浦东发展银行·VISA白金信用卡", "浦东发展银行·浦发万事达白金卡", - "浦东发展银行·浦发JCB普卡", "浦东发展银行·浦发万事达金卡", "浦东发展银行·浦发万事达普卡", - "浦东发展银行·浦发单币卡", "浦东发展银行·浦发银联单币麦兜普卡", "浦东发展银行·东方轻松理财卡", - "浦东发展银行·东方-轻松理财卡普卡", "浦东发展银行·东方轻松理财卡", "浦东发展银行·东方轻松理财智业金卡", - "浦东发展银行·东方卡(银联卡)", "浦东发展银行·东方卡(银联卡)", "浦东发展银行·东方卡(银联卡)", - "浦东发展银行·公务卡金卡", "浦东发展银行·公务卡普卡", "浦东发展银行·东方卡", "浦东发展银行·东方卡", - "浦东发展银行·浦发单币卡", "浦东发展银行·浦发联名信用卡", "浦东发展银行·浦发银联白金卡", - "浦东发展银行·轻松理财普卡", "浦东发展银行·移动联名卡", "浦东发展银行·轻松理财消贷易卡", - "浦东发展银行·轻松理财普卡(复合卡)", "浦东发展银行·贷记卡", "浦东发展银行·贷记卡", - "浦东发展银行·东方借记卡(复合卡)", "浦东发展银行·电子现金卡(IC卡)", "浦东发展银行·移动浦发联名卡", - "浦东发展银行·东方-标准准贷记卡", "浦东发展银行·轻松理财金卡(复合卡)", "浦东发展银行·轻松理财白金卡(复合卡)", - "浦东发展银行·轻松理财钻石卡(复合卡)", "浦东发展银行·东方卡", "恒丰银行·九州IC卡", - "恒丰银行·九州借记卡(银联卡)", "恒丰银行·九州借记卡(银联卡)", "天津市商业银行·银联卡(银联卡)", - "烟台商业银行·金通卡", "潍坊银行·鸢都卡(银联卡)", "潍坊银行·鸳都卡(银联卡)", "临沂商业银行·沂蒙卡(银联卡)", - "临沂商业银行·沂蒙卡(银联卡)", "日照市商业银行·黄海卡", "日照市商业银行·黄海卡(银联卡)", "浙商银行·商卡", - "浙商银行·商卡", "渤海银行·浩瀚金卡", "渤海银行·渤海银行借记卡", "渤海银行·金融IC卡", - "渤海银行·渤海银行公司借记卡", "星展银行·星展银行借记卡", "星展银行·星展银行借记卡", "恒生银行·恒生通财卡", - "恒生银行·恒生优越通财卡", "新韩银行·新韩卡", "上海银行·慧通钻石卡", "上海银行·慧通金卡", - "上海银行·私人银行卡", "上海银行·综合保险卡", "上海银行·申卡社保副卡(有折)", "上海银行·申卡社保副卡(无折)", - "上海银行·白金IC借记卡", "上海银行·慧通白金卡(配折)", "上海银行·慧通白金卡(不配折)", - "上海银行·申卡(银联卡)", "上海银行·申卡借记卡", "上海银行·银联申卡(银联卡)", "上海银行·单位借记卡", - "上海银行·首发纪念版IC卡", "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·J分期付款信用卡", - "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·上海申卡IC", "上海银行·申卡贷记卡", - "上海银行·申卡贷记卡普通卡", "上海银行·申卡贷记卡金卡", "上海银行·万事达白金卡", "上海银行·万事达星运卡", - "上海银行·申卡贷记卡金卡", "上海银行·申卡贷记卡普通卡", "上海银行·安融卡", "上海银行·分期付款信用卡", - "上海银行·信用卡", "上海银行·个人公务卡", "上海银行·安融卡", "上海银行·上海银行银联白金卡", - "上海银行·贷记IC卡", "上海银行·中国旅游卡(IC普卡)", "上海银行·中国旅游卡(IC金卡)", - "上海银行·中国旅游卡(IC白金卡)", "上海银行·万事达钻石卡", "上海银行·淘宝IC普卡", "北京银行·京卡借记卡", - "北京银行·京卡(银联卡)", "北京银行·京卡借记卡", "北京银行·京卡", "北京银行·京卡", "北京银行·借记IC卡", - "北京银行·京卡贵宾金卡", "北京银行·京卡贵宾白金卡", "吉林银行·君子兰一卡通(银联卡)", - "吉林银行·君子兰卡(银联卡)", "吉林银行·长白山金融IC卡", "吉林银行·信用卡", "吉林银行·信用卡", - "吉林银行·公务卡", "镇江市商业银行·金山灵通卡(银联卡)", "镇江市商业银行·金山灵通卡(银联卡)", - "宁波银行·银联标准卡", "宁波银行·汇通借记卡", "宁波银行·汇通卡(银联卡)", "宁波银行·明州卡", - "宁波银行·汇通借记卡", "宁波银行·汇通国际卡银联双币卡", "宁波银行·汇通国际卡银联双币卡", "平安银行·新磁条借记卡", - "平安银行·平安银行IC借记卡", "平安银行·万事顺卡", "平安银行·平安银行借记卡", "平安银行·平安银行借记卡", - "平安银行·万事顺借记卡", "焦作市商业银行·月季借记卡(银联卡)", "焦作市商业银行·月季城市通(银联卡)", - "焦作市商业银行·中国旅游卡", "温州银行·金鹿卡", "汉口银行·九通卡(银联卡)", "汉口银行·九通卡", - "汉口银行·借记卡", "汉口银行·借记卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰IC卡", "盛京银行·玫瑰IC卡", - "盛京银行·玫瑰卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰卡(银联卡)", "盛京银行·玫瑰卡(银联卡)", - "盛京银行·盛京银行公务卡", "洛阳银行·都市一卡通(银联卡)", "洛阳银行·都市一卡通(银联卡)", "洛阳银行·--", - "大连银行·北方明珠卡", "大连银行·人民币借记卡", "大连银行·金融IC借记卡", "大连银行·大连市社会保障卡", - "大连银行·借记IC卡", "大连银行·借记IC卡", "大连银行·大连市商业银行贷记卡", "大连银行·大连市商业银行贷记卡", - "大连银行·银联标准公务卡", "苏州市商业银行·姑苏卡", "杭州商业银行·西湖卡", "杭州商业银行·西湖卡", - "杭州商业银行·借记IC卡", "杭州商业银行·", "南京银行·梅花信用卡公务卡", "南京银行·梅花信用卡商务卡", - "南京银行·梅花贷记卡(银联卡)", "南京银行·梅花借记卡(银联卡)", "南京银行·白金卡", "南京银行·商务卡", - "东莞市商业银行·万顺通卡(银联卡)", "东莞市商业银行·万顺通卡(银联卡)", "东莞市商业银行·万顺通借记卡", - "东莞市商业银行·社会保障卡", "乌鲁木齐市商业银行·雪莲借记IC卡", "乌鲁木齐市商业银行·乌鲁木齐市公务卡", - "乌鲁木齐市商业银行·福农卡贷记卡", "乌鲁木齐市商业银行·福农卡准贷记卡", "乌鲁木齐市商业银行·雪莲准贷记卡", - "乌鲁木齐市商业银行·雪莲贷记卡(银联卡)", "乌鲁木齐市商业银行·雪莲借记IC卡", - "乌鲁木齐市商业银行·雪莲借记卡(银联卡)", "乌鲁木齐市商业银行·雪莲卡(银联卡)", "绍兴银行·兰花IC借记卡", - "绍兴银行·社保IC借记卡", "绍兴银行·兰花公务卡", "成都商业银行·芙蓉锦程福农卡", "成都商业银行·芙蓉锦程天府通卡", - "成都商业银行·锦程卡(银联卡)", "成都商业银行·锦程卡金卡", "成都商业银行·锦程卡定活一卡通金卡", - "成都商业银行·锦程卡定活一卡通", "成都商业银行·锦程力诚联名卡", "成都商业银行·锦程力诚联名卡", - "成都商业银行·锦程卡(银联卡)", "抚顺银行·借记IC卡", "临商银行·借记卡", "宜昌市商业银行·三峡卡(银联卡)", - "宜昌市商业银行·信用卡(银联卡)", "葫芦岛市商业银行·一通卡", "葫芦岛市商业银行·一卡通(银联卡)", - "天津市商业银行·津卡", "天津市商业银行·津卡贷记卡(银联卡)", "天津市商业银行·贷记IC卡", "天津市商业银行·--", - "天津银行·商务卡", "宁夏银行·宁夏银行公务卡", "宁夏银行·宁夏银行福农贷记卡", "宁夏银行·如意卡(银联卡)", - "宁夏银行·宁夏银行福农借记卡", "宁夏银行·如意借记卡", "宁夏银行·如意IC卡", "宁夏银行·宁夏银行如意借记卡", - "宁夏银行·中国旅游卡", "齐商银行·金达卡(银联卡)", "齐商银行·金达借记卡(银联卡)", "齐商银行·金达IC卡", - "徽商银行·黄山卡", "徽商银行·黄山卡", "徽商银行·借记卡", "徽商银行·徽商银行中国旅游卡(安徽)", - "徽商银行合肥分行·黄山卡", "徽商银行芜湖分行·黄山卡(银联卡)", "徽商银行马鞍山分行·黄山卡(银联卡)", - "徽商银行淮北分行·黄山卡(银联卡)", "徽商银行安庆分行·黄山卡(银联卡)", "重庆银行·长江卡(银联卡)", - "重庆银行·长江卡(银联卡)", "重庆银行·长江卡", "重庆银行·借记IC卡", "哈尔滨银行·丁香一卡通(银联卡)", - "哈尔滨银行·丁香借记卡(银联卡)", "哈尔滨银行·丁香卡", "哈尔滨银行·福农借记卡", - "无锡市商业银行·太湖金保卡(银联卡)", "丹东银行·借记IC卡", "丹东银行·丹东银行公务卡", "兰州银行·敦煌卡", - "南昌银行·金瑞卡(银联卡)", "南昌银行·南昌银行借记卡", "南昌银行·金瑞卡", "晋商银行·晋龙一卡通", - "晋商银行·晋龙一卡通", "晋商银行·晋龙卡(银联卡)", "青岛银行·金桥通卡", "青岛银行·金桥卡(银联卡)", - "青岛银行·金桥卡(银联卡)", "青岛银行·金桥卡", "青岛银行·借记IC卡", "吉林银行·雾凇卡(银联卡)", - "吉林银行·雾凇卡(银联卡)", "南通商业银行·金桥卡(银联卡)", "南通商业银行·金桥卡(银联卡)", - "日照银行·黄海卡、财富卡借记卡", "鞍山银行·千山卡(银联卡)", "鞍山银行·千山卡(银联卡)", "鞍山银行·千山卡", - "青海银行·三江银行卡(银联卡)", "青海银行·三江卡", "台州银行·大唐贷记卡", "台州银行·大唐准贷记卡", - "台州银行·大唐卡(银联卡)", "台州银行·大唐卡", "台州银行·借记卡", "台州银行·公务卡", - "泉州银行·海峡银联卡(银联卡)", "泉州银行·海峡储蓄卡", "泉州银行·海峡银联卡(银联卡)", "泉州银行·海峡卡", - "泉州银行·公务卡", "昆明商业银行·春城卡(银联卡)", "昆明商业银行·春城卡(银联卡)", - "昆明商业银行·富滇IC卡(复合卡)", "阜新银行·借记IC卡", "嘉兴银行·南湖借记卡(银联卡)", "廊坊银行·白金卡", - "廊坊银行·金卡", "廊坊银行·银星卡(银联卡)", "廊坊银行·龙凤呈祥卡", "内蒙古银行·百灵卡(银联卡)", - "内蒙古银行·成吉思汗卡", "湖州市商业银行·百合卡", "湖州市商业银行·", "沧州银行·狮城卡", - "南宁市商业银行·桂花卡(银联卡)", "包商银行·雄鹰卡(银联卡)", "包商银行·包头市商业银行借记卡", - "包商银行·雄鹰贷记卡", "包商银行·包商银行内蒙古自治区公务卡", "包商银行·贷记卡", "包商银行·借记卡", - "连云港市商业银行·金猴神通借记卡", "威海商业银行·通达卡(银联卡)", "威海市商业银行·通达借记IC卡", - "攀枝花市商业银行·攀枝花卡(银联卡)", "攀枝花市商业银行·攀枝花卡", "绵阳市商业银行·科技城卡(银联卡)", - "泸州市商业银行·酒城卡(银联卡)", "泸州市商业银行·酒城IC卡", "大同市商业银行·云冈卡(银联卡)", - "三门峡银行·天鹅卡(银联卡)", "广东南粤银行·南珠卡(银联卡)", "张家口市商业银行·好运IC借记卡", - "桂林市商业银行·漓江卡(银联卡)", "龙江银行·福农借记卡", "龙江银行·联名借记卡", "龙江银行·福农借记卡", - "龙江银行·龙江IC卡", "龙江银行·社会保障卡", "龙江银行·--", "江苏长江商业银行·长江卡", - "徐州市商业银行·彭城借记卡(银联卡)", "南充市商业银行·借记IC卡", "南充市商业银行·熊猫团团卡", - "莱商银行·银联标准卡", "莱芜银行·金凤卡", "莱商银行·借记IC卡", "德阳银行·锦程卡定活一卡通", - "德阳银行·锦程卡定活一卡通金卡", "德阳银行·锦程卡定活一卡通", "唐山市商业银行·唐山市城通卡", - "曲靖市商业银行·珠江源卡", "曲靖市商业银行·珠江源IC卡", "温州银行·金鹿信用卡", "温州银行·金鹿信用卡", - "温州银行·金鹿公务卡", "温州银行·贷记IC卡", "汉口银行·汉口银行贷记卡", "汉口银行·汉口银行贷记卡", - "汉口银行·九通香港旅游贷记普卡", "汉口银行·九通香港旅游贷记金卡", "汉口银行·贷记卡", "汉口银行·九通公务卡", - "江苏银行·聚宝借记卡", "江苏银行·月季卡", "江苏银行·紫金卡", "江苏银行·绿扬卡(银联卡)", - "江苏银行·月季卡(银联卡)", "江苏银行·九州借记卡(银联卡)", "江苏银行·月季卡(银联卡)", - "江苏银行·聚宝惠民福农卡", "江苏银行·江苏银行聚宝IC借记卡", "江苏银行·聚宝IC借记卡VIP卡", - "长治市商业银行·长治商行银联晋龙卡", "承德市商业银行·热河卡", "承德银行·借记IC卡", "德州银行·长河借记卡", - "德州银行·--", "遵义市商业银行·社保卡", "遵义市商业银行·尊卡", "邯郸市商业银行·邯银卡", - "邯郸市商业银行·邯郸银行贵宾IC借记卡", "安顺市商业银行·黄果树福农卡", "安顺市商业银行·黄果树借记卡", - "江苏银行·紫金信用卡(公务卡)", "江苏银行·紫金信用卡", "江苏银行·天翼联名信用卡", "平凉市商业银行·广成卡", - "玉溪市商业银行·红塔卡", "玉溪市商业银行·红塔卡", "浙江民泰商业银行·金融IC卡", "浙江民泰商业银行·民泰借记卡", - "浙江民泰商业银行·金融IC卡C卡", "浙江民泰商业银行·银联标准普卡金卡", "浙江民泰商业银行·商惠通", - "上饶市商业银行·三清山卡", "东营银行·胜利卡", "泰安市商业银行·岱宗卡", "泰安市商业银行·市民一卡通", - "浙江稠州商业银行·义卡", "浙江稠州商业银行·义卡借记IC卡", "浙江稠州商业银行·公务卡", "自贡市商业银行·借记IC卡", - "自贡市商业银行·锦程卡", "鄂尔多斯银行·天骄公务卡", "鹤壁银行·鹤卡", "许昌银行·连城卡", "铁岭银行·龙凤卡", - "乐山市商业银行·大福卡", "乐山市商业银行·--", "长安银行·长长卡", "长安银行·借记IC卡", - "重庆三峡银行·财富人生卡", "重庆三峡银行·借记卡", "石嘴山银行·麒麟借记卡", "石嘴山银行·麒麟借记卡", - "石嘴山银行·麒麟公务卡", "盘锦市商业银行·鹤卡", "盘锦市商业银行·盘锦市商业银行鹤卡", "平顶山银行·平顶山银行公务卡", - "朝阳银行·鑫鑫通卡", "朝阳银行·朝阳银行福农卡", "朝阳银行·红山卡", "宁波东海银行·绿叶卡", - "遂宁市商业银行·锦程卡", "遂宁是商业银行·金荷卡", "保定银行·直隶卡", "保定银行·直隶卡", - "凉山州商业银行·锦程卡", "凉山州商业银行·金凉山卡", "漯河银行·福卡", "漯河银行·福源卡", "漯河银行·福源公务卡", - "达州市商业银行·锦程卡", "新乡市商业银行·新卡", "晋中银行·九州方圆借记卡", "晋中银行·九州方圆卡", - "驻马店银行·驿站卡", "驻马店银行·驿站卡", "驻马店银行·公务卡", "衡水银行·金鼎卡", "衡水银行·借记IC卡", - "周口银行·如愿卡", "周口银行·公务卡", "阳泉市商业银行·金鼎卡", "阳泉市商业银行·金鼎卡", - "宜宾市商业银行·锦程卡", "宜宾市商业银行·借记IC卡", "库尔勒市商业银行·孔雀胡杨卡", "雅安市商业银行·锦城卡", - "雅安市商业银行·--", "安阳银行·安鼎卡", "信阳银行·信阳卡", "信阳银行·公务卡", "信阳银行·信阳卡", - "华融湘江银行·华融卡", "华融湘江银行·华融卡", "营口沿海银行·祥云借记卡", "景德镇商业银行·瓷都卡", - "哈密市商业银行·瓜香借记卡", "湖北银行·金牛卡", "湖北银行·汉江卡", "湖北银行·借记卡", "湖北银行·三峡卡", - "湖北银行·至尊卡", "湖北银行·金融IC卡", "西藏银行·借记IC卡", "新疆汇和银行·汇和卡", "广东华兴银行·借记卡", - "广东华兴银行·华兴银联公司卡", "广东华兴银行·华兴联名IC卡", "广东华兴银行·华兴金融IC借记卡", "濮阳银行·龙翔卡", - "宁波通商银行·借记卡", "甘肃银行·神舟兴陇借记卡", "甘肃银行·甘肃银行神州兴陇IC卡", "枣庄银行·借记IC卡", - "本溪市商业银行·借记卡", "盛京银行·医保卡", "上海农商银行·如意卡(银联卡)", "上海农商银行·如意卡(银联卡)", - "上海农商银行·鑫通卡", "上海农商银行·国际如意卡", "上海农商银行·借记IC卡", - "常熟市农村商业银行·粒金贷记卡(银联卡)", "常熟市农村商业银行·公务卡", "常熟市农村商业银行·粒金准贷卡", - "常熟农村商业银行·粒金借记卡(银联卡)", "常熟农村商业银行·粒金IC卡", "常熟农村商业银行·粒金卡", - "深圳农村商业银行·信通卡(银联卡)", "深圳农村商业银行·信通商务卡(银联卡)", "深圳农村商业银行·信通卡", - "深圳农村商业银行·信通商务卡", "广州农村商业银行·福农太阳卡", "广东南海农村商业银行·盛通卡", - "广东南海农村商业银行·盛通卡(银联卡)", "佛山顺德农村商业银行·恒通卡(银联卡)", "佛山顺德农村商业银行·恒通卡", - "佛山顺德农村商业银行·恒通卡(银联卡)", "江阴农村商业银行·暨阳公务卡", "江阴市农村商业银行·合作贷记卡(银联卡)", - "江阴农村商业银行·合作借记卡", "江阴农村商业银行·合作卡(银联卡)", "江阴农村商业银行·暨阳卡", - "重庆农村商业银行·江渝借记卡VIP卡", "重庆农村商业银行·江渝IC借记卡", "重庆农村商业银行·江渝乡情福农卡", - "东莞农村商业银行·信通卡(银联卡)", "东莞农村商业银行·信通卡(银联卡)", "东莞农村商业银行·信通信用卡", - "东莞农村商业银行·信通借记卡", "东莞农村商业银行·贷记IC卡", "张家港农村商业银行·一卡通(银联卡)", - "张家港农村商业银行·一卡通(银联卡)", "张家港农村商业银行·", "北京农村商业银行·信通卡", "北京农村商业银行·惠通卡", - "北京农村商业银行·凤凰福农卡", "北京农村商业银行·惠通卡", "北京农村商业银行·中国旅行卡", "北京农村商业银行·凤凰卡", - "天津农村商业银行·吉祥商联IC卡", "天津农村商业银行·信通借记卡(银联卡)", "天津农村商业银行·借记IC卡", - "鄞州农村合作银行·蜜蜂借记卡(银联卡)", "宁波鄞州农村合作银行·蜜蜂电子钱包(IC)", - "宁波鄞州农村合作银行·蜜蜂IC借记卡", "宁波鄞州农村合作银行·蜜蜂贷记IC卡", "宁波鄞州农村合作银行·蜜蜂贷记卡", - "宁波鄞州农村合作银行·公务卡", "成都农村商业银行·福农卡", "成都农村商业银行·福农卡", - "珠海农村商业银行·信通卡(银联卡)", "太仓农村商业银行·郑和卡(银联卡)", "太仓农村商业银行·郑和IC借记卡", - "无锡农村商业银行·金阿福", "无锡农村商业银行·借记IC卡", "黄河农村商业银行·黄河卡", - "黄河农村商业银行·黄河富农卡福农卡", "黄河农村商业银行·借记IC卡", "天津滨海农村商业银行·四海通卡", - "天津滨海农村商业银行·四海通e芯卡", "武汉农村商业银行·汉卡", "武汉农村商业银行·汉卡", - "武汉农村商业银行·中国旅游卡", "江南农村商业银行·阳湖卡(银联卡)", "江南农村商业银行·天天红火卡", - "江南农村商业银行·借记IC卡", "海口联合农村商业银行·海口联合农村商业银行合卡", "湖北嘉鱼吴江村镇银行·垂虹卡", - "福建建瓯石狮村镇银行·玉竹卡", "浙江平湖工银村镇银行·金平卡", "重庆璧山工银村镇银行·翡翠卡", - "重庆农村商业银行·银联标准贷记卡", "重庆农村商业银行·公务卡", "南阳村镇银行·玉都卡", - "晋中市榆次融信村镇银行·魏榆卡", "三水珠江村镇银行·珠江太阳卡", "东营莱商村镇银行·绿洲卡", "建设银行·单位结算卡", - "玉溪市商业银行·红塔卡" }; - - /** - * 校验银行卡卡号 是否合法 - * @param cardId - * @return - */ - public static boolean checkBankCard(String cardId) { - char bit = getBankCardCheckCode(cardId - .substring(0, cardId.length() - 1)); - if (bit == 'N') { - return false; - } - boolean isBankCard = cardId.charAt(cardId.length() - 1) == bit; - return isBankCard; - } - - /** - * 从不含校验位的银行卡卡号采用 Luhm 校验算法获得校验位 - * @param nonCheckCodeCardId - * @return - */ - public static char getBankCardCheckCode(String nonCheckCodeCardId) { - if (nonCheckCodeCardId == null - || nonCheckCodeCardId.trim().length() == 0 - || !nonCheckCodeCardId.matches("\\d+")) { - // 如果传的不是数据返回N - return 'N'; - } - char[] chs = nonCheckCodeCardId.trim().toCharArray(); - int luhmSum = 0; - for (int i = chs.length - 1, j = 0; i >= 0; i--, j++) { - int k = chs[i] - '0'; - if (j % 2 == 0) { - k *= 2; - k = k / 10 + k % 10; - } - luhmSum += k; - } - return (luhmSum % 10 == 0) ? '0' : (char) ((10 - luhmSum % 10) + '0'); - } - - /** - * 通过银行卡 的前六位确定 判断银行开户行及卡种 - * @param cardbin - * @return - */ - public static String getNameOfBank(String cardbin) { - // 通过银行卡的前6位确定 - cardbin = cardbin.substring(0, 6); - int index = -1; - for (int i = 0; i < BANKBIN.length; i++) { - if (cardbin.equals(BANKBIN[i])) { - index = i; - } - } - if (index == -1) { - return ""; - } - return BANKNAME[index]; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/validator/IDCardUtils.java b/DevLibUtils/src/main/java/dev/utils/common/validator/IDCardUtils.java deleted file mode 100644 index f4caf0fcdb..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/validator/IDCardUtils.java +++ /dev/null @@ -1,595 +0,0 @@ -package dev.utils.common.validator; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import dev.utils.JCLogUtils; - -/** - * detail: 居民身份证工具类 - * @Description:主要功能: 居民身份证工具类 - * @Prject: CommonUtilLibrary - * @Package: com.jingewenku.abrahamcaijin.commonutil - * @author: AbrahamCaiJin - * @date: 2017年07月21日 15:08 - * @Copyright: 个人版权所有 - * @Company: - * @version: 1.0.0 - */ - -public final class IDCardUtils { - - // 日志TAG - private static final String TAG = IDCardUtils.class.getSimpleName(); - - public static final int power[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}; - private static final int CHINA_ID_MAX_LENGTH = 18; - public static final int CHINA_ID_MIN_LENGTH = 15; - public static Map cityCodes = new HashMap(); - public static Map twFirstCode = new HashMap(); - //台湾身份首字母对应数字 - public static Map hkFirstCode = new HashMap(); - - //香港身份首字母对应数字 - static { - cityCodes.put("11", "北京"); - cityCodes.put("12", "天津"); - cityCodes.put("13", "河北"); - cityCodes.put("14", "山西"); - cityCodes.put("15", "内蒙古"); - cityCodes.put("21", "辽宁"); - cityCodes.put("22", "吉林"); - cityCodes.put("23", "黑龙江"); - cityCodes.put("31", "上海"); - cityCodes.put("32", "江苏"); - cityCodes.put("33", "浙江"); - cityCodes.put("34", "安徽"); - cityCodes.put("35", "福建"); - cityCodes.put("36", "江西"); - cityCodes.put("37", "山东"); - cityCodes.put("41", "河南"); - cityCodes.put("42", "湖北"); - cityCodes.put("43", "湖南"); - cityCodes.put("44", "广东"); - cityCodes.put("45", "广西"); - cityCodes.put("46", "海南"); - cityCodes.put("50", "重庆"); - cityCodes.put("51", "四川"); - cityCodes.put("52", "贵州"); - cityCodes.put("53", "云南"); - cityCodes.put("54", "西藏"); - cityCodes.put("61", "陕西"); - cityCodes.put("62", "甘肃"); - cityCodes.put("63", "青海"); - cityCodes.put("64", "宁夏"); - cityCodes.put("65", "新疆"); - cityCodes.put("71", "台湾"); - cityCodes.put("81", "香港"); - cityCodes.put("82", "澳门"); - cityCodes.put("91", "国外"); - twFirstCode.put("A", 10); - twFirstCode.put("B", 11); - twFirstCode.put("C", 12); - twFirstCode.put("D", 13); - twFirstCode.put("E", 14); - twFirstCode.put("F", 15); - twFirstCode.put("G", 16); - twFirstCode.put("H", 17); - twFirstCode.put("J", 18); - twFirstCode.put("K", 19); - twFirstCode.put("L", 20); - twFirstCode.put("M", 21); - twFirstCode.put("N", 22); - twFirstCode.put("P", 23); - twFirstCode.put("Q", 24); - twFirstCode.put("R", 25); - twFirstCode.put("S", 26); - twFirstCode.put("T", 27); - twFirstCode.put("U", 28); - twFirstCode.put("V", 29); - twFirstCode.put("X", 30); - twFirstCode.put("Y", 31); - twFirstCode.put("W", 32); - twFirstCode.put("Z", 33); - twFirstCode.put("I", 34); - twFirstCode.put("O", 35); - hkFirstCode.put("A", 1); - hkFirstCode.put("B", 2); - hkFirstCode.put("C", 3); - hkFirstCode.put("R", 18); - hkFirstCode.put("U", 21); - hkFirstCode.put("Z", 26); - hkFirstCode.put("X", 24); - hkFirstCode.put("W", 23); - hkFirstCode.put("O", 15); - hkFirstCode.put("N", 14); - } - - /** - * 将身份证的每位和对应位的加权因子相乘之后,再获取和值 - * - * @param iArr int[] - * @return 身份证编码 - */ - public static int getPowerSum(int[] iArr) { - int iSum = 0; - if (power.length == iArr.length) { - for (int i = 0; i < iArr.length; i++) { - for (int j = 0; j < power.length; j++) { - if (i == j) { - iSum = iSum + iArr[i] * power[j]; - } - } - } - } - return iSum; - } - - /** - * 将power和值与1 1取模获得余数进行校验码判断 - * - * @param iSum sum - * @return 校验位 - */ - public static String getCheckCode18(int iSum) { - String sCode = ""; - switch (iSum % 11) { - case 10: - sCode = "2"; - break; - case 9: - sCode = "3"; - break; - case 8: - sCode = "4"; - break; - case 7: - sCode = "5"; - break; - case 6: - sCode = "6"; - break; - case 5: - sCode = "7"; - break; - case 4: - sCode = "8"; - break; - case 3: - sCode = "9"; - break; - case 2: - sCode = "x"; - break; - case 1: - sCode = "0"; - break; - case 0: - sCode = "1"; - break; - } - return sCode; - } - - /** - * 将字符数组转换成数字数组 - * - * @param ca 字符数组 - * @return 数字数组 - */ - public static int[] converCharToInt(char[] ca) { - int len = ca.length; - int[] iArr = new int[len]; - try { - for (int i = 0; i < len; i++) { - iArr[i] = Integer.parseInt(String.valueOf(ca[i])); - } - } catch (NumberFormatException e) { - JCLogUtils.eTag(TAG, e, "converCharToInt"); - } - return iArr; - } - - /** - * 数字验证 - * - * @param val 待验证的字符串 - * @return 是否是数字 - */ - private static boolean isNum(String val) { - return !(val == null || "".equals(val)) && val.matches("^[0-9]*$"); - } - - /** - * 身份证校验规则,验证18位身份编码是否合法 - * - * @param idCard 待验证的字符串 - * @return 校验结果 - */ - public static boolean validateIdCard18(String idCard) { - - boolean bTrue = false; - if (idCard == null) { - return false; - } - if (idCard.length() == CHINA_ID_MAX_LENGTH) { - // 前17位 - String code17 = idCard.substring(0, 17); - // 第18位 - String code18 = idCard.substring(17, CHINA_ID_MAX_LENGTH); - if (isNum(code17)) { - char[] cArr = code17.toCharArray(); - if (cArr != null) { - int[] iCard = converCharToInt(cArr); - int iSum17 = getPowerSum(iCard); - // 获取校验位 - String val = getCheckCode18(iSum17); - if (val.length() > 0) { - if (val.equalsIgnoreCase(code18)) { - bTrue = true; - } - } - } - } - } - return bTrue; - } - - /** - * 身份证校验规则,验证15位身份编码是否合法 - * - * @param idCard 待验证的字符串 - * @return 校验结果 - */ - public static boolean validateIdCard15(String idCard) { - if (idCard.length() != CHINA_ID_MIN_LENGTH) { - return false; - } - if (isNum(idCard)) { - String proCode = idCard.substring(0, 2); - if (cityCodes.get(proCode) == null) { - return false; - } - String birthCode = idCard.substring(6, 12); - Date birthDate = null; - try { - birthDate = new SimpleDateFormat("yy").parse(birthCode.substring(0, 2)); - } catch (ParseException e) { - JCLogUtils.eTag(TAG, e, "validateIdCard15"); - } - Calendar cal = Calendar.getInstance(); - if (birthDate != null) cal.setTime(birthDate); - if (!validateDateSmllerThenNow(cal.get(Calendar.YEAR), Integer.valueOf(birthCode.substring(2, 4)), Integer.valueOf(birthCode.substring(4, 6)))) { - return false; - } - } else { - return false; - } - return true; - } - - /** - * 验证小于当前日期 是否有效 - * - * @param iYear 待验证日期(年) - * @param iMonth 待验证日期(月 1-12) - * @param iDate 待验证日期(日) - * @return 是否有效 - */ - private static boolean validateDateSmllerThenNow(int iYear, int iMonth, int iDate) { - Calendar cal = Calendar.getInstance(); - int year = cal.get(Calendar.YEAR); - int datePerMonth; - int MIN = 1930; - if (iYear < MIN || iYear >= year) { - return false; - } - if (iMonth < 1 || iMonth > 12) { - return false; - } - switch (iMonth) { - case 4: - case 6: - case 9: - case 11: - datePerMonth = 30; - break; - case 2: - boolean dm = ((iYear % 4 == 0 && iYear % 100 != 0) || (iYear % 400 == 0)) && (iYear > MIN && iYear < year); - datePerMonth = dm ? 29 : 28; - break; - default: - datePerMonth = 31; - } - return (iDate >= 1) && (iDate <= datePerMonth); - } - - /** - * 将15位身份证号码转换为18位 - * - * @param idCard 15位身份编码 - * @return 18位身份编码 - */ - public static String convert15CardTo18(String idCard) { - String idCard18 = ""; - if (idCard.length() != CHINA_ID_MIN_LENGTH) { - return null; - } - if (isNum(idCard)) { - // 获取出生年月日 - String birthday = idCard.substring(6, 12); - Date birthDate = null; - try { - birthDate = new SimpleDateFormat("yyMMdd").parse(birthday); - } catch (ParseException e) { - JCLogUtils.eTag(TAG, e, "convert15CardTo18"); - } - Calendar cal = Calendar.getInstance(); - if (birthDate != null) cal.setTime(birthDate); - // 获取出生年(完全表现形式,如:2010) - String sYear = String.valueOf(cal.get(Calendar.YEAR)); - idCard18 = idCard.substring(0, 6) + sYear + idCard.substring(8); - // 转换字符数组 - char[] cArr = idCard18.toCharArray(); - if (cArr != null) { - int[] iCard = converCharToInt(cArr); - int iSum17 = getPowerSum(iCard); - // 获取校验位 - String sVal = getCheckCode18(iSum17); - if (sVal.length() > 0) { - idCard18 += sVal; - } else { - return null; - } - } - } else { - return null; - } - return idCard18; - } - - /** - * 验证台湾身份证号码 - * - * @param idCard 身份证号码 - * @return 是否符合 - */ - public static boolean validateTWCard(String idCard) { - String start = idCard.substring(0, 1); - String mid = idCard.substring(1, 9); - String end = idCard.substring(9, 10); - Integer iStart = twFirstCode.get(start); - Integer sum = iStart / 10 + (iStart % 10) * 9; - char[] chars = mid.toCharArray(); - Integer iflag = 8; - for (char c : chars) { - sum = sum + Integer.valueOf(c + "") * iflag; - iflag--; - } - return (sum % 10 == 0 ? 0 : 10 - sum % 10) == Integer.valueOf(end); - } - - /** - * 验证香港身份证号码(存在Bug,部份特殊身份证无法检查) - * 身份证前2位为英文字符,如果只出现一个英文字符则表示第一位是空格,对应数字58 前2位英文字符A-Z分别对应数字10-35 - * 最后一位校验码为0-9的数字加上字符"A","A"代表10 - * 将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效 - * - * @param idCard 身份证号码 - * @return 验证码是否符合 - */ - public static boolean validateHKCard(String idCard) { - String card = idCard.replaceAll("[\\(|\\)]", ""); - Integer sum = 0; - if (card.length() == 9) { - sum = ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 9 + ((int) card.substring(1, 2).toUpperCase().toCharArray()[0] - 55) * 8; - card = card.substring(1, 9); - } else { - sum = 522 + ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 8; - } - String mid = card.substring(1, 7); - String end = card.substring(7, 8); - char[] chars = mid.toCharArray(); - Integer iflag = 7; - for (char c : chars) { - sum = sum + Integer.valueOf(c + "") * iflag; - iflag--; - } - if (end.toUpperCase().equals("A")) { - sum = sum + 10; - } else { - sum = sum + Integer.valueOf(end); - } - return (sum % 11 == 0); - } - - public static String[] validateIdCard10(String idCard) { - String[] info = new String[3]; - String card = idCard.replaceAll("[\\(|\\)]", ""); - if (card.length() != 8 && card.length() != 9 && idCard.length() != 10) { - return null; - } - if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 - info[0] = "台湾"; - String char2 = idCard.substring(1, 2); - if (char2.equals("1")) { - info[1] = "M"; - } else if (char2.equals("2")) { - info[1] = "F"; - } else { - info[1] = "N"; - info[2] = "false"; - return info; - } - info[2] = validateTWCard(idCard) ? "true" : "false"; - } else if (idCard.matches("^[1|5|7][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 - info[0] = "澳门"; - info[1] = "N"; - // TODO - } else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 - info[0] = "香港"; - info[1] = "N"; - info[2] = validateHKCard(idCard) ? "true" : "false"; - } else { - return null; - } - return info; - } - - /** - * 验证身份证是否合法 - * - * @param idCard 待验证的字符串 - * @return 身份证是否合法 - */ - public static boolean validateCard(String idCard) { - String card = idCard.trim(); - if (validateIdCard18(card)) { - return true; - } - if (validateIdCard15(card)) { - return true; - } - String[] cardval = validateIdCard10(card); - if (cardval != null) { - if (cardval[2].equals("true")) { - return true; - } - } - return false; - } - - /** - * 根据身份编号获取年龄 - * - * @param idCard 身份编号 - * @return 年龄 - */ - public static int getAgeByIdCard(String idCard) { - int iAge = 0; - if (idCard.length() == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - String year = idCard.substring(6, 10); - Calendar cal = Calendar.getInstance(); - int iCurrYear = cal.get(Calendar.YEAR); - iAge = iCurrYear - Integer.valueOf(year); - return iAge; - } - - /** - * 根据身份编号获取生日 - * - * @param idCard 身份编号 - * @return 生日(yyyyMMdd) - */ - public static String getBirthByIdCard(String idCard) { - Integer len = idCard.length(); - if (len < CHINA_ID_MIN_LENGTH) { - return null; - } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - return idCard.substring(6, 14); - } - - /** - * 根据身份编号获取生日 - * - * @param idCard 身份编号 - * @return 生日(yyyyMMdd) - */ - public static String getBirthdayByIdCard(String idCard) { - return getBirthByIdCard(idCard).replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1-$2-$3"); - } - - /** - * 根据身份编号获取生日年 - * - * @param idCard 身份编号 - * @return 生日(yyyy) - */ - public static Short getYearByIdCard(String idCard) { - Integer len = idCard.length(); - if (len < CHINA_ID_MIN_LENGTH) { - return null; - } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - return Short.valueOf(idCard.substring(6, 10)); - } - - /** - * 根据身份编号获取生日月 - * - * @param idCard 身份编号 - * @return 生日(MM) - */ - public static Short getMonthByIdCard(String idCard) { - Integer len = idCard.length(); - if (len < CHINA_ID_MIN_LENGTH) { - return null; - } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - return Short.valueOf(idCard.substring(10, 12)); - } - - /** - * 根据身份编号获取生日天 - * - * @param idCard 身份编号 - * @return 生日(dd) - */ - public static Short getDateByIdCard(String idCard) { - Integer len = idCard.length(); - if (len < CHINA_ID_MIN_LENGTH) { - return null; - } else if (len == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - return Short.valueOf(idCard.substring(12, 14)); - } - - /** - * 根据身份编号获取性别 - * - * @param idCard 身份编号 - * @return 性别(M-男,F-女,N-未知) - */ - public static String getGenderByIdCard(String idCard) { - String sGender = "N"; - if (idCard.length() == CHINA_ID_MIN_LENGTH) { - idCard = convert15CardTo18(idCard); - } - String sCardNum = idCard.substring(16, 17); - if (Integer.parseInt(sCardNum) % 2 != 0) { - sGender = "M"; - } else { - sGender = "F"; - } - return sGender; - } - - /** - * 根据身份编号获取户籍省份 - * - * @param idCard 身份编码 - * @return 省级编码 - */ - public static String getProvinceByIdCard(String idCard) { - int len = idCard.length(); - String sProvince = null; - String sProvinNum = ""; - if (len == CHINA_ID_MIN_LENGTH || len == CHINA_ID_MAX_LENGTH) { - sProvinNum = idCard.substring(0, 2); - } - sProvince = cityCodes.get(sProvinNum); - return sProvince; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToIDCardUtils.java b/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToIDCardUtils.java deleted file mode 100644 index 3711066452..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToIDCardUtils.java +++ /dev/null @@ -1,97 +0,0 @@ -package dev.utils.common.validator; - -import java.util.regex.Pattern; - -/** - * detail: 检验身份证工具类 - * Created by Ttt - */ -public final class ValiToIDCardUtils { - - private ValiToIDCardUtils() { - } - - /** 正则表达式:验证身份证 */ - static final String REGEX_ID_CARD = "(^\\d{15}$)|(^\\d{17}([0-9]|X)$)"; - - /** 正则表达式:验证台湾 */ - static final String REGEX_TW_ID_CARD = "/^[a-zA-Z][0-9]{9}$/"; - - /** 正则表达式:验证香港 */ - static final String REGEX_XG_ID_CARD = "[A-Z][0-9]{6}\\([0-9A]\\)"; - - /** 正则表达式:验证澳门 */ - static final String REGEX_AM_ID_CARD = "[157][0-9]{6}\\([0-9]\\)"; - - // ==== 内部方法 ===== - - /** - * 判断是否为null - * @param str - * @return - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - /** - * 通用匹配函数 - * @param regex - * @param input - * @return - */ - static boolean match(String regex, String input) { - return Pattern.matches(regex, input); - } - - // == - - /** - * 校验身份证 - * @param idCard - * @return - */ - public static boolean isIDCard(String idCard) { - if (!isEmpty(idCard)){ - return match(REGEX_ID_CARD, idCard); - } - return false; - } - - /** - * 校验身份证 -> 香港 - * @param idCard - * @return - */ - public static boolean isHKIDCard(String idCard){ - if (!isEmpty(idCard)){ - return match(REGEX_XG_ID_CARD, idCard); - } - return false; - } - - /** - * 校验身份证 -> 澳门 - * @param idCard - * @return - */ - public static boolean isAMIDCard(String idCard){ - if (!isEmpty(idCard)){ - return match(REGEX_AM_ID_CARD, idCard); - } - return false; - } - - /** - * 校验身份证 -> 台湾 - * @param idCard - * @return - */ - public static boolean isTWIDCard(String idCard){ - if (!isEmpty(idCard)){ - return match(REGEX_TW_ID_CARD, idCard); - } - return false; - } - -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java b/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java deleted file mode 100644 index 1c24de7e47..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java +++ /dev/null @@ -1,220 +0,0 @@ -package dev.utils.common.validator; - -import java.util.regex.Pattern; - -/** - * detail: 检验联系(手机号,座机)工具类 - * Created by Ttt - * http://blog.csdn.net/linbilin_/article/details/49796617 - * // -- - * http://www.cnblogs.com/zengxiangzhan/p/phone.html - */ -public final class ValiToPhoneUtils { - - private ValiToPhoneUtils() { - } - - // ==== 内部方法 ===== - - /** - * 判断是否为null - * @param str - * @return - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - /** - * 通用匹配函数 - * @param regex - * @param input - * @return - */ - static boolean match(String regex, String input) { - return Pattern.matches(regex, input); - } - - // == - - /** - * 中国手机号格式验证,在输入可以调用该方法,点击发送验证码,使用 isPhone - * @param phone - * @return - */ - public static boolean isPhoneCheck(String phone){ - if (!isEmpty(phone)){ - return match(CHAIN_PHONE_FORMAT_CHECK, phone); - } - return false; - } - - /** - * 是否中国手机号 - * @param phone - * @return - */ - public static boolean isPhone(String phone){ - if (!isEmpty(phone)){ - return match(CHINA_PHONE_PATTERN, phone); - } - return false; - } - - /** - * 是否中国电信手机号码 - * @param phone - * @return - */ - public static boolean isPhoneToChinaTelecom(String phone){ - if (!isEmpty(phone)){ - return match(CHINA_TELECOM_PATTERN, phone); - } - return false; - } - - /** - * 是否中国联通手机号码 - * @param phone - * @return - */ - public static boolean isPhoneToChinaUnicom(String phone){ - if (!isEmpty(phone)){ - return match(CHINA_UNICOM_PATTERN, phone); - } - return false; - } - - /** - * 是否中国移动手机号码 - * @param phone - * @return - */ - public static boolean isPhoneToChinaMobile(String phone){ - if (!isEmpty(phone)){ - return match(CHINA_MOBILE_PATTERN, phone); - } - return false; - } - - /** - * 判断是否香港手机号 - * @param phone - * @return - */ - public static boolean isPhoneToHkMobile(String phone){ - if (!isEmpty(phone)){ - return match(HK_PHONE_PATTERN, phone); - } - return false; - } - - /** - * 验证电话号码的格式 - * @param phone - * @return - */ - public static boolean isPhoneCallNum(String phone) { - if (!isEmpty(phone)){ - return match(PHONE_CALL_PATTERN, phone); - } - return false; - } - - // ====================== - - // == 手机号判断 == - - /** 简单手机号码校验 => 校验手机号码的长度和1开头 (是否11位)*/ - static final String CHAIN_PHONE_FORMAT_CHECK = "^(?:\\+86)?1\\d{10}$"; - - // 中国手机号正则 - static final String CHINA_PHONE_PATTERN; - - /** 中国电信号码正则 */ - static final String CHINA_TELECOM_PATTERN; - - /** 中国联通号码正则 */ - static final String CHINA_UNICOM_PATTERN; - - /** 中国移动号码正则 */ - static final String CHINA_MOBILE_PATTERN; - - /* 香港手机号码正则 => 香港手机号码8位数,5|6|8|9开头+7位任意数*/ - static final String HK_PHONE_PATTERN = "^(5|6|8|9)\\d{7}$"; - - // == 座机判断 == - - /** 座机电话格式验证 **/ - static final String PHONE_CALL_PATTERN = "^(?:\\(\\d{3,4}\\)|\\d{3,4}-)?\\d{7,8}(?:-\\d{1,4})?$"; - - static { - // ====== 中国电信 ====== - // 电信:133、153、180、181、189 、177(4G)、149、173、174、199 - // 进行拼接字符串,便于理解,后期修改 - StringBuffer sBuffer = new StringBuffer(); - sBuffer.append("^13[3]{1}\\d{8}$"); // 13开头 - sBuffer.append("|"); // 或 - sBuffer.append("^14[9]{1}\\d{8}$"); // 14开头 - sBuffer.append("|"); - sBuffer.append("^15[3]{1}\\d{8}$"); // 15开头 - sBuffer.append("|"); - sBuffer.append("^17[3,4,7]{1}\\d{8}$"); // 17开头 - sBuffer.append("|"); - sBuffer.append("^18[0,1,9]{1}\\d{8}$"); // 18开头 - sBuffer.append("|"); - sBuffer.append("^19[9]{1}\\d{8}$"); // 19开头 - // 手机正则 - CHINA_TELECOM_PATTERN = sBuffer.toString(); - // ======================= - - // ====== 中国联通 ====== - // 联通:130、131、132、155、156、185、186、176(4G)、145(上网卡)、146、166、171、175 - // 进行拼接字符串,便于理解,后期修改 - sBuffer = new StringBuffer(); - sBuffer.append("^13[0,1,2]{1}\\d{8}$"); // 13开头 - sBuffer.append("|"); // 或 - sBuffer.append("^14[5,6]{1}\\d{8}$"); // 14开头 - sBuffer.append("|"); - sBuffer.append("^15[5,6]{1}\\d{8}$"); // 15开头 - sBuffer.append("|"); - sBuffer.append("^16[6]{1}\\d{8}$"); // 16开头 - sBuffer.append("|"); - sBuffer.append("^17[1,5,6]{1}\\d{8}$"); // 17开头 - sBuffer.append("|"); - sBuffer.append("^18[5,6]{1}\\d{8}$"); // 18开头 - // 手机正则 - CHINA_UNICOM_PATTERN = sBuffer.toString(); - // ======================= - - // ====== 中国移动 ====== - // 移动:134、135、136、137、138、139、150、151、152、157、158、159、182、183、184、187、188、178(4G)、147(上网卡)、148、172、198 - // 进行拼接字符串,便于理解,后期修改 - sBuffer = new StringBuffer(); - sBuffer.append("^13[4,5,6,7,8,9]{1}\\d{8}$"); // 13开头 - sBuffer.append("|"); // 或 - sBuffer.append("^14[7,8]{1}\\d{8}$"); // 14开头 - sBuffer.append("|"); - sBuffer.append("^15[0,1,2,7,8,9]{1}\\d{8}$"); // 15开头 - sBuffer.append("|"); - sBuffer.append("^17[2,8]{1}\\d{8}$"); // 17开头 - sBuffer.append("|"); - sBuffer.append("^18[2,3,4,7,8]{1}\\d{8}$"); // 18开头 - sBuffer.append("|"); - sBuffer.append("^19[8]{1}\\d{8}$"); // 19开头 - // 手机正则 - CHINA_MOBILE_PATTERN = sBuffer.toString(); - // ======================= - - /** - * 验证手机号是否正确 - * 移动:134、135、136、137、138、139、150、151、152、157、158、159、182、183、184、187、188、178(4G)、147(上网卡)、148、172、198 - * 联通:130、131、132、155、156、185、186、176(4G)、145(上网卡)、146、166、171、175 - * 电信:133、153、180、181、189 、177(4G)、149、173、174、199 - * 卫星通信:1349 - * 虚拟运营商:170 - * http://www.cnblogs.com/zengxiangzhan/p/phone.html - */ - CHINA_PHONE_PATTERN = "^13[\\d]{9}$|^14[5,6,7,8,9]{1}\\d{8}$|^15[^4]{1}\\d{8}$|^16[6]{1}\\d{8}$|^17[0,1,2,3,4,5,6,7,8]{1}\\d{8}$|^18[\\d]{9}$|^19[8,9]{1}\\d{8}$"; - } -} diff --git a/DevLibUtils/src/main/java/dev/utils/common/validator/ValidatorUtils.java b/DevLibUtils/src/main/java/dev/utils/common/validator/ValidatorUtils.java deleted file mode 100644 index b4efe18e52..0000000000 --- a/DevLibUtils/src/main/java/dev/utils/common/validator/ValidatorUtils.java +++ /dev/null @@ -1,290 +0,0 @@ -package dev.utils.common.validator; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import dev.utils.JCLogUtils; - -/** - * detail: 校验工具类 - * Created by Ttt - */ -public final class ValidatorUtils { - - private ValidatorUtils() { - } - - // 日志TAG - private static final String TAG = ValidatorUtils.class.getSimpleName(); - - /** 正则表达式:验证数字 */ - static final String REGEX_NUMBER = "^[0-9]*$"; - - /** 正则表达式:不能输入特殊字符 ^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ */ - static final String REGEX_SPECIAL = "^[\\u4E00-\\u9FA5A-Za-z0-9]+$"; - - /** 正则表达式:验证微信号 ^[a-zA-Z]{1}[-_a-zA-Z0-9]{5,19}+$ */ - static final String REGEX_WX = "^[a-zA-Z\\\\d_]{5,19}$"; - - /** 正则表达式:验证真实姓名 |[\w·•])$ ^[\u4e00-\u9fa5]+(·[\u4e00-\u9fa5]+)*$ */ - static final String REGEX_REALNAME = "^[\\u4e00-\\u9fa5]+(•[\\u4e00-\\u9fa5]*)*$|^[\\u4e00-\\u9fa5]+(·[\\u4e00-\\u9fa5]*)*$"; - - /** 正则表达式:验证昵称 */ - static final String REGEX_NICKNAME = "^[\\u4E00-\\u9FA5A-Za-z0-9_]+$"; - - /** 正则表达式:验证用户名(不包含中文和特殊字符)如果用户名使用手机号码或邮箱 则结合手机号验证和邮箱验证 */ - static final String REGEX_USERNAME = "^[a-zA-Z]\\w{5,17}$"; - - /** 正则表达式:验证密码(不包含特殊字符) */ - static final String REGEX_PASSWORD = "^[a-zA-Z0-9]{6,18}$"; - - /** 正则表达式:验证邮箱 */ - static final String REGEX_EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; - - /** 正则表达式:验证URL */ - static final String REGEX_URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?"; - - /** 正则表达式:验证IP地址 */ - static final String REGEX_IP_ADDR = "(2[5][0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"; - - // ==== 内部方法 ===== - - /** - * 判断是否为null - * @param str - * @return - */ - public static boolean isEmpty(String str) { - return (str == null || str.length() == 0); - } - - /** - * 通用匹配函数 - * @param regex - * @param input - * @return - */ - static boolean match(String regex, String input) { - return Pattern.matches(regex, input); - } - - // ====== - - /** - * 检验数字 - * @param str - * @return - */ - public static boolean isNumber(String str) { - if (!isEmpty(str)){ - return match(REGEX_NUMBER, str); - } - return false; - } - - /** - * 判断字符串是不是全是字母 - * @param str - * @return - */ - public static boolean isLetter(String str) { - if (!isEmpty(str)){ - return match("^[A-Za-z]+$", str); - } - return false; - } - - /** - * 判断字符串是不是只含字母和数字 - * @param str - * @return - */ - public static boolean isNumberLetter(String str) { - if (!isEmpty(str)){ - return match("^[A-Za-z0-9]+$", str); - } - return false; - } - - /** - * 检验特殊符号 - * @param str - * @return - */ - public static boolean isSpec(String str) { - if (!isEmpty(str)){ - return match(REGEX_SPECIAL, str); - } - return false; - } - - /** - * 检验微信号 - * @param str - * @return - */ - public static boolean isWx(String str) { - if (!isEmpty(str)){ - return match(REGEX_WX, str); - } - return false; - } - - /** - * 检验真实姓名 - * @param str - * @return - */ - public static boolean isRealName(String str) { - if (!isEmpty(str)){ - return match(REGEX_REALNAME, str); - } - return false; - } - - /** - * 校验昵称 - * @param str - * @return - */ - public static boolean isNickName(String str) { - if (!isEmpty(str)){ - return match(REGEX_NICKNAME, str); - } - return false; - } - - /** - * 校验用户名 - * @param str - * @return - */ - public static boolean isUserName(String str) { - if (!isEmpty(str)){ - return match(REGEX_USERNAME, str); - } - return false; - } - - /** - * 校验密码 - * @param str - * @return - */ - public static boolean isPassword(String str) { - if (!isEmpty(str)){ - return match(REGEX_PASSWORD, str); - } - return false; - } - - /** - * 校验邮箱 - * @param str - * @return - */ - public static boolean isEmail(String str) { - if (!isEmpty(str)){ - return match(REGEX_EMAIL, str); - } - return false; - } - - /** - * 校验URL - * @param str - * @return - */ - public static boolean isUrl(String str) { - if (!isEmpty(str)){ - return match(REGEX_URL, str); - } - return false; - } - - /** - * 校验IP地址 - * @param str - * @return - */ - public static boolean isIPAddress(String str) { - if (!isEmpty(str)){ - return match(REGEX_IP_ADDR, str); - } - return false; - } - - /** - * IP地址校验 - * @param str 待校验是否是IP地址的字符串 - * @return - */ - public static boolean isIP(String str) { - if (!isEmpty(str)){ - Pattern pattern = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"); - Matcher matcher = pattern.matcher(str); - return matcher.matches(); - } - return false; - } - - // ======= - -// // http://blog.csdn.net/myfuturein/article/details/6885216 -// [\\u0391-\\uFFE5]匹配双字节字符(汉字+符号) -// [\\u4e00-\\u9fa5]注意只匹配汉字,不匹配双字节字符 - - /** 正则表达式:验证汉字 */ - static final String REGEX_CHINESE = "^[\u4e00-\u9fa5]+$"; - /** 正则表达式:验证汉字(含双角符号) */ - static final String REGEX_CHINESE_ALL = "^[\u0391-\uFFE5]+$"; - - /** - * 校验汉字(无符号,纯汉字) - * @param str - * @return - */ - public static boolean isChinese(String str) { - if (!isEmpty(str)){ - return match(REGEX_CHINESE, str); - } - return false; - } - - /** - * 判断字符串是不是全是中文 - * @param str - * @return - */ - public static boolean isChineseAll(String str) { - if (!isEmpty(str)){ - return match(REGEX_CHINESE_ALL, str); - } - return false; - } - - /** - * 判断字符串中包含中文、包括中文字符标点等 - * @param data 可能包含中文的字符串 - * @return 是否包含中文 - */ - public static boolean isContainChinese(String data) { - try { - String chinese = "[\u0391-\uFFE5]"; - int length; - if(data != null && (length = data.length()) != 0) { - char[] dChar = data.toCharArray(); - for (int i = 0; i < length; i++) { - boolean flag = String.valueOf(dChar[i]).matches(chinese); - if (flag) { - return true; - } - } - } - } catch (Exception e) { - JCLogUtils.eTag(TAG, e, "isContainChinese"); - } - return false; - } - -} diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_error_outline_white.png b/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_error_outline_white.png deleted file mode 100644 index c1dee40696..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_error_outline_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_warning_white.png b/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_warning_white.png deleted file mode 100644 index 59fa4428f3..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_warning_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_frame.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_frame.png deleted file mode 100644 index 778e4e6765..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_frame.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_check_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_check_white.png deleted file mode 100644 index 3b2b65d262..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_check_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_clear_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_clear_white.png deleted file mode 100644 index b7c7ffd0e7..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_clear_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_error_outline_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_error_outline_white.png deleted file mode 100644 index 4f519d871f..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_error_outline_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_info_outline_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_info_outline_white.png deleted file mode 100644 index c571b2e3e7..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_info_outline_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_outline_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_outline_white.png deleted file mode 100644 index acffe8dcb9..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_outline_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_white.png b/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_white.png deleted file mode 100644 index 7ca3842953..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-mdpi/dev_toast_ic_warning_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_error_outline_white.png b/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_error_outline_white.png deleted file mode 100644 index b144939b9b..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_error_outline_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_warning_white.png b/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_warning_white.png deleted file mode 100644 index 8683a2ea9a..0000000000 Binary files a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_warning_white.png and /dev/null differ diff --git a/DevLibUtils/src/main/res/layout/dev_toast_layout.xml b/DevLibUtils/src/main/res/layout/dev_toast_layout.xml deleted file mode 100644 index f220e2aa66..0000000000 --- a/DevLibUtils/src/main/res/layout/dev_toast_layout.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/DevLibUtils/src/main/res/values-zh-rCN/strings.xml b/DevLibUtils/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index 7f1d83c047..0000000000 --- a/DevLibUtils/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - 应用包名 - 应用 MD5签名 - 应用版本号(内部判断) - 应用版本名(用户展示) - 安装包地址 - SHA1 - SHA256 - 首次安装时间 - 最近更新时间 - app 最低支持版本 - app 兼容sdk版本 - Apk 安装包大小 - 有效时间 - 签名是否过期 - 已过期 - 未过期 - 证书发布方 - 证书版本号 - 证书算法名称 - 证书算法OID - 证书DER编码 - 证书机器码 - diff --git a/DevLibUtils/src/main/res/values/color.xml b/DevLibUtils/src/main/res/values/color.xml deleted file mode 100644 index 74328c04ef..0000000000 --- a/DevLibUtils/src/main/res/values/color.xml +++ /dev/null @@ -1,350 +0,0 @@ - - - - - - #00000000 - - #000000 - - #19000000 - #33000000 - #4C000000 - #66000000 - #7F000000 - #99000000 - #B2000000 - #CC000000 - #E5000000 - - #FFFFFF - - #808080 - - #FF0000 - - #0000FF - - #0080FF - - #FFFF00 - - #FFD700 - - #FFC0CB - - #00FFFF - - #cccccc - - #8f8f8f - - - - #111111 - #222222 - #333333 - #444444 - #555555 - #666666 - #777777 - #888888 - #999999 - - #f1f1f1 - #f2f2f2 - #f3f3f3 - #f4f4f4 - #f5f5f5 - #f6f6f6 - #f7f7f7 - #f8f8f8 - #f9f9f9 - - #e1e1e1 - #e2e2e2 - #e3e3e3 - #e4e4e4 - #e5e5e5 - #e6e6e6 - #e7e7e7 - #e8e8e8 - #e9e9e9 - - #d1d1d1 - #d2d2d2 - #d3d3d3 - #d4d4d4 - #d5d5d5 - #d6d6d6 - #d7d7d7 - #d8d8d8 - #d9d9d9 - - - - - - - #FFFFF0 - - #FFFFE0 - - #FFFAFA - - #FFFAF0 - - #FFFACD - - #FFF8DC - - #FFF5EE - - #FFF0F5 - - #FFEFD5 - - #FFEBCD - - #FFE4E1 - - #FFE4C4 - - #FFE4B5 - - #FFDEAD - - #FFDAB9 - - #FFB6C1 - - #FFA500 - - #FFA07A - - #FF8C00 - - #FF7F50 - - #FF69B4 - - #FF6347 - - #FF4500 - - #FF1493 - - #FF00FF - - #FDF5E6 - - #FAFAD2 - - #FAF0E6 - - #FAEBD7 - - #FA8072 - - #F8F8FF - - #F5FFFA - - #F5F5F5 - - #F5F5DC - - #F5DEB3 - - #F4A460 - - #F0FFFF - - #F0FFF0 - - #F0F8FF - - #F0E68C - - #F08080 - - #EEE8AA - - #EE82EE - - #E9967A - - #E6E6FA - - #E0FFFF - - #DEB887 - - #DDA0DD - - #DCDCDC - - #DC143C - - #DB7093 - - #DAA520 - - #DA70D6 - - #D8BFD8 - - #D3D3D3 - - #D2B48C - - #D2691E - - #CD853F - - #CD5C5C - - #C71585 - - #C0C0C0 - - #BDB76B - - #BC8F8F - - #BA55D3 - - #B8860B - - #B22222 - - #B0E0E6 - - #B0C4DE - - #AFEEEE - - #ADFF2F - - #ADD8E6 - - #A9A9A9 - - #A52A2A - - #A0522D - - #9932CC - - #98FB98 - - #9400D3 - - #9370DB - - #90EE90 - - #8FBC8F - - #8B4513 - - #8B008B - - #8B0000 - - #8A2BE2 - - #87CEFA - - #87CEEB - - #808080 - - #808000 - - #800080 - - #800000 - - #7FFFD4 - - #7FFF00 - - #7CFC00 - - #7B68EE - - #778899 - - #708090 - - #6B8E23 - - #6A5ACD - - #696969 - - #66CDAA - - #6495ED - - #5F9EA0 - - #556B2F - - #4B0082 - - #48D1CC - - #483D8B - - #4682B4 - - #4169E1 - - #40E0D0 - - #3CB371 - - #32CD32 - - #2F4F4F - - #2E8B57 - - #228B22 - - #20B2AA - - #1E90FF - - #191970 - - #00FFFF - - #00FF7F - - #00FF00 - - #00FA9A - - #00CED1 - - #00BFFF - - #008B8B - - #008080 - - #008000 - - #006400 - - #0000CD - - #00008B - - #000080 - - #99cc33 - diff --git a/DevLibUtils/src/main/res/values/strings.xml b/DevLibUtils/src/main/res/values/strings.xml deleted file mode 100644 index 2ff064c298..0000000000 --- a/DevLibUtils/src/main/res/values/strings.xml +++ /dev/null @@ -1,26 +0,0 @@ - - to - - Pack name - APP md5 signatures - App version code - App version name - apk file path - SHA1 - SHA256 - First install time - Last update time - MinSdkVersion - TargetSdkVersion - Apk length - Effective time - Signature whether expire - Expired - Not expired - Certificate publisher - Certificate number - Certificate algorithm name - Certificate algorithm OID - Certificate DER code - Certificate UUID - diff --git a/DevLibUtils/src/main/res/values/styles.xml b/DevLibUtils/src/main/res/values/styles.xml deleted file mode 100644 index aeace20609..0000000000 --- a/DevLibUtils/src/main/res/values/styles.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e9f..f49a4e16e6 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and - limitations under the License. + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 46a5b38fe2..0cc993c87d 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,229 @@ -# About -> DevUtils 是一个 Android 工具库, 主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用。 ->

该项目尽可能的便于开发人员,快捷、快速开发安全可靠的项目,以及内置部分常用的资源文件,如color.xml、(toast) layout.xml等 +

DevUtils

-# Gradle -Step 1. Add the JitPack repository to your build file -``` -allprojects { - repositories { - maven { url 'https://jitpack.io' } - } -} -``` +

+ + Profile + + + License + + + Version + + + API + + + Version + + + Utils + +

-Step 2. Add the dependency -``` -dependencies { - // 因为内含 res 文件, 使用 aar 方式调用 - implementation 'com.github.afkT:DevUtils:1.0.0@aar' - // implementation 'com.github.afkT:DevUtils:latest.release@aar' -} -``` -## Documentation +

+ 🔥 ( 持续更新,目前含 300+ 工具类 ) Roadmap +
+ DevUtils 是一个 Android 工具库,主要根据不同功能模块,封装快捷使用的工具类及 API 方法调用。 +
+ 该项目尽可能的便于开发人员,快捷、高效开发安全可靠的项目。 +

+ + +

+ + Android 规范 + 、 + + Java 规范 + 、 + + Git 规范 + +

+ + +![module](https://github.com/afkT/DevUtils/raw/master/art/module.png) + + +## Download + +下载 DevUtils 系列开发库演示应用 [APK](https://github.com/afkT/Resources/blob/main/APK) + + +## Documentation - [Lib](https://github.com/afkT/DevUtils/blob/master/lib) **( 全部已迁移至 Maven Central )** + +### DevApp - Android 工具类库 + +- [README - API](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md) + +- [Use and Config](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/USE_CONFIG.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/CHANGELOG.md) + +### DevAssist - 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 + +- [README - API](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/CHANGELOG.md) + +### DevBase - Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevBase/CHANGELOG.md) + +### DevBaseMVVM - MVVM ( ViewDataBinding + ViewModel ) 基类库 + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/CHANGELOG.md) + +### DevEngine - 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/CHANGELOG.md) + +### DevHttpCapture - OkHttp 抓包工具库 + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/CHANGELOG.md) + +### DevHttpCaptureCompiler - OkHttp 抓包工具库 ( 可视化功能 ) + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/HttpCapture/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/HttpCapture/CHANGELOG.md) + +### DevHttpManager - OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/CHANGELOG.md) -- [README - API](https://github.com/afkT/DevUtils/blob/master/DevLibUtils/README.md) +### DevRetrofit - Retrofit + Kotlin Coroutines 封装 +- [README](https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md) -## Use +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/CHANGELOG.md) -> 只需要在 Application 中调用 DevUtils.init() 进行初始化就行 ->

DevUtils.openLog() 是打开内部工具类 日志输出, 发包则不调用此句 ->

DevLogger => https://github.com/afkT/DevLogger +### DevWidget - 自定义 View UI 库 -```java -/** - * detail: 全局Application - * Created by Ttt - */ -public class BaseApplication extends Application{ +- [README - API](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md) - // 日志TAG - private final String LOG_TAG = BaseApplication.class.getSimpleName(); +- [Preview README](https://github.com/afkT/DevUtils-repo/blob/main/lib/DevWidget_Preview.md) - @Override - public void onCreate() { - super.onCreate(); +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/CHANGELOG.md) - // 初始化工具类 - DevUtils.init(this.getApplicationContext()); - // == 初始化日志配置 == - // 设置默认Logger配置 - LogConfig logConfig = new LogConfig(); - logConfig.logLevel = LogLevel.DEBUG; - logConfig.tag = LOG_TAG; - DevLogger.init(logConfig); - // 打开 lib 内部日志 - DevUtils.openLog(); - DevUtils.openDebug(); - } -} +### DevEnvironment - Android 环境配置切换库 + +- [README - API](https://github.com/afkT/DevUtils/blob/master/lib/Environment) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironment/CHANGELOG.md) + +### DevJava - Java 工具类库 ( 不依赖 android api ) + +- [README - API](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/CHANGELOG.md) + + +## Other + +### DevComponent + +**[DevComponent](https://github.com/afkT/DevComponent)** 【100% Kotlin 实现 Android 项目组件化示例代码】 +基于 Android JetPack + Kotlin + Coroutines + MVVM 架构(DataBinding、ViewModel、Lifecycle) +等最新技术栈进行组件化基础搭建,使用 ARouter 方案实现组件化 + +### DevUtils-repo + +**[DevUtils-repo](https://github.com/afkT/DevUtils-repo)** 该项目是针对 [DevUtils](https://github.com/afkT/DevUtils) 第三方库封装扩展、新技术 Demo 编写、大文件资源等迁移存储仓库。 + +减少 `DevUtils` 仓库大小方便快速 clone,并让 `DevUtils` 项目**更加纯粹**只保留 Dev 系列开发库相关代码。 + +移除多余的第三方库、插件依赖配置,避免过多无关且繁杂配置影响快速理解项目,降低第三方库下载数量、编译运行 `DevUtils 演示 Demo App` 难度,使项目可更加快捷运行。 + +### DevUtils API Generate + +[JavaDoc API Generate](https://github.com/afkT/JavaDoc) 该工具类 (DevUtils) API 文档,是通过 JavaDoc 项目读取 class 信息生成, +并且进行代码、注释间距规范检测,生成效果示范 [DevApp - API](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md) + +### DevOther + +[DevOther](https://github.com/afkT/DevUtils-repo/blob/main/lib/LocalModules/DevOther) +功能、工具类二次封装,直接 copy 使用【 大部分迁移至 DevUtils-repo 】 + +### DevSKU + +[DevSKU](https://github.com/afkT/DevUtils/blob/master/lib/LocalModules/DevSKU/src/main/java/dev/sku/SKU.kt) +商品 SKU 组合封装实现 ( 如何使用搜索 DevSKUActivity ) + + +## Dev 系列开发库全部 Lib Gradle + +```gradle + +// DevApp - Android 工具类库 +implementation 'io.github.afkt:DevAppX:2.4.2' + +// DevAssist - 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 +implementation 'io.github.afkt:DevAssist:1.3.8' + +// DevBase - Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 +implementation 'io.github.afkt:DevBase:1.1.4' + +// DevBaseMVVM - MVVM ( ViewDataBinding + ViewModel ) 基类库 +implementation 'io.github.afkt:DevBaseMVVM:1.1.2' + +// DevEngine - 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 +implementation 'io.github.afkt:DevEngine:1.1.0' + +// DevHttpCapture - OkHttp 抓包工具库 +implementation 'io.github.afkt:DevHttpCapture:1.1.3' + +// DevHttpCaptureCompiler - OkHttp 抓包工具库 ( 可视化功能 ) +debugImplementation 'io.github.afkt:DevHttpCaptureCompiler:1.1.3' +releaseImplementation 'io.github.afkt:DevHttpCaptureCompilerRelease:1.1.3' + +// DevHttpManager - OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) +implementation 'io.github.afkt:DevHttpManager:1.0.3' + +// DevRetrofit - Retrofit + Kotlin Coroutines 封装 +implementation 'io.github.afkt:DevRetrofit:1.0.2' + +// DevWidget - 自定义 View UI 库 +implementation 'io.github.afkt:DevWidgetX:1.2.0' + +// DevEnvironment - Android 环境配置切换库 +implementation 'io.github.afkt:DevEnvironment:1.1.2' +debugAnnotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.2' // kaptDebug +releaseAnnotationProcessor 'io.github.afkt:DevEnvironmentCompilerRelease:1.1.2' // kaptRelease +//annotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.2' // kapt + +// DevJava - Java 工具类库 ( 不依赖 android api ) +implementation 'io.github.afkt:DevJava:1.4.8' // 用于纯 Java 开发,如果依赖了 DevApp 则不需要依赖 DevJava ``` -# Thanks -> 感谢以下开源项目的作者,本项目中有些功能受你们项目灵感的启发,有些功能也用到你们的代码完成。 +## License + + Copyright 2018 afkT + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 -- [orhanobut/logger](https://github.com/orhanobut/logger) -- [laobie/StatusBarUtil](https://github.com/laobie/StatusBarUtil) -- [GrenderG/Toasty](https://github.com/GrenderG/Toasty) -- [Blankj/AndroidUtilCode](https://github.com/Blankj/AndroidUtilCode) -- [l123456789jy/Lazy](https://github.com/l123456789jy/Lazy) -- [yangfuhai/ASimpleCache](https://github.com/yangfuhai/ASimpleCache) -- [AbrahamCaiJin/CommonUtilLibrary](https://github.com/AbrahamCaiJin/CommonUtilLibrary) -- [litesuits/android-common](https://github.com/litesuits/android-common) \ No newline at end of file + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README/android_standard.md b/README/android_standard.md new file mode 100644 index 0000000000..ba04abe8a8 --- /dev/null +++ b/README/android_standard.md @@ -0,0 +1,1285 @@ + +> 原文链接:https://github.com/Blankj/AndroidStandardDevelop + +## 摘要 + +* [1 前言](#1-前言) +* [2 AS 规范](#2-as-规范) +* [3 命名规范](#3-命名规范) +* [4 代码样式规范](#4-代码样式规范) +* [5 资源文件规范](#5-资源文件规范) +* [6 版本统一规范](#6-版本统一规范) +* [7 第三方库规范](#7-第三方库规范) +* [8 注释规范](#8-注释规范) +* [9 测试规范](#9-测试规范) +* [10 其他的一些规范](#10-其他的一些规范) +* [附录](#附录) + + +### 1 前言 + +为了有利于项目维护、增强代码可读性、提升 Code Review 效率以及规范团队安卓开发,故提出以下安卓开发规范, +该规范结合本人多年的开发经验并吸取多家之精华,可谓是本人的呕心沥血之作,称其为当前最完善的安卓开发规范一点也不为过。 + + +### 2 AS 规范 + +工欲善其事,必先利其器。 + +1. 尽量使用最新的稳定版的 IDE 进行开发; +2. 编码格式统一为 **UTF-8**; +3. 编辑完 .java、.xml 等文件后一定要 **格式化,格式化,格式化**(如果团队有公共的样式包,那就遵循它,否则统一使用 AS 默认模板即可); +4. 删除多余的 import,减少警告出现,可利用 AS 的 Optimize Imports(Settings -> Keymap -> Optimize Imports)快捷键; +5. Android 开发者工具可以参考这里:**[Android 开发者工具][Android 开发者工具]**; + + +### 3 命名规范 + +代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。正确的英文拼写和语法可以让阅读者易于理解,避免歧义。 + +> 注意:即使纯拼音命名方式也要避免采用。但 `alibaba`、`taobao`、`youku`、`hangzhou` 等国际通用的名称,可视同英文。 + +#### 3.1 包名 + +包名全部小写,连续的单词只是简单地连接起来,不使用下划线,采用反域名命名规则,全部使用小写字母。一级包名是顶级域名, +通常为 `com`、`edu`、`gov`、`net`、`org` 等,二级包名为公司名,三级包名根据应用进行命名,后面就是对包名的划分了, +关于包名的划分,推荐采用 PBF(按功能分包 Package By Feature),一开始我们采用的也是 PBL(按层分包 Package By Layer), +很坑爹。PBF 可能不是很好区分在哪个功能中,不过也比 PBL 要好找很多,且 PBF 与 PBL 相比较有如下优势: + +* package 内高内聚,package 间低耦合 + + 哪块要添新功能,只改某一个 package 下的东西。 + + PBL 降低了代码耦合,但带来了 package 耦合,要添新功能,需要改 model、dbHelper、view、service 等等, + 需要改动好几个 package 下的代码,改动的地方越多,越容易产生新问题,不是吗? + + PBF 的话 featureA 相关的所有东西都在 featureA 包,feature 内高内聚、高度模块化, + 不同 feature 之间低耦合,相关的东西都放在一起,还好找。 + +* package 有私有作用域(package-private scope) + + 你负责开发这块功能,这个目录下所有东西都是你的。 + + PBL 的方式是把所有工具方法都放在 util 包下,小张开发新功能时候发现需要一个 xxUtil,但它又不是通用的,那应该放在哪里? + 没办法,按照分层原则,我们还得放在 util 包下,好像不太合适,但放在其它包更不合适,功能越来越多,util 类也越定义越多。 + 后来小李负责开发一块功能时发现需要一个 xxUtil,同样不通用,去 util 包一看,怎么已经有了,而且还没法复用, + 只好放弃 xx 这个名字,改为 xxxUtil……,因为 PBL 的 package 没有私有作用域, + 每一个包都是 public(跨包方法调用是很平常的事情,每一个包对其它包来说都是可访问的); + 如果是 PBF,小张的 xxUtil 自然放在 featureA 下,小李的 xxUtil 在 featureB 下, + 如果觉得 util 好像是通用的,就去 util 包看看要不要把工具方法添进 xxUtil,class 命名冲突没有了。 + + PBF 的 package 有私有作用域,featureA 不应该访问 featureB 下的任何东西(如果非访问不可,那就说明接口定义有问题)。 + +* 很容易删除功能 + + 统计发现新功能没人用,这个版本那块功能得去掉。 + + 如果是 PBL,得从功能入口到整个业务流程把受到牵连的所有能删的代码和 class 都揪出来删掉,一不小心就完蛋。 + + 如果是 PBF,好说,先删掉对应包,再删掉功能入口(删掉包后入口肯定报错了),完事。 + +* 高度抽象 + + 解决问题的一般方法是从抽象到具体,PBF 包名是对功能模块的抽象,包内的 class 是实现细节,符合从抽象到具体,而 PBL 弄反了。 + + PBF 从确定 AppName 开始,根据功能模块划分 package,再考虑每块的具体实现细节,而 PBL 从一开始就要考虑要不要 dao 层,要不要 com 层等等。 + +* 只通过 class 来分离逻辑代码 + + PBL 既分离 class 又分离 package,而 PBF 只通过 class 来分离逻辑代码。 + + 没有必要通过 package 分离,因为 PBL 中也可能出现尴尬的情况: + + ``` + ├── service + ├── MainService.java + ``` + + 按照 PBL,service 包下的所有东西都是 Service,应该不需要 Service 后缀, + 但实际上通常为了方便,直接 import service 包,Service 后缀是为了避免引入的 class 和当前包下的 class 命名冲突, + 当然,不用后缀也可以,得写清楚包路径,比如 `new com.domain.service.MainService()`,麻烦; + 而 PBF 就很方便,无需 import,直接 `new MainService()` 即可。 + +* package 的大小有意义了 + + PBL 中包的大小无限增长是合理的,因为功能越添越多,而 PBF 中包太大(包里 class 太多)表示这块需要重构(划分子包)。 + +如要知道更多好处,可以查看这篇博文:**[Package by features,not layers][Package by features,not layers]**, +当然,谷歌也有相应的 Sample:**[todo-mvp][todo-mvp]**,其结构如下所示,很值得学习。 + +``` +com +└── example + └── android + └── architecture + └── blueprints + └── todoapp + ├── BasePresenter.java + ├── BaseView.java + ├── addedittask + │   ├── AddEditTaskActivity.java + │   ├── AddEditTaskContract.java + │   ├── AddEditTaskFragment.java + │   └── AddEditTaskPresenter.java + ├── data + │   ├── Task.java + │   └── source + │   ├── TasksDataSource.java + │   ├── TasksRepository.java + │   ├── local + │   │   ├── TasksDbHelper.java + │   │   ├── TasksLocalDataSource.java + │   │   └── TasksPersistenceContract.java + │   └── remote + │   └── TasksRemoteDataSource.java + ├── statistics + │   ├── StatisticsActivity.java + │   ├── StatisticsContract.java + │   ├── StatisticsFragment.java + │   └── StatisticsPresenter.java + ├── taskdetail + │   ├── TaskDetailActivity.java + │   ├── TaskDetailContract.java + │   ├── TaskDetailFragment.java + │   └── TaskDetailPresenter.java + ├── tasks + │   ├── ScrollChildSwipeRefreshLayout.java + │   ├── TasksActivity.java + │   ├── TasksContract.java + │   ├── TasksFilterType.java + │   ├── TasksFragment.java + │   └── TasksPresenter.java + └── util + ├── ActivityUtils.java + ├── EspressoIdlingResource.java + └── SimpleCountingIdlingResource.java +``` + +参考以上的代码结构,按功能分包具体可以这样做: + +``` +com +└── domain + └── app + ├── App.java 定义 Application 类 + ├── Config.java 定义配置数据(常量) + ├── base 基础组件 + ├── custom_view 自定义视图 + ├── data 数据处理 + │   ├── DataManager.java 数据管理器, + │   ├── local 来源于本地的数据,比如 SP,Database,File + │   ├── model 定义 model(数据结构以及 getter/setter、compareTo、equals 等等,不含复杂操作) + │   └── remote 来源于远端的数据 + ├── feature 功能 + │   ├── feature0 功能 0 + │   │ ├── feature0Activity.java + │   │ ├── feature0Fragment.java + │ │ ├── xxAdapter.java + │ │ └── ... 其他 class + │ └── ...其他功能 + ├── injection 依赖注入 + ├── util 工具类 + └── widget 小部件 +``` + + +#### 3.2 类名 + +类名都以 `UpperCamelCase` 风格编写。 + +类名通常是名词或名词短语,接口名称有时可能是形容词或形容词短语。现在还没有特定的规则或行之有效的约定来命名注解类型。 + +名词,采用大驼峰命名法,尽量避免缩写,除非该缩写是众所周知的,比如 HTML、URL,如果类名称中包含单词缩写,则单词缩写的每个字母均应大写。 + +| 类 | 描述 | 例如 | +| ---------------------- | ----------------------------- | --------------------------------------------- | +| `Activity` 类 | `Activity` 为后缀标识 | 欢迎页面类 `WelcomeActivity` | +| `Adapter` 类 | `Adapter` 为后缀标识 | 新闻详情适配器 `NewsDetailAdapter` | +| 解析类 | `Parser` 为后缀标识 | 首页解析类 `HomePosterParser` | +| 工具方法类 | `Utils` 或 `Manager` 为后缀标识 | 线程池管理类:`ThreadPoolManager`
日志工具类:`LogUtils`(`Logger` 也可)
打印工具类:`PrinterUtils` | +| 数据库类 | 以 `DBHelper` 后缀标识 | 新闻数据库:`NewsDBHelper` | +| `Service` 类 | 以 `Service` 为后缀标识 | 时间服务 `TimeService` | +| `BroadcastReceiver` 类 | 以 `Receiver` 为后缀标识 | 推送接收 `JPushReceiver` | +| `ContentProvider` 类 | 以 `Provider` 为后缀标识 | `ShareProvider` | +| 自定义的共享基础类 | 以 `Base` 开头 | `BaseActivity`、`BaseFragment` | + +测试类的命名以它要测试的类的名称开始,以 Test 结束。例如:`HashTest` 或 `HashIntegrationTest`。 + +接口(interface):命名规则与类一样采用大驼峰命名法,多以 able 或 ible 结尾,如 `interface Runnable`、`interface Accessible`。 + +> 注意:如果项目采用 MVP,所有 Model、View、Presenter 的接口都以 I 为前缀,不加后缀,其他的接口采用上述命名规则。 + + +#### 3.3 方法名 + +方法名都以 `lowerCamelCase` 风格编写。 + +方法名通常是动词或动词短语。 + +| 方法 | 说明 | +| --------------------------- | ------------------------------------------------------- | +| `initXX()` | 初始化相关方法,使用 init 为前缀标识,如初始化布局 `initView()` | +| `isXX()`、`checkXX()` | 方法返回值为 boolean 型的请使用 is/check 为前缀标识 | +| `getXX()` | 返回某个值的方法,使用 get 为前缀标识 | +| `setXX()` | 设置某个属性值 | +| `handleXX()`、`processXX()` | 对数据进行处理的方法 | +| `displayXX()`、`showXX()` | 弹出提示框和提示信息,使用 display/show 为前缀标识 | +| `updateXX()` | 更新数据 | +| `saveXX()`、`insertXX()` | 保存或插入数据 | +| `resetXX()` | 重置数据 | +| `clearXX()` | 清除数据 | +| `removeXX()`、`deleteXX()` | 移除数据或者视图等,如 `removeView()` | +| `drawXX()` | 绘制数据或效果相关的,使用 draw 前缀标识 | + + +#### 3.4 常量名 + +常量名命名模式为 `CONSTANT_CASE`,全部字母大写,用下划线分隔单词。那到底什么算是一个常量? + +每个常量都是一个 `static final` 字段,但不是所有 `static final` 字段都是常量。 +在决定一个字段是否是一个常量时,得考虑它是否真的感觉像是一个常量。 +例如,如果观测任何一个该实例的状态是可变的,则它几乎肯定不会是一个常量。 +只是永远不打算改变的对象一般是不够的,它要真的一直不变才能将它示为常量。 + +```java +// Constants +static final int NUMBER = 5; +static final ImmutableListNAMES = ImmutableList.of("Ed", "Ann"); +static final Joiner COMMA_JOINER = Joiner.on(','); // because Joiner is immutable +static final SomeMutableType[] EMPTY_ARRAY = {}; +enum SomeEnum { ENUM_CONSTANT } + +// Not constants +static String nonFinal = "non-final"; +final String nonStatic = "non-static"; +static final SetmutableCollection = new HashSet(); +static final ImmutableSetmutableElements = ImmutableSet.of(mutable); +static final Logger logger = Logger.getLogger(MyClass.getName()); +static final String[] nonEmptyArray = {"these", "can", "change"}; +``` + + +#### 3.5 非常量字段名 + +非常量字段名以 `lowerCamelCase` 风格的基础上改造为如下风格: +基本结构为 `scope{Type0}VariableName{Type1}`、`type0VariableName{Type1}`、`variableName{Type1}`。 + +说明:`{}` 中的内容为可选。 + +> 注意:所有的 VO(值对象)统一采用标准的 lowerCamelCase 风格编写,所有的 DTO(数据传输对象)就按照接口文档中定义的字段名编写。 + +##### 3.5.1 scope(范围) + +非公有,非静态字段命名以 `m` 开头。 + +静态字段命名以 `s` 开头。 + +其他字段以小写字母开头。 + +例如: + +```java +public class MyClass { + public int publicField; + private static MyClass sSingleton; + int mPackagePrivate; + private int mPrivate; + protected int mProtected; +} +``` + +使用 1 个字符前缀来表示作用范围,1 个字符的前缀必须小写,前缀后面是由表意性强的一个单词或多个单词组成的名字, +而且每个单词的首写字母大写,其它字母小写,这样保证了对变量名能够进行正确的断句。 + + +##### 3.5.2 Type0(控件类型) + +考虑到 Android 众多的 UI 控件,为避免控件和普通成员变量混淆以及更好地表达意思, +所有用来表示控件的成员变量统一加上控件缩写作为前缀(具体见附录 [UI 控件缩写表](#ui-控件缩写表))。 + +例如:`mIvAvatar`、`rvBooks`、`flContainer`。 + + +##### 3.5.3 VariableName(变量名) + +变量名中可能会出现量词,我们需要创建统一的量词,它们更容易理解,也更容易搜索。 + +例如:`mFirstBook`、`mPreBook`、`curBook`。 + +| 量词列表 | 量词后缀说明 | +| --------- | ----------------- | +| `First` | 一组变量中的第一个 | +| `Last` | 一组变量中的最后一个 | +| `Next` | 一组变量中的下一个 | +| `Pre` | 一组变量中的上一个 | +| `Cur` | 一组变量中的当前变量 | + + +##### 3.5.4 Type1(数据类型) + +对于表示集合或者数组的非常量字段名,我们可以添加后缀来增强字段的可读性,比如: + +集合添加如下后缀:List、Map、Set。 + +数组添加如下后缀:Arr。 + +例如:`mIvAvatarList`、`userArr`、`firstNameSet`。 + +> 注意:如果数据类型不确定的话,比如表示的是很多书,那么使用其复数形式来表示也可,例如 `mBooks`。 + + +#### 3.6 参数名 + +参数名以 `lowerCamelCase` 风格编写,参数应该避免用单个字符命名。 + + +#### 3.7 局部变量名 + +局部变量名以 `lowerCamelCase` 风格编写,比起其它类型的名称,局部变量名可以有更为宽松的缩写。 + +虽然缩写更宽松,但还是要避免用单字符进行命名,除了临时变量和循环变量。 + +即使局部变量是 `final` 和不可改变的,也不应该把它示为常量,自然也不能用常量的规则去命名它。 + + +#### 3.8 临时变量 + +临时变量通常被取名为 `i`、`j`、`k`、`m` 和 `n`, +它们一般用于整型;`c`、`d`、`e`,它们一般用于字符型。如:`for (int i = 0; i < len; i++)`。 + + +#### 3.9 类型变量名 + +类型变量可用以下两种风格之一进行命名: + +1. 单个的大写字母,后面可以跟一个数字(如:`E`、`T`、`X`、`T2`)。 +2. 以类命名方式(参考[3.2 类名](#32-类名)),后面加个大写的 T(如:`RequestT`、`FooBarT`)。 + +更多还可参考:**[阿里巴巴 Java 开发手册][阿里巴巴 Java 开发手册]** + + +### 4 代码样式规范 + +#### 4.1 使用标准大括号样式 + +左大括号不单独占一行,与其前面的代码位于同一行: + +```java +class MyClass { + int func() { + if (something) { + // ... + } else if (somethingElse) { + // ... + } else { + // ... + } + } +} +``` + +我们需要在条件语句周围添加大括号。例外情况:如果整个条件语句(条件和主体)适合放在同一行, +那么您可以(但不是必须)将其全部放在一行上。例如,我们接受以下样式: + +```java +if (condition) { + body(); +} +``` + +同样也接受以下样式: + +```java +if (condition) body(); +``` + +但不接受以下样式: + +```java +if (condition) + body(); // bad! +``` + + +#### 4.2 编写简短方法 + +在可行的情况下,尽量编写短小精炼的方法。我们了解,有些情况下较长的方法是恰当的, +因此对方法的代码长度没有做出硬性限制。如果某个方法的代码超出 40 行,请考虑是否可以在不破坏程序结构的前提下对其拆解。 + + +#### 4.3 类成员的顺序 + +这并没有唯一的正确解决方案,但如果都使用一致的顺序将会提高代码的可读性,推荐使用如下排序: + +1. 常量 +2. 字段 +3. 构造函数 +4. 重写函数和回调 +5. 公有函数 +6. 私有函数 +7. 内部类或接口 + +例如: + +```java +public class MainActivity extends Activity { + + private static final String TAG = MainActivity.class.getSimpleName(); + + private String mTitle; + private TextView mTextViewTitle; + + @Override + public void onCreate() { + ... + } + + public void setTitle(String title) { + mTitle = title; + } + + private void setUpView() { + ... + } + + static class AnInnerClass { + + } +} +``` + +如果类继承于 Android 组件(例如 `Activity` 或 `Fragment`), +那么把重写函数按照他们的生命周期进行排序是一个非常好的习惯, +例如,`Activity` 实现了 `onCreate()`、`onDestroy()`、`onPause()`、`onResume()`,它的正确排序如下所示: + +```java +public class MainActivity extends Activity { + //Order matches Activity lifecycle + @Override + public void onCreate() {} + + @Override + public void onResume() {} + + @Override + public void onPause() {} + + @Override + public void onDestroy() {} +} +``` + + +#### 4.4 函数参数的排序 + +在 Android 开发过程中,`Context` 在函数参数中是再常见不过的了,我们最好把 `Context` 作为其第一个参数。 + +正相反,我们把回调接口应该作为其最后一个参数。 + +例如: + +```java +// Context always goes first +public User loadUser(Context context, int userId); + +// Callbacks always go last +public void loadUserAsync(Context context, int userId, UserCallback callback); +``` + + +#### 4.5 字符串常量的命名和值 + +Android SDK 中的很多类都用到了键值对函数,比如 `SharedPreferences`、`Bundle`、`Intent`, +所以,即便是一个小应用,我们最终也不得不编写大量的字符串常量。 + +当时用到这些类的时候,我们 **必须** 将它们的键定义为 `static final` 字段,并遵循以下指示作为前缀。 + +| 类 | 字段名前缀 | +| ------------------ | ----------- | +| SharedPreferences | `PREF_` | +| Bundle | `BUNDLE_` | +| Fragment Arguments | `ARGUMENT_` | +| Intent Extra | `EXTRA_` | +| Intent Action | `ACTION_` | + +说明:虽然 `Fragment.getArguments()` 得到的也是 `Bundle`,但因为这是 `Bundle` 的常用用法,所以特意为此定义一个不同的前缀。 + +例如: + +```java +// 注意:字段的值与名称相同以避免重复问题 +static final String PREF_EMAIL = "PREF_EMAIL"; +static final String BUNDLE_AGE = "BUNDLE_AGE"; +static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID"; + +// 与意图相关的项使用完整的包名作为值的前缀 +static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME"; +static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER"; +``` + + +#### 4.6 Activities 和 Fragments 的传参 + +当 `Activity` 或 `Fragment` 传递数据通过 `Intent` 或 `Bundle` 时,不同值的键须遵循上一条所提及到的。 + +当 `Activity` 或 `Fragment` 启动需要传递参数时,那么它需要提供一个 `public static` 的函数来帮助启动或创建它。 + +这方面,AS 已帮你写好了相关的 Live Templates,启动相关 `Activity` 的只需要在其内部输入 `starter` 即可生成它的启动器,如下所示: + +```java +public static void start(Context context, User user) { + Intent starter = new Intent(context, MainActivity.class); + starter.putParcelableExtra(EXTRA_USER, user); + context.startActivity(starter); +} +``` + +同理,启动相关 `Fragment` 在其内部输入 `newInstance` 即可,如下所示: + +```java +public static MainFragment newInstance(User user) { + Bundle args = new Bundle(); + args.putParcelable(ARGUMENT_USER, user); + MainFragment fragment = new MainFragment(); + fragment.setArguments(args); + return fragment; +} +``` + +> 注意:这些函数需要放在 `onCreate()` 之前的类的顶部;如果我们使用了这种方式, +> 那么 `extras` 和 `arguments` 的键应该是 `private` 的,因为它们不再需要暴露给其他类来使用。 + + +#### 4.7 行长限制 + +代码中每一行文本的长度都应该不超过 100 个字符。虽然关于此规则存在很多争论, +但最终决定仍是以 100 个字符为上限,如果行长超过了 100(AS 窗口右侧的竖线就是设置的行宽末尾),我们通常有两种方法来缩减行长。 + +* 提取一个局部变量或方法(最好)。 +* 使用换行符将一行换成多行。 + +不过存在以下例外情况: + +* 如果备注行包含长度超过 100 个字符的示例命令或文字网址,那么为了便于剪切和粘贴,该行可以超过 100 个字符。 +* 导入语句行可以超出此限制,因为用户很少会看到它们(这也简化了工具编写流程)。 + +##### 4.7.1 换行策略 + +这没有一个准确的解决方案来决定如何换行,通常不同的解决方案都是有效的,但是有一些规则可以应用于常见的情况。 + +##### 4.7.1.1 操作符的换行 + +除赋值操作符之外,我们把换行符放在操作符之前,例如: + +```java +int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + + theFinalOne; +``` + +赋值操作符的换行我们放在其后,例如: + +```java +int longName = + anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne; +``` + + +##### 4.7.1.2 函数链的换行 + +当同一行中调用多个函数时(比如使用构建器时),对每个函数的调用应该在新的一行中,我们把换行符插入在 `.` 之前。 + +例如: + +```java +Picasso.with(context).load("imageUrl").into(ivAvatar); +``` + +我们应该使用如下规则: + +```java +Picasso.with(context) + .load("imageUrl") + .into(ivAvatar); +``` + + +##### 4.7.1.3 多参数的换行 + +当一个方法有很多参数或者参数很长的时候,我们应该在每个 `,` 后面进行换行。 + +比如: + +```java +loadPicture(context, "imageUrl", ivAvatar, "Avatar of the user", clickListener); +``` + +我们应该使用如下规则: + +```java +loadPicture(context, + "imageUrl", + ivAvatar, + "Avatar of the user", + clickListener); +``` + + +##### 4.7.1.4 RxJava 链式的换行 + +RxJava 的每个操作符都需要换新行,并且把换行符插入在 `.` 之前。 + +例如: + +```java +public Observable syncLocations() { + return mDatabaseHelper.getAllLocations() + .concatMap(new Func1>() { + @Override + public Observable call(Location location) { + return mRetrofitService.getLocation(location.id); + } + }) + .retry(new Func2() { + @Override + public Boolean call(Integer numRetries, Throwable throwable) { + return throwable instanceof RetrofitError; + } + }); +} +``` + + +### 5 资源文件规范 + +资源文件命名为全部小写,采用下划线命名法。 + +如果是组件化开发,我们可以在组件和公共模块间创建一个 ui 模块来专门存放资源文件, +然后让每个组件都依赖 ui 模块。这样做的好处是如果老项目要实现组件化的话,只需把资源文件都放入 ui 模块即可, +如果想对资源文件进行分包,可以参考这篇文章:**[Android Studio 下对资源进行分包][Android Studio 下对资源进行分包]**; +还避免了多个模块间资源不能复用的问题。 + +如果是三方库开发,其使用到的资源文件及相关的 `name` 都应该使用库名作为前缀,这样做可以避免三方库资源和实际应用资源重名的冲突。 + +#### 5.1 动画资源文件(anim/ 和 animator/) + +安卓主要包含属性动画和视图动画,其视图动画包括补间动画和逐帧动画。 +属性动画文件需要放在 `res/animator/` 目录下,视图动画文件需放在 `res/anim/` 目录下。 + +命名规则:`{模块名_}逻辑名称`。 + +说明:`{}` 中的内容为可选,`逻辑名称` 可由多个单词加下划线组成。 + +例如:`refresh_progress.xml`、`market_cart_add.xml`、`market_cart_remove.xml`。 + +如果是普通的补间动画或者属性动画,可采用:`动画类型_方向` 的命名方式。 + +例如: + +| 名称 | 说明 | +| ------------------- | ----------- | +| `fade_in` | 淡入 | +| `fade_out` | 淡出 | +| `push_down_in` | 从下方推入 | +| `push_down_out` | 从下方推出 | +| `push_left` | 推向左方 | +| `slide_in_from_top` | 从头部滑动进入 | +| `zoom_enter` | 变形进入 | +| `slide_in` | 滑动进入 | +| `shrink_to_middle` | 中间缩小 | + + +#### 5.2 颜色资源文件(color/) + +专门存放颜色相关的资源文件。 + +命名规则:`类型{_模块名}_逻辑名称`。 + +说明:`{}` 中的内容为可选。 + +例如:`sel_btn_font.xml`。 + +颜色资源也可以放于 `res/drawable/` 目录,引用时则用 `@drawable` 来引用,但不推荐这么做,最好还是把两者分开。 + + +#### 5.3 图片资源文件(drawable/ 和 mipmap/) + +`res/drawable/` 目录下放的是位图文件(.png、.9.png、.jpg、.gif) +或编译为可绘制对象资源子类型的 XML 文件,而 `res/mipmap/` 目录下放的是不同密度的启动图标, +所以 `res/mipmap/` 只用于存放启动图标,其余图片资源文件都应该放到 `res/drawable/` 目录下。 + +命名规则:`类型{_模块名}_逻辑名称`、`类型{_模块名}_颜色`。 + +说明:`{}` 中的内容为可选;`类型` 可以是[可绘制对象资源类型][可绘制对象资源类型], +也可以是控件类型(具体见附录[UI 控件缩写表](#ui-控件缩写表));最后可加后缀 `_small` 表示小图,`_big` 表示大图。 + +例如: + +| 名称 | 说明 | +| ------------------------- | ------------------------------------ | +| `btn_main_about.png` | 主页关于按键 `类型_模块名_逻辑名称` | +| `btn_back.png` | 返回按键 `类型_逻辑名称` | +| `divider_maket_white.png` | 商城白色分割线 `类型_模块名_颜色` | +| `ic_edit.png` | 编辑图标 `类型_逻辑名称` | +| `bg_main.png` | 主页背景 `类型_逻辑名称` | +| `btn_red.png` | 红色按键 `类型_颜色` | +| `btn_red_big.png` | 红色大按键 `类型_颜色` | +| `ic_head_small.png` | 小头像图标 `类型_逻辑名称` | +| `bg_input.png` | 输入框背景 `类型_逻辑名称` | +| `divider_white.png` | 白色分割线 `类型_颜色` | +| `bg_main_head.png` | 主页头部背景 `类型_模块名_逻辑名称` | +| `def_search_cell.png` | 搜索页面默认单元图片 `类型_模块名_逻辑名称` | +| `ic_more_help.png` | 更多帮助图标 `类型_逻辑名称` | +| `divider_list_line.png` | 列表分割线 `类型_逻辑名称` | +| `sel_search_ok.xml` | 搜索界面确认选择器 `类型_模块名_逻辑名称` | +| `shape_music_ring.xml` | 音乐界面环形形状 `类型_模块名_逻辑名称` | + +如果有多种形态,如按钮选择器:`sel_btn_xx.xml`,采用如下命名: + +| 名称 | 说明 | +| ----------------------- | --------------------------------- | +| `sel_btn_xx` | 作用在 `btn_xx` 上的 `selector` | +| `btn_xx_normal` | 默认状态效果 | +| `btn_xx_pressed` | `state_pressed` 点击效果 | +| `btn_xx_focused` | `state_focused` 聚焦效果 | +| `btn_xx_disabled` | `state_enabled` 不可用效果 | +| `btn_xx_checked` | `state_checked` 选中效果 | +| `btn_xx_selected` | `state_selected` 选中效果 | +| `btn_xx_hovered` | `state_hovered` 悬停效果 | +| `btn_xx_checkable` | `state_checkable` 可选效果 | +| `btn_xx_activated` | `state_activated` 激活效果 | +| `btn_xx_window_focused` | `state_window_focused` 窗口聚焦效果 | + +> 注意:使用 Android Studio 的插件 SelectorChapek 可以快速生成 selector,前提是命名要规范。 + + +#### 5.4 布局资源文件(layout/) + +命名规则:`类型_模块名`、`类型{_模块名}_逻辑名称`。 + +说明:`{}` 中的内容为可选。 + +例如: + +| 名称 | 说明 | +| --------------------------- | -------------------------------------- | +| `activity_main.xml` | 主窗体 `类型_模块名` | +| `activity_main_head.xml` | 主窗体头部 `类型_模块名_逻辑名称` | +| `fragment_music.xml` | 音乐片段 `类型_模块名` | +| `fragment_music_player.xml` | 音乐片段的播放器 `类型_模块名_逻辑名称` | +| `dialog_loading.xml` | 加载对话框 `类型_逻辑名称` | +| `ppw_info.xml` | 信息弹窗(PopupWindow)`类型_逻辑名称` | +| `item_main_song.xml` | 主页歌曲列表项 `类型_模块名_逻辑名称` | + + +#### 5.5 菜单资源文件(menu/) + +菜单相关的资源文件应放在该目录下。 + +命名规则:`{模块名_}逻辑名称` + +说明:`{}` 中的内容为可选。 + +例如:`main_drawer.xml`、`navigation.xml`。 + + +#### 5.6 values 资源文件(values/) + +`values/` 资源文件下的文件都以 `s` 结尾,如 `attrs.xml`、`colors.xml`、`dimens.xml`, +起作用的不是文件名称,而是 `` 标签下的各种标签,比如 ` +``` + +应用到 `TextView` 中: + +``` + +``` + +或许你需要为按钮控件做同样的事情,不要停止在那里,将一组相关的和重复 `android:xxxx` 的属性放到一个通用的 ` - - diff --git a/application/DevBaseDemo/.gitignore b/application/DevBaseDemo/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/application/DevBaseDemo/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/application/DevBaseDemo/build.gradle b/application/DevBaseDemo/build.gradle new file mode 100644 index 0000000000..ed8dae9b62 --- /dev/null +++ b/application/DevBaseDemo/build.gradle @@ -0,0 +1,20 @@ +apply from: rootProject.file(files.build_app_gradle) + +android { + defaultConfig { + applicationId "afkt.demo" + } +} + +dependencies { + implementation project(':DevApp') + implementation project(':DevBaseMVVM') + implementation project(':DevMVVM') + implementation project(':DevEngine') + + // ========= + // = 非 lib = + // ========= + + implementation project(':DevOther') +} \ No newline at end of file diff --git a/application/DevBaseDemo/proguard-rules.pro b/application/DevBaseDemo/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/application/DevBaseDemo/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/AndroidManifest.xml b/application/DevBaseDemo/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..503d374e49 --- /dev/null +++ b/application/DevBaseDemo/src/main/AndroidManifest.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/base/BaseApplication.kt b/application/DevBaseDemo/src/main/java/afkt/demo/base/BaseApplication.kt new file mode 100644 index 0000000000..28f29731b3 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/base/BaseApplication.kt @@ -0,0 +1,63 @@ +package afkt.demo.base + +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.multidex.MultiDexApplication +import dev.DevUtils +import dev.engine.DevEngine +import dev.utils.app.logger.DevLogger +import dev.utils.app.logger.LogConfig +import dev.utils.app.logger.LogLevel + +class BaseApplication : MultiDexApplication(), + ViewModelStoreOwner { + + // 日志 TAG + private val TAG = "DevBaseDemo_TAG" + + // ViewModelStore + private lateinit var mAppViewModelStore: ViewModelStore + + override fun onCreate() { + super.onCreate() + + // 初始化 Logger 配置 + DevLogger.initialize( + LogConfig() + .logLevel(LogLevel.DEBUG) + .tag(TAG) + .sortLog(true) + .methodCount(0) + ) + // 打开 lib 内部日志 - 线上环境, 不调用方法 + DevUtils.openLog() + DevUtils.openDebug() + + // DevEngine 完整初始化 + DevEngine.completeInitialize(this) + + application = this + mAppViewModelStore = ViewModelStore() + } + + // ========== + // = 静态方法 = + // ========== + + companion object { + + private lateinit var application: BaseApplication + + fun getApplication(): BaseApplication { + return application + } + } + + // ======================= + // = ViewModelStoreOwner = + // ======================= + + override fun getViewModelStore(): ViewModelStore { + return mAppViewModelStore + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseActivity.kt new file mode 100644 index 0000000000..8afbd5f5a5 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseActivity.kt @@ -0,0 +1,23 @@ +package afkt.demo.base.app + +import android.os.Bundle +import android.view.View +import androidx.databinding.ViewDataBinding +import dev.base.expand.viewdata.DevBaseViewDataBindingActivity + +/** + * detail: Activity 基类 + * @author Ttt + */ +abstract class BaseActivity : DevBaseViewDataBindingActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 初始化顺序 ( 按顺序调用方法 ) + initOrder() + } + + override fun baseContentView(): View? { + return null + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseFragment.kt new file mode 100644 index 0000000000..1f6dd48f26 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/base/app/BaseFragment.kt @@ -0,0 +1,26 @@ +package afkt.demo.base.app + +import android.os.Bundle +import android.view.View +import androidx.databinding.ViewDataBinding +import dev.base.expand.viewdata.DevBaseViewDataBindingFragment + +/** + * detail: Fragment 基类 + * @author Ttt + */ +abstract class BaseFragment : DevBaseViewDataBindingFragment() { + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + // 初始化顺序 ( 按顺序调用方法 ) + initOrder() + } + + override fun baseContentView(): View? { + return null + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/model/ActivityViewModel.kt b/application/DevBaseDemo/src/main/java/afkt/demo/model/ActivityViewModel.kt new file mode 100644 index 0000000000..a134dfd81e --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/model/ActivityViewModel.kt @@ -0,0 +1,13 @@ +package afkt.demo.model + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ActivityViewModel : ViewModel() { + + val number = MutableLiveData() + + init { + number.value = -100 + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/model/ApplicationViewModel.kt b/application/DevBaseDemo/src/main/java/afkt/demo/model/ApplicationViewModel.kt new file mode 100644 index 0000000000..44700931a9 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/model/ApplicationViewModel.kt @@ -0,0 +1,13 @@ +package afkt.demo.model + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ApplicationViewModel : ViewModel() { + + val number = MutableLiveData() + + init { + number.value = -100 + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/model/FragmentViewModel.kt b/application/DevBaseDemo/src/main/java/afkt/demo/model/FragmentViewModel.kt new file mode 100644 index 0000000000..f2b662f978 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/model/FragmentViewModel.kt @@ -0,0 +1,13 @@ +package afkt.demo.model + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class FragmentViewModel : ViewModel() { + + val number = MutableLiveData() + + init { + number.value = -100 + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivity.kt new file mode 100644 index 0000000000..588f6b3cf4 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivity.kt @@ -0,0 +1,40 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.base.app.BaseActivity +import afkt.demo.databinding.ActivityMainBinding +import android.content.Intent +import android.os.Bundle +import dev.utils.common.ColorUtils + +/** + * detail: Main Activity + * @author Ttt + */ +class MainActivity : BaseActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.title = "DataBinding Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + +// // 跳转校验 +// startActivity(Intent(this, MainNonViewDataActivity::class.java)) +// startActivity(Intent(this, MainBaseContentActivity::class.java)) +// startActivity(Intent(this, MainApplicationViewModelActivity::class.java)) +// startActivity(Intent(this, MainApplicationMVVMActivity::class.java)) +// startActivity(Intent(this, MainActivityMVVMActivity::class.java)) +// startActivity(Intent(this, MainFragmentMVVMActivity::class.java)) +// startActivity(Intent(this, MainFragmentParentMVVMActivity::class.java)) +// startActivity(Intent(this, MainBaseContentVMImplActivity::class.java)) +// startActivity(Intent(this, MainMVVMUtilsActivity::class.java)) + startActivity(Intent(this, MainViewPagerActivity::class.java)) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivityMVVMActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivityMVVMActivity.kt new file mode 100644 index 0000000000..a01be1526e --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainActivityMVVMActivity.kt @@ -0,0 +1,52 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainActivityMvvmBinding +import afkt.demo.model.ActivityViewModel +import afkt.demo.ui.fragment.ActivityMVVMFragment +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import dev.base.expand.mvvm.DevBaseMVVMActivity +import dev.utils.common.ColorUtils + +/** + * detail: Main Activity MVVM Activity + * @author Ttt + */ +class MainActivityMVVMActivity : + DevBaseMVVMActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_activity_mvvm + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + binding.title = "Activity MVVM Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + // 嵌套处理 + ActivityMVVMFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + viewModel = getActivityViewModel(ActivityViewModel::class.java)!! + // 复用方法进行监听 + ViewModelTempUtils.observe(TAG, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = Int.MAX_VALUE + }, 2000) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationMVVMActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationMVVMActivity.kt new file mode 100644 index 0000000000..63f93db61c --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationMVVMActivity.kt @@ -0,0 +1,56 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.base.BaseApplication +import afkt.demo.databinding.ActivityMainApplicationMvvmBinding +import afkt.demo.model.ApplicationViewModel +import afkt.demo.ui.fragment.ApplicationMVVMFragment +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import dev.base.expand.mvvm.DevBaseMVVMActivity +import dev.utils.common.ColorUtils + +/** + * detail: Main Application MVVM Activity + * @author Ttt + */ +class MainApplicationMVVMActivity : + DevBaseMVVMActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_application_mvvm + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + binding.title = "Application MVVM Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + // 嵌套处理 + ApplicationMVVMFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + viewModel = getAppViewModel( + BaseApplication.getApplication(), + ApplicationViewModel::class.java + )!! + // 复用方法进行监听 + ViewModelTempUtils.observe(TAG, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = Int.MAX_VALUE + }, 2000) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationViewModelActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationViewModelActivity.kt new file mode 100644 index 0000000000..203e0f4873 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainApplicationViewModelActivity.kt @@ -0,0 +1,55 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.base.BaseApplication +import afkt.demo.model.ApplicationViewModel +import afkt.demo.ui.fragment.ApplicationViewModelFragment +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import dev.base.expand.viewmodel.DevBaseViewModelActivity +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.ColorUtils + +/** + * detail: Main Application ViewModel Activity + * @author Ttt + */ +class MainApplicationViewModelActivity : DevBaseViewModelActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_application_view_model + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + ViewHelper.get().setText( + "Application ViewModel Title", + findViewById(R.id.vid_title_tv) + ).setBackgroundColor( + ColorUtils.getRandomColor(), findViewById(R.id.vid_view) + ) + + // 嵌套处理 + ApplicationViewModelFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + viewModel = + getAppViewModel(BaseApplication.getApplication(), ApplicationViewModel::class.java)!! + // 复用方法进行监听 + ViewModelTempUtils.observe(TAG, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = Int.MAX_VALUE + }, 2000) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentActivity.kt new file mode 100644 index 0000000000..a6f04dae19 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentActivity.kt @@ -0,0 +1,79 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainContentBinding +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import dev.base.expand.content.DevBaseContentViewDataBindingActivity +import dev.utils.app.ResourceUtils +import dev.utils.app.TextViewUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.ColorUtils + +/** + * detail: Main Content Activity + * @author Ttt + */ +class MainBaseContentActivity : + DevBaseContentViewDataBindingActivity() { + + override fun baseLayoutId(): Int { + return R.layout.activity_main_content + } + + override fun baseLayoutView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.title = "DataBinding Content Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + contentAssist.addTitleView( + QuickHelper.get(TextView(this)) + .setText("随机标题: " + ChineseUtils.randomWord(10)) + .setTextColors(ResourceUtils.getColor(R.color.red)) + .setBackgroundColor(ResourceUtils.getColor(R.color.pink)) + .setWidthHeight( + ViewUtils.MATCH_PARENT, + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setGravity(Gravity.CENTER) + .setBold() + .setOnClick { view -> + showToast(TextViewUtils.getText(view)) + } + .getView() + ) + + // 悬浮 View + contentAssist.addFloatView( + QuickHelper.get(ImageView(this)) + .setWidthHeight( + ResourceUtils.getDimensionInt(R.dimen.dp_50), + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setImageResource(R.mipmap.icon_launcher_round) + .getView() + ) + + // 悬浮 View 居下显示 ( 需要 addBodyView 后进行设置 LayoutParams 才有效 ) + contentAssist.floatFrame?.let { + QuickHelper.get(ViewUtils.getChildAtLast(it)) + .setMargin(ResourceUtils.getDimensionInt(R.dimen.dp_20)) + .setLayoutGravity(Gravity.END or Gravity.BOTTOM) + .setOnClick { + showToast("点击了悬浮 View") + } + } + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentVMImplActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentVMImplActivity.kt new file mode 100644 index 0000000000..9dfd4fb49d --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainBaseContentVMImplActivity.kt @@ -0,0 +1,83 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainContentVmImplBinding +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import dev.base.expand.content.DevBaseContentMVVMActivity +import dev.base.expand.mvvm.MVVM +import dev.utils.app.ResourceUtils +import dev.utils.app.TextViewUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.ColorUtils + +/** + * detail: Main Content Activity MVVM Activity + * @author Ttt + */ +class MainBaseContentVMImplActivity : + DevBaseContentMVVMActivity() { + + override fun baseLayoutId(): Int { + return R.layout.activity_main_content_vm_impl + } + + override fun baseLayoutView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.title = "MVVM VMImpl Content Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + contentAssist.addTitleView( + QuickHelper.get(TextView(this)) + .setText("随机标题: " + ChineseUtils.randomWord(10)) + .setTextColors(ResourceUtils.getColor(R.color.red)) + .setBackgroundColor(ResourceUtils.getColor(R.color.pink)) + .setWidthHeight( + ViewUtils.MATCH_PARENT, + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setGravity(Gravity.CENTER) + .setBold() + .setOnClick { view -> + showToast(TextViewUtils.getText(view)) + } + .getView() + ) + + // 悬浮 View + contentAssist.addFloatView( + QuickHelper.get(ImageView(this)) + .setWidthHeight( + ResourceUtils.getDimensionInt(R.dimen.dp_50), + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setImageResource(R.mipmap.icon_launcher_round) + .getView() + ) + + // 悬浮 View 居下显示 ( 需要 addBodyView 后进行设置 LayoutParams 才有效 ) + contentAssist.floatFrame?.let { + QuickHelper.get(ViewUtils.getChildAtLast(it)) + .setMargin(ResourceUtils.getDimensionInt(R.dimen.dp_20)) + .setLayoutGravity(Gravity.END or Gravity.BOTTOM) + .setOnClick { + showToast("点击了悬浮 View") + } + } + } + + override fun initViewModel() { + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentMVVMActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentMVVMActivity.kt new file mode 100644 index 0000000000..b5ce59223f --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentMVVMActivity.kt @@ -0,0 +1,43 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainFragmentMvvmBinding +import afkt.demo.model.FragmentViewModel +import afkt.demo.ui.fragment.FragmentMVVMFragment +import android.os.Bundle +import android.view.View +import dev.base.expand.mvvm.DevBaseMVVMActivity +import dev.utils.common.ColorUtils + +/** + * detail: Main Fragment MVVM Activity + * @author Ttt + */ +class MainFragmentMVVMActivity : + DevBaseMVVMActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_fragment_mvvm + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + binding.title = "Fragment MVVM Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + // 嵌套处理 + FragmentMVVMFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentParentMVVMActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentParentMVVMActivity.kt new file mode 100644 index 0000000000..931cbbb873 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainFragmentParentMVVMActivity.kt @@ -0,0 +1,43 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainFragmentMvvmBinding +import afkt.demo.model.FragmentViewModel +import afkt.demo.ui.fragment.FragmentParentMVVMFragment +import android.os.Bundle +import android.view.View +import dev.base.expand.mvvm.DevBaseMVVMActivity +import dev.utils.common.ColorUtils + +/** + * detail: Main Parent Fragment MVVM Activity + * @author Ttt + */ +class MainFragmentParentMVVMActivity : + DevBaseMVVMActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_fragment_mvvm + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + binding.title = "Parent Fragment MVVM Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + // 嵌套处理 + FragmentParentMVVMFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainMVVMUtilsActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainMVVMUtilsActivity.kt new file mode 100644 index 0000000000..228ee8e934 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainMVVMUtilsActivity.kt @@ -0,0 +1,59 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainMvvmUtilsBinding +import afkt.demo.model.ActivityViewModel +import afkt.demo.ui.fragment.MVVMUtilsFragment +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import dev.base.expand.mvvm.DevBaseMVVMActivity +import dev.base.utils.ViewModelUtils +import dev.utils.common.ColorUtils + +/** + * detail: MVVM Utils Activity + * @author Ttt + */ +class MainMVVMUtilsActivity : + DevBaseMVVMActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_mvvm_utils + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + + binding.title = "MVVM Utils Title" + + // 随机设置背景色 + binding.vidInclude.color = ColorUtils.getRandomColor() + + // 嵌套处理 + MVVMUtilsFragment.commit(supportFragmentManager, R.id.vid_fl, 0, 4) + } + + override fun initViewModel() { + viewModel = ViewModelUtils.getActivityViewModel( + viewModelAssist, + this, + ActivityViewModel::class.java + )!! + +// viewModel = ViewModelUtils.getActivityViewModel(this, ActivityViewModel::class.java)!! + // 复用方法进行监听 + ViewModelTempUtils.observe(TAG, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = Int.MAX_VALUE + }, 2000) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainNonViewDataActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainNonViewDataActivity.kt new file mode 100644 index 0000000000..2d852202e9 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainNonViewDataActivity.kt @@ -0,0 +1,31 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainNonViewdataBinding +import android.os.Bundle +import android.view.View +import dev.base.expand.viewbinding.DevBaseViewBindingActivity + +/** + * detail: Main Activity + * @author Ttt + * 非使用 Data 的 ViewBinding + * 无法使用 DevBaseViewDataBindingActivity 只能使用 DevBaseViewBindingActivity 才能编译 + */ +class MainNonViewDataActivity : DevBaseViewBindingActivity() { +//class MainActivityNonViewData : DevBaseViewDataBindingActivity() { + + override fun baseContentId(): Int { + return R.layout.activity_main_non_viewdata + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.vidTv.text = "非使用 Data 的 ViewDataBinding" + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainViewPagerActivity.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainViewPagerActivity.kt new file mode 100644 index 0000000000..034b08019b --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/activity/MainViewPagerActivity.kt @@ -0,0 +1,92 @@ +package afkt.demo.ui.activity + +import afkt.demo.R +import afkt.demo.databinding.ActivityMainViewpagerBinding +import afkt.demo.ui.adapter.MainTabAdapter +import afkt.demo.ui.fragment.ItemValueFragment +import android.os.Bundle +import android.view.View +import dev.base.expand.viewbinding.DevBaseViewBindingActivity +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Main Activity + * @author Ttt + * 使用 ViewPager2 + */ +class MainViewPagerActivity : DevBaseViewBindingActivity() { + + // Tab Fragment + private val fragments = mutableListOf( + ItemValueFragment.get(1), + ItemValueFragment.get(2), + ItemValueFragment.get(3), + ItemValueFragment.get(4) + ) + + override fun baseContentId(): Int { + return R.layout.activity_main_viewpager + } + + override fun baseContentView(): View? { + return null + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding.vidVp.apply { + // 设置适配器 + adapter = MainTabAdapter(fragments, this@MainViewPagerActivity) + } + + ViewHelper.get() + .setOnClick({ + if (isSelectTab(TAB_1)) { + return@setOnClick + } + setSelectTab(TAB_1) + }, binding.vidTab1Tv) + .setOnClick({ + if (isSelectTab(TAB_2)) { + return@setOnClick + } + setSelectTab(TAB_2) + }, binding.vidTab2Tv) + .setOnClick({ + if (isSelectTab(TAB_3)) { + return@setOnClick + } + setSelectTab(TAB_3) + }, binding.vidTab3Tv) + .setOnClick({ + if (isSelectTab(TAB_4)) { + return@setOnClick + } + setSelectTab(TAB_4) + }, binding.vidTab4Tv) + } + + // Tab 类型索引 + private val TAB_1 = 0 + private val TAB_2 = 1 + private val TAB_3 = 2 + private val TAB_4 = 3 + + /** + * 判断是否选中 Tab 类型 + * @param tabType Int + */ + private fun isSelectTab(tabType: Int): Boolean { + return (binding.vidVp.currentItem == tabType) + } + + /** + * 设置选中 Tab + * @param tabType Int + */ + private fun setSelectTab(tabType: Int) { + // 滑动到指定 Tab + binding.vidVp.setCurrentItem(tabType, false) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/adapter/MainTabAdapter.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/adapter/MainTabAdapter.kt new file mode 100644 index 0000000000..e300e998f3 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/adapter/MainTabAdapter.kt @@ -0,0 +1,19 @@ +package afkt.demo.ui.adapter + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +class MainTabAdapter( + private val fragments: List, + activity: FragmentActivity +) : FragmentStateAdapter(activity) { + + override fun getItemCount(): Int { + return fragments.size + } + + override fun createFragment(position: Int): Fragment { + return fragments[position] + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ActivityMVVMFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ActivityMVVMFragment.kt new file mode 100644 index 0000000000..eca8314758 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ActivityMVVMFragment.kt @@ -0,0 +1,94 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentParentDataBinding +import afkt.demo.model.ActivityViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.mvvm.DevBaseMVVMFragment +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 Activity MVVM Fragment + * @author Ttt + */ +class ActivityMVVMFragment : + DevBaseMVVMFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent_data + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + initViewModel() + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.position = positionStr + + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag(LOG_TAG, "ActivityMVVMFragment => parentFragment: %s", parentFragment) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseMVVMFragment { + val fragment = ActivityMVVMFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "ActivityMVVMFragment_TAG" + } + + override fun initViewModel() { + viewModel = getActivityViewModel(ActivityViewModel::class.java)!! + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationMVVMFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationMVVMFragment.kt new file mode 100644 index 0000000000..404779e5aa --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationMVVMFragment.kt @@ -0,0 +1,97 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.base.BaseApplication +import afkt.demo.databinding.FragmentParentDataBinding +import afkt.demo.model.ApplicationViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.mvvm.DevBaseMVVMFragment +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 Application MVVM Fragment + * @author Ttt + */ +class ApplicationMVVMFragment : + DevBaseMVVMFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent_data + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + initViewModel() + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.position = positionStr + + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag(LOG_TAG, "ApplicationMVVMFragment => parentFragment: %s", parentFragment) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseMVVMFragment { + val fragment = ApplicationMVVMFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "ApplicationMVVMFragment_TAG" + } + + override fun initViewModel() { + viewModel = getAppViewModel( + BaseApplication.getApplication(), ApplicationViewModel::class.java + )!! + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationViewModelFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationViewModelFragment.kt new file mode 100644 index 0000000000..f41ef60114 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ApplicationViewModelFragment.kt @@ -0,0 +1,96 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.base.BaseApplication +import afkt.demo.databinding.FragmentParentBinding +import afkt.demo.model.ApplicationViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.viewbinding.DevBaseViewBindingFragment +import dev.base.utils.assist.DevBaseViewModelAssist +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 Application ViewModel Fragment + * @author Ttt + */ +class ApplicationViewModelFragment : DevBaseViewBindingFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.vidPositionTv.text = positionStr + + val viewModel = DevBaseViewModelAssist().getAppViewModel( + BaseApplication.getApplication(), ApplicationViewModel::class.java + )!! + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag( + LOG_TAG, + "ApplicationViewModelFragment => parentFragment: %s", + parentFragment + ) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseViewBindingFragment { + val fragment = ApplicationViewModelFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "ApplicationViewModelFragment_TAG" + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentMVVMFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentMVVMFragment.kt new file mode 100644 index 0000000000..8d95a08046 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentMVVMFragment.kt @@ -0,0 +1,94 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentParentDataBinding +import afkt.demo.model.FragmentViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.mvvm.DevBaseMVVMFragment +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 Fragment MVVM Fragment + * @author Ttt + */ +class FragmentMVVMFragment : + DevBaseMVVMFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent_data + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + initViewModel() + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.position = positionStr + + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag(LOG_TAG, "FragmentMVVMFragment => parentFragment: %s", parentFragment) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseMVVMFragment { + val fragment = FragmentMVVMFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "FragmentMVVMFragment_TAG" + } + + override fun initViewModel() { + viewModel = getFragmentViewModel(FragmentViewModel::class.java)!! + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentParentMVVMFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentParentMVVMFragment.kt new file mode 100644 index 0000000000..ed0835cf94 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/FragmentParentMVVMFragment.kt @@ -0,0 +1,102 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentParentDataBinding +import afkt.demo.model.FragmentViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.mvvm.DevBaseMVVMFragment +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 Parent Fragment MVVM Fragment + * @author Ttt + */ +class FragmentParentMVVMFragment : + DevBaseMVVMFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent_data + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + initViewModel() + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.position = positionStr + + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag( + LOG_TAG, + "FragmentParentMVVMFragment => parentFragment: %s", + parentFragment + ) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseMVVMFragment { + val fragment = FragmentParentMVVMFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "FragmentParentMVVMFragment_TAG" + } + + override fun initViewModel() { + viewModel = if (parentFragment == null) { + getFragmentViewModel(FragmentViewModel::class.java)!! + } else { + getFragmentViewModel(parentFragment, FragmentViewModel::class.java)!! + } + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ItemValueFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ItemValueFragment.kt new file mode 100644 index 0000000000..48d6752b6e --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ItemValueFragment.kt @@ -0,0 +1,49 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentItemValueBinding +import android.os.Bundle +import android.view.View +import dev.base.expand.content.DevBaseContentViewBindingFragment +import dev.utils.DevFinal +import dev.utils.common.ColorUtils + +/** + * detail: Item Value Fragment + * @author Ttt + */ +class ItemValueFragment : DevBaseContentViewBindingFragment() { + + override fun baseLayoutId(): Int { + return R.layout.fragment_item_value + } + + override fun baseLayoutView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + arguments?.let { + val value = it.getInt(DevFinal.STR.VALUE) + + binding.vidTv.text = "$value" + + binding.root.setBackgroundColor(ColorUtils.getRandomColor()) + } + } + + companion object { + fun get(value: Int): DevBaseContentViewBindingFragment { + val fragment = ItemValueFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.VALUE, value) + fragment.arguments = bundle + return fragment + } + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/MVVMUtilsFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/MVVMUtilsFragment.kt new file mode 100644 index 0000000000..f7026aaab9 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/MVVMUtilsFragment.kt @@ -0,0 +1,121 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentParentDataBinding +import afkt.demo.model.ActivityViewModel +import afkt.demo.utils.ViewModelTempUtils +import android.os.Bundle +import android.os.Handler +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.mvvm.DevBaseMVVMFragment +import dev.base.utils.ViewModelUtils +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.RandomUtils + +/** + * detail: 测试 MVVM Utils Fragment + * @author Ttt + */ +class MVVMUtilsFragment : + DevBaseMVVMFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent_data + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + initViewModel() + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.position = positionStr + + // 进行 ViewModel 绑定 + ViewModelTempUtils.observe(TAG + positionStr, this, viewModel) + // 临时改变值 + Handler().postDelayed({ + viewModel.number.value = RandomUtils.nextInt() + }, (position + 1) * 1000L + 2000L) + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag(LOG_TAG, "MVVMUtilsFragment => parentFragment: %s", parentFragment) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseMVVMFragment { + val fragment = MVVMUtilsFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "MVVMUtilsFragment_TAG" + } + + override fun initViewModel() { +// viewModel = ViewModelUtils.getActivityViewModel( +// viewModelAssist, +// activity, +// ActivityViewModel::class.java +// )!! + +// viewModel = ViewModelUtils.getActivityViewModel(activity, ActivityViewModel::class.java)!! + +// viewModel = if (parentFragment == null) { +// ViewModelUtils.getFragmentViewModel(this, ActivityViewModel::class.java)!! +// } else { +// ViewModelUtils.getFragmentViewModel(parentFragment, ActivityViewModel::class.java)!! +// } + + viewModel = if (parentFragment == null) { + ViewModelUtils.getFragmentViewModel( + viewModelAssist, + this, + ActivityViewModel::class.java + )!! + } else { + ViewModelUtils.getFragmentViewModel( + viewModelAssist, + parentFragment, + ActivityViewModel::class.java + )!! + } + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ParentFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ParentFragment.kt new file mode 100644 index 0000000000..63c3bb4359 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/ParentFragment.kt @@ -0,0 +1,76 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentParentBinding +import android.os.Bundle +import android.view.View +import androidx.fragment.app.FragmentManager +import dev.base.expand.viewbinding.DevBaseViewBindingFragment +import dev.utils.DevFinal +import dev.utils.LogPrintUtils + +/** + * detail: 测试 parentFragment + * @author Ttt + */ +class ParentFragment : DevBaseViewBindingFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_parent + } + + override fun baseContentView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + arguments?.let { + val position = it.getInt(DevFinal.STR.POSITION) + val max = it.getInt(DevFinal.STR.MAX) + + val positionStr = (position + 1).toString() + + // 设置索引文案 + binding.vidPositionTv.text = positionStr + // 判断是否达到最大值 + if (position >= max) return + + // 设置 Fragment + commit(childFragmentManager, binding.vidFl.id, position + 1, max) + } + + LogPrintUtils.dTag(LOG_TAG, "ParentFragment => parentFragment: %s", parentFragment) + } + + companion object { + fun get( + position: Int, + max: Int + ): DevBaseViewBindingFragment { + val fragment = ParentFragment() + val bundle = Bundle() + bundle.putInt(DevFinal.STR.POSITION, position) + bundle.putInt(DevFinal.STR.MAX, max) + fragment.arguments = bundle + return fragment + } + + fun commit( + manager: FragmentManager, + id: Int, + position: Int, + max: Int + ) { + val transaction = manager.beginTransaction() + transaction.add(id, get(position, max)) + transaction.commit() + } + + const val LOG_TAG = "ParentFragment_TAG" + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentFragment.kt new file mode 100644 index 0000000000..f3f8056ff7 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentFragment.kt @@ -0,0 +1,64 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentVdbContentBinding +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.TextView +import dev.base.expand.content.DevBaseContentViewDataBindingFragment +import dev.utils.LogPrintUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.TextViewUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +class VDBContentFragment : DevBaseContentViewDataBindingFragment() { + + override fun baseLayoutId(): Int { + return R.layout.fragment_vdb_content + } + + override fun baseLayoutView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + // 设置图片 + binding.image = ResourceUtils.getDrawable(R.mipmap.icon_launcher_round) + // 设置随机内容 + binding.content = ChineseUtils.randomWord(RandomUtils.getRandom(40, 80)) + + contentAssist.addTitleView( + QuickHelper.get(TextView(context)) + .setText("Fragment new TextView ( addTitleView )") + .setTextColors(ResourceUtils.getColor(R.color.black)) + .setWidthHeight( + ViewUtils.MATCH_PARENT, + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setGravity(Gravity.CENTER) + .setBold() + .setOnClick { view -> + showToast(TextViewUtils.getText(view)) + } + .getView() + ) + + LogPrintUtils.dTag( + ParentFragment.LOG_TAG, + "VDBContentFragment => parentFragment: %s", + parentFragment + ) + + // 嵌套处理 + ParentFragment.commit(childFragmentManager, binding.vidFl.id, 0, 4) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentVMImplFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentVMImplFragment.kt new file mode 100644 index 0000000000..13fd24766e --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBContentVMImplFragment.kt @@ -0,0 +1,69 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.databinding.FragmentVdbContentBinding +import android.os.Bundle +import android.view.Gravity +import android.view.View +import android.widget.TextView +import dev.base.expand.content.DevBaseContentMVVMFragment +import dev.base.expand.mvvm.MVVM +import dev.utils.LogPrintUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.TextViewUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +class VDBContentVMImplFragment : + DevBaseContentMVVMFragment() { + + override fun baseLayoutId(): Int { + return R.layout.fragment_vdb_content + } + + override fun baseLayoutView(): View? { + return null + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + + // 设置图片 + binding.image = ResourceUtils.getDrawable(R.mipmap.icon_launcher_round) + // 设置随机内容 + binding.content = ChineseUtils.randomWord(RandomUtils.getRandom(40, 80)) + + contentAssist.addTitleView( + QuickHelper.get(TextView(context)) + .setText("Fragment new TextView ( addTitleView )") + .setTextColors(ResourceUtils.getColor(R.color.black)) + .setWidthHeight( + ViewUtils.MATCH_PARENT, + ResourceUtils.getDimensionInt(R.dimen.dp_50) + ) + .setGravity(Gravity.CENTER) + .setBold() + .setOnClick { view -> + showToast(TextViewUtils.getText(view)) + } + .getView() + ) + + LogPrintUtils.dTag( + ParentFragment.LOG_TAG, + "VDBContentFragment => parentFragment: %s", + parentFragment + ) + + // 嵌套处理 + ParentFragment.commit(childFragmentManager, binding.vidFl.id, 0, 4) + } + + override fun initViewModel() { + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBFragment.kt b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBFragment.kt new file mode 100644 index 0000000000..efbb5cc9ca --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/ui/fragment/VDBFragment.kt @@ -0,0 +1,24 @@ +package afkt.demo.ui.fragment + +import afkt.demo.R +import afkt.demo.base.app.BaseFragment +import afkt.demo.databinding.FragmentVdbBinding +import dev.utils.app.ResourceUtils +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +class VDBFragment : BaseFragment() { + + override fun baseContentId(): Int { + return R.layout.fragment_vdb + } + + override fun initValue() { + super.initValue() + + // 设置图片 + binding.image = ResourceUtils.getDrawable(R.mipmap.icon_launcher_round) + // 设置随机内容 + binding.content = ChineseUtils.randomWord(RandomUtils.getRandom(40, 80)) + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/java/afkt/demo/utils/ViewModelTempUtils.kt b/application/DevBaseDemo/src/main/java/afkt/demo/utils/ViewModelTempUtils.kt new file mode 100644 index 0000000000..bdd27cef48 --- /dev/null +++ b/application/DevBaseDemo/src/main/java/afkt/demo/utils/ViewModelTempUtils.kt @@ -0,0 +1,83 @@ +package afkt.demo.utils + +import afkt.demo.model.ActivityViewModel +import afkt.demo.model.ApplicationViewModel +import afkt.demo.model.FragmentViewModel +import androidx.lifecycle.LifecycleOwner +import dev.expand.engine.log.log_dTag + +/** + * detail: ViewModel 临时工具类 + * @author Ttt + */ +object ViewModelTempUtils { + + // 日志 TAG + private val TAG = ViewModelTempUtils::class.java.simpleName + + /** + * 统一监听方法 + * @param tag TAG + * @param owner [LifecycleOwner] + * @param viewModel [ApplicationViewModel] + */ + fun observe( + tag: String, + owner: LifecycleOwner, + viewModel: ApplicationViewModel? + ) { + viewModel?.let { + // 进行监听 + viewModel.number.observe(owner) { + TAG.log_dTag( + message = "%s observe number: %s", + args = arrayOf(tag, viewModel.number.value) + ) + } + } + } + + /** + * 统一监听方法 + * @param tag TAG + * @param owner [LifecycleOwner] + * @param viewModel [ApplicationViewModel] + */ + fun observe( + tag: String, + owner: LifecycleOwner, + viewModel: ActivityViewModel? + ) { + viewModel?.let { + // 进行监听 + viewModel.number.observe(owner) { + TAG.log_dTag( + message = "%s observe number: %s", + args = arrayOf(tag, viewModel.number.value) + ) + } + } + } + + /** + * 统一监听方法 + * @param tag TAG + * @param owner [LifecycleOwner] + * @param viewModel [ApplicationViewModel] + */ + fun observe( + tag: String, + owner: LifecycleOwner, + viewModel: FragmentViewModel? + ) { + viewModel?.let { + // 进行监听 + viewModel.number.observe(owner) { + TAG.log_dTag( + message = "%s observe number: %s", + args = arrayOf(tag, viewModel.number.value) + ) + } + } + } +} \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main.xml b/application/DevBaseDemo/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..a04edbdae0 --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_activity_mvvm.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_activity_mvvm.xml new file mode 100644 index 0000000000..f296421e5d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_activity_mvvm.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_application_mvvm.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_application_mvvm.xml new file mode 100644 index 0000000000..f296421e5d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_application_mvvm.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_application_view_model.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_application_view_model.xml new file mode 100644 index 0000000000..f296421e5d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_application_view_model.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_content.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_content.xml new file mode 100644 index 0000000000..998a9c1b9b --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_content.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_content_vm_impl.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_content_vm_impl.xml new file mode 100644 index 0000000000..af735409f4 --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_content_vm_impl.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_fragment_mvvm.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_fragment_mvvm.xml new file mode 100644 index 0000000000..f296421e5d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_fragment_mvvm.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_mvvm_utils.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_mvvm_utils.xml new file mode 100644 index 0000000000..f296421e5d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_mvvm_utils.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_non_viewdata.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_non_viewdata.xml new file mode 100644 index 0000000000..cd5028a38a --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_non_viewdata.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/activity_main_viewpager.xml b/application/DevBaseDemo/src/main/res/layout/activity_main_viewpager.xml new file mode 100644 index 0000000000..196749bdb7 --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/activity_main_viewpager.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/fragment_item_value.xml b/application/DevBaseDemo/src/main/res/layout/fragment_item_value.xml new file mode 100644 index 0000000000..013848d77b --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/fragment_item_value.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/fragment_parent.xml b/application/DevBaseDemo/src/main/res/layout/fragment_parent.xml new file mode 100644 index 0000000000..a3523e975c --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/fragment_parent.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/fragment_parent_data.xml b/application/DevBaseDemo/src/main/res/layout/fragment_parent_data.xml new file mode 100644 index 0000000000..b3c0638b7a --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/fragment_parent_data.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/fragment_vdb.xml b/application/DevBaseDemo/src/main/res/layout/fragment_vdb.xml new file mode 100644 index 0000000000..abfdaebd1d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/fragment_vdb.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/fragment_vdb_content.xml b/application/DevBaseDemo/src/main/res/layout/fragment_vdb_content.xml new file mode 100644 index 0000000000..90a70a170b --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/fragment_vdb_content.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/layout/include_view_data.xml b/application/DevBaseDemo/src/main/res/layout/include_view_data.xml new file mode 100644 index 0000000000..b25bb9cd6a --- /dev/null +++ b/application/DevBaseDemo/src/main/res/layout/include_view_data.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher.png b/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher.png new file mode 100644 index 0000000000..233c84f1e8 Binary files /dev/null and b/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher.png differ diff --git a/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher_round.png b/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher_round.png new file mode 100644 index 0000000000..c9c406235f Binary files /dev/null and b/application/DevBaseDemo/src/main/res/mipmap-xxhdpi/icon_launcher_round.png differ diff --git a/application/DevBaseDemo/src/main/res/values/colors.xml b/application/DevBaseDemo/src/main/res/values/colors.xml new file mode 100644 index 0000000000..1b5173691d --- /dev/null +++ b/application/DevBaseDemo/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3f51b5 + #303f9f + #ff4081 + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/values/strings.xml b/application/DevBaseDemo/src/main/res/values/strings.xml new file mode 100644 index 0000000000..82ca433081 --- /dev/null +++ b/application/DevBaseDemo/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + DevBaseDemo + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/values/styles.xml b/application/DevBaseDemo/src/main/res/values/styles.xml new file mode 100644 index 0000000000..1199e7dadf --- /dev/null +++ b/application/DevBaseDemo/src/main/res/values/styles.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevBaseDemo/src/main/res/values/unified.xml b/application/DevBaseDemo/src/main/res/values/unified.xml new file mode 100644 index 0000000000..c0c2e35f92 --- /dev/null +++ b/application/DevBaseDemo/src/main/res/values/unified.xml @@ -0,0 +1,22 @@ + + + + + 0.5dp + 5.0dp + 8.0dp + 10.0dp + 15.0dp + 20.0dp + 50.0dp + 100.0dp + 150.0dp + 200.0dp + + + 14.0sp + 18.0sp + 20.0sp + 40.0sp + + \ No newline at end of file diff --git a/application/DevUtilsApp/.gitignore b/application/DevUtilsApp/.gitignore new file mode 100644 index 0000000000..84bf89b64f --- /dev/null +++ b/application/DevUtilsApp/.gitignore @@ -0,0 +1,2 @@ +/build +/schemas \ No newline at end of file diff --git a/application/DevUtilsApp/build.gradle b/application/DevUtilsApp/build.gradle new file mode 100644 index 0000000000..e53e57fd7c --- /dev/null +++ b/application/DevUtilsApp/build.gradle @@ -0,0 +1,56 @@ +apply from: rootProject.file(files.build_app_gradle) +apply from: rootProject.file(files.build_arouter_app_gradle) +apply from: rootProject.file(files.deps_other_lib) +apply from: rootProject.file(files.deps_qa_lib) + +android { + defaultConfig { + applicationId "afkt.project" + } + + sourceSets { + main { + // https://www.jianshu.com/p/f5a49d54e16e + // 优化 res 资源管理, 使用 sourceSets 资源分包 + res.srcDirs = [ + 'src/main/res', // 全局通用资源文件 + 'src/main/res-base', // Base 基础资源 + 'src/main/res-framework', // Framework 架构 + 'src/main/res-function', // 其他功能 + 'src/main/res-lib', // Lib 框架 + 'src/main/res-sku', // 商品 SKU + 'src/main/res-ui', // UI 效果 + 'src/main/res-ui-widget', // DevWidget UI 库 + ] + } + } +} + +dependencies { + implementation project(':DevApp') + implementation project(':DevAssist') + implementation project(':DevBase') + implementation project(':DevBaseMVVM') + implementation project(':DevMVVM') + implementation project(':DevEngine') + implementation project(':DevWidget') + + implementation project(':DevHttpCapture') + debugImplementation project(':DevHttpCaptureCompiler') + releaseImplementation project(':DevHttpCaptureCompilerRelease') + + implementation project(':DevHttpManager') + implementation project(':DevRetrofit') + + implementation project(':DevEnvironment') + kaptDebug project(':DevEnvironmentCompiler') // debugAnnotationProcessor + kaptRelease project(':DevEnvironmentCompilerRelease') // releaseAnnotationProcessor + + // ========= + // = 非 lib = + // ========= + + implementation project(':DevBaseView') + implementation project(':DevOther') + implementation project(':DevSKU') +} \ No newline at end of file diff --git a/application/DevUtilsApp/proguard-rules.pro b/application/DevUtilsApp/proguard-rules.pro new file mode 100644 index 0000000000..642d9a95e5 --- /dev/null +++ b/application/DevUtilsApp/proguard-rules.pro @@ -0,0 +1,24 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + + +-keep class afkt.project.databinding.** {*;} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/AndroidManifest.xml b/application/DevUtilsApp/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e7b51f3760 --- /dev/null +++ b/application/DevUtilsApp/src/main/AndroidManifest.xml @@ -0,0 +1,725 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/assets/filter/August.acv b/application/DevUtilsApp/src/main/assets/filter/August.acv new file mode 100644 index 0000000000..06ff629c3e Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/August.acv differ diff --git a/application/DevUtilsApp/src/main/assets/filter/Darker.acv b/application/DevUtilsApp/src/main/assets/filter/Darker.acv new file mode 100644 index 0000000000..1a384f49a1 Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/Darker.acv differ diff --git a/application/DevUtilsApp/src/main/assets/filter/Dream.acv b/application/DevUtilsApp/src/main/assets/filter/Dream.acv new file mode 100644 index 0000000000..a18f97cbba Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/Dream.acv differ diff --git a/application/DevUtilsApp/src/main/assets/filter/Fornature.acv b/application/DevUtilsApp/src/main/assets/filter/Fornature.acv new file mode 100644 index 0000000000..60b821ee8f Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/Fornature.acv differ diff --git a/application/DevUtilsApp/src/main/assets/filter/Greens.acv b/application/DevUtilsApp/src/main/assets/filter/Greens.acv new file mode 100644 index 0000000000..db5588131a Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/Greens.acv differ diff --git a/application/DevUtilsApp/src/main/assets/filter/Miami.acv b/application/DevUtilsApp/src/main/assets/filter/Miami.acv new file mode 100644 index 0000000000..cdd5bf87f3 Binary files /dev/null and b/application/DevUtilsApp/src/main/assets/filter/Miami.acv differ diff --git a/application/DevUtilsApp/src/main/assets/sku.json b/application/DevUtilsApp/src/main/assets/sku.json new file mode 100644 index 0000000000..6ba2943521 --- /dev/null +++ b/application/DevUtilsApp/src/main/assets/sku.json @@ -0,0 +1,424 @@ +{ + "specList": [ + { + "specId": 1045, + "specName": "黑色 500g 2", + "picUrl": "https://picsum.photos/300", + "inventory": 15, + "salePrice": 1.5, + "attrValueIdList": [ + 333, + 334, + 338 + ] + }, + { + "specId": 1046, + "specName": "白色 500g 2", + "picUrl": "https://picsum.photos/301", + "inventory": 16, + "salePrice": 2.5, + "attrValueIdList": [ + 332, + 334, + 338 + ] + }, + { + "specId": 1047, + "specName": "红色 500g 2", + "picUrl": "https://picsum.photos/302", + "inventory": 17, + "salePrice": 3.5, + "attrValueIdList": [ + 335, + 334, + 338 + ] + }, + { + "specId": 1048, + "specName": "黑色 800g 2", + "picUrl": "https://picsum.photos/303", + "inventory": 18, + "salePrice": 4.5, + "attrValueIdList": [ + 333, + 343, + 338 + ] + }, + { + "specId": 1049, + "specName": "白色 800g 2", + "picUrl": "https://picsum.photos/304", + "inventory": 19, + "salePrice": 5.5, + "attrValueIdList": [ + 332, + 343, + 338 + ] + }, + { + "specId": 1050, + "specName": "红色 800g 2", + "picUrl": "https://picsum.photos/305", + "inventory": 20, + "salePrice": 6.5, + "attrValueIdList": [ + 335, + 343, + 338 + ] + }, + { + "specId": 1051, + "specName": "黑色 500g 3", + "picUrl": "https://picsum.photos/306", + "inventory": 21, + "salePrice": 7.5, + "attrValueIdList": [ + 333, + 334, + 339 + ] + }, + { + "specId": 1052, + "specName": "白色 500g 3", + "picUrl": "https://picsum.photos/307", + "inventory": 22, + "salePrice": 8.5, + "attrValueIdList": [ + 332, + 334, + 339 + ] + }, + { + "specId": 1053, + "specName": "红色 500g 3", + "picUrl": "https://picsum.photos/308", + "inventory": 23, + "salePrice": 9.5, + "attrValueIdList": [ + 335, + 334, + 339 + ] + }, + { + "specId": 1054, + "specName": "黑色 800g 3", + "picUrl": "https://picsum.photos/309", + "inventory": 24, + "salePrice": 10.5, + "attrValueIdList": [ + 333, + 343, + 339 + ] + }, + { + "specId": 1055, + "specName": "白色 800g 3", + "picUrl": "https://picsum.photos/310", + "inventory": 25, + "salePrice": 11.5, + "attrValueIdList": [ + 332, + 343, + 339 + ] + }, + { + "specId": 1056, + "specName": "红色 800g 3", + "picUrl": "https://picsum.photos/311", + "inventory": 26, + "salePrice": 12.5, + "attrValueIdList": [ + 335, + 343, + 339 + ] + }, + { + "specId": 1057, + "specName": "黑色 500g 4", + "picUrl": "https://picsum.photos/312", + "inventory": 27, + "salePrice": 13.5, + "attrValueIdList": [ + 333, + 334, + 340 + ] + }, + { + "specId": 1058, + "specName": "白色 500g 4", + "picUrl": "https://picsum.photos/313", + "inventory": 28, + "salePrice": 14.5, + "attrValueIdList": [ + 332, + 334, + 340 + ] + }, + { + "specId": 1059, + "specName": "红色 500g 4", + "picUrl": "https://picsum.photos/314", + "inventory": 29, + "salePrice": 15.5, + "attrValueIdList": [ + 335, + 334, + 340 + ] + }, + { + "specId": 1060, + "specName": "黑色 800g 4", + "picUrl": "https://picsum.photos/315", + "inventory": 30, + "salePrice": 16.5, + "attrValueIdList": [ + 333, + 343, + 340 + ] + }, + { + "specId": 1061, + "specName": "白色 800g 4", + "picUrl": "https://picsum.photos/316", + "inventory": 31, + "salePrice": 17.5, + "attrValueIdList": [ + 332, + 343, + 340 + ] + }, + { + "specId": 1062, + "specName": "红色 800g 4", + "picUrl": "https://picsum.photos/317", + "inventory": 32, + "salePrice": 18.5, + "attrValueIdList": [ + 335, + 343, + 340 + ] + }, + { + "specId": 1063, + "specName": "黑色 500g 5", + "picUrl": "https://picsum.photos/318", + "inventory": 33, + "salePrice": 19.5, + "attrValueIdList": [ + 333, + 334, + 341 + ] + }, + { + "specId": 1064, + "specName": "白色 500g 5", + "picUrl": "https://picsum.photos/319", + "inventory": 34, + "salePrice": 20.5, + "attrValueIdList": [ + 332, + 334, + 341 + ] + }, + { + "specId": 1065, + "specName": "红色 500g 5", + "picUrl": "https://picsum.photos/320", + "inventory": 35, + "salePrice": 21.5, + "attrValueIdList": [ + 335, + 334, + 341 + ] + }, + { + "specId": 1066, + "specName": "黑色 800g 5", + "picUrl": "https://picsum.photos/321", + "inventory": 36, + "salePrice": 22.5, + "attrValueIdList": [ + 333, + 343, + 341 + ] + }, + { + "specId": 1067, + "specName": "白色 800g 5", + "picUrl": "https://picsum.photos/322", + "inventory": 37, + "salePrice": 23.5, + "attrValueIdList": [ + 332, + 343, + 341 + ] + }, + { + "specId": 1068, + "specName": "红色 800g 5", + "picUrl": "https://picsum.photos/323", + "inventory": 38, + "salePrice": 24.5, + "attrValueIdList": [ + 335, + 343, + 341 + ] + }, + { + "specId": 1069, + "specName": "黑色 500g 6", + "picUrl": "https://picsum.photos/324", + "inventory": 39, + "salePrice": 25.5, + "attrValueIdList": [ + 333, + 334, + 342 + ] + }, + { + "specId": 1070, + "specName": "白色 500g 6", + "picUrl": "https://picsum.photos/325", + "inventory": 40, + "salePrice": 26.5, + "attrValueIdList": [ + 332, + 334, + 342 + ] + }, + { + "specId": 1071, + "specName": "红色 500g 6", + "picUrl": "https://picsum.photos/326", + "inventory": 41, + "salePrice": 27.5, + "attrValueIdList": [ + 335, + 334, + 342 + ] + }, + { + "specId": 1072, + "specName": "黑色 800g 6", + "picUrl": "https://picsum.photos/327", + "inventory": 42, + "salePrice": 28.5, + "attrValueIdList": [ + 333, + 343, + 342 + ] + }, + { + "specId": 1073, + "specName": "白色 800g 6", + "picUrl": "https://picsum.photos/328", + "inventory": 43, + "salePrice": 29.5, + "attrValueIdList": [ + 332, + 343, + 342 + ] + }, + { + "specId": 1074, + "specName": "红色 800g 6", + "picUrl": "https://picsum.photos/329", + "inventory": 44, + "salePrice": 30.5, + "attrValueIdList": [ + 335, + 343, + 342 + ] + } + ], + "attributeList": [ + { + "id": 165, + "attrName": "颜色", + "attrValueList": [ + { + "id": 332, + "attrValue": "白色" + }, + { + "id": 333, + "attrValue": "黑色" + }, + { + "id": 335, + "attrValue": "红色" + } + ] + }, + { + "id": 166, + "attrName": "大小", + "attrValueList": [ + { + "id": 334, + "attrValue": "500g" + }, + { + "id": 343, + "attrValue": "800g" + } + ] + }, + { + "id": 168, + "attrName": "重量", + "attrValueList": [ + { + "id": 338, + "attrValue": "2" + }, + { + "id": 339, + "attrValue": "3" + }, + { + "id": 340, + "attrValue": "4" + }, + { + "id": 341, + "attrValue": "5" + }, + { + "id": 342, + "attrValue": "6" + } + ] + } + ] +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/assets/sku_test.json b/application/DevUtilsApp/src/main/assets/sku_test.json new file mode 100644 index 0000000000..10c802aa58 --- /dev/null +++ b/application/DevUtilsApp/src/main/assets/sku_test.json @@ -0,0 +1,340 @@ +{ + "specList": [ + { + "specId": 1045, + "specName": "黑色 500g 2", + "picUrl": "https://picsum.photos/300", + "inventory": 15, + "salePrice": 1.5, + "attrValueIdList": [ + 333, + 334, + 338 + ] + }, + { + "specId": 1046, + "specName": "白色 500g 2", + "picUrl": "https://picsum.photos/301", + "inventory": 16, + "salePrice": 2.5, + "attrValueIdList": [ + 332, + 334, + 338 + ] + }, + { + "specId": 1047, + "specName": "红色 500g 2", + "picUrl": "https://picsum.photos/302", + "inventory": 17, + "salePrice": 3.5, + "attrValueIdList": [ + 335, + 334, + 338 + ] + }, + { + "specId": 1049, + "specName": "白色 800g 2", + "picUrl": "https://picsum.photos/304", + "inventory": 19, + "salePrice": 5.5, + "attrValueIdList": [ + 332, + 343, + 338 + ] + }, + { + "specId": 1051, + "specName": "黑色 500g 3", + "picUrl": "https://picsum.photos/306", + "inventory": 21, + "salePrice": 7.5, + "attrValueIdList": [ + 333, + 334, + 339 + ] + }, + { + "specId": 1052, + "specName": "白色 500g 3", + "picUrl": "https://picsum.photos/307", + "inventory": 22, + "salePrice": 8.5, + "attrValueIdList": [ + 332, + 334, + 339 + ] + }, + { + "specId": 1053, + "specName": "红色 500g 3", + "picUrl": "https://picsum.photos/308", + "inventory": 23, + "salePrice": 9.5, + "attrValueIdList": [ + 335, + 334, + 339 + ] + }, + { + "specId": 1057, + "specName": "黑色 500g 4", + "picUrl": "https://picsum.photos/312", + "inventory": 27, + "salePrice": 13.5, + "attrValueIdList": [ + 333, + 334, + 340 + ] + }, + { + "specId": 1058, + "specName": "白色 500g 4", + "picUrl": "https://picsum.photos/313", + "inventory": 28, + "salePrice": 14.5, + "attrValueIdList": [ + 332, + 334, + 340 + ] + }, + { + "specId": 1059, + "specName": "红色 500g 4", + "picUrl": "https://picsum.photos/314", + "inventory": 29, + "salePrice": 15.5, + "attrValueIdList": [ + 335, + 334, + 340 + ] + }, + { + "specId": 1060, + "specName": "黑色 800g 4", + "picUrl": "https://picsum.photos/315", + "inventory": 30, + "salePrice": 16.5, + "attrValueIdList": [ + 333, + 343, + 340 + ] + }, + { + "specId": 1061, + "specName": "白色 800g 4", + "picUrl": "https://picsum.photos/316", + "inventory": 31, + "salePrice": 17.5, + "attrValueIdList": [ + 332, + 343, + 340 + ] + }, + { + "specId": 1062, + "specName": "红色 800g 4", + "picUrl": "https://picsum.photos/317", + "inventory": 32, + "salePrice": 18.5, + "attrValueIdList": [ + 335, + 343, + 340 + ] + }, + { + "specId": 1063, + "specName": "黑色 500g 5", + "picUrl": "https://picsum.photos/318", + "inventory": 33, + "salePrice": 19.5, + "attrValueIdList": [ + 333, + 334, + 341 + ] + }, + { + "specId": 1064, + "specName": "白色 500g 5", + "picUrl": "https://picsum.photos/319", + "inventory": 34, + "salePrice": 20.5, + "attrValueIdList": [ + 332, + 334, + 341 + ] + }, + { + "specId": 1065, + "specName": "红色 500g 5", + "picUrl": "https://picsum.photos/320", + "inventory": 35, + "salePrice": 21.5, + "attrValueIdList": [ + 335, + 334, + 341 + ] + }, + { + "specId": 1066, + "specName": "黑色 800g 5", + "picUrl": "https://picsum.photos/321", + "inventory": 36, + "salePrice": 22.5, + "attrValueIdList": [ + 333, + 343, + 341 + ] + }, + { + "specId": 1067, + "specName": "白色 800g 5", + "picUrl": "https://picsum.photos/322", + "inventory": 37, + "salePrice": 23.5, + "attrValueIdList": [ + 332, + 343, + 341 + ] + }, + { + "specId": 1068, + "specName": "红色 800g 5", + "picUrl": "https://picsum.photos/323", + "inventory": 38, + "salePrice": 24.5, + "attrValueIdList": [ + 335, + 343, + 341 + ] + }, + { + "specId": 1069, + "specName": "黑色 500g 6", + "picUrl": "https://picsum.photos/324", + "inventory": 39, + "salePrice": 25.5, + "attrValueIdList": [ + 333, + 334, + 342 + ] + }, + { + "specId": 1070, + "specName": "白色 500g 6", + "picUrl": "https://picsum.photos/325", + "inventory": 40, + "salePrice": 26.5, + "attrValueIdList": [ + 332, + 334, + 342 + ] + }, + { + "specId": 1071, + "specName": "红色 500g 6", + "picUrl": "https://picsum.photos/326", + "inventory": 41, + "salePrice": 27.5, + "attrValueIdList": [ + 335, + 334, + 342 + ] + }, + { + "specId": 1072, + "specName": "黑色 800g 6", + "picUrl": "https://picsum.photos/327", + "inventory": 42, + "salePrice": 28.5, + "attrValueIdList": [ + 333, + 343, + 342 + ] + } + ], + "attributeList": [ + { + "id": 165, + "attrName": "颜色", + "attrValueList": [ + { + "id": 332, + "attrValue": "白色" + }, + { + "id": 333, + "attrValue": "黑色" + }, + { + "id": 335, + "attrValue": "红色" + } + ] + }, + { + "id": 166, + "attrName": "大小", + "attrValueList": [ + { + "id": 334, + "attrValue": "500g" + }, + { + "id": 343, + "attrValue": "800g" + } + ] + }, + { + "id": 168, + "attrName": "重量", + "attrValueList": [ + { + "id": 338, + "attrValue": "2" + }, + { + "id": 339, + "attrValue": "3" + }, + { + "id": 340, + "attrValue": "4" + }, + { + "id": 341, + "attrValue": "5" + }, + { + "id": 342, + "attrValue": "6" + } + ] + } + ] +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/MainActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/MainActivity.kt new file mode 100644 index 0000000000..26c6eebb66 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/MainActivity.kt @@ -0,0 +1,148 @@ +package afkt.project + +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityMainBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import android.Manifest +import dev.callback.DevItemClickCallback +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.log.log_dTag +import dev.expand.engine.log.log_eTag +import dev.expand.engine.permission.permission_againRequest +import dev.expand.engine.permission.permission_request +import dev.utils.app.VersionUtils +import dev.utils.app.toast.ToastUtils +import dev.utils.common.HttpURLConnectionUtils +import kotlin.math.abs + +class MainActivity : BaseActivity() { + + override fun isToolBar(): Boolean = false + + override fun baseLayoutId(): Int = R.layout.activity_main + + override fun initOther() { + super.initOther() + + // ========== + // = 时间校验 = + // ========== + + HttpURLConnectionUtils.getNetTime(object : HttpURLConnectionUtils.TimeCallback { + override fun onResponse(millis: Long) { + val curTime = System.currentTimeMillis() + if (millis >= 1) { + val diffTime = abs(curTime - millis) + // 判断是否误差超过 10 秒 + if (diffTime >= 10000L) { + ToastUtils.showShort("当前时间与网络时间不一致, 误差: ${diffTime / 1000} 秒") + } + } + } + + override fun onFail(error: Throwable) { + TAG.log_eTag( + throwable = error, + message = "getNetTime" + ) + } + }) + + // ========== + // = 申请权限 = + // ========== + + // 方式一 + permission_request( + permissions = arrayOf( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + TAG.log_dTag( + message = "permission granted" + ) + } + + override fun onDenied( + grantedList: MutableList, + deniedList: MutableList, + notFoundList: MutableList + ) { + TAG.log_dTag( + message = StringBuilder().apply { + append("permission") + append("\ngrantedList: ") + append(grantedList.toTypedArray().contentToString()) + append("\ndeniedList: ") + append(deniedList.toTypedArray().contentToString()) + append("\nnotFoundList: ") + append(notFoundList.toTypedArray().contentToString()) + }.toString() + ) + // 拒绝了则再次请求处理 + permission_againRequest( + callback = this, + deniedList = deniedList + ) + ToastUtils.showShort("请开启读写手机存储权限.") + } + } + ) + +// // 方式二 +// DevEngine.getPermission()?.request( +// this, arrayOf( +// Manifest.permission.READ_EXTERNAL_STORAGE, +// Manifest.permission.WRITE_EXTERNAL_STORAGE +// ), object : IPermissionEngine.Callback { +// override fun onGranted() { +// TAG.log_dTag( +// message = "permission granted" +// ) +// } +// +// override fun onDenied( +// grantedList: MutableList, +// deniedList: MutableList, +// notFoundList: MutableList +// ) { +// TAG.log_dTag( +// message = StringBuilder().apply { +// append("permission") +// append("\ngrantedList: ") +// append(grantedList.toTypedArray().contentToString()) +// append("\ndeniedList: ") +// append(deniedList.toTypedArray().contentToString()) +// append("\nnotFoundList: ") +// append(notFoundList.toTypedArray().contentToString()) +// }.toString() +// ) +// // 拒绝了则再次请求处理 +// DevEngine.getPermission() +// .againRequest(this@MainActivity, this, deniedList) +// ToastUtils.showShort("请开启读写手机存储权限.") +// } +// } +// ) + } + + override fun initValue() { + super.initValue() + // 设置 Android 版本信息 + binding.vidAndroidTv.text = VersionUtils.convertSDKVersion() + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.mainButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + routerActivity(buttonValue) + } + }).bindAdapter(binding.vidInclude.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/AppConfig.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/AppConfig.kt new file mode 100644 index 0000000000..f643cf7177 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/AppConfig.kt @@ -0,0 +1,14 @@ +package afkt.project.base + +/** + * detail: App 配置 + * @author Ttt + */ +object AppConfig { + + // 项目名 + const val BASE_NAME = "DevUtils" + + // 项目日志 TAG + const val LOG_TAG = BASE_NAME + "_Log" +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/BaseApplication.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/BaseApplication.kt new file mode 100644 index 0000000000..1e45d306d8 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/BaseApplication.kt @@ -0,0 +1,310 @@ +package afkt.project.base + +import afkt.project.R +import afkt.project.base.http.RetrofitManagerUse +import afkt.project.utils.initAppImageConfigCreator +import android.content.Context +import android.net.Uri +import android.os.Build +import android.util.Log +import android.view.View +import android.webkit.WebSettings +import androidx.multidex.MultiDexApplication +import com.alibaba.android.arouter.launcher.ARouter +import dev.DevAssist +import dev.DevHttpCapture +import dev.DevUtils +import dev.assist.WebViewAssist +import dev.base.DevBase +import dev.base.DevBaseMVVM +import dev.engine.DevEngine +import dev.environment.DevEnvironment +import dev.environment.DevEnvironmentActivity +import dev.expand.engine.log.log_d +import dev.expand.engine.log.log_i +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.app.* +import dev.utils.app.CrashUtils.CrashCatchListener +import dev.utils.app.logger.DevLogger +import dev.utils.app.logger.LogConfig +import dev.utils.app.logger.LogLevel +import dev.utils.common.DateUtils +import dev.utils.common.StringUtils +import dev.utils.common.assist.TimeCounter +import dev.widget.DevWidget +import dev.widget.assist.ViewAssist +import dev.widget.function.StateLayout +import me.jessyan.autosize.AutoSizeConfig + +/** + * detail: Base Application + * @author Ttt + */ +class BaseApplication : MultiDexApplication() { + + override fun onCreate() { + super.onCreate() + + // 初始化计时器 + val timeCounter = TimeCounter() + + ARouter.openLog() + ARouter.openDebug() + // 打印日志的时候打印线程堆栈 + ARouter.printStackTrace() + // 尽可能早, 推荐在 Application 中初始化 + ARouter.init(this) + + // ============ + // = DevUtils = + // ============ + + // 初始化工具类 - 可不调用, 在 DevUtils FileProviderDevApp 中已初始化, 无需主动调用 + DevUtils.init(this.applicationContext) + // 初始化日志配置 + DevLogger.initialize( + LogConfig().logLevel(LogLevel.DEBUG) + .tag(AppConfig.LOG_TAG) + .sortLog(true) // 美化日志, 边框包围 + .methodCount(0) + ) + // 打开 lib 内部日志 - 线上环境, 不调用方法 + DevUtils.openLog() + DevUtils.openDebug() + + // 可进行日志拦截编码 + // DevLogger.setPrint(new DevLogger.Print()) + // JCLogUtils.setPrint(new JCLogUtils.Print()) + LogPrintUtils.setPrint(LogPrintUtils.Print { logType, tag, msg -> + if (msg == null) return@Print + // 进行编码处理 + val message = StringUtils.strEncode(msg, DevFinal.ENCODE.UTF_8) + when (logType) { + Log.VERBOSE -> Log.v(tag, message) + Log.DEBUG -> Log.d(tag, message) + Log.INFO -> Log.i(tag, message) + Log.WARN -> Log.w(tag, message) + Log.ERROR -> Log.e(tag, message) + Log.ASSERT -> Log.wtf(tag, message) + else -> Log.wtf(tag, message) + } + }) + + // ============ + // = 初始化操作 = + // ============ + + // 初始化 + initialize() + + // 属于 Debug 才打印信息 + if (isDebug()) printProInfo(timeCounter) + } + + /** + * 打印项目信息 + * @param timeCounter [TimeCounter] + */ + private fun printProInfo(timeCounter: TimeCounter) { + val builder = StringBuilder() + .append("项目名: ") + .append(ResourceUtils.getString(R.string.str_app_name)) + .append("\nSDK: ").append(Build.VERSION.SDK_INT).append("(") + .append(VersionUtils.convertSDKVersion(Build.VERSION.SDK_INT)).append(")") + .append("\nPackageName: ").append(AppUtils.getPackageName()) + .append("\nVersionCode: ").append(AppUtils.getAppVersionCode()) + .append("\nVersionName: ").append(AppUtils.getAppVersionName()) + .append("\nDevUtils 版本: ").append(DevUtils.getDevAppVersion()) + .append("\nDevAssist 版本: ").append(DevAssist.getDevAssistVersion()) + .append("\nDevBase 版本: ").append(DevBase.getDevBaseVersion()) + .append("\nDevBaseMVVM 版本: ").append(DevBaseMVVM.getDevBaseMVVMVersion()) + .append("\nDevHttpCapture 版本: ").append(DevHttpCapture.getDevHttpCaptureVersion()) + .append("\nDevJava 版本: ").append(DevUtils.getDevJavaVersion()) + .append("\nDevWidget 版本: ").append(DevWidget.getDevWidgetVersion()) + .append("\nDevEnvironment 版本: ") + .append(DevEnvironmentActivity.getDevEnvironmentVersion()) + .append("\n时间: ").append(DateUtils.getDateNow()) + .append("\n初始化耗时(毫秒): ").append(timeCounter.duration()) + log_i(message = builder.toString()) + } + + // ============ + // = 初始化方法 = + // ============ + + /** + * 统一初始化方法 + */ + private fun initialize() { + // 初始化引擎 + initEngine() + // 初始化状态布局配置 + initStateLayout() + // 初始化异常捕获处理 + initCrash() + // 初始化 WebView 辅助类全局配置 + initWebViewBuilder() + // 初始化其他 lib + initOther() + } + + /** + * 初始化引擎 + */ + private fun initEngine() { + // DevEngine 完整初始化 + DevEngine.completeInitialize(this) + } + + /** + * 初始化状态布局配置 + */ + private fun initStateLayout() { + val global = StateLayout.Global(object : StateLayout.Listener { + override fun onRemove( + layout: StateLayout, + type: Int, + removeView: Boolean + ) { + if (removeView) layout.gone() + } + + override fun onNotFound( + layout: StateLayout, + type: Int + ) { + layout.gone() + } + + override fun onChange( + layout: StateLayout, + type: Int, + oldType: Int, + view: View + ) { + if (type == ViewAssist.TYPE_EMPTY_DATA) { // NO_DATA + val vidTipsTv = ViewUtils.findViewById(view, R.id.vid_tips_tv) + TextViewUtils.setText(vidTipsTv, "暂无数据") + } else if (type == ViewAssist.TYPE_FAILED) { // FAIL + val vidTipsTv = ViewUtils.findViewById(view, R.id.vid_tips_tv) + TextViewUtils.setText(vidTipsTv, "请求失败, 请稍后重试!") + } + } + }) + .register(ViewAssist.TYPE_ING, R.layout.state_layout_ing) + .register(ViewAssist.TYPE_FAILED, R.layout.state_layout_fail) + .register(ViewAssist.TYPE_EMPTY_DATA, R.layout.state_layout_no_data) + // 设置全局配置 + StateLayout.setGlobal(global) + } + + /** + * 初始化异常捕获处理 + */ + private fun initCrash() { + // 捕获异常处理 => 在 BaseApplication 中调用 + CrashUtils.getInstance().initialize(applicationContext, object : CrashCatchListener { + override fun handleException(ex: Throwable) { + // 保存日志信息 + } + + override fun uncaughtException( + context: Context, + thread: Thread, + ex: Throwable + ) { +// // 退出 JVM (Java 虚拟机 ) 释放所占内存资源, 0 表示正常退出、非 0 的都为异常退出 +// System.exit(-1) +// // 从操作系统中结束掉当前程序的进程 +// android.os.Process.killProcess(android.os.Process.myPid()) + // 关闭 APP + ActivityUtils.getManager().exitApplication() + // 可开启定时任务, 延迟几秒启动 APP + } + }) + } + + /** + * 初始化 WebView 辅助类全局配置 + */ + private fun initWebViewBuilder() { + WebViewAssist.Builder().apply { + // 显示内置缩放工具 + setBuiltInZoomControls(false) + // 显示缩放工具 + setDisplayZoomControls(false) + // Application Caches 地址 + setAppCachePath(PathUtils.getInternal().getAppCachePath("cache")) + // 数据库缓存路径 + setDatabasePath(PathUtils.getInternal().getAppCachePath("db")) + // 渲染优先级高 + setRenderPriority(WebSettings.RenderPriority.HIGH) + // 基础布局算法 + if (VersionUtils.isLollipop()) { + setLayoutAlgorithm(WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING) + } else { + setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN) + } + // 应用配置监听事件 + setOnApplyListener(object : WebViewAssist.Builder.OnApplyListener { + override fun onApply( + webViewAssist: WebViewAssist?, + builder: WebViewAssist.Builder + ) { + log_d(message = "WebViewAssist Builder onApply") + } + }) + // WebViewAssist 构造函数会使用全局配置 + WebViewAssist.setGlobalBuilder(this) + } + } + + /** + * 初始化其他 lib + */ + private fun initOther() { + // https://github.com/JessYanCoding/AndroidAutoSize/blob/master/demo-subunits/src/main/java/me/jessyan/autosize/demo/subunits/BaseApplication.java + // 可不调用, 默认开启 DP 转换 + AutoSizeConfig.getInstance().unitsManager.isSupportDP = true + + // 环境 ( 服务器地址 ) 改变通知 + DevEnvironment.addOnEnvironmentChangeListener { _, _, _ -> + // 改变地址重新初始化 + RetrofitManagerUse.operation().reset() + } + + // 截图监听 + ScreenshotUtils.getInstance() + .setListener { contentUri: Uri?, selfChange: Boolean, rowId: Long, dataPath: String?, dateTaken: Long -> + val builder = StringBuilder() + .append("截图监听回调") + .append(DevFinal.SYMBOL.NEW_LINE) + .append("contentUri: ").append(contentUri) + .append(DevFinal.SYMBOL.NEW_LINE) + .append("selfChange: ").append(selfChange) + .append(DevFinal.SYMBOL.NEW_LINE) + .append("rowId: ").append(rowId) + .append(DevFinal.SYMBOL.NEW_LINE) + .append("dataPath: ").append(dataPath) + .append(DevFinal.SYMBOL.NEW_LINE) + .append("dateTaken: ").append(dateTaken).append(" ( ") + .append(DateUtils.formatTime(dateTaken)).append(" )") + log_d(message = builder.toString()) + }.startListener() + + // 初始化 App ImageConfig 创建器 + initAppImageConfigCreator() + } + + companion object { + + /** + * 是否 Debug 模式 + * @return `true` yes, `false` no + */ + fun isDebug(): Boolean { + return DevUtils.isDebug() + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseActivity.kt new file mode 100644 index 0000000000..6594350212 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseActivity.kt @@ -0,0 +1,119 @@ +package afkt.project.base.app + +import afkt.project.feature.other_function.floating.Utils2 +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.viewbinding.ViewBinding +import dev.base.expand.mvp.MVP +import dev.utils.app.assist.floating.IFloatingActivity + +/** + * detail: Base ViewBinding 基类 + * @author Ttt + */ +abstract class BaseActivity : + BaseMVPActivity, VB>(), + IFloatingActivity { + + override fun createPresenter(): MVP.Presenter { + return MVP.Presenter(MVP.ViewImpl()) + } + + // ===================== + // = IFloatingActivity = + // ===================== + + // ======== + // = 方式一 = + // ======== + + val floatingLifecycle: FloatingLifecycle by lazy { + FloatingLifecycle(this) + } + + // ======== + // = 方式二 = + // ======== + + override fun getAttachActivity(): Activity { + return this + } + + override fun getMapFloatingKey(): String { + return this.toString() + } + + override fun getMapFloatingView(): View { + return Utils2.instance.createFloatingView(this) + } + + override fun getMapFloatingViewLayoutParams(): ViewGroup.LayoutParams { + return Utils2.instance.createLayoutParams(this) + } + + // = + + override fun onResume() { + super.onResume() + // 添加悬浮窗 View + Utils2.instance.addFloatingView(this) + } + + override fun onDestroy() { + super.onDestroy() + // 移除悬浮窗 View + Utils2.instance.removeFloatingView(this) + } +} + +// ======== +// = 方式一 = +// ======== + +class FloatingLifecycle(val activity: AppCompatActivity) : DefaultLifecycleObserver, + IFloatingActivity { + + init { + activity.lifecycle.addObserver(this) + } + + // ===================== + // = IFloatingActivity = + // ===================== + + override fun getAttachActivity(): Activity { + return activity + } + + override fun getMapFloatingKey(): String { + return this.toString() + } + + override fun getMapFloatingView(): View { + return Utils2.instance.createFloatingView(this) + } + + override fun getMapFloatingViewLayoutParams(): ViewGroup.LayoutParams { + return Utils2.instance.createLayoutParams(this) + } + + // ============================ + // = DefaultLifecycleObserver = + // ============================ + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + // 添加悬浮窗 View + Utils2.instance.addFloatingView(this) + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + // 移除悬浮窗 View + Utils2.instance.removeFloatingView(this) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseFragment.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseFragment.kt new file mode 100644 index 0000000000..420d8fe0c3 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseFragment.kt @@ -0,0 +1,29 @@ +package afkt.project.base.app + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.expand.viewbinding.DevBaseViewBindingFragment + +/** + * detail: Base ViewBinding 基类 + * @author Ttt + */ +abstract class BaseFragment : + DevBaseViewBindingFragment() { + + override fun baseContentView(): View? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + // 初始化顺序 ( 按顺序调用方法 ) + initOrder() + return mContentView + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVPActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVPActivity.kt new file mode 100644 index 0000000000..38620900b9 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVPActivity.kt @@ -0,0 +1,267 @@ +package afkt.project.base.app + +import afkt.project.R +import afkt.project.model.item.ButtonValue +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.LinearLayout +import androidx.appcompat.widget.Toolbar +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver +import androidx.viewbinding.ViewBinding +import com.alibaba.android.arouter.facade.annotation.Autowired +import com.alibaba.android.arouter.launcher.ARouter +import dev.base.expand.content.DevBaseContentMVPViewBindingActivity +import dev.base.expand.mvp.MVP +import dev.utils.DevFinal +import dev.utils.app.AppUtils +import dev.utils.app.ViewUtils +import dev.utils.app.toast.ToastTintUtils +import dev.widget.function.StateLayout + +/** + * detail: Base MVP ViewBinding 基类 + * @author Ttt + */ +abstract class BaseMVPActivity

, VB : ViewBinding> : + DevBaseContentMVPViewBindingActivity() { + + @JvmField // Context + protected var mContext: Context? = null + + @JvmField // Activity + protected var mActivity: Activity? = null + + // 状态布局 + lateinit var stateLayout: StateLayout + + // ToolBar + var toolbar: Toolbar? = null + + @JvmField + @Autowired(name = DevFinal.STR.TITLE) + var moduleTitle: String? = null + + @JvmField + @Autowired(name = DevFinal.STR.TYPE) + var moduleType: Int = 0 + + override fun baseLayoutView(): View? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 获取 Context、Activity + mContext = this + mActivity = this + // 内部初始化 + innerInitialize() + // 是否需要 ToolBar + if (isToolBar()) { + // 初始化 ToolBar + initToolBar() + // 设置 ToolBar 标题 + toolbar?.title = moduleTitle + } + // 插入 StateLayout + insertStateLayout() + // 初始化顺序 ( 按顺序调用方法 ) + initOrder() + } + + // =============== + // = StateLayout = + // =============== + + /** + * 插入 State Layout + */ + private fun insertStateLayout() { + stateLayout = StateLayout(this) + // 添加 View + contentAssist.addStateView( + stateLayout, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + ) + } + + // ========= + // = Toast = + // ========= + + /** + * 显示 Toast + * @param success 是否成功样式 + */ + fun showToast(success: Boolean) { + showToast(success, "操作成功", "操作失败") + } + + /** + * 显示 Toast + * @param success 是否成功样式 + * @param successText 成功 Toast 文本 + * @param errorText 错误 Toast 文本 + */ + fun showToast( + success: Boolean, + successText: String?, + errorText: String? + ) { + showToast(success, if (success) successText else errorText) + } + + /** + * 显示 Toast + * @param success 是否成功样式 + * @param text Toast 文本 + */ + fun showToast( + success: Boolean, + text: String? + ) { + if (success) { + ToastTintUtils.success(text) + } else { + ToastTintUtils.error(text) + } + } + + // ============ + // = 适配器相关 = + // ============ + + /** + * 注册 Adapter 观察者 + * @param recyclerView [RecyclerView] + */ + fun registerAdapterDataObserver(recyclerView: RecyclerView?) { + registerAdapterDataObserver(recyclerView, null, false) + } + + /** + * 注册 Adapter 观察者 + * @param recyclerView [RecyclerView] + * @param isRefAdapter 是否刷新适配器 + */ + fun registerAdapterDataObserver( + recyclerView: RecyclerView?, + isRefAdapter: Boolean + ) { + registerAdapterDataObserver(recyclerView, null, isRefAdapter) + } + + /** + * 注册 Adapter 观察者 + * @param recyclerView [RecyclerView] + * @param adapterDataObserver Adapter 观察者 + * @param isRefAdapter 是否刷新适配器 + */ + fun registerAdapterDataObserver( + recyclerView: RecyclerView?, + adapterDataObserver: AdapterDataObserver?, + isRefAdapter: Boolean + ) { + recyclerView?.adapter?.let { + it.registerAdapterDataObserver(object : AdapterDataObserver() { + override fun onChanged() { + super.onChanged() + // 获取数据总数 + val itemCount = it.itemCount + // 如果为 null 特殊处理 + ViewUtils.reverseVisibilitys( + itemCount != 0, + contentAssist.contentLinear, + contentAssist.stateLinear + ) + // 判断是否不存在数据 + if (itemCount == 0) { + stateLayout.showEmptyData() + } + adapterDataObserver?.onChanged() + } + }) + // 刷新适配器 + if (isRefAdapter) it.notifyDataSetChanged() + } + } + + // =========== + // = ToolBar = + // =========== + + /** + * 是否需要 ToolBar + */ + open fun isToolBar(): Boolean { + return true + } + + /** + * 初始化 ToolBar + */ + private fun initToolBar() { + val titleView = ViewUtils.inflate(this, R.layout.base_toolbar, null) + toolbar = titleView.findViewById(R.id.vid_tb) + contentAssist.addTitleView(titleView) + + setSupportActionBar(toolbar) + supportActionBar?.let { + // 给左上角图标的左边加上一个返回的图标 + it.setDisplayHomeAsUpEnabled(true) + // 对应 ActionBar.DISPLAY_SHOW_TITLE + it.setDisplayShowTitleEnabled(false) + } + // 设置点击事件 + toolbar?.setNavigationOnClickListener { finish() } + } + + // ============ + // = 内部初始化 = + // ============ + + private fun innerInitialize() { + try { + ARouter.getInstance().inject(this) + } catch (ignored: Exception) { + } + } + + // ======= + // = 通用 = + // ======= + + /** + * ARouter 跳转方法 + * @param buttonValue 按钮参数 + */ + fun routerActivity( + buttonValue: ButtonValue + ) { + ARouter.getInstance().build(buttonValue.path) + .withInt(DevFinal.STR.TYPE, buttonValue.type) + .withString(DevFinal.STR.TITLE, buttonValue.text) + .navigation(this) + } + + /** + * 跳转方法 + * @param clazz 跳转 + * @param buttonValue 按钮参数 + * @return `true` success, `false` fail + */ + private fun classStartActivity( + clazz: Class<*>?, + buttonValue: ButtonValue + ): Boolean { + val intent = Intent(this, clazz) + intent.putExtra(DevFinal.STR.TYPE, buttonValue.type) + intent.putExtra(DevFinal.STR.TITLE, buttonValue.text) + return AppUtils.startActivity(intent) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVVMActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVVMActivity.kt new file mode 100644 index 0000000000..5c730437b8 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/app/BaseMVVMActivity.kt @@ -0,0 +1,39 @@ +package afkt.project.base.app + +import android.os.Bundle +import android.view.View +import androidx.databinding.ViewDataBinding +import androidx.lifecycle.ViewModel +import com.alibaba.android.arouter.launcher.ARouter +import dev.base.expand.mvvm.DevBaseMVVMActivity + +/** + * detail: Activity MVVM 基类 + * @author Ttt + */ +abstract class BaseMVVMActivity : + DevBaseMVVMActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 内部初始化 + innerInitialize() + // 初始化顺序 ( 按顺序调用方法 ) + initOrder() + } + + override fun baseContentView(): View? { + return null + } + + // ============ + // = 内部初始化 = + // ============ + + private fun innerInitialize() { + try { + ARouter.getInstance().inject(this) + } catch (ignored: Exception) { + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/http/HttpConstants.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/http/HttpConstants.kt new file mode 100644 index 0000000000..a194081b07 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/http/HttpConstants.kt @@ -0,0 +1,59 @@ +package afkt.project.base.http + +import dev.environment.annotation.Environment +import dev.environment.annotation.Module + +/** + * detail: Http 常量 + * @author Ttt + */ +private class HttpConstants private constructor() { + + @Module(alias = "服务器请求地址") + private inner class Service { + @Environment(value = "https://www.wanandroid.com", isRelease = true, alias = "线上环境") + private val release: String? = null + + @Environment(value = "https://debug.com", alias = "测试环境") + private val debug: String? = null + + @Environment(value = "https://pre_release.com", alias = "预发布环境") + private val pre_release: String? = null + + @Environment(value = "https://development.com", alias = "开发环境") + private val development: String? = null + } + + @Module(alias = "开关") + private inner class Switch { + @Environment(value = "true", isRelease = true) + private val open: String? = null + + @Environment(value = "false") + private val close: String? = null + } + + @Module(alias = "IM 模块") + private inner class IM { + @Environment(value = "https://im.release.com", isRelease = true, alias = "线上环境") + private val release: String? = null + + @Environment(value = "https://im.debug.com", alias = "测试环境") + private val debug: String? = null + } + + @Module(alias = "地图") + private inner class Map { + @Environment(value = "a3f4a5b080e2a4ef4a708b9c9f5ad003", isRelease = true, alias = "百度地图") + private val baidu: String? = null + + @Environment(value = "9cc1b3fbd4e4d2f69994df700d648c40", alias = "高德地图") + private val gaode: String? = null + + @Environment(value = "6b3d3b354aff2b2e4e37db5409e0ce7f", alias = "谷歌地图") + private val google: String? = null + + @Environment(value = "1977803150186fe4d2a3e226e2869497", alias = "腾讯地图") + private val qq: String? = null + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/http/ProgressManagerUse.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/http/ProgressManagerUse.kt new file mode 100644 index 0000000000..73b32725db --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/http/ProgressManagerUse.kt @@ -0,0 +1,247 @@ +package afkt.project.base.http + +import dev.DevHttpManager +import dev.expand.engine.log.log_dTag +import dev.http.progress.Progress +import dev.http.progress.ProgressOperation +import dev.utils.common.MapUtils +import dev.utils.common.StringUtils +import dev.utils.common.ThrowableUtils +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +/** + * detail: DevHttpManager Progress 监听使用示范 + * @author Ttt + */ +class ProgressManagerUse private constructor() { + + companion object { + + private val instance: ProgressManagerUse by lazy { ProgressManagerUse() } + + fun operation(): ProgressOperation { + return instance.operation() + } + } + + // 日志 TAG + private val TAG = ProgressManagerUse::class.java.simpleName + + // 模拟链接 + private val url = "https://developer.android.com/docs?type=1&abc=afkt" + + // ============= + // = 模拟使用方法 = + // ============= + + fun use() { + /** + * 需要切换内部实现方式, 必须先调用该方法 + * 实现方式差异可以查看 [ProgressOperation] 类注释 + * 可不调用默认使用 PLAN_A + */ + mOperation.setPlanType(ProgressOperation.PLAN_A) + mOperation.setPlanType(ProgressOperation.PLAN_B) + + // 进行拦截器包装 ( 必须调用 ) + val okHttpClient = mOperation.wrap(OkHttpClient.Builder()).build() + + // 基于 OkHttp 库, 不同库封装使用不同, 只要使用 wrap build 后的 client 就能够实现监听 + val retrofit = Retrofit.Builder() + // Gson 解析 + .addConverterFactory(GsonConverterFactory.create()) + // OkHttpClient + .client(okHttpClient) + // 服务器地址 + .baseUrl("") + .build() + + // 添加指定 url 上行监听事件 + mOperation.addRequestListener(url, progressCallback) + // 添加指定 url 下行监听事件 + mOperation.addResponseListener(url, progressCallback) + } + + // ===================== + // = ProgressOperation = + // ===================== + + /** + * 对外提供操作对象 + * @return ProgressOperation + */ + fun operation(): ProgressOperation { + return mOperation + } + + // Progress Operation + private val mOperation: ProgressOperation by lazy { + // 监听上下行 + DevHttpManager.PM.putOperationTypeAll("MODULE_NAME") +// // 监听上行 +// DevHttpManager.PM.putOperationTypeRequest("MODULE_NAME") +// // 监听下行 +// DevHttpManager.PM.putOperationTypeResponse("MODULE_NAME") + } + + // ========== + // = 内部方法 = + // ========== + + // Progress Callback 模拟监听 + private val progressCallback: Progress.Callback by lazy { + createCallback { extras -> + // 判断请求链接是否一致 + if (StringUtils.isNotEmpty(url) + && StringUtils.equals(url, extras.getUrl()) + ) { + val urlParams = extras.getUrlExtras().urlParams + // 判断参数 ( 便于演示实际应该是传参 or 静态存储 ) + if (StringUtils.equals("afkt", MapUtils.get(urlParams, "abc"))) { + // 还可以其他判断, 符合条件返回 true + return@createCallback true + } + } + return@createCallback false + } + } + + /** + * 创建 Progress Callback + * @param filter 传入方法体外部进行过滤判断是否属于需要监听的进度 + * @return Progress.Callback + * 回调不是表示上传、下载结果, 而是表示上传、下载这个操作流程回调 + *

+ * 如何判断是否需要处理各个方法, 只需要在 [onStart] 判断 [Progress.Extras] 信息 + * 并存储当前的 [Progress.id] 其他方法都用已存储的 id 和传入的 Progress.id 对比即可 + */ + private fun createCallback(filter: (Progress.Extras) -> Boolean): Progress.Callback { + return object : Progress.Callback { + + // ========== + // = 内部变量 = + // ========== + + // 存储处理的进度 id ( 用于演示多个相同请求, 参数不同情况下监听指定请求进度 ) + private var progressId = -1L + + /** + * 有多个相同请求且参数一致, 需要监听不同进度, 则自行新增一个无用的随机参数 or 头信息进行区分 + * 不同请求则不推荐进行复用, 方便回收、判断等 ( 本身功能设计也是监听指定需求 ) + * 有复用需求可以调用 [ProgressOperation.setCallback] 设置为全局通用回调 + * 设置后, 会优先通知全局回调, 接着通知对应 url Callback List + */ + + // ===================== + // = Progress.Callback = + // ===================== + + /** + * 是否自动释放监听对象 + * @param progress Progress + * @return `true` yes, `false` no + */ + override fun isAutoRecycle(progress: Progress): Boolean { + if (progressId == progress.getId()) { + return true + } + return false + } + + /** + * 开始回调 + * @param progress Progress + */ + override fun onStart(progress: Progress) { + // 获取额外携带信息, 如 url、method、headers、param + val extras = progress.getExtras() + if (progressId == -1L) { // 没有更新值时才进行判断 + if (extras != null && filter(extras)) { + progressId = progress.getId() + } + } + if (progressId == progress.getId()) { + TAG.log_dTag( + message = StringBuilder().apply { + append("onStart - ").append(progress.getId()) + append(", totalSize: ").append(progress.getTotalSize()) + append(", currentSize: ").append(progress.getCurrentSize()) + }.toString() + ) + } + } + + /** + * 进度回调 + * @param progress Progress + */ + override fun onProgress(progress: Progress) { + if (progressId == progress.getId()) { + TAG.log_dTag( + message = StringBuilder().apply { + append("onProgress - ").append(progress.getId()) + append(", totalSize: ").append(progress.getTotalSize()) + append(", currentSize: ").append(progress.getCurrentSize()) + append(", 网速: ").append(progress.getSpeed().getSpeedFormatSecond()) + append(", 百分比进度: ").append(progress.getPercent()) + }.toString() + ) + } + } + + /** + * 流程异常回调 + * @param progress Progress + */ + override fun onError(progress: Progress) { + if (progressId == progress.getId()) { + TAG.log_dTag( + message = StringBuilder().apply { + append("onError - ").append(progress.getId()) + append(", totalSize: ").append(progress.getTotalSize()) + append(", currentSize: ").append(progress.getCurrentSize()) + append(", 网速: ").append(progress.getSpeed().getSpeedFormatSecond()) + append(", 百分比进度: ").append(progress.getPercent()) + append(", 异常信息: ").append(ThrowableUtils.getThrowable(progress.getException())) + }.toString() + ) + } + } + + /** + * 流程完成回调 + * @param progress Progress + */ + override fun onFinish(progress: Progress) { + if (progressId == progress.getId()) { + TAG.log_dTag( + message = StringBuilder().apply { + append("onFinish - ").append(progress.getId()) + append(", 网速: ").append(progress.getSpeed().getSpeedFormatSecond()) + append(", 百分比进度: ").append(progress.getPercent()) + }.toString() + ) + } + } + + /** + * 流程结束回调 + * @param progress Progress + * 不管是 [onError]、[onFinish] 最终都会触发该结束方法 + */ + override fun onEnd(progress: Progress) { + if (progressId == progress.getId()) { + TAG.log_dTag( + message = StringBuilder().apply { + append("onEnd - ").append(progress.getId()) + append(", 网速: ").append(progress.getSpeed().getSpeedFormatSecond()) + append(", 百分比进度: ").append(progress.getPercent()) + }.toString() + ) + } + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/base/http/RetrofitManagerUse.kt b/application/DevUtilsApp/src/main/java/afkt/project/base/http/RetrofitManagerUse.kt new file mode 100644 index 0000000000..e8e4da08c5 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/base/http/RetrofitManagerUse.kt @@ -0,0 +1,333 @@ +package afkt.project.base.http + +import afkt.project.model.bean.ArticleBean +import dev.DevHttpCapture +import dev.DevHttpManager +import dev.DevUtils +import dev.capture.CallbackInterceptor +import dev.capture.CaptureInfo +import dev.capture.IHttpCaptureEnd +import dev.environment.DevEnvironment +import dev.http.manager.OkHttpBuilder +import dev.http.manager.OnRetrofitResetListener +import dev.http.manager.RetrofitBuilder +import dev.http.manager.RetrofitOperation +import dev.utils.LogPrintUtils +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.GET +import retrofit2.http.Path +import java.util.concurrent.TimeUnit + +/** + * detail: 玩 Android API Service + * @author Ttt + */ +interface WanAndroidService { + + @GET("/article/list/{page}/json") + suspend fun getArticleList(@Path("page") page: Int): ArticleBean +} + +/** + * detail: DevHttpManager RetrofitManager 使用示范 + * @author Ttt + */ +class RetrofitManagerUse private constructor() { + + companion object { + + private val instance: RetrofitManagerUse by lazy { RetrofitManagerUse() } + + // 日志 TAG + private val TAG = RetrofitManagerUse::class.java.simpleName + + fun api(): WanAndroidService { + return instance.api() + } + + fun operation(): RetrofitOperation { + return instance.operation() + } + } + + // 全局通用 OkHttp Builder + private val mOkHttpBuilderGlobal = OkHttpBuilderGlobal() + + // 全局 Retrofit 重新构建监听事件 + private val mRetrofitResetListenerGlobal = RetrofitResetListenerGlobal() + + init { + // 设置全局 OkHttp Builder 接口对象 + DevHttpManager.RM.setOkHttpBuilder( + mOkHttpBuilderGlobal + ) + // 设置全局 Retrofit 重新构建监听事件 + DevHttpManager.RM.setRetrofitResetListener( + mRetrofitResetListenerGlobal + ) + } + + // ===================== + // = WanAndroidService = + // ===================== + + @Volatile + private var mWanAndroidService: WanAndroidService? = null + + fun api(): WanAndroidService { + if (mWanAndroidService == null) { + synchronized(WanAndroidService::class.java) { + if (mWanAndroidService == null) { + createAPI() + } + } + } + return mWanAndroidService as WanAndroidService + } + + private fun createAPI() { + mWanAndroidService = operation().create( + WanAndroidService::class.java + ) + } + + // ================== + // = DevEnvironment = + // ================== + + private fun apiBaseUrl(): HttpUrl { + return DevEnvironment.getServiceEnvironmentValue( + DevUtils.getContext() + ).toHttpUrl() + } + + // ===================== + // = RetrofitOperation = + // ===================== + + /** + * 对外提供操作对象 + * @return RetrofitOperation + */ + fun operation(): RetrofitOperation { + return mOperation + } + + // Retrofit Operation + private val mOperation: RetrofitOperation by lazy { + DevHttpManager.RM.putRetrofitBuilder( + "MODULE_NAME", mRetrofitBuilder + ) + } + + // =================== + // = RetrofitBuilder = + // =================== + + // Retrofit Builder 接口 + private val mRetrofitBuilder: RetrofitBuilder by lazy { + object : RetrofitBuilder { + + /** + * 创建 Retrofit Builder + * @param oldRetrofit 上一次构建的 Retrofit + * @param httpUrl 构建使用指定 baseUrl + * @param okHttp OkHttpClient 构建全局复用 + * @return Retrofit.Builder + */ + override fun createRetrofitBuilder( + oldRetrofit: Retrofit?, + httpUrl: HttpUrl?, + okHttp: OkHttpClient.Builder? + ): Retrofit.Builder { + return Retrofit.Builder().apply { + // Gson 解析 + addConverterFactory(GsonConverterFactory.create()) + // OkHttpClient + client((okHttp ?: OkHttpClient.Builder()).build()) + // 服务器地址 + baseUrl(httpUrl ?: apiBaseUrl()) + } + } + + // ========== + // = 通知事件 = + // ========== + + /** + * 重新构建前调用 + * @param key String + * @param oldRetrofit 上一次构建的 Retrofit + * 在 [createRetrofitBuilder] 之前调用 + */ + override fun onResetBefore( + key: String, + oldRetrofit: Retrofit? + ) { + + } + + /** + * 重新构建后调用 + * @param key String + * @param newRetrofit 重新构建的 Retrofit 对象 + * 在 [createRetrofitBuilder] 之后调用 + */ + override fun onReset( + key: String, + newRetrofit: Retrofit? + ) { + // 重新构建后创建新的代理对象 + createAPI() + } + } + } +} + +/** + * detail: 全局通用 OkHttp Builder + * @author Ttt + * 全局 ( 通过 Key 进行特殊化创建 ) + * 可用于 [RetrofitBuilder.createRetrofitBuilder] okHttp 参数传入并创建 + */ +class OkHttpBuilderGlobal : OkHttpBuilder { + + /** + * 创建 OkHttp Builder + * @param key Key ( RetrofitBuilder Manager Key ) + * @return OkHttpClient.Builder + */ + override fun createOkHttpBuilder(key: String): OkHttpClient.Builder { +// // 可搭配监听进度使用 +// return ProgressManagerUse.operation().wrap( +// commonOkHttpBuilder(key) +// ) + return commonOkHttpBuilder(key) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 通用 OkHttp Builder 创建方法 + * @param key String + * @return OkHttpClient.Builder + */ + private fun commonOkHttpBuilder(key: String): OkHttpClient.Builder { + return OkHttpClient.Builder().apply { + + // ========== + // = 通用配置 = + // ========== + + // 全局的读取超时时间 + readTimeout(60000L, TimeUnit.MILLISECONDS) + // 全局的写入超时时间 + writeTimeout(60000L, TimeUnit.MILLISECONDS) + // 全局的连接超时时间 + connectTimeout(60000L, TimeUnit.MILLISECONDS) + + // ============= + // = 不同版本构建 = + // ============= + + // 是否 Release 版本标记 ( 用于标记 APK 是否 Release 发布版本 ) + if (!DevUtils.isDebug()) { + releaseOkHttpBuilder(key, this) + } else { + debugOkHttpBuilder(key, this) + } + } + } + + // =========== + // = Release = + // =========== + + /** + * release 版本构建 OkHttp Builder + * @param key String + * @param builder Builder + */ + private fun releaseOkHttpBuilder( + key: String, + builder: OkHttpClient.Builder + ) { + builder.apply { + } + } + + // ========= + // = Debug = + // ========= + + /** + * debug 版本构建 OkHttp Builder + * @param key String + * @param builder Builder + */ + private fun debugOkHttpBuilder( + key: String, + builder: OkHttpClient.Builder + ) { + builder.apply { + // 使用 DevHttpCapture 库进行 Http 拦截回调 ( 抓包数据存储 ) + DevHttpCapture.addInterceptor(builder, "ModuleName") + + // Http 抓包拦截器 ( 无存储逻辑, 进行回调通知 ) + addInterceptor(CallbackInterceptor(endCall = object : IHttpCaptureEnd { + override fun callEnd(info: CaptureInfo) { + // 打印 Http 请求信息 log + LogPrintUtils.jsonTag( + key, info.toJson() + ) + } + })) + } + } +} + +/** + * detail: 全局 Retrofit 重新构建监听事件 + * @author Ttt + */ +class RetrofitResetListenerGlobal : OnRetrofitResetListener { + + /** + * 重新构建前调用 + * @param key String + * @param oldRetrofit 上一次构建的 Retrofit + */ + override fun onResetBefore( + key: String, + oldRetrofit: Retrofit? + ) { + oldRetrofit?.let { retrofit -> + val factory = retrofit.callFactory() + if (factory is OkHttpClient) { + // 重新构建则把之前的请求全部取消 ( 视情况自行处理 ) + factory.dispatcher.cancelAll() + } + } + } + + /** + * 重新构建后调用 + * @param key String + * @param newRetrofit 重新构建的 Retrofit 对象 + * 在 [onResetBefore] 之后调用 + */ + override fun onReset( + key: String, + newRetrofit: Retrofit? + ) { + newRetrofit?.let { retrofit -> + // 构建成功如自动请求等 + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonAdapter.kt new file mode 100644 index 0000000000..cf555dd63b --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonAdapter.kt @@ -0,0 +1,44 @@ +package afkt.project.feature + +import afkt.project.R +import afkt.project.databinding.BaseViewButtonBinding +import afkt.project.model.item.ButtonValue +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Button 适配器 + * @author Ttt + */ +class ButtonAdapter(data: List) : DevDataAdapterExt>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.base_view_button) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + holder.binding.vidBtn.apply { + text = item.text + setOnClickListener { + mItemCallback?.onItemClick(item, position) + } + setOnLongClickListener { + mItemCallback?.onItemLongClick(item, position) + true + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonItemActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonItemActivity.kt new file mode 100644 index 0000000000..3dc0cfa33a --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ButtonItemActivity.kt @@ -0,0 +1,36 @@ +package afkt.project.feature + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback + +/** + * detail: Button 列表 Activity + * @author Ttt + */ +@Route(path = RouterPath.ButtonItemActivity_PATH) +class ButtonItemActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.getButtonValues(moduleType)) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + routerActivity(buttonValue) + } + }).bindAdapter(binding.vidRv) + // 注册观察者 + registerAdapterDataObserver(binding.vidRv, true) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ModuleActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ModuleActivity.kt new file mode 100644 index 0000000000..1e40f8fca1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ModuleActivity.kt @@ -0,0 +1,56 @@ +package afkt.project.feature + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: Module 列表 Activity + * @author Ttt + */ +@Route(path = RouterPath.ModuleActivity_PATH) +class ModuleActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.getModuleButtonValues(moduleType)) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_ROOM, + ButtonValue.BTN_GREEN_DAO -> ToastTintUtils.info( + "具体请查看: 【DevUtils-repo】application/AppDB" + ) + ButtonValue.BTN_EVENT_BUS, + ButtonValue.BTN_GLIDE, + ButtonValue.BTN_IMAGE_LOADER, + ButtonValue.BTN_GSON, + ButtonValue.BTN_FASTJSON, + ButtonValue.BTN_ZXING, + ButtonValue.BTN_PICTURE_SELECTOR, + ButtonValue.BTN_OKGO, + ButtonValue.BTN_LUBAN, + ButtonValue.BTN_MMKV, + ButtonValue.BTN_WORK_MANAGER -> ToastTintUtils.info( + "具体请搜索: 【DevUtils-repo】lib/LocalModules/DevOther " + buttonValue.text + ) + else -> routerActivity(buttonValue) + } + } + }).bindAdapter(binding.vidRv) + // 注册观察者 + registerAdapterDataObserver(binding.vidRv, true) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_engine/DevAssistEngineActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_engine/DevAssistEngineActivity.kt new file mode 100644 index 0000000000..7f9c0aa7ab --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_engine/DevAssistEngineActivity.kt @@ -0,0 +1,124 @@ +package afkt.project.feature.dev_engine + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.moduleDevAssistEngineButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.graphics.Color +import com.alibaba.android.arouter.facade.annotation.Route +import dev.DevUtils +import dev.base.widget.BaseTextView +import dev.callback.DevItemClickCallback +import dev.engine.DevEngine +import dev.engine.log.DevLogEngine +import dev.engine.log.DevLoggerEngineImpl +import dev.expand.engine.log.log_dTag +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.SnackbarUtils +import dev.utils.app.SpanUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: DevAssist Engine 实现 + * @author Ttt + */ +@Route(path = RouterPath.DEV_LIBS.DevAssistEngineActivity_PATH) +class DevAssistEngineActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + val span = SpanUtils().apply { + append("DevAssist Engine 主要为了解决项目代码中") + appendLine("对第三方框架强依赖使用、以及部分功能版本适配。") + setBold().setForegroundColor(Color.RED) + append("通过实现对应功能模块 Engine 接口, 实现对应的方法功能, ") + append("对外无需关注实现代码, 直接通过 DevXXXEngine 进行调用, ") + setBold().setForegroundColor(Color.BLUE) + append("实现对第三方框架解耦、一键替换第三方库、同类库多 Engine 混合使用、") + setBold().setForegroundColor(Color.RED) + append("以及部分功能适配 ( 如外部文件存储 MediaStore 全局适配 ) 等") + setBold().setForegroundColor(Color.RED) + } + + val view = QuickHelper.get(BaseTextView(this)) + .setText(span.create()) + .setTextColors(ResourceUtils.getColor(R.color.black)) + .setTextSizeBySp(15.0F) + .setLineSpacingAndMultiplier(15.0F, 1.1F) + .setPadding(ResourceUtils.getDimensionInt(R.dimen.dp_20)) + .setPaddingBottom(ResourceUtils.getDimensionInt(R.dimen.dp_10), false) + .getView() + // 添加 View + contentAssist.addContentView(view = view, index = 0) + + // 初始化布局管理器、适配器 + ButtonAdapter(moduleDevAssistEngineButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + val builder = StringBuilder() + .append("Java 实现在 DevOther Module java.dev.engine 目录下") + .append(DevFinal.SYMBOL.NEW_LINE) + .append("Kotlin 实现已封装为 DevEngine 库") + // 进行显示 + SnackbarUtils.with(mActivity).also { sn -> + // 设置多行显示 + sn.style.textMaxLines = Int.MAX_VALUE + // 显示 Snackbar + sn.setAction({ sn.dismiss() }, "我知道了") + .showIndefinite(builder.toString()) + } + } + }).bindAdapter(binding.vidRv) + + // ================= + // = 模拟初始化、使用 = + // ================= + + // 设置 LogEngine 实现类 + DevLogEngine.setEngine(object : DevLoggerEngineImpl() { + override fun isPrintLog(): Boolean { + return DevUtils.isDebug() + } + }) + // 进行使用 + DevEngine.getLog()?.dTag( + TAG, "Log Engine 方法调用" + ) + + TAG.log_dTag( + message = "Log Engine 方法调用" + ) + + // ========================= + // = 同类库多 Engine 混合使用 = + // ========================= + + val KEY = "QPQPQPQP" + // 通过设置 KEY 区分不同 Engine 实现类 + DevLogEngine.setEngine( + KEY, object : DevLoggerEngineImpl() { + override fun isPrintLog(): Boolean { + return DevUtils.isDebug() + } + } + ) + DevLogEngine.getEngine(KEY)?.dTag( + TAG, "多 Log Engine 方法调用" + ) + + TAG.log_dTag( + engine = KEY, + message = "多 Log Engine 方法调用" + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_environment/DevEnvironmentLibActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_environment/DevEnvironmentLibActivity.kt new file mode 100644 index 0000000000..ecfcf7c201 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_environment/DevEnvironmentLibActivity.kt @@ -0,0 +1,86 @@ +package afkt.project.feature.dev_environment + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.moduleDevEnvironmentButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.environment.DevEnvironment +import dev.environment.DevEnvironmentActivity +import dev.environment.bean.EnvironmentBean +import dev.expand.engine.log.log_dTag +import dev.utils.app.ActivityUtils +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: DevEnvironment 环境配置切换库 + * @author Ttt + */ +@Route(path = RouterPath.DEV_LIBS.DevEnvironmentLibActivity_PATH) +class DevEnvironmentLibActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(moduleDevEnvironmentButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + val result: Boolean + when (buttonValue.type) { + ButtonValue.BTN_DEV_ENVIRONMENT -> { + result = DevEnvironmentActivity.start(mContext) { + ActivityUtils.getManager().exitApplication() + } + showToast(result, "跳转成功", "跳转失败") + } + ButtonValue.BTN_USE_CUSTOM -> { + // 如果准备设置环境等于当前选中的环境, 则会返回 false + val custom = + EnvironmentBean( + "自定义配置", + "https://custom.com", "动态自定义", DevEnvironment.getServiceModule() + ) + result = DevEnvironment.setServiceEnvironment(mContext, custom) + showToast(result, "设置成功", "设置失败") + } + else -> ToastTintUtils.warning("未处理 " + buttonValue.text + " 事件") + } + } + }).bindAdapter(binding.vidRv) + + // 环境改变通知 + DevEnvironment.addOnEnvironmentChangeListener { module, oldEnvironment, newEnvironment -> // 可以进行重新请求等操作 + StringBuilder().apply { + append("module") + append("\nname: ").append(module.name) + append("\nalias: ").append(module.alias) + append("\n\n") + append("oldEnvironment") + append("\nname: ").append(oldEnvironment.name) + append("\nvalue: ").append(oldEnvironment.value) + append("\nalias: ").append(oldEnvironment.alias) + append("\n\n") + append("newEnvironment") + append("\nname: ").append(newEnvironment.name) + append("\nvalue: ").append(newEnvironment.value) + append("\nalias: ").append(newEnvironment.alias) + + val content = toString() + ToastTintUtils.normal(content) + TAG.log_dTag( + message = content + ) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_http_capture/DevHttpCaptureActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_http_capture/DevHttpCaptureActivity.kt new file mode 100644 index 0000000000..046a1c9180 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_http_capture/DevHttpCaptureActivity.kt @@ -0,0 +1,57 @@ +package afkt.project.feature.dev_http_capture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.RouterPath +import android.graphics.Color +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.SpanUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: DevAssist Engine 实现 + * @author Ttt + */ +@Route(path = RouterPath.DEV_LIBS.DevHttpCaptureActivity_PATH) +class DevHttpCaptureActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + val span = SpanUtils().apply { + append("该库主要对使用 OkHttp 网络请求库的项目,提供 Http 抓包功能,并支持抓包数据加密存储。") + append(DevFinal.SYMBOL.NEW_LINE_X2) + appendLine("并且是以 Module ( ModuleName Key ) 为基础,支持组件化不同 Module 各自的抓包功能,") + setBold().setForegroundColor(Color.RED) + append("支持实时开关抓包功能、可控 Http 拦截过滤器。") + setBold().setForegroundColor(Color.BLUE) + append(DevFinal.SYMBOL.NEW_LINE_X2) + append("内置两个 Http 抓包拦截器,CallbackInterceptor ( 无存储逻辑,进行回调通知 )、") + append("HttpCaptureInterceptor ( 存在存储抓包数据逻辑 )") + append(DevFinal.SYMBOL.NEW_LINE_X2) + append("DevHttpCaptureCompiler") + setBold().setForegroundColor(Color.BLUE) + append(" 提供对 ") + append("DevHttpCapture") + setBold().setForegroundColor(Color.RED) + append(" 库 抓包库可视化功能") + } + + val view = QuickHelper.get(BaseTextView(this)) + .setText(span.create()) + .setTextColors(ResourceUtils.getColor(R.color.black)) + .setTextSizeBySp(15.0F) + .setLineSpacingAndMultiplier(15.0F, 1.1F) + .setPadding(ResourceUtils.getDimensionInt(R.dimen.dp_20)) + .setPaddingBottom(ResourceUtils.getDimensionInt(R.dimen.dp_10), false) + .getView() + // 添加 View + contentAssist.addContentView(view = view, index = 0) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/DevSKUActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/DevSKUActivity.kt new file mode 100644 index 0000000000..0f0420bf6d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/DevSKUActivity.kt @@ -0,0 +1,227 @@ +package afkt.project.feature.dev_sku + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.json.fromJson +import dev.sku.SKU +import dev.sku.SKUData +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.toast.ToastTintUtils +import dev.utils.common.CollectionUtils + +/** + * detail: DevSKU 商品 SKU 组合封装实现 + * @author Ttt + */ +@Route(path = RouterPath.DEV_LIBS.DevSKUActivity_PATH) +class DevSKUActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.moduleDevSKUButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_SKU_DIALOG -> { + val skuFileName = "sku_test.json" // sku.json + val skuFileContent = ResourceUtils.readStringFromAssets(skuFileName) + val commoditySKU = skuFileContent.fromJson( + classOfT = CommoditySKU::class.java + ) + + // ============== + // = SKU Dialog = + // ============== + + commoditySKU?.let { model -> + SKUDialog(this@DevSKUActivity, object : SKUCallback { + override fun callback( + spec: Spec, + number: Int, + buyType: BuyType + ) { + ToastTintUtils.success(StringBuilder().apply { + append("购买数量: ") + append(number) + append(DevFinal.SYMBOL.NEW_LINE) + append("购买方式: ") + append(buyType.desc) + append(DevFinal.SYMBOL.NEW_LINE) + append("规格名: ") + append(spec.specName) + append(DevFinal.SYMBOL.NEW_LINE) + append("规格 id: ") + append(spec.specId) + }.toString()) + } + }).apply { + // 显示 SKU Dialog + assist.setDevDialog(this) + .showDialog(BuyType.BUY, model, 1045) + } + } + } + else -> routerActivity(buttonValue) + } + } + }).bindAdapter(binding.vidRv) + } +} + +// ========== +// = 数据模型 = +// ========== + +// 映射 assets sku json 实体类 +// sku.json 完整 SKU 模拟数据 +// sku_test.json 删除部分规格信息方便测试无库存情况 + +data class CommoditySKU( + val attributeList: MutableList, + val specList: MutableList +) + +data class Spec( + val specId: Int, + val specName: String, + val picUrl: String, + val inventory: Int, + val salePrice: Double, + val attrValueIdList: List +) + +data class Attribute( + val id: Int, + val attrName: String, + val attrValueList: List +) + +data class AttrValue( + val id: Int, + val attrValue: String +) + +// ================= +// = DevSKU 转换处理 = +// ================= + +/** + * detail: DevSKU [SKU] 封装类转换 + * @author Ttt + * 如何使用 DevSKU: + * 首先编写 Convert 类将服务器返回的信息转换为 DevSKU 封装类 + * [SKU.Model] 数据集基本模型 + * [SKU.Attr] 属性 + * [SKU.AttrValue] 属性值 + * 接着创建 [SKUData] 数据处理包装类 + * 通过 [SKUData.initialize] 进行初始化并通过 [SKUData.refreshStateData] 方法获取 Adapter 数据源 + * 每次选择、取消选择规格都需要重新调用 [SKUData.refreshStateData] 获取最新状态 + */ +class SKUConvert { + + // SKU 数据处理包装 ( 对外公开快捷使用 ) + val skuData = SKUData() + + // =============================================== + // = 以下方法作用于将服务器返回的信息转换为 DevSKU 封装类 = + // =============================================== + + /** + * 转换数据 + * @param model 商品 SKU 模型 + * @param specId 默认选中规格数据 + */ + fun convert( + model: CommoditySKU, + specId: Int = Int.MIN_VALUE + ) { + // 清空 null 数据 ( 可不添加该操作, 视自身情况而定 ) + CollectionUtils.clearNull(model.specList) + CollectionUtils.clearNull(model.attributeList) + model.attributeList.forEach { attr -> + CollectionUtils.clearNull(attr.attrValueList) + } + // 初始化操作 + init(model, specId) + } + + /** + * 初始化操作 + * @param model 商品 SKU 模型 + * @param specId 默认选中规格数据 + */ + private fun init( + model: CommoditySKU, + specId: Int + ) { + + // ============= + // = 转换规格属性 = + // ============= + + val attrs = mutableListOf() + model.attributeList.forEach { attr -> + val lists = mutableListOf() + attr.attrValueList.forEach { attrValue -> + lists.add( + SKU.AttrValue( + attrValue.id, attrValue.attrValue, + SKU.State.OPTIONAL + ) + ) + } + if (lists.isNotEmpty()) { + attrs.add(SKU.Attr(attr.id, attr.attrName, lists)) + } + } + + // ======================= + // = 转换 SKU 数据集基本模型 = + // ======================= + + val skuModels = mutableMapOf, SKU.Model>() + model.specList.forEach { spec -> + spec.attrValueIdList.apply { + skuModels[this] = SKU.Model( + spec.inventory, + spec.salePrice, + spec + ) + } + } + + // ============ + // = 初始化数据 = + // ============ + + skuData.initialize(attrs, skuModels) + + // ================= + // = 自动选中规格属性 = + // ================= + + if (specId != Int.MIN_VALUE) { + model.specList.forEach { spec -> + spec.attrValueIdList.apply { + if (spec.specId == specId) { + skuData.autoSelectAttr(this) + } + } + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUAdapter.kt new file mode 100644 index 0000000000..68ede94f34 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUAdapter.kt @@ -0,0 +1,176 @@ +package afkt.project.feature.dev_sku + +import afkt.project.R +import afkt.project.databinding.SkuAdapterSpecBinding +import afkt.project.databinding.SkuAdapterSpecItemBinding +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import dev.adapter.DevDataAdapterExt +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.sku.SKU +import dev.utils.app.ResourceUtils + +/** + * detail: SKU 适配器 + * @author Ttt + */ +class SKUAdapter( + private val skuConvert: SKUConvert, + val listener: View.OnClickListener? +) : DevDataAdapterExt>() { + + // 字体颜色 + private val color92ba37 = ResourceUtils.getColor(R.color.color_92ba37) + private val color999999 = ResourceUtils.getColor(R.color.color_999999) + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.sku_adapter_spec) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + // 设置属性名 + holder.binding.vidNameTv.text = item.name + // 添加属性值 + holder.binding.vidWv.apply { + removeAllViews() + // 循环创建子属性值并添加 + item.attrList.forEach { value -> + addView(createSubAttr(item, value)) + } + } + } + + /** + * 创建子属性 View + * @param attr SKU.Attr + * @param attrValue SKU.AttrValue + */ + private fun createSubAttr( + attr: SKU.Attr, + attrValue: SKU.AttrValue + ): LinearLayout { + return SkuAdapterSpecItemBinding.inflate( + LayoutInflater.from(context) + ).apply { + vidValueTv.text = attrValue.value + if (isSelect(attr.id, attrValue)) { + // 已选中 + vidValueTv.apply { + setTextColor(color92ba37) + setBackgroundResource(R.drawable.sku_shape_stroked_92ba37_corners6) + setOnClickListener { + unselect(attr.id) + // 刷新状态数据 + refreshStateData() + // 触发点击 + listener?.onClick(it) + } + } + } else { + if (attrValue.isOptional()) { + // 可点击 + vidValueTv.apply { + setTextColor(color999999) + setBackgroundResource(R.drawable.sku_shape_f6f6f6_corners6) + setOnClickListener { + select(attr.id, attrValue) + // 刷新状态数据 + refreshStateData() + // 触发点击 + listener?.onClick(it) + } + } + } else if (attrValue.isNotOptional()) { + // 不可点击 + vidValueTv.apply { + setTextColor(color999999) + setBackgroundResource(R.drawable.sku_shape_stroked_f6f6f6_corners6_dash) + } + } + } + }.root + } + + // ============= + // = 对外公开方法 = + // ============= + + // ================== + // = SKUData 二次封装 = + // ================== + + /** + * 以下方法可直接通过 [skuConvert.skuData] 获取 + * 为了美化代码以及代码控制, 统一通过 Adapter 进行获取 + */ + + /** + * 刷新状态数据 + */ + fun refreshStateData() { + setDataList(skuConvert.skuData.refreshStateData()) + } + + /** + * 获取选中属性对应的数据集基本模型 + * @return SKU.Model + */ + fun getModel(): SKU.Model? { + return skuConvert.skuData.getModel() + } + + /** + * 是否全选每个属性 ( 每个属性的属性值集合都选中 ) + * @return `true` yes, `false` no + */ + fun isAllSelectAttr(): Boolean { + return skuConvert.skuData.isAllSelectAttr() + } + + /** + * 设置非选中操作 + * @param attrId SKU.Attr.id + * @return SKUAdapter + */ + fun unselect(attrId: Int): SKUAdapter { + skuConvert.skuData.unselect(attrId) + return this + } + + /** + * 设置选中操作 + * @param attrId SKU.Attr.id + * @param attrValue SKU.AttrValue + * @return SKUAdapter + */ + fun select( + attrId: Int, + attrValue: SKU.AttrValue + ): SKUAdapter { + skuConvert.skuData.select(attrId, attrValue) + return this + } + + /** + * 是否选中对应属性 + * @param attrId SKU.Attr.id + * @param attrValue SKU.AttrValue + * @return `true` yes, `false` no + */ + fun isSelect( + attrId: Int, + attrValue: SKU.AttrValue + ): Boolean { + return skuConvert.skuData.isSelect(attrId, attrValue) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUDialog.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUDialog.kt new file mode 100644 index 0000000000..d167479dee --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_sku/SKUDialog.kt @@ -0,0 +1,303 @@ +package afkt.project.feature.dev_sku + +import afkt.project.R +import afkt.project.databinding.SkuDialogSpecBinding +import afkt.project.utils.IMAGE_ROUND_10 +import android.app.Dialog +import android.view.Gravity +import androidx.fragment.app.FragmentActivity +import dev.assist.NumberControlAssist +import dev.base.number.INumberListener +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource +import dev.utils.app.EditTextUtils +import dev.utils.app.ViewUtils +import dev.utils.app.toast.ToastUtils +import dev.utils.common.BigDecimalUtils +import dev.utils.common.ConvertUtils +import kotlin.math.max +import kotlin.math.min + +/** + * detail: 购买操作类型 + * @author Ttt + */ +enum class BuyType( + val type: Int, + val desc: String +) { + + CART(1, "加入购物车"), + + BUY(2, "立即购买"), + + ; + + /** + * 是否加入购物车 + * @return `true` yes, `false` no + */ + fun isCart(): Boolean { + return this == CART + } + + /** + * 是否立即购买 + * @return `true` yes, `false` no + */ + fun isBuy(): Boolean { + return this == BUY + } +} + +/** + * detail: 确定购买指定规格回调 + * @author Ttt + */ +interface SKUCallback { + + /** + * 回调方法 + * @param spec 规格信息 + * @param number 购买数量 + * @param buyType 购买操作类型 + */ + fun callback( + spec: Spec, + number: Int, + buyType: BuyType + ) +} + +/** + * detail: SKU Dialog + * @author Ttt + */ +class SKUDialog( + activity: FragmentActivity, + private val callback: SKUCallback +) : Dialog(activity, R.style.DevDialogFullScreenTheme) { + + private val binding: SkuDialogSpecBinding by lazy { + SkuDialogSpecBinding.inflate(layoutInflater) + } + + // DevSKU [SKU] 封装类转换 + private val skuConvert = SKUConvert() + + // SKU 适配器 + private val adapter: SKUAdapter by lazy { + SKUAdapter(skuConvert) { + // 规格选择刷新 + refreshBySPEC() + } + } + + init { + this.setContentView(binding.root) + + window?.let { + val params = it.attributes + params.dimAmount = 0.5F + params.width = ViewUtils.MATCH_PARENT + params.gravity = Gravity.BOTTOM + it.attributes = params + } + + // ========== + // = 监听处理 = + // ========== + + binding.vidCloseIv.setOnClickListener { + dismiss() + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 显示 Dialog + * @param buyType 购买操作类型 + * @param model 商品 SKU 模型 + * @param specId 默认选中规格数据 + */ + fun showDialog( + buyType: BuyType, + model: CommoditySKU, + specId: Int = Int.MIN_VALUE + ): SKUDialog { + + // 绑定购买数量功能事件 + bindNumberListener() + + // ========== + // = 点击确定 = + // ========== + + binding.vidConfirmTv.setOnClickListener { + if (adapter.isAllSelectAttr()) { + // 获取规格信息 + adapter.getModel()?.value?.let { spec -> + dismiss() + // 触发回调 + callback.callback(spec, mNumberAssist.currentNumber, buyType) + } + } else { + ToastUtils.showShort("请选择规格") + } + } + + // 绑定适配器 + adapter.bindAdapter(binding.vidRecy) + // SKU 转换数据 + skuConvert.convert(model, specId) + // 刷新数据源 + adapter.refreshStateData() + // 规格选择刷新 + refreshBySPEC() + // 显示弹窗 + show() + return this + } + + // ========== + // = 内部方法 = + // ========== + + // 最小购买数量 + private val MIN_NUMBER = 1 + + // 最大购买数量 + private val MAX_NUMBER = 99999 + + // 购买数量辅助类 + private val mNumberAssist = NumberControlAssist(MIN_NUMBER, MAX_NUMBER) + + /** + * 刷新规格价格 + */ + private fun refreshPrice() { + binding.vidPriceTv.text = adapter.getModel()?.value?.let { spec -> + val price = (mNumberAssist.currentNumber * spec.salePrice) + BigDecimalUtils.round(price).toString() + } + } + + /** + * 规格选择刷新 + */ + private fun refreshBySPEC() { + adapter.getModel()?.value?.let { spec -> + mNumberAssist + // 设置最小购买数量 + .setMinNumber(MIN_NUMBER) + // 设置最大购买数量 + .setMaxNumber(max(min(spec.inventory, MAX_NUMBER), MIN_NUMBER)) + // 设置当前购买数量 + .setCurrentNumber(MIN_NUMBER) + + // ============ + // = 更新 View = + // ============ + + // 设置规格价格 + refreshPrice() + // 加载图片 + binding.vidPicIv.display( + source = spec.picUrl.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + return + } + // 设置购买数量 ( 初始化设置最小购买数量 ) + mNumberAssist.currentNumber = MIN_NUMBER + } + + /** + * 绑定购买数量功能事件 + */ + private fun bindNumberListener() { + mNumberAssist.apply { + + // ================= + // = 数量监听事件接口 = + // ================= + + numberListener = object : INumberListener { + override fun onPrepareChanged( + isAdd: Boolean, + curNumber: Int, + afterNumber: Int + ): Boolean { + return (afterNumber in minNumber..maxNumber) + } + + override fun onNumberChanged( + isAdd: Boolean, + curNumber: Int + ) { + // 小于等于最小购买数量显示灰色按钮 + ViewUtils.setSelected( + curNumber <= minNumber, + binding.vidLeftIv + ) + // 大于等于最大购买数量显示灰色 + ViewUtils.setSelected( + curNumber >= maxNumber, + binding.vidRightIv + ) + EditTextUtils.setText( + binding.vidNumberEt, curNumber.toString() + ) + } + } + + // ========== + // = 输入监听 = + // ========== + + binding.vidNumberEt + .addTextChangedListener(object : EditTextUtils.DevTextWatcher() { + override fun onTextChanged( + text: CharSequence, + start: Int, + before: Int, + count: Int + ) { + val inputNumber = ConvertUtils.toInt(text) + // 数量相同则不处理 + if (inputNumber == currentNumber) { + return + } + // 如果小于最小值则修改为最小值 + if (isLessThanMinNumber(inputNumber)) { + currentNumber = MIN_NUMBER + return + } + // 如果大于最大值则修改为最大值 + if (isGreaterThanMaxNumber(inputNumber)) { + currentNumber = MAX_NUMBER + return + } + currentNumber = inputNumber + } + }) + + // ========== + // = 按钮处理 = + // ========== + + binding.vidLeftIv.setOnClickListener { + subtractionNumber() + } + binding.vidRightIv.setOnClickListener { + addNumber() + } + } + // 设置购买数量 ( 初始化设置最小购买数量 ) + mNumberAssist.currentNumber = MIN_NUMBER // 该代码可不加用于在计算前就更新底部 UI + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/corner_label/CornerLabelActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/corner_label/CornerLabelActivity.kt new file mode 100644 index 0000000000..f4acd9916d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/corner_label/CornerLabelActivity.kt @@ -0,0 +1,134 @@ +package afkt.project.feature.dev_widget.corner_label + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCornerLabelBinding +import afkt.project.model.item.RouterPath +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import com.alibaba.android.arouter.facade.annotation.Route +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ListenerUtils +import dev.utils.common.RandomUtils + +/** + * detail: 自定义角标 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.CornerLabelActivity_PATH) +class CornerLabelActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_corner_label + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidBtnColorTv, binding.vidBtnLeftTv, + binding.vidBtnTopTv, binding.vidBtnTriangleTv, + binding.vidBtnText1MinusTv, binding.vidBtnText1PlusTv, + binding.vidBtnHeight1MinusTv, binding.vidBtnHeight1PlusTv, + binding.vidBtnText2MinusTv, binding.vidBtnText2PlusTv, + binding.vidBtnHeight2MinusTv, binding.vidBtnHeight2PlusTv + ) + } + + override fun onClick(v: View) { + super.onClick(v) + val labelView = binding.vidClv + val layoutParams: FrameLayout.LayoutParams + when (v.id) { + R.id.vid_btn_color_tv -> labelView.setFillColor( + -0x1000000 or RandomUtils.getRandom(0, 0xffffff) + ) + R.id.vid_btn_left_tv -> { + if (mLeft) { + labelView.right() + } else { + labelView.left() + } + mLeft = !mLeft + layoutParams = labelView.layoutParams as FrameLayout.LayoutParams + layoutParams.gravity = + (if (mLeft) Gravity.START else Gravity.END) or if (mTop) Gravity.TOP else Gravity.BOTTOM + labelView.layoutParams = layoutParams + } + R.id.vid_btn_top_tv -> { + if (mTop) { + labelView.bottom() + } else { + labelView.top() + } + mTop = !mTop + layoutParams = labelView.layoutParams as FrameLayout.LayoutParams + layoutParams.gravity = + (if (mLeft) Gravity.START else Gravity.END) or if (mTop) Gravity.TOP else Gravity.BOTTOM + labelView.layoutParams = layoutParams + } + R.id.vid_btn_triangle_tv -> { + mTriangle = !mTriangle + labelView.triangle(mTriangle) + } + R.id.vid_btn_text1_minus_tv -> { + mText1Index = (mText1Index - 1 + TEXTS.size) % TEXTS.size + labelView.setText1(TEXTS[mText1Index]) + } + R.id.vid_btn_text1_plus_tv -> { + mText1Index = (mText1Index + 1) % TEXTS.size + labelView.setText1(TEXTS[mText1Index]) + } + R.id.vid_btn_height1_minus_tv -> { + if (mText1Height < 8) return + mText1Height -= 2F + convertPx = AppSize.sp2pxf(mText1Height) + labelView.setTextHeight1(convertPx) + labelView.setPaddingTop(convertPx) + labelView.setPaddingCenter(convertPx / 3) + labelView.setPaddingBottom(convertPx / 3) + } + R.id.vid_btn_height1_plus_tv -> { + if (mText1Height > 30) return + mText1Height += 2F + convertPx = AppSize.sp2pxf(mText1Height) + labelView.setTextHeight1(convertPx) + labelView.setPaddingTop(convertPx) + labelView.setPaddingCenter(convertPx / 3) + labelView.setPaddingBottom(convertPx / 3) + } + R.id.vid_btn_text2_minus_tv -> { + mText2Index = (mText2Index + 5 - 1) % 5 + labelView.setText2("1234567890".substring(0, mText2Index)) + } + R.id.vid_btn_text2_plus_tv -> { + mText2Index = (mText2Index + 5 + 1) % 5 + labelView.setText2("1234567890".substring(0, mText2Index)) + } + R.id.vid_btn_height2_minus_tv -> { + if (mText2Height < 4) return + mText2Height -= 2F + convertPx = AppSize.sp2pxf(mText2Height) + labelView.setTextHeight2(convertPx) + } + R.id.vid_btn_height2_plus_tv -> { + if (mText2Height > 20) return + mText2Height += 2F + convertPx = AppSize.sp2pxf(mText2Height) + labelView.setTextHeight2(convertPx) + } + } + } + + private var convertPx = 0F + private var mText1Index = 3 + private var mText1Height = 12F + private var mText2Index = 3 + private var mText2Height = 8F + private var mLeft = true + private var mTop = true + private var mTriangle = false + + companion object { + val TEXTS = arrayOf("滿減", "赠品", "满送", "包邮", "拼图", "新人", "砍价", "预售", "众筹") + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardActivity.kt new file mode 100644 index 0000000000..e741ddc8cd --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardActivity.kt @@ -0,0 +1,76 @@ +package afkt.project.feature.dev_widget.flip_card + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityFlipCardBinding +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.utils.app.HandlerUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.timer.DevTimer + +/** + * detail: 翻转卡片 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.FlipCardActivity_PATH) +class FlipCardActivity : BaseActivity() { + + // 翻转定时器 + private var flipTimer: DevTimer? = null + + override fun baseLayoutId(): Int = R.layout.activity_flip_card + + override fun initView() { + super.initView() + +// // 也可以自定义动画效果 +// binding.vidFcv1.setInOutAnimator( +// AnimatorInflater.loadAnimator(mContext, R.animator.dev_flip_card_in), +// AnimatorInflater.loadAnimator(mContext, R.animator.dev_flip_card_out) +// ) + +// binding.vidFcv1.adapter = FlipCardAdapter( +// mutableListOf( +// DevSource.create(R.drawable.bg_wallpaper), +// DevSource.create(ResourceUtils.openRawResource(R.raw.wallpaper_1)), +// DevSource.create("https://picsum.photos/20${RandomUtils.getRandom(0, 10)}"), +// DevSource.create(ResourceUtils.openRawResource(R.raw.wallpaper_2)), +// ) +// ) +// +// binding.vidFcv2.adapter = FlipCardAdapter( +// mutableListOf( +// DevSource.create(ResourceUtils.openRawResource(R.raw.wallpaper_5)), +// DevSource.create(ResourceUtils.openRawResource(R.raw.wallpaper_4)), +// DevSource.create("https://picsum.photos/20${RandomUtils.getRandom(0, 10)}"), +// DevSource.create("https://picsum.photos/20${RandomUtils.getRandom(0, 10)}"), +// ) +// ) + + binding.vidFcv3.adapter = FlipCardAdapter( + mutableListOf( + DevSource.create(ResourceUtils.openRawResource(R.raw.wallpaper_3)) + ) + ) + HandlerUtils.postRunnable({ + binding.vidFcv3.flip() + }, 1000 * 10) + +// flipTimer = DevTimer.Builder(5000L, 5000L, -1) +// .build().setHandler(Handler()) +// .setCallback { timer: DevTimer?, number: Int, end: Boolean, infinite: Boolean -> +// if (!ActivityUtils.isFinishing(mActivity)) { +//// binding.vidFcv1.flip() +//// binding.vidFcv2.flip() +// binding.vidFcv3.flip() +// } +// }.start() + } + + override fun onDestroy() { + super.onDestroy() + flipTimer?.stop() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardAdapter.kt new file mode 100644 index 0000000000..5e865e75b9 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flip_card/FlipCardAdapter.kt @@ -0,0 +1,37 @@ +package afkt.project.feature.dev_widget.flip_card + +import afkt.project.utils.IMAGE_ROUND_10 +import android.content.Context +import android.view.View +import dev.base.DevSource +import dev.base.widget.BaseImageView +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.widget.ui.FlipCardView + +/** + * detail: 翻转卡片适配器 + * @author Ttt + */ +class FlipCardAdapter(val lists: List) : FlipCardView.Adapter { + + override fun getItemCount(): Int { + return lists.size + } + + override fun getItemView( + context: Context?, + position: Int, + isFrontView: Boolean + ): View? { + context?.let { + val imageView = BaseImageView(it) + imageView.display( + source = lists[position], + config = IMAGE_ROUND_10.toImageConfig() + ) + return imageView + } + return null + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flow_like/FlowLikeActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flow_like/FlowLikeActivity.kt new file mode 100644 index 0000000000..a22c9ff029 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/flow_like/FlowLikeActivity.kt @@ -0,0 +1,51 @@ +package afkt.project.feature.dev_widget.flow_like + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityFlowLikeBinding +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route + +/** + * detail: 自定义点赞效果 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.FlowLikeActivity_PATH) +class FlowLikeActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_flow_like + + override fun initValue() { + super.initValue() + +// app:dev_animDuration="2000" +// app:dev_iconHeight="30.0dp" +// app:dev_iconWidth="30.0dp" + +// // 设置动画时间 +// binding.vidFlv.animDuration = 2000 +// // 设置图标宽度 +// binding.vidFlv.iconWidth = AppSize.dp2px(30F) +// // 设置图标高度 +// binding.vidFlv.iconHeight = AppSize.dp2px(30F) +// +// // 设置漂浮图标 +// val lists = mutableListOf() +// lists.add(ResourceUtils.getDrawable(R.drawable.icon_live_brow_1)) +// lists.add(ResourceUtils.getDrawable(R.drawable.icon_live_brow_2)) +// lists.add(ResourceUtils.getDrawable(R.drawable.icon_live_brow_3)) +// lists.add(ResourceUtils.getDrawable(R.drawable.icon_live_brow_4)) +// lists.add(ResourceUtils.getDrawable(R.drawable.icon_live_brow_5)) +// binding.vidFlv.drawables = lists + + // 设置漂浮图标 + binding.vidFlv.setDrawablesById( + R.drawable.icon_live_brow_1, R.drawable.icon_live_brow_2, + R.drawable.icon_live_brow_3, R.drawable.icon_live_brow_4, R.drawable.icon_live_brow_5 + ) + binding.vidFlv.setOnClickListener { + binding.vidFlv.like() + binding.vidFlv.like() // 演示效果 + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/line_view/LineActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/line_view/LineActivity.kt new file mode 100644 index 0000000000..3ed6a124ac --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/line_view/LineActivity.kt @@ -0,0 +1,46 @@ +package afkt.project.feature.dev_widget.line_view + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityLineBinding +import afkt.project.model.item.RouterPath +import android.graphics.Color +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +/** + * detail: 换行监听 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.LineActivity_PATH) +class LineActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_line + + override fun initValue() { + super.initValue() + + // 设置监听 + binding.vidContentTv.setNewLineCallback { isNewLine, line -> + val builder = StringBuilder() + builder.append("是否换行: ").append(isNewLine) + builder.append("\n换行数量: ").append(line) + binding.vidTv.text = builder.toString() + } + binding.vidContentTv.setOnClickListener { // 随机字符串 + val text = ChineseUtils.randomWord(RandomUtils.getRandom(300)) + + RandomUtils.getRandomLetters(RandomUtils.getRandom(50)) + val randomText = RandomUtils.getRandom(text.toCharArray(), text.length) + // 设置内容 + QuickHelper.get(binding.vidContentTv) + .setTextColors(Color.BLACK) + .setTextSizeBySp(RandomUtils.getRandom(13, 25).toFloat()) + .setBold(RandomUtils.nextBoolean()) + .setText(randomText) + } + // 默认点击 + binding.vidContentTv.performClick() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/progress_bar/ProgressBarActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/progress_bar/ProgressBarActivity.kt new file mode 100644 index 0000000000..1caa5f3afd --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/progress_bar/ProgressBarActivity.kt @@ -0,0 +1,75 @@ +package afkt.project.feature.dev_widget.progress_bar + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityProgressbarBinding +import afkt.project.model.item.RouterPath +import android.annotation.SuppressLint +import android.os.Handler +import android.os.Message +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.ActivityUtils + +/** + * detail: 自定义 ProgressBar 样式 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.ProgressBarActivity_PATH) +class ProgressBarActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_progressbar + + override fun initValue() { + super.initValue() + +// // 内外圆环 + 数字 + 无扇形 +// binding.vidLpb1.setProgressStyle(LoadProgressBar.ProgressStyle.RINGS) +// .setOuterRingWidth(AppSize.dp2px(5F).toFloat()) // 内环宽度 +// .setOuterRingColor(ResourceUtils.getColor(R.color.khaki)) // 内环颜色 +// .setProgressColor(ResourceUtils.getColor(R.color.color_88)).isCanvasNumber = +// true // 是否绘制数字 +// +// // 扇形 + 数字 + 无内外圆环 +// binding.vidLpb2.setProgressStyle(LoadProgressBar.ProgressStyle.FAN_SHAPED) +// .setProgressColor(ResourceUtils.getColor(R.color.sky_blue)).isCanvasNumber = +// true // 是否绘制数字 +// +// // 扇形 + 数字 + 外圆环 +// binding.vidLpb3.setProgressStyle(LoadProgressBar.ProgressStyle.ARC_FAN_SHAPED) +// .setOuterRingWidth(AppSize.dp2px(1F).toFloat()) // 内环宽度 +// .setOuterRingColor(Color.RED) // 内环颜色 +// .setProgressColor(ResourceUtils.getColor(R.color.mediumturquoise)) // 进度颜色 +// .setNumberTextColor(Color.parseColor("#FB7D00")).isCanvasNumber = true // 是否绘制数字 +// +// // 单独字体 +// binding.vidLpb4.setProgressStyle(LoadProgressBar.ProgressStyle.NUMBER) +// .setNumberTextSize(20F).numberTextColor = +// ResourceUtils.getColor(R.color.deeppink) // 字体颜色 + + // 延迟发送通知 + handler.sendEmptyMessageDelayed(0, 100) + } + + @SuppressLint("HandlerLeak") + private val handler = object : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + // 如果页面销毁了则不处理 + if (ActivityUtils.isFinishing(mActivity)) return + try { + val progress = binding.vidLpb1.progress + 1 + // 每次进行累加 + binding.vidLpb1.progress = progress + binding.vidLpb2.progress = progress + binding.vidLpb3.progress = progress + binding.vidLpb4.progress = progress + // 判断是否符合条件 + if (binding.vidLpb1.progress < binding.vidLpb1.max) { + // 延迟发送通知 + sendEmptyMessageDelayed(0, 100) + } + } catch (ignored: Exception) { + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/CommonColorItemDecorationAssist.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/CommonColorItemDecorationAssist.kt new file mode 100644 index 0000000000..75588d7296 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/CommonColorItemDecorationAssist.kt @@ -0,0 +1,100 @@ +package afkt.project.feature.dev_widget.recy_item_decoration + +import androidx.recyclerview.widget.RecyclerView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.helper.quick.QuickHelper +import dev.widget.decoration.BaseColorItemDecoration +import java.util.concurrent.atomic.AtomicInteger + +/** + * detail: DevWidget Color ItemDecoration 演示通用处理辅助类 + * @author Ttt + */ +class CommonColorItemDecorationAssist( + private val recyclerView: RecyclerView, + // 最大添加数量 + private val MAX: Int = 3 +) { + + /** + * 循环设置 Item Left、Right + * @param list ItemDecoration List + */ + fun forItemLeftRight(vararg list: List) { + list.forEach { + it.forEachIndexed { index, item -> + val value = AppSize.dp2pxf((index + 1) * 5.0F) + item.setLeftRight(value, value) + } + } + } + + /** + * 通用添加 ItemDecoration 方法 + * @param list ItemDecoration List + * @param index AtomicInteger + */ + fun addItemDecoration( + list: List, + index: AtomicInteger + ) { + if (index.get() >= MAX) { + return + } + // 计算偏差值 + calcOffset(list, index.get() + 1) + // 添加 ItemDecoration + QuickHelper.get(recyclerView) + .addItemDecoration(list[index.get()]) + .notifyDataSetChanged() + index.incrementAndGet() + } + + /** + * 通用移除 ItemDecoration 方法 + * @param list ItemDecoration List + * @param index AtomicInteger + */ + fun removeItemDecoration( + list: List, + index: AtomicInteger + ) { + if (index.get() <= 0) { + return + } + index.decrementAndGet() + // 计算偏差值 + calcOffset(list, index.get()) + // 移除 ItemDecoration + QuickHelper.get(recyclerView) + .removeItemDecoration(list[index.get()]) + .notifyDataSetChanged() + } + + // = + + /** + * 计算 Item 偏差值 + * @param list ItemDecoration List + * @param number Int + */ + private fun calcOffset( + list: List, + number: Int + ) { + val numberIndex = number - 1 + list.forEachIndexed { index, item -> + // 先重置为 0 + item.offset = 0.0F + if (numberIndex >= index) { + // 偏差值 ( 用于解决多个 ItemDecoration 叠加覆盖问题 ) + var offset = 0.0F + for (i in (index + 1)..numberIndex) { + offset += list[i].height + } + // 设置偏差值 + item.offset = offset + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationActivity.kt new file mode 100644 index 0000000000..b9055a1193 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationActivity.kt @@ -0,0 +1,51 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.color_grid + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityGridItemDecorationBinding +import afkt.project.feature.dev_widget.recy_item_decoration.common.GridHorizontalTextAdapter +import afkt.project.feature.dev_widget.recy_item_decoration.common.GridVerticalTextAdapter +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.RecyclerViewUtils + +/** + * detail: Grid Color ItemDecoration + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.GridColorItemDecorationActivity_PATH) +class GridColorItemDecorationActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_grid_item_decoration + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 1..11) { + lists.add(i.toString()) + } + + when (moduleType) { + ButtonValue.BTN_GRID_ITEM_VERTICAL -> { + RecyclerViewUtils.setOrientation( + binding.vidRv, RecyclerView.VERTICAL + ) + GridVerticalTextAdapter(lists).bindAdapter( + binding.vidRv + ) + } + ButtonValue.BTN_GRID_ITEM_HORIZONTAL -> { + RecyclerViewUtils.setOrientation( + binding.vidRv, RecyclerView.HORIZONTAL + ) + GridHorizontalTextAdapter(lists).bindAdapter( + binding.vidRv + ) + } + } + GridColorItemDecorationAssist(binding.vidRv, binding.vidInclude) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationAssist.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationAssist.kt new file mode 100644 index 0000000000..93129914a6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_grid/GridColorItemDecorationAssist.kt @@ -0,0 +1,125 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.color_grid + +import afkt.project.databinding.IncludeGridItemDecorationAssistBinding +import afkt.project.feature.dev_widget.recy_item_decoration.CommonColorItemDecorationAssist +import androidx.recyclerview.widget.RecyclerView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.RecyclerViewUtils +import dev.utils.common.ColorUtils +import dev.utils.common.RandomUtils +import dev.widget.decoration.BaseColorGridItemDecoration +import dev.widget.decoration.grid.GridColumnColorItemDecoration +import dev.widget.decoration.grid.GridRowColorItemDecoration +import java.util.concurrent.atomic.AtomicInteger + +/** + * detail: DevWidget Color ItemDecoration 演示通用处理辅助类 + * @author Ttt + */ +internal class GridColorItemDecorationAssist( + private val recyclerView: RecyclerView, + private val binding: IncludeGridItemDecorationAssistBinding +) { + + // DevWidget Color ItemDecoration 演示通用处理辅助类 + private val assist: CommonColorItemDecorationAssist by lazy { + CommonColorItemDecorationAssist(recyclerView) + } + + // 每条数据底部添加 列分割线处理 ItemDecoration + private val lineColumnList = mutableListOf() + + // 每条数据底部添加 行分割线处理 ItemDecoration + private val lineRowList = mutableListOf() + + // 递增数 + private var lineColumnIndex = AtomicInteger() + private var lineRowIndex = AtomicInteger() + + // ============ + // = 初始化数据 = + // ============ + + init { + val height = AppSize.dp2pxf(10.0F) + val vertical = RecyclerViewUtils.canScrollVertically(recyclerView) + val spanCount = RecyclerViewUtils.getSpanCount(recyclerView) + + // ======== + // = Line = + // ======== + + lineColumnList.add( + GridColumnColorItemDecoration( + spanCount, vertical, height, ColorUtils.CHOCOLATE + ) + ) + lineColumnList.add( + GridColumnColorItemDecoration( + spanCount, vertical, height, ColorUtils.CYAN + ) + ) + lineColumnList.add( + GridColumnColorItemDecoration( + spanCount, vertical, height, ColorUtils.ORANGE + ) + ) + + lineRowList.add( + GridRowColorItemDecoration( + spanCount, vertical, height, ColorUtils.CHOCOLATE + ) + ) + lineRowList.add( + GridRowColorItemDecoration( + spanCount, vertical, height, ColorUtils.CYAN + ) + ) + lineRowList.add( + GridRowColorItemDecoration( + spanCount, vertical, height, ColorUtils.ORANGE + ) + ) + + // ================================= + // = 通用设置 ItemDecoration 左右边距 = + // ================================= + + if (RandomUtils.nextBoolean()) { + setItemLeftRight() + } + initListener() + } + + /** + * 通用设置 ItemDecoration 左右边距 + */ + private fun setItemLeftRight() { + assist.forItemLeftRight( + lineColumnList, lineRowList + ) + } + + /** + * 初始化事件 + */ + private fun initListener() { + + // ======== + // = Line = + // ======== + + binding.vidLineColumnAddBtn.setOnClickListener { + assist.addItemDecoration(lineColumnList, lineColumnIndex) + } + binding.vidLineColumnRemoveBtn.setOnClickListener { + assist.removeItemDecoration(lineColumnList, lineColumnIndex) + } + binding.vidLineRowAddBtn.setOnClickListener { + assist.addItemDecoration(lineRowList, lineRowIndex) + } + binding.vidLineRowRemoveBtn.setOnClickListener { + assist.removeItemDecoration(lineRowList, lineRowIndex) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationActivity.kt new file mode 100644 index 0000000000..4663e3bb1d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationActivity.kt @@ -0,0 +1,51 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.color_linear + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityLinearItemDecorationBinding +import afkt.project.feature.dev_widget.recy_item_decoration.common.LinearHorizontalTextAdapter +import afkt.project.feature.dev_widget.recy_item_decoration.common.LinearVerticalTextAdapter +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.RecyclerViewUtils + +/** + * detail: Linear Color ItemDecoration + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.LinearColorItemDecorationActivity_PATH) +class LinearColorItemDecorationActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_linear_item_decoration + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 1..3) { + lists.add(i.toString()) + } + + when (moduleType) { + ButtonValue.BTN_LINEAR_ITEM_VERTICAL -> { + RecyclerViewUtils.setOrientation( + binding.vidRv, RecyclerView.VERTICAL + ) + LinearVerticalTextAdapter(lists).bindAdapter( + binding.vidRv + ) + } + ButtonValue.BTN_LINEAR_ITEM_HORIZONTAL -> { + RecyclerViewUtils.setOrientation( + binding.vidRv, RecyclerView.HORIZONTAL + ) + LinearHorizontalTextAdapter(lists).bindAdapter( + binding.vidRv + ) + } + } + LinearColorItemDecorationAssist(binding.vidRv, binding.vidInclude) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationAssist.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationAssist.kt new file mode 100644 index 0000000000..18d63fb1ff --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/color_linear/LinearColorItemDecorationAssist.kt @@ -0,0 +1,169 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.color_linear + +import afkt.project.databinding.IncludeLinearItemDecorationAssistBinding +import afkt.project.feature.dev_widget.recy_item_decoration.CommonColorItemDecorationAssist +import androidx.recyclerview.widget.RecyclerView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.RecyclerViewUtils +import dev.utils.common.ColorUtils +import dev.utils.common.RandomUtils +import dev.widget.decoration.BaseColorItemDecoration +import dev.widget.decoration.linear.FirstLinearColorItemDecoration +import dev.widget.decoration.linear.LastLinearColorItemDecoration +import dev.widget.decoration.linear.LinearColorItemDecoration +import java.util.concurrent.atomic.AtomicInteger + +/** + * detail: DevWidget Color ItemDecoration 演示通用处理辅助类 + * @author Ttt + */ +internal class LinearColorItemDecorationAssist( + private val recyclerView: RecyclerView, + private val binding: IncludeLinearItemDecorationAssistBinding +) { + + // DevWidget Color ItemDecoration 演示通用处理辅助类 + private val assist: CommonColorItemDecorationAssist by lazy { + CommonColorItemDecorationAssist(recyclerView) + } + + // 第一条数据顶部添加 ItemDecoration + private val firstList = mutableListOf() + + // 最后一条数据底部添加 ItemDecoration + private val lastList = mutableListOf() + + // 每条数据底部添加 ItemDecoration + private val lineList = mutableListOf() + + // 递增数 + private var firstIndex = AtomicInteger() + private var lastIndex = AtomicInteger() + private var lineIndex = AtomicInteger() + + // ============ + // = 初始化数据 = + // ============ + + init { + val height = AppSize.dp2pxf(10.0F) + val vertical = RecyclerViewUtils.canScrollVertically(recyclerView) + + // ======== + // = First = + // ======== + + firstList.add( + FirstLinearColorItemDecoration( + vertical, height, ColorUtils.RED + ) + ) + firstList.add( + FirstLinearColorItemDecoration( + vertical, height, ColorUtils.BLUE + ) + ) + firstList.add( + FirstLinearColorItemDecoration( + vertical, height, ColorUtils.GREEN + ) + ) + + // ======== + // = Last = + // ======== + + lastList.add( + LastLinearColorItemDecoration( + vertical, height, ColorUtils.GOLD + ) + ) + lastList.add( + LastLinearColorItemDecoration( + vertical, height, ColorUtils.PINK + ) + ) + lastList.add( + LastLinearColorItemDecoration( + vertical, height, ColorUtils.PURPLE + ) + ) + + // ======== + // = Line = + // ======== + + lineList.add( + LinearColorItemDecoration( + vertical, height, ColorUtils.CHOCOLATE + ) + ) + lineList.add( + LinearColorItemDecoration( + vertical, height, ColorUtils.CYAN + ) + ) + lineList.add( + LinearColorItemDecoration( + vertical, height, ColorUtils.ORANGE + ) + ) + + // ================================= + // = 通用设置 ItemDecoration 左右边距 = + // ================================= + + if (RandomUtils.nextBoolean()) { + setItemLeftRight() + } + initListener() + } + + /** + * 通用设置 ItemDecoration 左右边距 + */ + private fun setItemLeftRight() { + assist.forItemLeftRight( + firstList, lastList, lineList + ) + } + + /** + * 初始化事件 + */ + private fun initListener() { + + // ======== + // = First = + // ======== + + binding.vidFirstAddBtn.setOnClickListener { + assist.addItemDecoration(firstList, firstIndex) + } + binding.vidFirstRemoveBtn.setOnClickListener { + assist.removeItemDecoration(firstList, firstIndex) + } + + // ======== + // = Last = + // ======== + + binding.vidLastAddBtn.setOnClickListener { + assist.addItemDecoration(lastList, lastIndex) + } + binding.vidLastRemoveBtn.setOnClickListener { + assist.removeItemDecoration(lastList, lastIndex) + } + + // ======== + // = Line = + // ======== + + binding.vidLineAddBtn.setOnClickListener { + assist.addItemDecoration(lineList, lineIndex) + } + binding.vidLineRemoveBtn.setOnClickListener { + assist.removeItemDecoration(lineList, lineIndex) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridHorizontalTextAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridHorizontalTextAdapter.kt new file mode 100644 index 0000000000..ab694d26c4 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridHorizontalTextAdapter.kt @@ -0,0 +1,34 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.common + +import afkt.project.R +import afkt.project.databinding.AdapterGridHorizontalTextBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Grid Text Adapter + * @author Ttt + */ +class GridHorizontalTextAdapter(data: MutableList) : + DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_grid_horizontal_text) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTv.text = getDataItem(position) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridVerticalTextAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridVerticalTextAdapter.kt new file mode 100644 index 0000000000..06dae1fe52 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/GridVerticalTextAdapter.kt @@ -0,0 +1,34 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.common + +import afkt.project.R +import afkt.project.databinding.AdapterGridVerticalTextBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Grid Text Adapter + * @author Ttt + */ +class GridVerticalTextAdapter(data: MutableList) : + DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_grid_vertical_text) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTv.text = getDataItem(position) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearHorizontalTextAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearHorizontalTextAdapter.kt new file mode 100644 index 0000000000..eecdcd62a0 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearHorizontalTextAdapter.kt @@ -0,0 +1,34 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.common + +import afkt.project.R +import afkt.project.databinding.AdapterLinearHorizontalTextBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Linear Text Adapter + * @author Ttt + */ +class LinearHorizontalTextAdapter(data: MutableList) : + DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_linear_horizontal_text) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTv.text = getDataItem(position) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearVerticalTextAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearVerticalTextAdapter.kt new file mode 100644 index 0000000000..d33f3eecbb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/recy_item_decoration/common/LinearVerticalTextAdapter.kt @@ -0,0 +1,34 @@ +package afkt.project.feature.dev_widget.recy_item_decoration.common + +import afkt.project.R +import afkt.project.databinding.AdapterLinearVerticalTextBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Linear Text Adapter + * @author Ttt + */ +class LinearVerticalTextAdapter(data: MutableList) : + DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_linear_vertical_text) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTv.text = getDataItem(position) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeActivity.kt new file mode 100644 index 0000000000..8451f46ca6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeActivity.kt @@ -0,0 +1,200 @@ +package afkt.project.feature.dev_widget.scan_shape + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityScanShapeBinding +import afkt.project.model.item.RouterPath +import android.Manifest +import android.view.SurfaceHolder +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.log.log_eTag +import dev.expand.engine.permission.permission_request +import dev.utils.app.ListenerUtils +import dev.utils.app.ViewUtils +import dev.utils.app.camera.camera1.CameraAssist +import dev.utils.app.camera.camera1.CameraUtils +import dev.utils.app.camera.camera1.FlashlightUtils +import dev.utils.app.permission.PermissionUtils +import dev.utils.app.toast.ToastTintUtils +import dev.widget.ui.ScanShapeView + +/** + * detail: 自定义扫描 View ( QRCode、AR ) + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.ScanShapeActivity_PATH) +class ScanShapeActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_scan_shape + + override fun onDestroy() { + // 销毁处理 + binding.vidSsv.destroy() + super.onDestroy() + } + + override fun onResume() { + super.onResume() + // 开始动画 + binding.vidSsv.startAnim() + // 添加回调 + binding.vidSurface.holder?.addCallback(mHolderCallback) + } + + override fun onPause() { + super.onPause() + // 停止动画 + binding.vidSsv.stopAnim() + try {// 停止预览 + cameraAssist.stopPreview() + } catch (ignored: Exception) { + } + } + + override fun initValue() { + super.initValue() + // 设置扫描类型 + ScanShapeUtils.refShape(binding.vidSsv, ScanShapeView.Shape.Square) + } + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidFlashlightIv, + binding.vidSquareIv, + binding.vidHexagonIv, + binding.vidAnnulusIv + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_flashlight_iv -> { + if (!FlashlightUtils.isFlashlightEnable()) { + showToast(false, "暂不支持开启手电筒") + return + } + // 设置开关 + setFlashlight(!ViewUtils.isSelected(binding.vidFlashlightIv)) + } + R.id.vid_square_iv -> ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Square + ) + R.id.vid_hexagon_iv -> ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Hexagon + ) + R.id.vid_annulus_iv -> ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Annulus + ) + } + } + + // ============ + // = 摄像头预览 = + // ============ + + // 摄像头辅助类 + val cameraAssist = CameraAssist() + + private val mHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + // 检查权限 + checkPermission() + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + // 关闭手电筒 + setFlashlight(false) + try { + cameraAssist.stopPreview() + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "surfaceDestroyed" + ) + } + } + } + + /** + * 检查摄像头权限 + */ + private fun checkPermission() { + // 摄像头权限 + val cameraPermission = Manifest.permission.CAMERA + // 判断是否允许权限 + if (PermissionUtils.isGranted(cameraPermission)) { + try { + // 打开摄像头 + val camera = CameraUtils.open() + camera.setDisplayOrientation(90) // 设置竖屏显示 + cameraAssist.camera = camera + // 获取预览大小 + val size = cameraAssist.cameraResolution + // 设置预览大小, 需要这样设置, 开闪光灯才不会闪烁 + val parameters = camera.parameters + parameters.setPreviewSize(size.width, size.height) + camera.parameters = parameters + // 开始预览 + cameraAssist.openDriver(binding.vidSurface.holder).startPreview() +// // 默认开启自动对焦, 设置不需要自动对焦 +// cameraAssist.setAutoFocus(false) + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "checkPermission startPreview" + ) + } + } else { + permission_request( + permissions = arrayOf(cameraPermission), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + // 刷新处理 + checkPermission() + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + ToastTintUtils.warning("需要摄像头权限预览") + } + } + ) + } + } + + // ============ + // = 手电筒处理 = + // ============ + + /** + * 设置手电筒开关 + * @param open 是否打开 + */ + private fun setFlashlight(open: Boolean) { + if (open) { + cameraAssist.setFlashlightOn() + } else { + cameraAssist.setFlashlightOff() + } + ViewUtils.setSelected(open, binding.vidFlashlightIv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeUtils.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeUtils.kt new file mode 100644 index 0000000000..68fe48bd75 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/scan_shape/ScanShapeUtils.kt @@ -0,0 +1,197 @@ +package afkt.project.feature.dev_widget.scan_shape + +import afkt.project.R +import android.graphics.Color +import android.graphics.Rect +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ResourceUtils +import dev.utils.app.ScreenUtils +import dev.widget.ui.ScanShapeView + +/** + * detail: ScanShapeView 工具类 + * @author Ttt + */ +object ScanShapeUtils { + + /** + * 刷新类型处理 + * @param scanView 扫描 View + * @param scanShape 扫描类型 + */ + @JvmStatic + fun refShape( + scanView: ScanShapeView, + scanShape: ScanShapeView.Shape + ) { + // 设置扫描 View 类型 + scanView.shapeType = scanShape + // 设置扫描区域大小 ( 扫描 View) 无关阴影背景以及整个 View 宽高 + scanView.setRegion(ScreenUtils.getScreenWidth() * 0.66F) + + val isExecute = false + if (isExecute) { + + // 销毁方法 + scanView.destroy() + // 启动动画 + scanView.startAnim() + // 停止动画 + scanView.stopAnim() + // 动画是否运行中 + scanView.isAnimRunning + + // ======= + // = 共用 = + // ======= + + // 设置扫描 View 类型 + scanView.shapeType = scanShape + // 获取扫描 View 类型 + scanView.shapeType + // 设置是否绘制背景 + scanView.isDrawBackground = true + // 设置背景颜色 - ( 黑色 百分之 40 透明度 ) #66000000 + scanView.bgColor = Color.argb(102, 0, 0, 0) + // 设置是否自动启动动画 + scanView.isAutoAnim = false + // 是否需要绘制动画 ( 效果 ) + scanView.isDrawAnim = false + // 设置拐角效果 + scanView.setCornerEffect(ScanShapeView.CornerEffect(10.0F)) + // 设置扫描区域大小 ( 扫描 View) 无关阴影背景以及整个 View 宽高 + scanView.setRegion(500F) + scanView.setRegion(500F, 500F) + scanView.setRegion(Rect(0, 0, 500, 500)) + // 获取扫描区域 距离 整个 View 的左 / 右边距 距离 + scanView.regionLeft + // 获取扫描区域 距离 整个 View 的上 / 下边距 距离 + scanView.regionTop + // 获取扫描区域位置信息 + scanView.region // 获取扫描区域位置信息 + scanView.getRegion(100F, 200F) // 获取纠偏 ( 偏差 ) 位置后的扫描区域 + scanView.regionParent // 获取扫描区域在 View 中的位置 + scanView.regionWidth + scanView.regionHeight + // 获取边框边距 + scanView.borderMargin + // 设置扫描区域绘制边框边距 + scanView.borderMargin = 0F + // 设置扫描区域边框颜色 + scanView.borderColor = Color.WHITE + // 设置扫描区域边框宽度 + scanView.borderWidth = AppSize.dp2pxf(2F) + // 是否绘制边框 + scanView.isDrawBorder = true + + // =============== + // = 正方形特殊配置 = + // =============== + + // 设置 正方形描边 ( 边框 ), 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 + scanView.borderToSquare = 0 + // 设置四个角落与边框共存时, 对应边框宽度 + scanView.borderWidthToSquare = AppSize.dp2pxf(1F) + // 设置每个角的点距离 ( 长度 ) + scanView.triAngleLength = AppSize.dp2pxf(20F) + // 设置特殊处理 ( 正方形边框 ) - 当 描边类型为 2 , 并且存在圆角时, 设置距离尺寸过大会出现边框圆角 + 四个角落圆角有部分透出背景情况 + scanView.isSpecialToSquare = + false // 出现的时候则设置 true, 小尺寸 (setBorderWidthToSquare, setBorderWidth) 则不会出现 + // 设置正方形扫描动画速度 ( 毫秒 ) + scanView.lineDurationToSquare = 10L + // 设置正方形扫描线条 Bitmap + scanView.bitmapToSquare = ResourceUtils.getBitmap(R.drawable.dev_scan_line) + // 设置正方形线条动画 ( 着色 ) -> 如果不使用自己的 Bitmap(setBitmapToSquare), 则可以使用默认内置的图片, 进行着色达到想要的颜色 + scanView.lineColorToSquare = Color.WHITE + // 设置正方形扫描线条向上 ( 下 ) 边距 + scanView.lineMarginTopToSquare = 0F + // 设置正方形扫描线条向左 ( 右 ) 边距 + scanView.lineMarginLeftToSquare = 0F + + // =============== + // = 六边形特殊配置 = + // =============== + + // 设置六边形线条动画 - 线条宽度 + scanView.lineWidthToHexagon = 4F + // 置六边形线条动画 - 线条边距 + scanView.lineMarginToHexagon = 20F + // 设置六边形线条动画方向 true = 从左到右, false = 从右到左 + scanView.isLineAnimDirection = true + // 设置六边形线条动画颜色 + scanView.lineColorToHexagon = Color.WHITE + + // ============= + // = 环形特殊配置 = + // ============= + + // 设置环形扫描线条 Bitmap + scanView.bitmapToAnnulus = ResourceUtils.getBitmap(R.drawable.dev_scan_line) + // 设置环形线条动画 ( 着色 ) + scanView.lineColorToAnnulus = Color.WHITE + // 设置环形扫描线条速度 + scanView.lineOffsetSpeedToAnnulus = 4F + // 设置环形对应的环是否绘制 0 - 外环, 1 - 中间环, 2 - 外环 + scanView.setAnnulusDraws(false, true, true) + // 设置环形对应的环绘制颜色 0 - 外环, 1 - 中间环, 2 - 外环 + scanView.setAnnulusColors(Color.BLUE, Color.RED, Color.WHITE) + // 设置环形对应的环绘制长度 0 - 外环, 1 - 中间环, 2 - 外环 + scanView.setAnnulusLengths(20, 30, 85) + // 设置环形对应的环绘制宽度 0 - 外环, 1 - 中间环, 2 - 外环 + scanView.setAnnulusWidths( + AppSize.dp2pxf(3F), + AppSize.dp2pxf(7F), + AppSize.dp2pxf(7F) + ) + // 设置环形对应的环绘制边距 0 - 外环, 1 - 中间环, 2 - 外环 + scanView.setAnnulusMargins( + AppSize.dp2pxf(7F), + AppSize.dp2pxf(7F), + AppSize.dp2pxf(7F) + ) + } + + // 设置是否需要阴影背景 + scanView.isDrawBackground = true + // 判断类型 + when (scanShape) { + // 正方形 + ScanShapeView.Shape.Square -> { + // 天蓝色 + val squareColor = Color.argb(255, 0, 128, 255) + // 设置扫描线条颜色 + scanView.lineColorToSquare = squareColor + // 边框颜色 + scanView.borderColor = squareColor + // 设置圆角 + scanView.setCornerEffect(ScanShapeView.CornerEffect(10.0F)) +// // 不需要圆角 +// scanView.setCornerEffect(null) +// // 设置 正方形描边 ( 边框 ), 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 +// scanView.borderToSquare = 2 + } + // 六边形 + ScanShapeView.Shape.Hexagon -> { + // 白色 + val hexagonColor = Color.WHITE + // 边框颜色 + scanView.borderColor = hexagonColor + // 设置六边形线条动画颜色 + scanView.lineColorToHexagon = hexagonColor +// // 设置六边形线条动画方向 true = 从左到右, false = 从右到左 +// scanView.isLineAnimDirection = false + } + // 环形 + ScanShapeView.Shape.Annulus -> { + // 设置环形线条动画 ( 着色 ) + scanView.lineColorToAnnulus = Color.RED + // 设置是否需要阴影背景 + scanView.isDrawBackground = false +// // 设置环形扫描线条速度 +// scanView.lineOffsetSpeedToAnnulus = 6F + } + } + // 重新绘制 + scanView.postInvalidate() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/sign_view/SignActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/sign_view/SignActivity.kt new file mode 100644 index 0000000000..7e995591e1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/sign_view/SignActivity.kt @@ -0,0 +1,39 @@ +package afkt.project.feature.dev_widget.sign_view + +import afkt.project.base.app.BaseActivity +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.graphics.Paint +import android.view.View +import android.widget.LinearLayout +import androidx.viewbinding.ViewBinding +import com.alibaba.android.arouter.facade.annotation.Route +import dev.widget.function.SignView + +/** + * detail: 签名 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.SignActivity_PATH) +class SignActivity : BaseActivity() { + + override fun isViewBinding(): Boolean = false + + override fun baseLayoutId(): Int = 0 + + override fun baseLayoutView(): View { + val signView = SignView(this) + signView.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + + // 设置画笔 + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + paint.style = Paint.Style.STROKE + paint.strokeWidth = 30F + paint.color = Color.BLACK + signView.paint = paint + return signView + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/RecyclerLoadingAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/RecyclerLoadingAdapter.kt new file mode 100644 index 0000000000..ca26ab89e7 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/RecyclerLoadingAdapter.kt @@ -0,0 +1,73 @@ +package afkt.project.feature.dev_widget.view_assist + +import afkt.project.R +import afkt.project.databinding.AdapterRecyclerLoadingBinding +import android.graphics.drawable.Drawable +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.DevSource +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.base.widget.BaseImageView +import dev.engine.image.listener.DrawableListener +import dev.expand.engine.image.display +import dev.mvvm.utils.toSource +import dev.widget.assist.ViewAssist + +/** + * detail: ViewAssist RecyclerView 适配器 + * @author Ttt + */ +class RecyclerLoadingAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_recycler_loading) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val url = getDataItem(position) + val viewAssist = ViewAssist.wrap(holder.binding.vidFl) + ViewAssistUtils.registerRecyclerLoading(viewAssist) { + loadImage(holder.binding.vidIv, viewAssist, url) + } + loadImage(holder.binding.vidIv, viewAssist, url) + } + + private fun loadImage( + imageView: BaseImageView, + viewAssist: ViewAssist, + url: String + ) { + viewAssist.showIng() + // 加载图片 + imageView.display( + source = url.toSource(), + listener = object : DrawableListener() { + override fun onStart(source: DevSource) {} + override fun onResponse( + source: DevSource, + value: Drawable + ) { + viewAssist.showSuccess() + } + + override fun onFailure( + source: DevSource, + throwable: Throwable + ) { + viewAssist.showFailed() + } + } + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistActivity.kt new file mode 100644 index 0000000000..2ba74c808a --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistActivity.kt @@ -0,0 +1,169 @@ +package afkt.project.feature.dev_widget.view_assist + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityViewAssistBinding +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.view.LayoutInflater +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.mvvm.utils.size.AppSize +import dev.utils.app.HandlerUtils +import dev.utils.app.ListenerUtils +import dev.utils.app.ViewUtils +import dev.utils.app.toast.ToastTintUtils +import dev.widget.assist.ViewAssist + +/** + * detail: ViewAssist Activity + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.ViewAssistActivity_PATH) +class ViewAssistActivity : BaseActivity() { + + private lateinit var viewAssist: ViewAssist + + override fun baseLayoutId(): Int = R.layout.activity_view_assist + + override fun initValue() { + super.initValue() + + viewAssist = ViewAssist.wrap(binding.vidFl) + + when (moduleType) { + ButtonValue.BTN_VIEW_ASSIST_ERROR -> errorType() + ButtonValue.BTN_VIEW_ASSIST_EMPTY -> emptyType() + ButtonValue.BTN_VIEW_ASSIST_CUSTOM -> customType() + } + } + + private fun errorType() { + ViewUtils.setPadding(binding.vidFl, AppSize.dp2px(50F)) + viewAssist.register(ViewAssist.TYPE_ING, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_loading, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + val isContent = assist.tag == null + HandlerUtils.postRunnable({ + if (isContent) { + assist.showType(100) + } else { + assist.showType(200) + } + }, if (isContent) 3000 else 2000.toLong()) + } + }).register(100, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_error, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + ListenerUtils.setOnClicks({ + ToastTintUtils.normal("click retry") + assist.setTag("").showIng() + }, view) + } + }).register(200, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_content, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + } + }).showIng() + } + + private fun emptyType() { + viewAssist.register(ViewAssist.TYPE_ING, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_loading2, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + HandlerUtils.postRunnable({ assist.showType(Int.MAX_VALUE) }, 3000) + } + }).register(Int.MAX_VALUE, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_empty, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + } + }).showIng() + } + + private fun customType() { + viewAssist.register(ViewAssist.TYPE_ING, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_loading3, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + HandlerUtils.postRunnable({ assist.showType(159) }, 3000) + } + }).register(159, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View { + return inflater.inflate(R.layout.view_assist_custom, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + ListenerUtils.setOnClicks( + { ToastTintUtils.normal("Custom Type") }, + view.findViewById(R.id.vid_cv) + ) + } + }).showIng() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistRecyclerViewLoadActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistRecyclerViewLoadActivity.kt new file mode 100644 index 0000000000..f5dfe10ad3 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistRecyclerViewLoadActivity.kt @@ -0,0 +1,37 @@ +package afkt.project.feature.dev_widget.view_assist + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.RouterPath +import android.view.ViewGroup +import androidx.recyclerview.widget.GridLayoutManager +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.RandomUtils + +/** + * detail: ViewAssist RecyclerView Loading + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.ViewAssistRecyclerViewLoadActivity_PATH) +class ViewAssistRecyclerViewLoadActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + + val url = "https://picsum.photos/id/%s/1080/1920" + val lists = mutableListOf() + for (i in 0..19) { + lists.add(String.format(url, RandomUtils.getRandom(1, 1000))) + } + // 初始化布局管理器、适配器 + binding.vidRv.layoutManager = GridLayoutManager(this, 2) + RecyclerLoadingAdapter(lists).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistUtils.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistUtils.kt new file mode 100644 index 0000000000..742215b93e --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_assist/ViewAssistUtils.kt @@ -0,0 +1,77 @@ +package afkt.project.feature.dev_widget.view_assist + +import afkt.project.R +import android.view.LayoutInflater +import android.view.View +import dev.widget.assist.ViewAssist + +/** + * detail: ViewAssist Adapter 工具类 + * @author Ttt + */ +object ViewAssistUtils { + + /** + * 注册 Recycler Loading type + * @param viewAssist [ViewAssist] + * @param listener 点击事件 + */ + fun registerRecyclerLoading( + viewAssist: ViewAssist?, + listener: View.OnClickListener? + ) { + viewAssist?.let { + // 设置加载中样式 + it.register(ViewAssist.TYPE_ING, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View? { + return inflater.inflate(R.layout.view_assist_recy_loading, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + } + }) + // 设置加载成功样式 + it.register(ViewAssist.TYPE_SUCCESS, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View? { + return null + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + // 可以设置渐变动画, 并在结束时隐藏根布局 -> assist.gone() + assist.gone() + } + }) + // 设置加载失败样式 + it.register(ViewAssist.TYPE_FAILED, object : ViewAssist.Adapter { + override fun onCreateView( + assist: ViewAssist, + inflater: LayoutInflater + ): View? { + return inflater.inflate(R.layout.view_assist_recy_failed, null) + } + + override fun onBindView( + assist: ViewAssist, + view: View, + type: Int + ) { + listener?.apply { view.setOnClickListener(this) } + } + }) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerActivity.kt new file mode 100644 index 0000000000..6172b83431 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerActivity.kt @@ -0,0 +1,62 @@ +package afkt.project.feature.dev_widget.view_pager + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityViewPagerBinding +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.expand.engine.log.log_dTag +import dev.widget.custom.CustomViewPager.OnDirectionListener + +/** + * detail: ViewPager 滑动监听、控制滑动 + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.ViewPagerActivity_PATH) +class ViewPagerActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_view_pager + + override fun initValue() { + super.initValue() + val lists = mutableListOf() + for (i in 0..4) lists.add((i + 1).toString()) + binding.vidVp.adapter = ViewPagerAdapter(lists) + binding.vidVp.setCurrentItem(lists.size * 100, false) + binding.vidVp.setOnPageChangeListener(object : OnDirectionListener() { + override fun onSlideDirection( + left: Boolean, + right: Boolean + ) { + if (left && !right) { + TAG.log_dTag( + message = "往左滑 - 从右往左" + ) + } else { + TAG.log_dTag( + message = "往右滑 - 从左往右" + ) + } + } + + override fun onPageSelected(index: Int) { + TAG.log_dTag( + message = "索引变动: $index" + ) + if (mLeftScroll) { + showToast("往左滑 - 从右往左") + } else { + showToast("往右滑 - 从左往右") + } + } + }) + binding.vidAllowBtn.setOnClickListener { + binding.vidVp.isSlide = true + showToast(true, "已允许滑动") + } + binding.vidBanBtn.setOnClickListener { + binding.vidVp.isSlide = false + showToast(false, "已禁止滑动") + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerAdapter.kt new file mode 100644 index 0000000000..0210c89e02 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/view_pager/ViewPagerAdapter.kt @@ -0,0 +1,47 @@ +package afkt.project.feature.dev_widget.view_pager + +import afkt.project.databinding.ViewPagerItemViewBinding +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewpager.widget.PagerAdapter + +/** + * detail: ViewPager 适配器 + * @author Ttt + */ +class ViewPagerAdapter( + private val lists: List +) : PagerAdapter() { + + override fun instantiateItem( + container: ViewGroup, + position: Int + ): Any { + val binding = ViewPagerItemViewBinding.inflate(LayoutInflater.from(container.context)) + // 设置文本 + binding.vidContentTv.text = lists[position % lists.size] + // 保存 View + container.addView(binding.root) + return binding.root + } + + override fun getCount(): Int { + return Int.MAX_VALUE + } + + override fun isViewFromObject( + view: View, + obj: Any + ): Boolean { + return view == obj + } + + override fun destroyItem( + container: ViewGroup, + position: Int, + obj: Any + ) { + container.removeView(obj as? View) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wave_view/WaveViewActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wave_view/WaveViewActivity.kt new file mode 100644 index 0000000000..4af2c141a6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wave_view/WaveViewActivity.kt @@ -0,0 +1,124 @@ +package afkt.project.feature.dev_widget.wave_view + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityWaveViewBinding +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.widget.SeekBar +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.BarUtils +import dev.utils.app.ResourceUtils +import dev.widget.ui.WaveView +import dev.widget.utils.WaveHelper + +/** + * detail: 波浪 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.WaveViewActivity_PATH) +class WaveViewActivity : BaseActivity() { + + val helper: WaveHelper by lazy { + val lazyObj = WaveHelper.get(binding.vidWave) + // 通过属性动画进行设置波浪 View 动画效果 + lazyObj.buildPropertyAnimation( + WaveHelper.WaveProperty.Builder() + // 设置水位高度属性值 + .setWaterLevelRatio( + 0F, 0.7F, 10000L + ).build() + ) + lazyObj + } + + override fun baseLayoutId(): Int = R.layout.activity_wave_view + + override fun onPause() { + super.onPause() + helper.cancel() + } + + override fun onResume() { + super.onResume() + helper.start() + } + + override fun initView() { + super.initView() + + toolbar?.let { bar -> + val rgb = ResourceUtils.getColor(R.color.color_55) + bar.setBackgroundColor(rgb) + BarUtils.addMarginTopEqualStatusBarHeight(bar) + BarUtils.setStatusBarColor(mActivity, rgb) + } + } + + override fun initListener() { + super.initListener() + + // 设置波浪外形形状 + binding.vidShapeChoiceRg.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.vid_shape_circle_rb -> helper.shapeType = WaveView.ShapeType.CIRCLE + R.id.vid_shape_square_rb -> helper.shapeType = WaveView.ShapeType.SQUARE + } + } + + // 设置波浪边框信息 + binding.vidSb.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged( + seekBar: SeekBar?, + progress: Int, + fromUser: Boolean + ) { + helper.setBorder(progress.toFloat(), helper.borderColor) + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + } + }) + + // 设置波浪颜色 + binding.vidColorChoiceRg.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.vid_color_default_rb -> { + helper.setBorder( + helper.borderWidth, WaveView.DEFAULT_BORDER_COLOR + ).setWaveColor( + WaveView.DEFAULT_BEHIND_WAVE_COLOR, + WaveView.DEFAULT_FRONT_WAVE_COLOR, + ) + } + R.id.vid_color_red_rb -> { + helper.setBorder( + helper.borderWidth, Color.parseColor("#44f16d7a") + ).setWaveColor( + Color.parseColor("#28f16d7a"), + Color.parseColor("#3cf16d7a") + ) + } + R.id.vid_color_green_rb -> { + helper.setBorder( + helper.borderWidth, Color.parseColor("#B0b7d28d") + ).setWaveColor( + Color.parseColor("#40b7d28d"), + Color.parseColor("#80b7d28d") + ) + } + R.id.vid_color_blue_rb -> { + helper.setBorder( + helper.borderWidth, Color.parseColor("#b8f1ed") + ).setWaveColor( + Color.parseColor("#88b8f1ed"), + Color.parseColor("#b8f1ed") + ) + } + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wrap_view/WrapActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wrap_view/WrapActivity.kt new file mode 100644 index 0000000000..65a4913a85 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_widget/wrap_view/WrapActivity.kt @@ -0,0 +1,89 @@ +package afkt.project.feature.dev_widget.wrap_view + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityWrapBinding +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.os.Bundle +import android.text.TextUtils +import android.view.View +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.utils.app.ResourceUtils +import dev.utils.app.ShapeUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +/** + * detail: 自动换行 View + * @author Ttt + */ +@Route(path = RouterPath.DEV_WIDGET.WrapActivity_PATH) +class WrapActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_wrap + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val view = QuickHelper.get(BaseTextView(this)) + .setText("刷新") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.red)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { initValue() }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + binding.vidWrap +// // 设置最大行数 +// .setMaxLine(RandomUtils.getRandom(10, 30)) +// // 设置每一行向上的边距 ( 行间隔 ) +// .setRowTopMargin(30) +// // 设置每个 View 之间的 Left 边距 +// .setViewLeftMargin(30) + // 快捷设置两个边距 + .setRowViewMargin(30, 30) + .removeAllViews() + + val layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + // 设置点击效果 + val drawable = ShapeUtils.newShape(30F, ResourceUtils.getColor(R.color.color_88)).drawable + for (i in 1..20) { + val text = ChineseUtils.randomWord(RandomUtils.getRandom(7)) + + RandomUtils.getRandomLetters(RandomUtils.getRandom(5)) + val randomText = + i.toString() + "." + RandomUtils.getRandom(text.toCharArray(), text.length) + binding.vidWrap.addView(createView(randomText, layoutParams, drawable)) + } + } + + private fun createView( + text: String, + layoutParams: ViewGroup.LayoutParams, + drawable: GradientDrawable + ): BaseTextView { + return QuickHelper.get(BaseTextView(this)) + .setLayoutParams(layoutParams) + .setPadding(30, 15, 30, 15) + .setBackground(drawable) + .setMaxLines(1) + .setEllipsize(TextUtils.TruncateAt.END) + .setTextColors(Color.WHITE) + .setTextSizeBySp(15F) + .setBold(RandomUtils.nextBoolean()) + .setText(text) + .getView() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/ArticleAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/ArticleAdapter.kt new file mode 100644 index 0000000000..1e023ac9d1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/ArticleAdapter.kt @@ -0,0 +1,59 @@ +package afkt.project.feature.framework + +import afkt.project.R +import afkt.project.databinding.AdapterArticleBinding +import afkt.project.model.bean.ArticleBean.DataBean.ListBean +import afkt.project.utils.IMAGE_ROUND_3 +import android.content.Intent +import android.net.Uri +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource +import dev.utils.app.AppUtils +import dev.utils.app.ListenerUtils +import dev.utils.app.TextViewUtils +import dev.utils.common.NumberUtils +import dev.utils.common.StringUtils + +/** + * detail: 文章 Adapter + * @author Ttt + */ +class ArticleAdapter : DevDataAdapter>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_article) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + // 标题 + TextViewUtils.setHtmlText(holder.binding.vidTitleTv, item.title) + // 时间 + holder.binding.vidTimeTv.text = StringUtils.checkValue(item.niceShareDate, item.niceDate) + // 随机图片 + holder.binding.vidPicIv.display( + source = "https://picsum.photos/2${NumberUtils.addZero(position)}".toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + // 绑定点击事件 + ListenerUtils.setOnClicks({ + val link = item.link + if (!StringUtils.isEmpty(link)) { + val uri = Uri.parse(link) + val intent = Intent(Intent.ACTION_VIEW, uri) + AppUtils.startActivity(intent) + } + }, holder.binding.vidCv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVP.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVP.kt new file mode 100644 index 0000000000..e6597d6d43 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVP.kt @@ -0,0 +1,88 @@ +package afkt.project.feature.framework.mvp + +import afkt.project.base.http.RetrofitManagerUse +import afkt.project.model.bean.ArticleBean +import androidx.lifecycle.LifecycleOwner +import dev.base.expand.mvp.MVP +import dev.base.expand.mvp.MVP.IModel +import dev.base.expand.mvp.MVP.IView +import dev.retrofit.Notify +import dev.retrofit.simpleLaunchExecuteRequest +import java.util.* + +/** + * detail: 文章 MVP Contract + * @author Ttt + */ +class ArticleMVP { + + interface Model : IModel { + + /** + * 请求文章列表 + */ + fun requestArticleLists() + } + + interface View : IView { + + /** + * 获取文章列表响应回调 + * @param succeed 请求是否成功 + * @param articleBean 文章列表 + */ + fun onArticleListResponse( + succeed: Boolean, + articleBean: ArticleBean? + ) + } + + interface IPresenter : MVP.IPresenter { + /** + * 获取文章列表 + */ + fun articleLists() + } + + /** + * detail: 文章 Presenter + * @author Ttt + */ + class Presenter( + view: View, + owner: LifecycleOwner + ) : MVP.Presenter(view), + IPresenter { + + init { + mvpModel = object : Model { + override fun requestArticleLists() { + owner.simpleLaunchExecuteRequest( + block = { + RetrofitManagerUse.api().getArticleList(0) + }, + callback = object : Notify.Callback() { + override fun onSuccess( + uuid: UUID, + data: ArticleBean? + ) { + mvpView?.onArticleListResponse(true, data) + } + + override fun onError( + uuid: UUID, + error: Throwable? + ) { + mvpView?.onArticleListResponse(false, null) + } + } + ) + } + } + } + + override fun articleLists() { + mvpModel?.requestArticleLists() + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVPActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVPActivity.kt new file mode 100644 index 0000000000..89c244b501 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvp/ArticleMVPActivity.kt @@ -0,0 +1,141 @@ +package afkt.project.feature.framework.mvp + +import afkt.project.R +import afkt.project.base.app.BaseMVPActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.framework.ArticleAdapter +import afkt.project.model.bean.ArticleBean +import afkt.project.model.item.RouterPath +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import com.tt.whorlviewlibrary.WhorlView +import dev.utils.app.ViewUtils +import dev.utils.common.CollectionUtils +import dev.widget.assist.ViewAssist +import dev.widget.function.StateLayout + +/** + * detail: 文章 MVP Activity + * @author Ttt + */ +@Route(path = RouterPath.FRAMEWORK.ArticleMVPActivity_PATH) +class ArticleMVPActivity : BaseMVPActivity(), + ArticleMVP.View { + + // 加载动画 + var loadView: WhorlView? = null + + // 适配器 + var adapter = ArticleAdapter() + + override fun createPresenter(): ArticleMVP.Presenter { + return ArticleMVP.Presenter(this, this) + } + + override fun baseLayoutId(): Int { + return R.layout.base_view_recyclerview + } + + override fun initView() { + super.initView() + // 初始化 View + val view = stateLayout.getView(ViewAssist.TYPE_ING) + loadView = ViewUtils.findViewById(view, R.id.vid_whv) + } + + override fun initValue() { + super.initValue() + // 初始化布局管理器、适配器 + adapter.bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + // 设置监听 + stateLayout.setListener(object : StateLayout.Listener { + override fun onRemove( + layout: StateLayout, + type: Int, + removeView: Boolean + ) { + } + + override fun onNotFound( + layout: StateLayout, + type: Int + ) { + // 切换 View 操作 + if (type == ViewAssist.TYPE_SUCCESS) { + ViewUtils.reverseVisibilitys( + true, + contentAssist.contentLinear, + contentAssist.stateLinear + ) + } + } + + override fun onChange( + layout: StateLayout, + type: Int, + oldType: Int, + view: View + ) { + // 判断是否操作成功 + val success = (type == ViewAssist.TYPE_SUCCESS) + // 切换 View 操作 + if (ViewUtils.reverseVisibilitys( + success, + contentAssist.contentLinear, + contentAssist.stateLinear + ) + ) { + // 属于请求成功 + } else { + if (type == ViewAssist.TYPE_ING) { + loadView?.apply { + if (!isCircling) start() + } + } else { + loadView?.stop() + } + } + } + }) + } + + override fun initOther() { + super.initOther() + // 表示请求中 + stateLayout.showIng() + // 获取文章列表 + presenter.articleLists() + } + + override fun onDestroy() { + super.onDestroy() + // 停止动画 + loadView?.stop() + } + + // =================== + // = ArticleMVP.View = + // =================== + + override fun onArticleListResponse( + succeed: Boolean, + articleBean: ArticleBean? + ) { + articleBean?.data?.apply { + if (CollectionUtils.isEmpty(datas)) { // 无数据 + stateLayout.showEmptyData() + } else { // 请求成功 + stateLayout.showSuccess() + // 设置数据源 + adapter.setDataList(datas) + } + return + } + // 请求失败 + stateLayout.showFailed() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleMVVMActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleMVVMActivity.kt new file mode 100644 index 0000000000..60b0a44f4b --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleMVVMActivity.kt @@ -0,0 +1,154 @@ +package afkt.project.feature.framework.mvvm + +import afkt.project.BR +import afkt.project.R +import afkt.project.base.app.BaseMVVMActivity +import afkt.project.databinding.ActivityArticleMvvmBinding +import afkt.project.feature.framework.ArticleAdapter +import afkt.project.model.bean.ArticleBean +import afkt.project.model.item.RouterPath +import android.view.View +import androidx.lifecycle.Observer +import com.alibaba.android.arouter.facade.annotation.Route +import com.tt.whorlviewlibrary.WhorlView +import dev.utils.DevFinal +import dev.utils.app.ViewUtils +import dev.utils.common.CollectionUtils +import dev.widget.assist.ViewAssist +import dev.widget.function.StateLayout + +/** + * detail: 文章 MVVM Activity + * @author Ttt + */ +@Route(path = RouterPath.FRAMEWORK.ArticleMVVMActivity_PATH) +class ArticleMVVMActivity : BaseMVVMActivity() { + + // 加载动画 + var loadView: WhorlView? = null + + // 适配器 + var adapter = ArticleAdapter() + + override fun baseContentId(): Int = R.layout.activity_article_mvvm + + override fun initOrder() { + initViewModel() + super.initOrder() + } + + override fun initView() { + super.initView() + + setSupportActionBar(binding.vidTb) + supportActionBar?.apply { + // 给左上角图标的左边加上一个返回的图标 + setDisplayHomeAsUpEnabled(true) + // 对应 ActionBar.DISPLAY_SHOW_TITLE + setDisplayShowTitleEnabled(false) + } + + // 设置点击事件 + binding.vidTb.setNavigationOnClickListener { v: View? -> finish() } + + // 获取标题 + val title = intent.getStringExtra(DevFinal.STR.TITLE) + // 设置标题 + binding.title = title // 或用下面设置 + binding.setVariable(BR.title, title) // 设置后, 会动态刷新 + + // 初始化 View + val view = binding.vidStateSl.getView(ViewAssist.TYPE_ING) + loadView = ViewUtils.findViewById(view, R.id.vid_whv) + } + + override fun initValue() { + super.initValue() + // 初始化布局管理器、适配器 + adapter.bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + // 设置监听 + binding.vidStateSl.setListener(object : StateLayout.Listener { + override fun onRemove( + layout: StateLayout, + type: Int, + removeView: Boolean + ) { + } + + override fun onNotFound( + layout: StateLayout, + type: Int + ) { + // 切换 View 操作 + if (type == ViewAssist.TYPE_SUCCESS) { + ViewUtils.reverseVisibilitys(true, binding.vidRv, binding.vidStateSl) + } + } + + override fun onChange( + layout: StateLayout, + type: Int, + oldType: Int, + view: View + ) { + // 判断是否操作成功 + val success = (type == ViewAssist.TYPE_SUCCESS) + // 切换 View 操作 + if (ViewUtils.reverseVisibilitys( + success, + binding.vidRv, + binding.vidStateSl + ) + ) { + // 属于请求成功 + } else { + if (type == ViewAssist.TYPE_ING) { + loadView?.apply { + if (!isCircling) start() + } + } else { + loadView?.stop() + } + } + } + }) + } + + override fun initOther() { + super.initOther() + // 表示请求中 + binding.vidStateSl.showIng() + // 开始请求 + viewModel.requestArticleLists().observe(this, Observer { articleBean: ArticleBean? -> + articleBean?.data?.apply { + if (CollectionUtils.isEmpty(datas)) { // 无数据 + binding.vidStateSl.showEmptyData() + } else { // 请求成功 + binding.vidStateSl.showSuccess() + // 设置数据源 + adapter.setDataList(datas) + } + return@Observer + } + binding.vidStateSl.showFailed() + }) + } + + override fun initViewModel() { + getActivityViewModel(ArticleViewModel::class.java)?.apply { + viewModel = this +// lifecycle.addObserver(viewModel) + viewModel.lifecycle(this@ArticleMVVMActivity) + } + } + + override fun onDestroy() { + super.onDestroy() + // 停止动画 + loadView?.stop() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleRepository.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleRepository.kt new file mode 100644 index 0000000000..0929b7deec --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleRepository.kt @@ -0,0 +1,28 @@ +package afkt.project.feature.framework.mvvm + +import afkt.project.base.http.RetrofitManagerUse +import afkt.project.model.bean.ArticleBean +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import dev.retrofit.liveDataLaunchExecuteRequest + +/** + * detail: 文章相关 Repository + * @author Ttt + * 在组件化下可考虑以模块命名, 全部统一在一个文件内 + * 如 UserRepository 便于减少类数量 ( 以及复杂性, 方便整个模块便捷使用统一维护 ) + */ +class ArticleRepository { + + fun requestArticleLists( + owner: LifecycleOwner, + article: MutableLiveData + ): MutableLiveData { + owner.liveDataLaunchExecuteRequest( + block = { + RetrofitManagerUse.api().getArticleList(0) + }, article + ) + return article + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleViewModel.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleViewModel.kt new file mode 100644 index 0000000000..b1a67f023d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/framework/mvvm/ArticleViewModel.kt @@ -0,0 +1,41 @@ +package afkt.project.feature.framework.mvvm + +import afkt.project.model.bean.ArticleBean +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** + * detail: 文章相关 ViewModel + * @author Ttt + * 在组件化下可考虑以模块命名, 全部统一在一个文件内 + * 如 UserViewModel 便于减少类数量 ( 以及复杂性, 方便整个模块便捷使用统一维护 ) + */ +class ArticleViewModel : ViewModel(), + DefaultLifecycleObserver { + + val article = MutableLiveData() + + // Repository + private val repository = ArticleRepository() + + // LifecycleOwner + private lateinit var lifecycleOwner: LifecycleOwner + + /** + * 请求文章列表 + */ + fun requestArticleLists(): MutableLiveData { + return repository.requestArticleLists(lifecycleOwner, article) + } + + // ============= + // = lifecycle = + // ============= + + fun lifecycle(owner: LifecycleOwner) { + lifecycleOwner = owner + owner.lifecycle.addObserver(this) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreActivity.kt new file mode 100644 index 0000000000..40bb88e23e --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreActivity.kt @@ -0,0 +1,27 @@ +package afkt.project.feature.lib_frame.data_store + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityDataStoreBinding +import afkt.project.model.item.RouterPath +import androidx.lifecycle.lifecycleScope +import com.alibaba.android.arouter.facade.annotation.Route +import kotlinx.coroutines.launch + +/** + * detail: DataStore Use Activity + * @author Ttt + */ +@Route(path = RouterPath.LIB_FRAME.DataStoreActivity_PATH) +class DataStoreActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_data_store + + override fun initValue() { + super.initValue() + + lifecycleScope.launch { + DataStoreUse.use(this@DataStoreActivity) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreUse.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreUse.kt new file mode 100644 index 0000000000..6544116e49 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/lib_frame/data_store/DataStoreUse.kt @@ -0,0 +1,187 @@ +package afkt.project.feature.lib_frame.data_store + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.asLiveData +import dev.expand.engine.log.log_dTag +import dev.other.DataStoreUtils +import dev.utils.app.share.SPUtils +import kotlinx.coroutines.flow.first + +object DataStoreUse { + + val TAG = DataStoreUse::class.java.simpleName + + private const val spStoreName = "spStore" + + suspend fun use(activity: AppCompatActivity) { + // 监听数据变化 + listener(activity) + // 写入数据 + write(activity) + // 读取数据 + read(activity) + } + + /** + * 写入数据 + */ + private suspend fun write(activity: AppCompatActivity) { + + // 首先进行 SP 数据存储存储 + // 会在 /data/data/afkt.project/shared_prefs/ 创建 **.xml + // 接着运行 migrationSPToDataStore 则会把 shared_prefs 文件夹内的 xml + // 存储到 /data/data/afkt.project/files/datastore/ 中指定的 **.preferences_pb + // 并且会把 shared_prefs 下迁移成功的文件进行删除 + + SPUtils.getPreference(activity, "AA").put("type", "AA") + + SPUtils.getPreference(activity, "AA").put("errorType", "AA") + + SPUtils.getPreference(activity, "BB").put("type", "BB") + + SPUtils.getPreference(activity, "BB").put("errorType", 1) + + SPUtils.getPreference(activity, "BB").put("abc", "def") + + val dataStore = DataStoreUtils.migrationSPToDataStore( + spStoreName, "AA", "BB" + ) + + /** + * 监听 [spStoreName] DataStore key "one" 值变化 + * [listener] + */ + DataStoreUtils.get(spStoreName).getStringFlow("one")?.let { + it.asLiveData().observe(activity) { value -> + TAG.log_dTag( + message = "listener %s, key : %s, value : %s", + args = arrayOf(spStoreName, "one", value) + ) + } + } + + dataStore.put("one", "设置值看监听效果") + + DataStoreUtils.get(spStoreName).put("two", "二") + + dataStore.put("type", "修改值, 查看监听") + + // ======= + // = TAG = + // ======= + + DataStoreUtils.get(TAG).put("int", 9) + + DataStoreUtils.get(TAG).put("String", "xx") + + DataStoreUtils.get(TAG).put("boolean", true) + + DataStoreUtils.get(TAG).put("float", 0.48791F) + + DataStoreUtils.get(TAG).put("long", 555L) + + DataStoreUtils.get(TAG).put("double", 1.2312) + } + + /** + * 读取数据 + */ + private suspend fun read(activity: AppCompatActivity) { + TAG.log_dTag( + message = "getFlow %s, key : %s, value : %s", + args = arrayOf( + TAG, "aaaaa", + DataStoreUtils.get(TAG).getStringFlow("aaaaa", "不存在该 key 返回指定值")?.first() + ) + ) + + TAG.log_dTag( + message = "getFlow %s, key : %s, value : %s", + args = arrayOf(TAG, "double", DataStoreUtils.get(TAG).getDoubleFlow("double")?.first()) + ) + + TAG.log_dTag( + message = "getFlow %s, key : %s, value : %s", + args = arrayOf( + spStoreName, "type", + DataStoreUtils.get(spStoreName).getStringFlow("type")?.first() + ) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf( + spStoreName, "errorType", + DataStoreUtils.get(spStoreName).getString("errorType") + ) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf( + spStoreName, "errorType", + DataStoreUtils.get(spStoreName).getInt("errorType") + ) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf(spStoreName, "one", DataStoreUtils.get(spStoreName).getInt("one")) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf(spStoreName, "two", DataStoreUtils.get(spStoreName).getString("two")) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf(spStoreName, "abc", DataStoreUtils.get(spStoreName).getString("abc")) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf(TAG, "double", DataStoreUtils.get(TAG).getDouble("double")) + ) + + TAG.log_dTag( + message = "getValue %s, key : %s, value : %s", + args = arrayOf(TAG, "double", DataStoreUtils.get(TAG).getBoolean("double")) + ) + } + + /** + * 监听数据变化 + */ + private fun listener(activity: AppCompatActivity) { + /** + * 监听 [TAG] DataStore key "int" 值变化 + */ + DataStoreUtils.get(TAG).getIntFlow("int")?.let { + it.asLiveData().observe(activity) { value -> + TAG.log_dTag( + message = "listener %s, key : %s, value : %s", + args = arrayOf(TAG, "int", value) + ) + } + } + /** + * 最新正式版 - 限制同一个 name 只能创建一次 DataStore 并存储对象进行存储复用 + * 具体查看 [DataStoreUtils] 顶部注意事项注释 + * 因为进行 SharedPreferences 迁移, 进行创建 spStoreName 会导致同一个 name 多次创建 + * 所以下面的监听代码, 需要放在迁移后的代码下进行监听 + * 从而使用 [DataStoreUtils] 的 cacheMap 进行复用 + */ +// /** +// * 监听 [spStoreName] DataStore key "one" 值变化 +// */ +// DataStoreUtils.get(spStoreName).getStringFlow("one")?.let { +// it.asLiveData().observe(activity) { value -> +// TAG.log_dTag( +// message = "listener %s, key : %s, value : %s", +// args = arrayOf(spStoreName, "one", value) +// ) +// } +// } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/add_contact/AddContactActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/add_contact/AddContactActivity.kt new file mode 100644 index 0000000000..baed1baa35 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/add_contact/AddContactActivity.kt @@ -0,0 +1,271 @@ +package afkt.project.feature.other_function.add_contact + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityAddContactBinding +import afkt.project.model.item.RouterPath +import android.Manifest +import android.annotation.SuppressLint +import android.content.ContentUris +import android.content.ContentValues +import android.content.DialogInterface +import android.os.Handler +import android.os.Message +import android.provider.ContactsContract +import android.provider.ContactsContract.CommonDataKinds.* +import android.provider.ContactsContract.RawContacts +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.permission.permission_request +import dev.utils.app.* +import dev.utils.app.DialogUtils.DialogListener +import dev.utils.app.toast.ToastTintUtils +import dev.utils.app.toast.ToastUtils +import dev.utils.common.ConvertUtils +import dev.utils.common.StringUtils +import dev.utils.common.thread.DevThreadManager +import dev.utils.common.validator.ValiToPhoneUtils +import dev.utils.common.validator.ValidatorUtils +import java.util.concurrent.atomic.AtomicInteger + +/** + * detail: 添加联系人 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.AddContactActivity_PATH) +class AddContactActivity : BaseActivity() { + + // 待创建总数 + var count = 0 + + // 是否运行中 + var running = false + + // 递增数 + var index = AtomicInteger() + + override fun baseLayoutId(): Int = R.layout.activity_add_contact + + override fun initValue() { + super.initValue() + + binding.vidAddBtn.setOnClickListener(View.OnClickListener { + if (running) { + ToastTintUtils.warning("运行中") + return@OnClickListener + } + permission_request( + permissions = arrayOf( + Manifest.permission.WRITE_CONTACTS + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + // 联系人创建校验 + createCheck() + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + ToastUtils.showShort("请开启联系人写入权限") + } + } + ) + }) + } + + /** + * 联系人创建校验 + */ + private fun createCheck() { + // 获取手机号码开头、结尾 + val start = EditTextUtils.getText(binding.vidStartEt) + val end = EditTextUtils.getText(binding.vidEndEt) + // 判断是否符合条件 + val temp = start + end + if (!ValidatorUtils.isNumber(temp)) { + ToastTintUtils.error("请输入数字") + return + } + val length = temp.length + if (length >= 11) { + ToastTintUtils.error("开头与结尾需少于11位") + return + } + val diff = 11 - length + // 待生成号码总数 + val middle = ConvertUtils.toInt("1" + StringUtils.forString(diff, "0")) + // 临时号码 + val tempNumber = start + StringUtils.forString(diff, "0") + end + val tempNumber2 = start + StringUtils.forString(diff, "9") + end + // 判断是否手机号 + if (!ValiToPhoneUtils.isPhone(tempNumber)) { + ToastTintUtils.error("请输入正确的手机号") + return + } + // 进行提示 + val builder = StringBuilder() + builder.append("将会创建 ").append(middle).append(" 条联系人数据\n") + builder.append(tempNumber).append(" - ").append(tempNumber2) + DialogUtils.createAlertDialog(mContext, "创建提示", builder.toString(), + "取消", "创建", object : DialogListener() { + override fun onLeftButton(dialog: DialogInterface) { + dialog.dismiss() + } + + override fun onRightButton(dialog: DialogInterface) { + dialog.dismiss() + // 创建联系人 + createContact(start, end, middle) + } + }).show() + } + + /** + * 创建联系人 + * @param start 开头 + * @param end 结尾 + * @param count 创建总数 + */ + private fun createContact( + start: String, + end: String, + count: Int + ) { + ToastTintUtils.normal("创建中...") + ViewUtils.setVisibility(true, binding.vidTipsTv) + KeyBoardUtils.closeKeyboard() + this.count = count + running = true + index.set(0) + // 线程数 + val threadCount = 5 + // 平均数 + val average = count / 5 + // 间隔数 + val intervals = IntArray(threadCount) + for (i in 0 until threadCount) { + intervals[i] = (i + 1) * average + } + for (i in 0 until threadCount) { + if (i == 0) { + createThread(start, end, 0, intervals[i], threadCount) + } else { + createThread(start, end, intervals[i - 1], intervals[i], threadCount) + } + } + } + + /** + * 创建线程 + * @param start 开头 + * @param end 结尾 + * @param first 起点 + * @param interval 间隔值 ( size ) + * @param threadCount 线程数量 + */ + private fun createThread( + start: String, + end: String, + first: Int, + interval: Int, + threadCount: Int + ) { + DevThreadManager.getInstance(threadCount).execute(Runnable { + if (first == 0) { + val length = interval.toString().length + for (i in first until interval) { + val length1 = i.toString().length + if (length1 == length) { // 长度相同则不需要补 0 + val phoneNumber = start + i + end + addContact(phoneNumber, phoneNumber) + } else { + val zero = StringUtils.forString(length - length1, "0") + val phoneNumber = start + zero + i + end + addContact(phoneNumber, phoneNumber) + } + // 更新提示语 + handler.sendEmptyMessage(0) + // 如果页面销毁了则不处理 + if (ActivityUtils.isFinishing(mActivity)) return@Runnable + } + } else { + for (i in first until interval) { + val phoneNumber = start + i + end + addContact(phoneNumber, phoneNumber) + // 更新提示语 + handler.sendEmptyMessage(0) + // 如果页面销毁了则不处理 + if (ActivityUtils.isFinishing(mActivity)) return@Runnable + } + } + }) + } + + /** + * 添加联系人 + * @param name 姓名 + * @param phoneNumber 手机号 + */ + private fun addContact( + name: String?, + phoneNumber: String? + ) { + // 创建一个空的 ContentValues + val values = ContentValues() + // 向 RawContacts.CONTENT_URI 空值插入, 先获取 Android 系统返回的 rawContactId + // 后面要基于此 id 插入值 + val rawContactUri = contentResolver.insert(RawContacts.CONTENT_URI, values) + rawContactUri?.let { + val rawContactId = ContentUris.parseId(it) + values.clear() + values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId) + // 内容类型 + values.put(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + // 联系人名字 + values.put(StructuredName.GIVEN_NAME, name) + // 向联系人 URI 添加联系人名字 + contentResolver.insert(ContactsContract.Data.CONTENT_URI, values) + values.clear() + values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId) + values.put(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE) + // 联系人的电话号码 + values.put(Phone.NUMBER, phoneNumber) + // 电话类型 + values.put(Phone.TYPE, Phone.TYPE_MOBILE) + // 向联系人电话号码 URI 添加电话号码 + contentResolver.insert(ContactsContract.Data.CONTENT_URI, values) + values.clear() + values.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId) + values.put(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE) + // 联系人的 Email 地址 + values.put(Email.DATA, "jtongttt@gmail.com") + // 电子邮件的类型 + values.put(Email.TYPE, Email.TYPE_WORK) + // 向联系人 Email URI 添加 Email数据 + contentResolver.insert(ContactsContract.Data.CONTENT_URI, values) + } + } + + @SuppressLint("HandlerLeak") + private var handler = object : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + val value = index.getAndIncrement() + if (count == value + 1) { + val tips = "$count 条数据, 创建成功" + TextViewUtils.setText(binding.vidTipsTv, tips) + ToastTintUtils.success(tips) + running = false + } else { + TextViewUtils.setText( + binding.vidTipsTv, + "需创建 " + count + " 条数据, 已创建 " + (value + 1) + " 条" + ) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultAPIActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultAPIActivity.kt new file mode 100644 index 0000000000..0b8450d726 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultAPIActivity.kt @@ -0,0 +1,100 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityActivityResultApiBinding +import afkt.project.model.item.RouterPath +import android.Manifest +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.app.ActivityOptionsCompat +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.image.display +import dev.expand.engine.log.log_dTag +import dev.expand.engine.permission.permission_againRequest +import dev.expand.engine.permission.permission_request +import dev.mvvm.utils.toSource +import dev.utils.app.MediaStoreUtils +import dev.utils.app.activity_result.ActivityResultAssist +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: Activity Result API + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.ActivityResultAPIActivity_PATH) +class ActivityResultAPIActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_activity_result_api + + override fun initListener() { + super.initListener() + binding.vidTakeBtn.setOnClickListener { + permission_request( + permissions = arrayOf(Manifest.permission.CAMERA), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + mAssist?.launch(MediaStoreUtils.createImageUri()) + } + + override fun onDenied( + grantedList: MutableList?, + deniedList: MutableList?, + notFoundList: MutableList? + ) { + // 拒绝了则再次请求处理 + permission_againRequest( + callback = this, + deniedList = deniedList + ) + ToastTintUtils.warning("拍照需摄像头权限") + } + } + ) + } + } + + // 如果使用 by lazy 要确保在 Lifecycle STARTED 状态前初始化 + private var mAssist: ActivityResultAssist? = null + + init { + mAssist = ActivityResultAssist( + this, ActivityResultContracts.TakePicture() + ) { + if (it) { + binding.vidIv.display(source = mAssist?.inputValue?.toSource()) + } else { + ToastTintUtils.warning("非成功操作") + } + }.setOperateCallback(object : ActivityResultAssist.OperateCallback() { + + // ====================================== + // = 该回调可不设置, 只是提供此功能便于特殊需求 = + // ====================================== + + override fun onStart( + assist: ActivityResultAssist, + type: Int, + input: Uri?, + options: ActivityOptionsCompat? + ) { + TAG.log_dTag( + message = "开始调用 ${ActivityResultAssist.getMethodType(type)} 方法前" + ) + } + + override fun onState( + assist: ActivityResultAssist, + type: Int, + input: Uri?, + options: ActivityOptionsCompat?, + result: Boolean + ) { + TAG.log_dTag( + message = "调用 ${ActivityResultAssist.getMethodType(type)} 方法后, 调用结果: $result" + ) + } + }) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultCallbackActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultCallbackActivity.kt new file mode 100644 index 0000000000..33c70d1f57 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/ActivityResultCallbackActivity.kt @@ -0,0 +1,56 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityActivityResultCallbackBinding +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.app.Activity +import android.content.Intent +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.DevEngine +import dev.expand.engine.image.display +import dev.mvvm.utils.toSource +import dev.utils.app.AppUtils +import dev.utils.app.activity_result.DefaultActivityResult + +/** + * detail: 跳转 Activity 回传 Callback + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.ActivityResultCallbackActivity_PATH) +class ActivityResultCallbackActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_activity_result_callback + + override fun initListener() { + super.initListener() + binding.vidSelectBtn.setOnClickListener { + AppUtils.startActivityForResult(object : DefaultActivityResult.ResultCallback { + override fun onStartActivityForResult(activity: Activity): Boolean { + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + return true + } + + override fun onActivityResult( + result: Boolean, + resultCode: Int, + intent: Intent? + ) { + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + if (imgUri != null) { + binding.vidIv.display(source = imgUri.toSource()) + } else { + Thread { + Thread.sleep(100L) + // 延迟 100 毫秒是防止 Activity 销毁对应的 Toast 显示在已销毁的 Activity + // 导致实际预览效果 Toast 并没有显示 + showToast(false, "非成功操作") + }.start() + } + } + }) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CacheActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CacheActivity.kt new file mode 100644 index 0000000000..78152fb7eb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CacheActivity.kt @@ -0,0 +1,110 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.cacheButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.PathUtils +import dev.utils.app.cache.DevCache +import dev.utils.app.toast.ToastTintUtils +import utils_use.cache.CacheUse +import java.io.Serializable + +/** + * detail: DevCache 缓存工具类 + * @author Ttt + * [CacheUse] + */ +@Route(path = RouterPath.OTHER_FUNCTION.CacheActivity_PATH) +class CacheActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(cacheButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + // 获取字符串 + val str: String? + when (buttonValue.type) { + ButtonValue.BTN_CACHE_STRING -> { + DevCache.newCache().put("str", "这是字符串", -1) + showToast(true, "存储字符串成功") + } + ButtonValue.BTN_CACHE_STRING_TIME -> { + DevCache.newCache().put("str", "这是有效期 3 秒字符串", 3) + showToast(true, "存储有效期字符串成功") + } + ButtonValue.BTN_CACHE_STRING_GET -> { + str = DevCache.newCache().getString("str") + showToast(str != null, str, "未存储 key 为 str 的字符串") + } + ButtonValue.BTN_CACHE_BEAN -> { + DevCache.newCache().put("bean", CacheVo("这是实体类"), -1) + showToast(true, "存储实体类成功") + } + ButtonValue.BTN_CACHE_BEAN_TIME -> { + DevCache.newCache().put("bean", CacheVo("这是有效期 3 秒实体类"), 3) + showToast(true, "存储有效期实体类成功") + } + ButtonValue.BTN_CACHE_BEAN_GET -> { + val `object` = DevCache.newCache().getSerializable("bean") + str = if (`object` != null) (`object` as CacheVo).toString() else null + showToast(`object` != null, str, "未存储 key 为 bean 的实体类") + } + ButtonValue.BTN_CACHE_FILE -> { + // 保存到指定文件夹下 + DevCache.newCache( + PathUtils.getSDCard().getSDCardFile("DevCache").absolutePath + ).put("fileStr", "这是指定位置字符串", -1) + showToast(true, "存储到指定位置成功") + } + ButtonValue.BTN_CACHE_FILE_GET -> { + str = DevCache.newCache( + PathUtils.getSDCard().getSDCardFile("DevCache").absolutePath + ).getString("fileStr") + showToast(str != null, str, "未存储 key 为 fileStr 的字符串") + } + ButtonValue.BTN_CACHE_CLEAR -> { + DevCache.newCache().clear() + DevCache.newCache(PathUtils.getSDCard().getSDCardPath("DevCache")) + .clear() + showToast(true, "清除全部数据成功") + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + /** + * detail: 缓存实体类 + * @author Ttt + */ + internal class CacheVo(var name: String) : Serializable { + + var time = System.currentTimeMillis() + + constructor( + name: String, + time: Long + ) : this(name) { + this.time = time + } + + override fun toString(): String { + return "name: $name, time: $time" + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CrashCatchActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CrashCatchActivity.kt new file mode 100644 index 0000000000..464986f6b4 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/CrashCatchActivity.kt @@ -0,0 +1,49 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.BaseApplication +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: 奔溃日志捕获 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.CrashCatchActivity_PATH) +class CrashCatchActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + /** + * 捕获异常处理 CrashUtils.getInstance().initialize() + * 参考 [BaseApplication.initCrash] + */ + + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.crashButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_CRASH_CLICK_CATCH -> { + val data: String? = null + data!!.split(",".toRegex()).toTypedArray() + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FileRecordActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FileRecordActivity.kt new file mode 100644 index 0000000000..2ac45323d3 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FileRecordActivity.kt @@ -0,0 +1,45 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.fileRecordButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.toast.ToastTintUtils +import utils_use.record.FileRecordUse + +/** + * detail: 日志、异常文件记录保存 + * @author Ttt + * [FileRecordUse] + */ +@Route(path = RouterPath.OTHER_FUNCTION.FileRecordActivity_PATH) +class FileRecordActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(fileRecordButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_FILE_RECORD_UTILS -> { + showToast(true, "保存成功") + FileRecordUse.fileRecordUse() + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FunctionActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FunctionActivity.kt new file mode 100644 index 0000000000..7531eb665d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/FunctionActivity.kt @@ -0,0 +1,194 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.MainActivity +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.functionButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.Manifest +import android.os.Build +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.log.log_dTag +import dev.expand.engine.permission.permission_request +import dev.utils.app.* +import dev.utils.app.assist.BeepVibrateAssist +import dev.utils.app.camera.camera1.FlashlightUtils +import dev.utils.app.toast.ToastTintUtils +import dev.utils.app.toast.ToastUtils + +/** + * detail: 铃声、震动、通知栏等功能 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.FunctionActivity_PATH) +class FunctionActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onDestroy() { + super.onDestroy() + // 关闭手电筒 + FlashlightUtils.getInstance().setFlashlightOff() + FlashlightUtils.getInstance().unregister() + } + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(functionButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + // 获取操作结果 + var result: Boolean + when (buttonValue.type) { + ButtonValue.BTN_FUNCTION_VIBRATE -> { + result = VibrationUtils.vibrate(200) + showToast(result) + } + ButtonValue.BTN_FUNCTION_BEEP -> { + // 表示不要震动、使用本地或者 raw 文件 + result = BeepVibrateAssist(mActivity, R.raw.dev_beep).setVibrate(false) + .playBeepSoundAndVibrate() + result = BeepVibrateAssist(mActivity, "xxx/a.mp3").setVibrate(false) + .playBeepSoundAndVibrate() + showToast(result) + } + ButtonValue.BTN_FUNCTION_NOTIFICATION_CHECK -> { + result = NotificationUtils.isNotificationEnabled() + showToast(result, "通知权限已开启", "通知权限未开启") + } + ButtonValue.BTN_FUNCTION_NOTIFICATION_OPEN -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + result = AppUtils.startActivity( + IntentUtils.getLaunchAppNotificationSettingsIntent(AppUtils.getPackageName()) + ) + showToast(result) + } else { + showToast(false) + } + } + ButtonValue.BTN_FUNCTION_NOTIFICATION -> { + result = NotificationUtils.notify( + 12, NotificationUtils.createNotification( + NotificationUtils.Params(R.mipmap.icon_launcher, "标题", "内容") + ) + ) + showToast(result) + } + ButtonValue.BTN_FUNCTION_NOTIFICATION_REMOVE -> { + result = NotificationUtils.cancel(12) + showToast(result) + } + ButtonValue.BTN_FUNCTION_HOME -> { + result = ActivityUtils.startHomeActivity() + showToast(result) + } + ButtonValue.BTN_FUNCTION_FLASHLIGHT_OPEN -> { + permission_request( + permissions = arrayOf( + Manifest.permission.CAMERA + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + // 非传入 Camera 方式需要注册 + FlashlightUtils.getInstance().register() + val result = FlashlightUtils.getInstance().setFlashlightOn() + showToast(result) + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + ToastTintUtils.warning("打开手电筒需摄像头权限") + } + } + ) + } + ButtonValue.BTN_FUNCTION_FLASHLIGHT_CLOSE -> { + result = FlashlightUtils.getInstance().setFlashlightOff() + showToast(result) + } + ButtonValue.BTN_FUNCTION_SHORTCUT_CHECK -> { + result = ShortCutUtils.hasShortcut("Dev 快捷方式") + showToast(result, "存在快捷方式", "不存在快捷方式") + } + ButtonValue.BTN_FUNCTION_SHORTCUT_CREATE -> { + permission_request( + permissions = arrayOf( + Manifest.permission.INSTALL_SHORTCUT + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + val result = ShortCutUtils.addShortcut( + MainActivity::class.java, + "Dev 快捷方式", + R.mipmap.icon_launcher_round + ) + showToast(result) + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + ToastTintUtils.warning("创建快捷方式需要该权限") + } + } + ) + } + ButtonValue.BTN_FUNCTION_SHORTCUT_DELETE -> { + result = ShortCutUtils.deleteShortcut( + MainActivity::class.java, + "Dev 快捷方式" + ) + showToast(result) + } + ButtonValue.BTN_FUNCTION_MEMORY_PRINT -> { + val memoryInfo = MemoryUtils.printMemoryInfo() + ToastUtils.showShort(memoryInfo) + TAG.log_dTag( + message = memoryInfo + ) + } + ButtonValue.BTN_FUNCTION_DEVICE_PRINT -> { + val deviceInfo = + DeviceUtils.handlerDeviceInfo(DeviceUtils.getDeviceInfo(), "") + ToastUtils.showShort(deviceInfo) + TAG.log_dTag( + message = deviceInfo + ) + } + ButtonValue.BTN_FUNCTION_APP_DETAILS_SETTINGS -> { + result = AppUtils.launchAppDetailsSettings() + showToast(result) + } + ButtonValue.BTN_FUNCTION_GPS_SETTINGS -> { + result = AppUtils.openGpsSettings() + showToast(result) + } + ButtonValue.BTN_FUNCTION_WIRELESS_SETTINGS -> { + result = AppUtils.openWirelessSettings() + showToast(result) + } + ButtonValue.BTN_FUNCTION_SYS_SETTINGS -> { + result = AppUtils.startSysSetting() + showToast(result) + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/LoggerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/LoggerActivity.kt new file mode 100644 index 0000000000..45699c2889 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/LoggerActivity.kt @@ -0,0 +1,71 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.loggerButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.logger.LogConfig +import dev.utils.app.logger.LogLevel +import dev.utils.app.toast.ToastTintUtils +import utils_use.logger.LoggerUse + +/** + * detail: DevLogger 日志工具类 + * @author Ttt + * [LoggerUse] + */ +@Route(path = RouterPath.OTHER_FUNCTION.LoggerActivity_PATH) +class LoggerActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化日志配置 + LogConfig().apply { + // 堆栈方法总数 ( 显示经过的方法 ) + methodCount = 3 + // 堆栈方法索引偏移 (0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + methodOffset = 0 + // 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + outputMethodAll = false + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + displayThreadInfo = false + // 是否排序日志 ( 格式化后 ) + sortLog = false // 是否美化日志, 边框包围 + // 日志级别 + logLevel = LogLevel.DEBUG + // 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + tag = "BaseLog" + // 进行初始化配置, 这样设置后, 默认全部日志都使用改配置, 特殊使用 DevLogger.other(config).d(xxx) +// DevLogger.initialize(this) + } + + // 初始化布局管理器、适配器 + ButtonAdapter(loggerButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_LOGGER_PRINT -> { + showToast(true, "打印成功, 请查看 Logcat") + LoggerUse.tempLog() + } + ButtonValue.BTN_LOGGER_TIME -> { + showToast(true, "打印成功, 请查看 Logcat") + LoggerUse.testTime() + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/PathActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/PathActivity.kt new file mode 100644 index 0000000000..6f83ff87ec --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/PathActivity.kt @@ -0,0 +1,196 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.utils.DevFinal +import dev.utils.app.PathUtils +import dev.utils.app.toast.ToastTintUtils +import dev.utils.common.StringUtils + +/** + * detail: 路径信息 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.PathActivity_PATH) +class PathActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.pathButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + val builder = StringBuilder() + when (buttonValue.type) { + ButtonValue.BTN_PATH_INTERNAL -> { + StringUtils.appendsIgnoreLast( + builder, DevFinal.SYMBOL.NEW_LINE, + "内部存储路径", + PathUtils.getInternal().rootPath, + PathUtils.getInternal().rootDirectory, + PathUtils.getInternal().dataPath, + PathUtils.getInternal().dataDirectory, + PathUtils.getInternal().downloadCachePath, + PathUtils.getInternal().downloadCacheDirectory, + PathUtils.getInternal().appDataPath, + PathUtils.getInternal().appDataDir, + PathUtils.getInternal().getAppDataPath("app_webview"), + PathUtils.getInternal().getAppDataDir("app_webview"), + PathUtils.getInternal().appCachePath, + PathUtils.getInternal().appCacheDir, + PathUtils.getInternal().getAppCachePath("ttt"), + PathUtils.getInternal().getAppCacheDir("ttt"), + PathUtils.getInternal().appCodeCachePath, + PathUtils.getInternal().appCodeCacheDir, + PathUtils.getInternal().appDbsPath, + PathUtils.getInternal().appDbsDir, + PathUtils.getInternal().getAppDbPath("dbName"), + PathUtils.getInternal().getAppDbFile("dbName"), + PathUtils.getInternal().appFilesPath, + PathUtils.getInternal().appFilesDir, + PathUtils.getInternal().getAppFilesPath("Temp"), + PathUtils.getInternal().getAppFilesDir("Temp"), + PathUtils.getInternal().appSpPath, + PathUtils.getInternal().appSpDir, + PathUtils.getInternal().getAppSpPath("SPConfig.xml"), + PathUtils.getInternal().getAppSpFile("SPConfig.xml"), + PathUtils.getInternal().appNoBackupFilesPath, + PathUtils.getInternal().appNoBackupFilesDir, + PathUtils.getInternal().appMusicPath, + PathUtils.getInternal().appMusicDir, + PathUtils.getInternal().appPodcastsPath, + PathUtils.getInternal().appPodcastsDir, + PathUtils.getInternal().appRingtonesPath, + PathUtils.getInternal().appRingtonesDir, + PathUtils.getInternal().appAlarmsPath, + PathUtils.getInternal().appAlarmsDir, + PathUtils.getInternal().appNotificationsPath, + PathUtils.getInternal().appNotificationsDir, + PathUtils.getInternal().appPicturesPath, + PathUtils.getInternal().appPicturesDir, + PathUtils.getInternal().appMoviesPath, + PathUtils.getInternal().appMoviesDir, + PathUtils.getInternal().appDownloadPath, + PathUtils.getInternal().appDownloadDir, + PathUtils.getInternal().appDCIMPath, + PathUtils.getInternal().appDCIMDir, + PathUtils.getInternal().appDocumentsPath, + PathUtils.getInternal().appDocumentsDir, + PathUtils.getInternal().appAudiobooksPath, + PathUtils.getInternal().appAudiobooksDir, + "" + ) + TAG.log_dTag( + message = builder.toString() + ) + showToast(true, "信息已打印, 请查看 Logcat") + } + ButtonValue.BTN_PATH_APP_EXTERNAL -> { + StringUtils.appendsIgnoreLast( + builder, DevFinal.SYMBOL.NEW_LINE, + "应用外部存储路径类", + PathUtils.getAppExternal().appDataPath, + PathUtils.getAppExternal().appDataDir, + PathUtils.getAppExternal().getAppDataPath("temp"), + PathUtils.getAppExternal().getAppDataDir("temp"), + PathUtils.getAppExternal().appCachePath, + PathUtils.getAppExternal().appCacheDir, + PathUtils.getAppExternal().getAppCachePath("devutils"), + PathUtils.getAppExternal().getAppCacheDir("devutils"), + PathUtils.getAppExternal().getExternalFilesPath(null), + PathUtils.getAppExternal().getExternalFilesDir(null), + PathUtils.getAppExternal().appFilesPath, + PathUtils.getAppExternal().appFilesDir, + PathUtils.getAppExternal().getAppFilesPath("project"), + PathUtils.getAppExternal().getAppFilesDir("project"), + PathUtils.getAppExternal().appMusicPath, + PathUtils.getAppExternal().appMusicDir, + PathUtils.getAppExternal().appPodcastsPath, + PathUtils.getAppExternal().appPodcastsDir, + PathUtils.getAppExternal().appRingtonesPath, + PathUtils.getAppExternal().appRingtonesDir, + PathUtils.getAppExternal().appAlarmsPath, + PathUtils.getAppExternal().appAlarmsDir, + PathUtils.getAppExternal().appNotificationsPath, + PathUtils.getAppExternal().appNotificationsDir, + PathUtils.getAppExternal().appPicturesPath, + PathUtils.getAppExternal().appPicturesDir, + PathUtils.getAppExternal().appMoviesPath, + PathUtils.getAppExternal().appMoviesDir, + PathUtils.getAppExternal().appDownloadPath, + PathUtils.getAppExternal().appDownloadDir, + PathUtils.getAppExternal().appDCIMPath, + PathUtils.getAppExternal().appDCIMDir, + PathUtils.getAppExternal().appDocumentsPath, + PathUtils.getAppExternal().appDocumentsDir, + PathUtils.getAppExternal().appAudiobooksPath, + PathUtils.getAppExternal().appAudiobooksDir, + PathUtils.getAppExternal().appObbPath, + PathUtils.getAppExternal().appObbDir, + "" + ) + TAG.log_dTag( + message = builder.toString() + ) + showToast(true, "信息已打印, 请查看 Logcat") + } + ButtonValue.BTN_PATH_SDCARD -> { + StringUtils.appendsIgnoreLast( + builder, DevFinal.SYMBOL.NEW_LINE, + "外部存储路径 ( SDCard )", + PathUtils.getSDCard().isSDCardEnable, + PathUtils.getSDCard().sdCardFile, + PathUtils.getSDCard().sdCardPath, + PathUtils.getSDCard().getSDCardFile("DevUtils"), + PathUtils.getSDCard().getSDCardPath("DevUtils"), + PathUtils.getSDCard().getExternalStoragePublicPath("project"), + PathUtils.getSDCard().getExternalStoragePublicDir("project"), + PathUtils.getSDCard().musicPath, + PathUtils.getSDCard().musicDir, + PathUtils.getSDCard().podcastsPath, + PathUtils.getSDCard().podcastsDir, + PathUtils.getSDCard().ringtonesPath, + PathUtils.getSDCard().ringtonesDir, + PathUtils.getSDCard().alarmsPath, + PathUtils.getSDCard().alarmsDir, + PathUtils.getSDCard().notificationsPath, + PathUtils.getSDCard().notificationsDir, + PathUtils.getSDCard().picturesPath, + PathUtils.getSDCard().picturesDir, + PathUtils.getSDCard().moviesPath, + PathUtils.getSDCard().moviesDir, + PathUtils.getSDCard().downloadPath, + PathUtils.getSDCard().downloadDir, + PathUtils.getSDCard().dcimPath, + PathUtils.getSDCard().dcimDir, + PathUtils.getSDCard().documentsPath, + PathUtils.getSDCard().documentsDir, + PathUtils.getSDCard().audiobooksPath, + PathUtils.getSDCard().audiobooksDir, + "" + ) + TAG.log_dTag( + message = builder.toString() + ) + showToast(true, "信息已打印, 请查看 Logcat") + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/WallpaperActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/WallpaperActivity.kt new file mode 100644 index 0000000000..0105bda19f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/dev_function/WallpaperActivity.kt @@ -0,0 +1,61 @@ +package afkt.project.feature.other_function.dev_function + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityWallpaperBinding +import afkt.project.model.item.RouterPath +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.WallpaperUtils +import dev.utils.common.FileUtils + +/** + * detail: 手机壁纸 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.WallpaperActivity_PATH) +class WallpaperActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_wallpaper + + override fun initValue() { + super.initValue() + + val wallpaper = WallpaperUtils.getDrawable() + + binding.vidSaveBtn.setOnClickListener { + if (wallpaper == null) { + showToast(false, "获取壁纸失败") + return@setOnClickListener + } + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + "${System.currentTimeMillis()}.jpg" + ), + DevSource.create( + wallpaper + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + } + wallpaper?.let { + binding.vidIv.background = it + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManager2Activity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManager2Activity.kt new file mode 100644 index 0000000000..9e25c8eec1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManager2Activity.kt @@ -0,0 +1,176 @@ +package afkt.project.feature.other_function.floating + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.graphics.PointF +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.ViewUtils +import dev.utils.app.assist.floating.* +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: 悬浮窗管理辅助类 ( 无需权限依赖 Activity ) + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.FloatingWindowManager2Activity_PATH) +class FloatingWindowManager2Activity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.floatingWindowButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_OPEN_FLOATING_WINDOW -> { + Utils2.instance.apply { + isNeedsAdd = true + // 添加悬浮窗 View + addFloatingView(this@FloatingWindowManager2Activity) + } + } + ButtonValue.BTN_CLOSE_FLOATING_WINDOW -> { + Utils2.instance.apply { + isNeedsAdd = false + // 移除所有悬浮窗 View + removeAllFloatingView() + } + } + } + } + }).bindAdapter(binding.vidRv) + } +} + +// ========== +// = 实现代码 = +// ========== + +/** + * detail: 悬浮窗工具类 + * @author Ttt + */ +internal class Utils2 private constructor() : IFloatingOperate { + + companion object { + + val instance: Utils2 by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + Utils2() + } + } + + // 悬浮窗管理辅助类 + private val mAssist = object : FloatingWindowManagerAssist2() { + override fun updateViewLayout( + floatingActivity: IFloatingActivity, + view: View + ) { + instance.updateViewLayout(floatingActivity, view) + } + }.apply { + // 默认不添加悬浮处理 + isNeedsAdd = false + } + + // 悬浮窗触摸辅助类实现 + private val mTouchAssist = DevFloatingTouchIMPL2().apply { + (floatingEdge as? DevFloatingEdgeIMPL)?.let { edge -> + edge.setStatusBarHeightMargin() + edge.setNavigationBarHeightMargin() + } + // 悬浮窗触摸事件接口 ( 如果不需要触发点击、长按则可不设置 ) + floatingListener = object : DevFloatingListener() { + override fun onClick( + view: View?, + event: MotionEvent, + firstPoint: PointF + ): Boolean { + if (DevFloatingCommon.isValidEvent(event, firstPoint)) { + ToastTintUtils.info("触发点击") + } + return true + } + + override fun onLongClick( + view: View?, + event: MotionEvent, + firstPoint: PointF + ): Boolean { + if (DevFloatingCommon.isValidEvent(event, firstPoint)) { + ToastTintUtils.info("触发长按") + } + return true + } + } + } + + /** + * 创建悬浮 View + * @param floatingActivity 悬浮窗辅助类接口 + * @return FloatingView + */ + fun createFloatingView(floatingActivity: IFloatingActivity): FloatingView { + return FloatingView(floatingActivity.attachActivity, mTouchAssist) + } + + /** + * 创建悬浮 View LayoutParams + * @param floatingActivity 悬浮窗辅助类接口 + * @return ViewGroup.LayoutParams + */ + fun createLayoutParams(floatingActivity: IFloatingActivity): ViewGroup.LayoutParams { + return FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(mTouchAssist.x, mTouchAssist.y, 0, 0) + } + } + + // ==================== + // = IFloatingOperate = + // ==================== + + override fun removeFloatingView(floatingActivity: IFloatingActivity): Boolean { + return mAssist.removeFloatingView(floatingActivity) + } + + override fun addFloatingView(floatingActivity: IFloatingActivity): Boolean { + return mAssist.addFloatingView(floatingActivity) + } + + override fun removeAllFloatingView() { + mAssist.removeAllFloatingView() + } + + override fun updateViewLayout( + floatingActivity: IFloatingActivity, + view: View + ) { + ViewUtils.setMargin(view, mTouchAssist.x, mTouchAssist.y, 0, 0) + } + + override fun isNeedsAdd(): Boolean { + return mAssist.isNeedsAdd + } + + override fun setNeedsAdd(needsAdd: Boolean) { + mAssist.isNeedsAdd = needsAdd + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManagerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManagerActivity.kt new file mode 100644 index 0000000000..3cf9788316 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/floating/FloatingWindowManagerActivity.kt @@ -0,0 +1,201 @@ +package afkt.project.feature.other_function.floating + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.graphics.PointF +import android.view.MotionEvent +import android.view.View +import android.widget.LinearLayout +import com.alibaba.android.arouter.facade.annotation.Route +import dev.DevUtils +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.utils.app.assist.floating.* +import dev.utils.app.toast.ToastTintUtils +import dev.utils.app.toast.ToastUtils + +/** + * detail: 悬浮窗管理辅助类 ( 需权限 ) + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.FloatingWindowManagerActivity_PATH) +class FloatingWindowManagerActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.floatingWindowButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_OPEN_FLOATING_WINDOW -> { + if (checkOverlayPermission()) { + Utils.instance.addView() + } + } + ButtonValue.BTN_CLOSE_FLOATING_WINDOW -> { + Utils.instance.removeView() + } + } + } + }).bindAdapter(binding.vidRv) + } + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + + if (FloatingWindowManagerAssist.isOverlayRequestCode(requestCode)) { + checkOverlayPermission() + } + } + + private fun checkOverlayPermission(): Boolean { + return if (FloatingWindowManagerAssist.checkOverlayPermission(this, true)) { + true + } else { + ToastUtils.showShort("请先开启悬浮窗权限") + false + } + } +} + +// ========== +// = 实现代码 = +// ========== + +/** + * detail: 悬浮窗工具类 + * @author Ttt + */ +internal class Utils private constructor() { + + companion object { + + val instance: Utils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + Utils() + } + } + + // 悬浮窗管理辅助类 + private val mAssist = FloatingWindowManagerAssist() + + // 悬浮窗触摸辅助类实现 + private val mTouchAssist: IFloatingTouch by lazy { + object : DevFloatingTouchIMPL() { + override fun updateViewLayout( + view: View?, + dx: Int, + dy: Int + ) { + mAssist.updateViewLayout(view, dx, dy) + } + }.apply { + // 悬浮窗触摸事件接口 ( 如果不需要触发点击、长按则可不设置 ) + floatingListener = object : DevFloatingListener() { + override fun onClick( + view: View?, + event: MotionEvent, + firstPoint: PointF + ): Boolean { + if (DevFloatingCommon.isValidEvent(event, firstPoint)) { + ToastTintUtils.info("触发点击") + } + return true + } + + override fun onLongClick( + view: View?, + event: MotionEvent, + firstPoint: PointF + ): Boolean { + if (DevFloatingCommon.isValidEvent(event, firstPoint)) { + ToastTintUtils.info("触发长按") + } + return true + } + } + } + } + + // 悬浮 View + private val mFloatingView: FloatingView by lazy { + FloatingView(DevUtils.getContext(), mTouchAssist) + } + + /** + * 添加悬浮 View + */ + fun addView() { + mAssist.addView(mFloatingView) + } + + /** + * 移除悬浮 View + */ + fun removeView() { + mAssist.removeView(mFloatingView) + } +} + +/** + * detail: 悬浮 View + * @author Ttt + */ +@SuppressLint("ViewConstructor") +class FloatingView( + thisContext: Context, + private val assist: IFloatingTouch? +) : LinearLayout(thisContext) { + + init { + initialize() + } + + private fun initialize() { + View.inflate(context, R.layout.layout_floating, this) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + assist?.onTouchEvent(this, event) + return true + } + + // = + + private val TAG = FloatingView::class.java.simpleName + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + + TAG.log_dTag( + message = "onAttachedToWindow" + ) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + TAG.log_dTag( + message = "onDetachedFromWindow" + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/listener/ListenerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/listener/ListenerActivity.kt new file mode 100644 index 0000000000..5dec58bbaa --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/listener/ListenerActivity.kt @@ -0,0 +1,689 @@ +package afkt.project.feature.other_function.listener + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCommonTipsBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.listenerButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.annotation.SuppressLint +import android.os.Handler +import android.os.Message +import android.telephony.SmsMessage +import android.view.OrientationEventListener +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.expand.engine.log.log_eTag +import dev.receiver.* +import dev.receiver.AppStateReceiver.Companion.setListener +import dev.receiver.BatteryReceiver.Companion.setListener +import dev.receiver.NetWorkReceiver.Companion.setListener +import dev.receiver.PhoneReceiver.CallState +import dev.receiver.PhoneReceiver.Companion.setListener +import dev.receiver.SMSReceiver.Companion.setListener +import dev.receiver.ScreenReceiver.Companion.setListener +import dev.receiver.TimeReceiver.Companion.setListener +import dev.receiver.WifiReceiver.Companion.setListener +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.assist.ScreenSensorAssist +import dev.utils.app.helper.view.ViewHelper +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: 事件 / 广播监听 ( 网络状态、屏幕旋转等 ) + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.ListenerActivity_PATH) +class ListenerActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_common_tips + + override fun onDestroy() { + super.onDestroy() + // 注销监听 + WifiReceiver.unregister() + NetWorkReceiver.unregister() + PhoneReceiver.unregister() + SMSReceiver.unregister() + TimeReceiver.unregister() + ScreenReceiver.unregister() + BatteryReceiver.unregister() + AppStateReceiver.unregister() + screenSensorAssist.stop() + mOrientationEventListener?.disable() + } + + override fun initValue() { + super.initValue() + + val view = ViewUtils.inflate(this, R.layout.base_view_textview) + ViewHelper.get().setText("单击绑定, 长按注销", view) + .setTextColors(ResourceUtils.getColor(R.color.gray), view) + binding.vidLl.addView(view) + + // 初始化布局管理器、适配器 + ButtonAdapter(listenerButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_WIFI_LISTENER -> wifiListener(true) + ButtonValue.BTN_NETWORK_LISTENER -> netListener(true) + ButtonValue.BTN_PHONE_LISTENER -> phoneListener(true) + ButtonValue.BTN_SMS_LISTENER -> smsListener(true) + ButtonValue.BTN_TIME_LISTENER -> timeListener(true) + ButtonValue.BTN_SCREEN_LISTENER -> screenListener(true) + ButtonValue.BTN_ROTA_LISTENER -> rotaListener(true) + ButtonValue.BTN_ROTA2_LISTENER -> rotaListener2(true) + ButtonValue.BTN_BATTERY_LISTENER -> batteryListener(true) + ButtonValue.BTN_APP_STATE_LISTENER -> appStateListener(true) + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + + override fun onItemLongClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_WIFI_LISTENER -> wifiListener(false) + ButtonValue.BTN_NETWORK_LISTENER -> netListener(false) + ButtonValue.BTN_PHONE_LISTENER -> phoneListener(false) + ButtonValue.BTN_SMS_LISTENER -> smsListener(false) + ButtonValue.BTN_TIME_LISTENER -> timeListener(false) + ButtonValue.BTN_SCREEN_LISTENER -> screenListener(false) + ButtonValue.BTN_ROTA_LISTENER -> rotaListener(false) + ButtonValue.BTN_ROTA2_LISTENER -> rotaListener2(false) + ButtonValue.BTN_BATTERY_LISTENER -> batteryListener(false) + ButtonValue.BTN_APP_STATE_LISTENER -> appStateListener(false) + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidInclude.vidRv) + } + + // ============ + // = Listener = + // ============ + + /** + * Wifi 监听 + * @param isBind 是否绑定 + */ + private fun wifiListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销 Wifi 监听成功") + // 清空回调 + WifiReceiver.setListener(null) + // 注销监听 + WifiReceiver.unregister() + } else { + ToastTintUtils.success("绑定 Wifi 监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : WifiReceiver.Listener { + override fun onWifiSwitch(isOpenWifi: Boolean) { // Wifi 开关状态 + TAG.log_dTag( + message = "Wifi 是否打开: $isOpenWifi" + ) + } + + override fun onIntoTrigger() { + TAG.log_dTag( + message = "触发回调通知 ( 每次进入都通知 )" + ) + } + + override fun onTrigger(what: Int) { + when (what) { + WifiReceiver.WIFI_SCAN_FINISH -> { + TAG.log_dTag( + message = "startScan() 扫描附近 Wifi 结束触发" + ) + } + WifiReceiver.WIFI_RSSI_CHANGED -> { + TAG.log_dTag( + message = "已连接的 Wifi 强度发生变化" + ) + } + WifiReceiver.WIFI_ERROR_AUTHENTICATING -> { + TAG.log_dTag( + message = "Wifi 认证错误 ( 密码错误等 )" + ) + } + WifiReceiver.WIFI_ERROR_UNKNOWN -> { + TAG.log_dTag( + message = "连接错误 ( 其他错误 )" + ) + } + WifiReceiver.WIFI_STATE_ENABLED -> { + TAG.log_dTag( + message = "Wifi 已打开" + ) + } + WifiReceiver.WIFI_STATE_ENABLING -> { + TAG.log_dTag( + message = "Wifi 正在打开" + ) + } + WifiReceiver.WIFI_STATE_DISABLED -> { + TAG.log_dTag( + message = "Wifi 已关闭" + ) + } + WifiReceiver.WIFI_STATE_DISABLING -> { + TAG.log_dTag( + message = "Wifi 正在关闭" + ) + } + WifiReceiver.WIFI_STATE_UNKNOWN -> { + TAG.log_dTag( + message = "Wifi 状态未知" + ) + } + WifiReceiver.CONNECTED -> { + TAG.log_dTag( + message = "Wifi 连接成功" + ) + } + WifiReceiver.CONNECTING -> { + TAG.log_dTag( + message = "Wifi 连接中" + ) + } + WifiReceiver.DISCONNECTED -> { + TAG.log_dTag( + message = "Wifi 连接失败、断开" + ) + } + WifiReceiver.SUSPENDED -> { + TAG.log_dTag( + message = "Wifi 暂停、延迟" + ) + } + WifiReceiver.UNKNOWN -> { + TAG.log_dTag( + message = "Wifi 未知" + ) + } + } + } + + override fun onTrigger( + what: Int, + message: Message? + ) { // Wifi 在连接过程的状态返回 + val ssid = message?.obj as? String + when (what) { + WifiReceiver.CONNECTED -> { + TAG.log_dTag( + message = "连接 Wifi 成功: $ssid" + ) + } + WifiReceiver.CONNECTING -> { + TAG.log_dTag( + message = "连接 Wifi 中: $ssid" + ) + } + WifiReceiver.DISCONNECTED -> { + TAG.log_dTag( + message = "连接 Wifi 断开" + ) + } + WifiReceiver.SUSPENDED -> { + TAG.log_dTag( + message = "连接 Wifi 暂停、延迟" + ) + } + WifiReceiver.UNKNOWN -> { + TAG.log_dTag( + message = "连接 Wifi 状态未知" + ) + } + } + } + }) + // 注册监听 + WifiReceiver.register() + } + } + + /** + * 网络监听 + * @param isBind 是否绑定 + */ + private fun netListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销网络监听成功") + // 清空回调 + NetWorkReceiver.setListener(null) + // 注销监听 + NetWorkReceiver.unregister() + } else { + ToastTintUtils.success("绑定网络监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : NetWorkReceiver.Listener { + override fun onNetworkState(nType: Int) { + var state = "" + when (nType) { + NetWorkReceiver.NET_WIFI -> state = "Wifi" + NetWorkReceiver.NET_MOBILE -> state = "移动网络" + NetWorkReceiver.NO_NETWORK -> state = "( 无网络 / 未知 ) 状态" + } + TAG.log_dTag( + message = "网络连接状态 $state" + ) + } + }) + // 注册监听 + NetWorkReceiver.register() + } + } + + /** + * 电话监听 + * @param isBind 是否绑定 + */ + private fun phoneListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销电话监听成功") + // 清空回调 + PhoneReceiver.setListener(null) + // 注销监听 + PhoneReceiver.unregister() + } else { + ToastTintUtils.success("绑定电话监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : PhoneReceiver.Listener { + override fun onPhoneStateChanged( + callState: CallState, + number: String? + ) { + when (callState) { + CallState.OUTGOING -> { + TAG.log_dTag( + message = "播出电话: $number" + ) + } + CallState.OUTGOING_END -> { + TAG.log_dTag( + message = "播出电话结束: $number" + ) + } + CallState.INCOMING_RING -> { + TAG.log_dTag( + message = "接入电话铃响: $number" + ) + } + CallState.INCOMING -> { + TAG.log_dTag( + message = "接入通话中: $number" + ) + } + CallState.INCOMING_END -> { + TAG.log_dTag( + message = "接入通话完毕: $number" + ) + } + } + } + }) + // 注册监听 + PhoneReceiver.register() + } + } + + /** + * 短信监听 + * @param isBind 是否绑定 + */ + private fun smsListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销短信监听成功") + // 清空回调 + SMSReceiver.setListener(null) + // 注销监听 + SMSReceiver.unregister() + } else { + ToastTintUtils.success("绑定短信监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : SMSReceiver.Listener { + override fun onMessage( + msg: String?, + fromAddress: String?, + serviceCenterAddress: String? + ) { + TAG.log_dTag( + message = "onMessage\nmsg: %s\nfromAddress: %s\nserviceCenterAddress: %s", + args = arrayOf(msg, fromAddress, serviceCenterAddress) + ) + } + + override fun onMessage(msg: SmsMessage?) { + TAG.log_dTag( + message = "onMessage\nSmsMessage: ${msg.toString()}" + ) + } + }) + // 注册监听 + SMSReceiver.register() + } + } + + /** + * 时区、时间监听 + * @param isBind 是否绑定 + */ + private fun timeListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销时区、时间监听成功") + // 清空回调 + TimeReceiver.setListener(null) + // 注销监听 + TimeReceiver.unregister() + } else { + ToastTintUtils.success("绑定时区、时间监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : TimeReceiver.Listener { + override fun onTimeZoneChanged() { + TAG.log_dTag( + message = "onTimeZoneChanged: 时区改变" + ) + } + + override fun onTimeChanged() { + TAG.log_dTag( + message = "onTimeChanged: 时间改变" + ) + } + + override fun onTimeTick() { + TAG.log_dTag( + message = "onTimeTick: 分钟改变" + ) + } + }) + // 注册监听 + TimeReceiver.register() + } + } + + /** + * 屏幕监听 + * @param isBind 是否绑定 + */ + private fun screenListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销屏幕监听成功") + // 清空回调 + ScreenReceiver.setListener(null) + // 注销监听 + ScreenReceiver.unregister() + } else { + ToastTintUtils.success("绑定屏幕监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : ScreenReceiver.Listener { + override fun screenOn() { + TAG.log_dTag( + message = "screenOn: 用户打开屏幕 - 屏幕变亮" + ) + } + + override fun screenOff() { + TAG.log_dTag( + message = "screenOff: 锁屏触发" + ) + } + + override fun userPresent() { + TAG.log_dTag( + message = "userPresent: 用户解锁触发" + ) + } + }) + // 注册监听 + ScreenReceiver.register() + } + } + + // 重力传感器辅助类 + private val screenSensorAssist = ScreenSensorAssist() + + // 切屏时间 + private var mOrientationTime = 0L + + /** + * 屏幕旋转监听 ( 重力传感器 ) + * @param isBind 是否绑定 + */ + @SuppressLint("HandlerLeak") + private fun rotaListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销屏幕旋转监听 ( 重力传感器 ) 成功") + // 注销监听 + screenSensorAssist.stop() + } else { + ToastTintUtils.success("绑定屏幕旋转监听 ( 重力传感器 ) 成功, 请查看 Logcat") + // 注册监听 + screenSensorAssist.start(object : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + when (msg.what) { + ScreenSensorAssist.CHANGE_ORIENTATION_WHAT -> { + if (!screenSensorAssist.isAllowChange) { // 如果不允许切屏, 则不显示 + return + } else if (isFinishing) { // 如果页面关闭了 + return + } + // 获取触发的方向 + val orientation = msg.arg1 + // 判断方向 + if (orientation == 1) { // 横屏 + // 当前时间 - 切屏的时间大于 1.5 秒间隔才进行处理 + if (System.currentTimeMillis() - mOrientationTime >= 1500) { + TAG.log_dTag( + message = "横屏" + ) + // 重置时间,防止多次触发 + mOrientationTime = System.currentTimeMillis() + // 跳转到横屏, 并且关闭监听 + //Intent intent = new Intent(mContext, Activity.class) + //mContext.startActivity(intent) + } + } else if (orientation == 2) { // 竖屏 + TAG.log_dTag( + message = "竖屏" + ) + } + } + } + } + }) + } + } + + // 判断是否竖屏 + private var mPortrait = true + + // 录制角度记录值 + private var mRotationFlag = 90 + + // 录制角度旋值 + private var mRotationRecord = 90 + + // 旋转监听事件 + private var mOrientationEventListener: OrientationEventListener? = null + + /** + * 屏幕旋转监听 ( OrientationEventListener ) + * @param isBind 是否绑定 + */ + private fun rotaListener2(isBind: Boolean) { + if (mOrientationEventListener == null) { + mOrientationEventListener = object : OrientationEventListener(mContext) { + override fun onOrientationChanged(rotation: Int) { + if (rotation in 0..30 || rotation >= 330) { + TAG.log_dTag( + message = "竖屏拍摄" + ) + mPortrait = true + // 竖屏拍摄 + if (mRotationFlag != 0) { + // 这是竖屏视频需要的角度 + mRotationRecord = 90 + // 这是记录当前角度的 flag + mRotationFlag = 0 + } + } else if (rotation in 230..310) { + TAG.log_dTag( + message = "横屏拍摄" + ) + mPortrait = false + // 横屏拍摄 + if (mRotationFlag != 90) { + // 这是正横屏视频需要的角度 + mRotationRecord = 0 + // 这是记录当前角度的 flag + mRotationFlag = 90 + } + } else if (rotation in 31..134) { + TAG.log_dTag( + message = "反横屏拍摄" + ) + mPortrait = false + // 反横屏拍摄 + if (mRotationFlag != 270) { + // 这是反横屏视频需要的角度 + mRotationRecord = 180 + // 这是记录当前角度的 flag + mRotationFlag = 270 + } + } else if (rotation in 136..229) { + TAG.log_dTag( + message = "反竖屏拍摄" + ) + mPortrait = true + // 竖屏拍摄 + if (mRotationFlag != 360) { + // 这是竖屏视频需要的角度 + mRotationRecord = 270 + // 这是记录当前角度的 flag + mRotationFlag = 360 + } + } + } + } + } + try { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销屏幕旋转监听 ( OrientationEventListener ) 成功") + // 注销监听 + mOrientationEventListener?.disable() + } else { + ToastTintUtils.success("绑定屏幕旋转监听 ( OrientationEventListener ) 成功, 请查看 Logcat") + // 注册监听 + mOrientationEventListener?.enable() + } + } catch (e: Exception) { + TAG.log_eTag( + message = "rotaListener2" + ) + } + } + + /** + * 电量监听 + * @param isBind 是否绑定 + */ + private fun batteryListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销电量监听成功") + // 清空回调 + BatteryReceiver.setListener(null) + // 注销监听 + BatteryReceiver.unregister() + } else { + ToastTintUtils.success("绑定电量监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : BatteryReceiver.Listener { + override fun onBatteryChanged(level: Int) { + TAG.log_dTag( + message = "电量改变通知 level: $level" + ) + } + + override fun onBatteryLow(level: Int) { + TAG.log_dTag( + message = "电量低通知 level: $level" + ) + } + + override fun onBatteryOkay(level: Int) { + TAG.log_dTag( + message = "电量从低变回高通知 level: $level" + ) + } + + override fun onPowerConnected( + level: Int, + isConnected: Boolean + ) { + TAG.log_dTag( + message = "充电状态改变通知 level: %s, 是否充电中: %s", + args = arrayOf(level, isConnected) + ) + } + + override fun onPowerUsageSummary(level: Int) { + TAG.log_dTag( + message = "电力使用情况总结 level: $level" + ) + } + }) + // 注册监听 + BatteryReceiver.register() + } + } + + /** + * 应用状态监听 + * @param isBind 是否绑定 + */ + private fun appStateListener(isBind: Boolean) { + if (!isBind) { // 取反判断, 方便代码顺序查看 + ToastTintUtils.success("注销应用状态监听成功") + // 清空回调 + AppStateReceiver.setListener(null) + // 注销监听 + AppStateReceiver.unregister() + } else { + ToastTintUtils.success("绑定应用状态监听成功, 请查看 Logcat") + // 设置监听事件 + setListener(object : AppStateReceiver.Listener { + override fun onAdded(packageName: String?) { + TAG.log_dTag( + message = "应用安装 packageName: $packageName" + ) + } + + override fun onReplaced(packageName: String?) { + TAG.log_dTag( + message = "应用更新 packageName: $packageName" + ) + } + + override fun onRemoved(packageName: String?) { + TAG.log_dTag( + message = "应用卸载 packageName: $packageName" + ) + } + }) + // 注册监听 + AppStateReceiver.register() + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/AccessibilityListenerServiceActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/AccessibilityListenerServiceActivity.kt new file mode 100644 index 0000000000..f0b847faea --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/AccessibilityListenerServiceActivity.kt @@ -0,0 +1,113 @@ +package afkt.project.feature.other_function.service + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.accessibilityListenerServiceButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.view.accessibility.AccessibilityEvent +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.service.AccessibilityListenerService +import dev.utils.app.AppUtils +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: 无障碍监听服务 ( AccessibilityListenerService ) + * @author Ttt + * @see https://www.jianshu.com/p/981e7de2c7be + * 所需权限 + * + */ +@Route(path = RouterPath.OTHER_FUNCTION.AccessibilityListenerServiceActivity_PATH) +class AccessibilityListenerServiceActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onDestroy() { + super.onDestroy() + // 注销监听 + AccessibilityListenerService.setListener(null) + AccessibilityListenerService.stopService() + } + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(accessibilityListenerServiceButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_ACCESSIBILITY_SERVICE_CHECK -> { + val check = + AccessibilityListenerService.isAccessibilitySettingsOn(AppUtils.getPackageName()) + showToast(check, "已开启无障碍功能", "未开启无障碍功能") + } + ButtonValue.BTN_ACCESSIBILITY_SERVICE_REGISTER -> { + if (!AccessibilityListenerService.checkAccessibility()) { + showToast(false, "请先开启无障碍功能") + return + } + showToast(true, "绑定无障碍监听服务成功, 请查看 Logcat") + // 注册监听 + AccessibilityListenerService.startService() + } + ButtonValue.BTN_ACCESSIBILITY_SERVICE_UNREGISTER -> { + showToast(true, "注销无障碍监听服务成功") + // 注销监听 + AccessibilityListenerService.stopService() + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + + // 设置监听事件 + AccessibilityListenerService.setListener(object : AccessibilityListenerService.Listener { + override fun onAccessibilityEvent( + accessibilityEvent: AccessibilityEvent?, + accessibilityListenerService: AccessibilityListenerService? + ) { + val builder = StringBuilder() + .append("onAccessibilityEvent") + .append("\naccessibilityEvent: ") + .append(accessibilityEvent) + TAG.log_dTag( + message = builder.toString() + ) + } + + override fun onInterrupt() { + super.onInterrupt() + TAG.log_dTag( + message = "onInterrupt" + ) + } + + override fun onServiceCreated(service: AccessibilityListenerService?) { + super.onServiceCreated(service) + TAG.log_dTag( + message = "onServiceCreated" + ) + } + + override fun onServiceDestroy() { + super.onServiceDestroy() + TAG.log_dTag( + message = "onServiceDestroy" + ) + } + }) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/NotificationServiceActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/NotificationServiceActivity.kt new file mode 100644 index 0000000000..b27195fdde --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/service/NotificationServiceActivity.kt @@ -0,0 +1,136 @@ +package afkt.project.feature.other_function.service + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.notificationServiceButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.content.Intent +import android.os.Build +import android.service.notification.StatusBarNotification +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.service.NotificationService +import dev.utils.DevFinal +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: 通知栏监听服务 ( NotificationService ) + * @author Ttt + * @see https://www.jianshu.com/p/981e7de2c7be + * 所需权限 + * + */ +@Route(path = RouterPath.OTHER_FUNCTION.NotificationServiceActivity_PATH) +class NotificationServiceActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onDestroy() { + super.onDestroy() + // 注销监听 + NotificationService.setListener(null) + NotificationService.stopService() + } + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(notificationServiceButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_NOTIFICATION_SERVICE_CHECK -> { + val check = NotificationService.isNotificationListenerEnabled() + showToast(check, "已开启服务通知", "未开启服务通知") + } + ButtonValue.BTN_NOTIFICATION_SERVICE_REGISTER -> { + if (!NotificationService.checkAndIntentSetting()) { + showToast(false, "请先开启服务通知权限") + return + } + showToast(true, "绑定通知栏监听服务成功, 请查看 Logcat") + // 注册监听 + NotificationService.startService() + } + ButtonValue.BTN_NOTIFICATION_SERVICE_UNREGISTER -> { + showToast(true, "注销通知栏监听服务成功") + // 注销监听 + NotificationService.stopService() + } + else -> ToastTintUtils.warning("未处理 " + buttonValue.text + " 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + + // 设置监听事件 + NotificationService.setListener(object : NotificationService.Listener { + override fun onServiceCreated(service: NotificationService?) { + TAG.log_dTag( + message = "服务创建通知" + ) + } + + override fun onServiceDestroy() { + TAG.log_dTag( + message = "服务销毁通知" + ) + } + + override fun onStartCommand( + service: NotificationService?, + intent: Intent?, + flags: Int, + startId: Int + ): Int { // 触发服务指令 + val builder = StringBuilder() + .append("onServiceStartCommand") + .append("\nintent: ").append(intent) + .append("\nflags: ").append(flags) + .append("\nstartId: ").append(startId) + TAG.log_dTag( + message = builder.toString() + ) + return 0 + } + + override fun onNotificationPosted(sbn: StatusBarNotification?) { // 当系统收到新的通知后触发回调 + sbn?.notification?.let { + val builder = StringBuilder() + .append("onNotificationPosted") + .append("\nstatusBarNotification: ").append(sbn) + .append("\ntickerText : ").append(it.tickerText) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + val bundle = it.extras + for (key in bundle.keySet()) { + builder.append(DevFinal.SYMBOL.NEW_LINE + key + ": " + bundle.get(key)) + } + } + TAG.log_dTag( + message = builder.toString() + ) + } + } + + override fun onNotificationRemoved(sbn: StatusBarNotification?) { // 当系统通知被删掉后触发回调 + val builder = StringBuilder() + .append("onNotificationRemoved") + .append("\nstatusBarNotification: ").append(sbn) + TAG.log_dTag( + message = builder.toString() + ) + } + }) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/timer/TimerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/timer/TimerActivity.kt new file mode 100644 index 0000000000..f9f25f7edf --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/timer/TimerActivity.kt @@ -0,0 +1,165 @@ +package afkt.project.feature.other_function.timer + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.timerButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.os.Handler +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.expand.engine.log.log_dTag +import dev.utils.app.HandlerUtils +import dev.utils.app.timer.DevTimer +import dev.utils.app.timer.TimerManager +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: TimerManager 定时器工具类 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.TimerActivity_PATH) +class TimerActivity : BaseActivity() { + + // UI Handler + private val mUiHandler = Handler() + + // 定时器 + private var mTimer: DevTimer? = null + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(timerButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + // 获取操作结果 + val result: Boolean + when (buttonValue.type) { + ButtonValue.BTN_TIMER_START -> { + if (mTimer == null) { + // 初始化定时器 + mTimer = DevTimer.Builder( + 500L, 2000L, + -1, TAG + ).build() + // 设置回调通过 Handler 触发 + mTimer?.apply { + setHandler(mUiHandler) + setCallback { timer: DevTimer?, number: Int, end: Boolean, infinite: Boolean -> + // 触发次数 + if (number == 1) { + TAG.log_dTag( + message = "第一次触发, 0.5 秒延迟" + ) + } else { + TAG.log_dTag( + message = "每隔 2 秒触发一次, 触发次数: $number" + ) + } + } + } + } + mTimer?.apply { + if (isRunning) { + showToast(false, "定时器已经启动, 请查看 Logcat") + } else { + showToast(true, "定时器启动成功, 请查看 Logcat") + // 运行定时器 + start() + } + } + } + ButtonValue.BTN_TIMER_STOP -> { + result = mTimer?.isRunning ?: false + showToast(result, "定时器关闭成功", "定时器未启动") + if (result) mTimer?.stop() + // 回收定时器 + TimerManager.recycle() + } + ButtonValue.BTN_TIMER_RESTART -> { + mTimer?.let { + showToast(true, "定时器启动成功, 请查看 Logcat") + // 运行定时器 + it.start() + return + } + showToast(false, "请先初始化定时器") + } + ButtonValue.BTN_TIMER_CHECK -> { + result = mTimer?.isRunning ?: false + showToast(result, "定时器已启动", "定时器未启动") + } + ButtonValue.BTN_TIMER_GET -> { + val timerTAG = TimerManager.getTimer(TAG) + showToast(timerTAG != null, "获取定时器成功", "暂无该定时器") + } + ButtonValue.BTN_TIMER_GET_NUMBER -> { + result = mTimer?.isRunning ?: false + showToast(result, "定时器运行次数: ${mTimer?.triggerNumber}", "定时器未启动") + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + val timer = DevTimer.Builder(1500L) + .setDelay(100L) // 延迟时间 ( 多少毫秒后开始执行 ) + .setPeriod(1500L) // 循环时间 ( 每隔多少毫秒执行一次 ) + .setTag(TAG) // 定时器 TAG + .setLimit(19) // 触发次数上限 ( 负数为无限循环 ) + .build() // 构建定时器 + timer.setCallback { _, _, _, _ -> + TAG.log_dTag( + message = "是否 UI 线程: ${HandlerUtils.isMainThread()}" + ) + } + // 设置了 Handler 则属于 UI 线程触发回调 + timer.setHandler(mUiHandler) + // 运行定时器 + timer.start() + // 关闭定时器 + timer.stop() + + val uuid = 0 + // 关闭所有对应 UUID 定时器 + TimerManager.closeAllUUID(uuid) + // 关闭所有对应 TAG 定时器 + TimerManager.closeAllTag(TAG) + // 关闭所有无限循环的定时器 + TimerManager.closeAllInfinite() + // 关闭所有未运行的定时器 + TimerManager.closeAllNotRunning() + // 关闭全部定时器 + TimerManager.closeAll() + // 回收定时器资源 + TimerManager.recycle() + // 获取全部定时器总数 + TimerManager.getSize() + // 获取对应 UUID 定时器 ( 优先获取符合的 ) + TimerManager.getTimer(uuid) + // 获取对应 TAG 定时器 ( 优先获取符合的 ) + TimerManager.getTimer(TAG) + // 获取对应 UUID 定时器集合 + TimerManager.getTimers(uuid) + // 获取对应 TAG 定时器集合 + TimerManager.getTimers(TAG) + } + + override fun onDestroy() { + super.onDestroy() + // 关闭所有对应 TAG 定时器 + TimerManager.closeAllTag(TAG) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/web_view/WebViewActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/web_view/WebViewActivity.kt new file mode 100644 index 0000000000..41d7402fa5 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/web_view/WebViewActivity.kt @@ -0,0 +1,143 @@ +package afkt.project.feature.other_function.web_view + +import afkt.project.R +import afkt.project.base.BaseApplication +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityWebviewBinding +import afkt.project.model.item.RouterPath +import android.net.http.SslError +import android.view.KeyEvent +import android.view.View.OnLongClickListener +import android.webkit.SslErrorHandler +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.webkit.WebView.HitTestResult +import android.webkit.WebViewClient +import com.alibaba.android.arouter.facade.annotation.Route +import dev.assist.WebViewAssist +import dev.expand.engine.log.log_dTag + +/** + * detail: WebView 辅助类 + * @author Ttt + */ +@Route(path = RouterPath.OTHER_FUNCTION.WebViewActivity_PATH) +class WebViewActivity : BaseActivity() { + + // WebView 辅助类 + private val mWebViewAssist = WebViewAssist() + + override fun baseLayoutId(): Int = R.layout.activity_webview + + override fun initValue() { + super.initValue() + + // 长按监听事件 + binding.vidWv.setOnLongClickListener(OnLongClickListener { view -> + ((view as WebView).hitTestResult).let { result -> + when (result.type) { + HitTestResult.SRC_IMAGE_ANCHOR_TYPE -> { + val imgUrl = result.extra + TAG.log_dTag( + message = "SRC_IMAGE_ANCHOR_TYPE $imgUrl" + ) + return@OnLongClickListener true + } + else -> { + } + } + } + false + }) + + // 设置 WebView + mWebViewAssist.setWebView(binding.vidWv) + // 设置辅助 WebView 处理 Javascript 对话框、标题等对象 + mWebViewAssist.setWebChromeClient(object : WebChromeClient() { + override fun onProgressChanged( + view: WebView, + position: Int + ) { + // 加载进度监听 + if (position == 100) { // 加载完成 + TAG.log_dTag( + message = "加载完成" + ) + } + super.onProgressChanged(view, position) + } + }) + // 设置处理各种通知和请求事件对象 + mWebViewAssist.setWebViewClient(object : WebViewClient() { + override fun onReceivedSslError( + view: WebView, + handler: SslErrorHandler, + error: SslError + ) { + handler.proceed() // 接受所有网站的证书 +// handler.cancel() // Android 默认的处理方式 + } + + override fun shouldOverrideUrlLoading( + view: WebView, + url: String + ): Boolean { + // 重定向 URL 请求, 返回 true 表示拦截此 url, 返回 false 表示不拦截此 url + if (url.startsWith("weixin://")) { + mWebViewAssist.loadUrl( + url.replace("weixin://", "http://") + ) + return true + } + // 在本页面的 WebView 打开, 防止外部浏览器打开此链接 + mWebViewAssist.loadUrl(url) + return true + } + }) + /** + * 默认使用全局配置 + * [BaseApplication.initWebViewBuilder] + */ +// // 如果使用全局配置, 则直接调用 apply 方法 +// mWebViewAssist.apply() + + // 克隆全局配置, 自行修改部分配置并使用 + WebViewAssist.getGlobalBuilder()?.let { + val builder = it.clone(true) + builder.setBuiltInZoomControls(false) + .setDisplayZoomControls(false) + // 当克隆方法 ( clone ) 传入 true 表示复用 OnApplyListener 但是想要能够添加其他配置处理则如下方法操作 + val applyListener = builder.getApplyListener() + builder.setOnApplyListener(object : WebViewAssist.Builder.OnApplyListener { + override fun onApply( + webViewAssist: WebViewAssist?, + builder: WebViewAssist.Builder + ) { + applyListener?.onApply(webViewAssist, builder) + // BaseApplication 也会打印 WebViewAssist Builder onApply + TAG.log_dTag( + message = "自定义监听" + ) + // 全局配置或者自定义配置以外, 再次配置操作 + // 加载网页 + mWebViewAssist.loadUrl("https://www.csdn.net/") + } + }) + // 应用配置 + mWebViewAssist.setBuilder(builder, true) + } + } + + override fun onKeyDown( + keyCode: Int, + event: KeyEvent + ): Boolean { + if (mWebViewAssist.handlerKeyDown(keyCode, event)) return true + return super.onKeyDown(keyCode, event) + } + + override fun onDestroy() { + super.onDestroy() + mWebViewAssist.destroy() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/QuickWifiHotUtils.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/QuickWifiHotUtils.kt new file mode 100644 index 0000000000..20bbf1f951 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/QuickWifiHotUtils.kt @@ -0,0 +1,334 @@ +package afkt.project.feature.other_function.wifi + +import android.annotation.SuppressLint +import android.net.wifi.WifiManager +import android.os.Handler +import android.os.Message +import dev.expand.engine.log.log_dTag +import dev.utils.app.wifi.WifiHotUtils +import dev.utils.app.wifi.WifiUtils + +/** + * detail: 封装 Wifi 热点快捷操作工具类 + * @author Ttt + * 开启热点前必须获取到所需权限 + */ +class QuickWifiHotUtils( + // Wifi 工具类 + private val wifiUtils: WifiUtils, + // Wifi 热点工具类 + private val wifiHotUtils: WifiHotUtils +) { + // 日志 TAG + private val TAG = QuickWifiHotUtils::class.java.simpleName + + // 是否停止线程检查 + private var mStop = false + + // 是否停止开启检查 + private var mCheck = false + + // 是否线程检查热点连接状态 + private var mThreadCheckHot = false + + // 热点 SSID + private var mHotSSID: String? = null + + // 热点密码 + private var mHotPwd: String? = null + + // 操作接口 + var mOperate: Operate? = null + + @SuppressLint("HandlerLeak") + private val hotHandler: Handler = object : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + // 如果属于需要销毁, 则全部不处理 + mOperate?.let { + if (it.isFinishing) { + mStop = true + mCheck = false + mThreadCheckHot = false + return + } + } + when (msg.what) { + CLOSE_WIFI_SUCCESS -> { + TAG.log_dTag( + message = "hotHandler 关闭 Wifi 成功, 开启热点中" + ) + // 停止线程检查 + setWifiCheck(false) + // 开始进行线程检查 + setWifiApCheck(true) + // 开启热点 + Thread(startWifiSpotThread).start() + } + START_WIFISPOT_SUCCESS -> { + TAG.log_dTag( + message = "hotHandler 开启热点成功" + ) + // 停止线程检查 + setWifiApCheck(false) + // 需要检查连接状态 + mThreadCheckHot = true + // 开启线程检查 + Thread(hotCheckThread).start() + } + CHECK_HOT_CONN -> { + TAG.log_dTag( + message = "hotHandler 检查是否连接热点" + ) + // 判断是否存在设备连接热点 + val isConnectHot = wifiHotUtils.isConnectHot + // 如果存在, 则尝试连接 + if (isConnectHot && mThreadCheckHot) { + // 表示不需要进行检查了 + mThreadCheckHot = false + // 通过获取是否存在其他设备 - 手机连接上该热点 + TAG.log_dTag( + message = "存在设备连接热点" + ) + } + } + } + } + } + + /** + * 设置 Wifi 线程检查状态 + * @param isCheck 是否检查 + */ + private fun setWifiCheck(isCheck: Boolean) { + if (isCheck) { + // 需要进行线程检查 + mStop = false + } else { + // 停止线程检查 + mStop = true + // 删除上一个任务, 并且重新绑定任务 + hotHandler.removeCallbacks(closeWifiThread) + } + } + + /** + * 设置 Wifi 热点线程检查状态 + * @param isCheck 是否检查 + */ + private fun setWifiApCheck(isCheck: Boolean) { + if (isCheck) { + // 需要进行线程检查 + this.mCheck = false + } else { + // 停止线程检查 + this.mCheck = true + // 删除上一个任务, 并且重新绑定任务 + hotHandler.removeCallbacks(startWifiSpotThread) + } + } + + /** + * 关闭 Wifi 检查线程 + */ + private val closeWifiThread = object : Thread() { + override fun run() { + if (mStop) return + // 如果属于需要销毁, 则全部不处理 + mOperate?.let { + if (it.isFinishing) { + mStop = true + mCheck = false + mThreadCheckHot = false + return + } + } + + // 是否延时检查 + var isPostDelayed = false + when (wifiUtils.wifiState) { + WifiManager.WIFI_STATE_ENABLED, + WifiManager.WIFI_STATE_ENABLING -> { + // case WifiManager.WIFI_STATE_UNKNOWN: // 未知 + isPostDelayed = true + TAG.log_dTag( + message = "Wifi 已打开、正在打开" + ) + wifiUtils.closeWifi() // 关闭 Wifi + } + WifiManager.WIFI_STATE_DISABLED -> { + isPostDelayed = false + TAG.log_dTag( + message = "Wifi 已关闭" + ) + } + WifiManager.WIFI_STATE_DISABLING -> { + isPostDelayed = true + TAG.log_dTag( + message = "Wifi 正在关闭" + ) + } + } + // 判断是否延时 0.4 秒进行开启热点 + if (isPostDelayed) { + // 删除上一个任务, 并且重新绑定任务 + hotHandler.removeCallbacks(this) + hotHandler.postDelayed(this, 400) + } else { // 开启热点 + hotHandler.sendEmptyMessage(CLOSE_WIFI_SUCCESS) + } + } + } + + /** + * 开启 Wifi 热点线程 + */ + private val startWifiSpotThread = object : Thread() { + @SuppressLint("MissingPermission") + override fun run() { + if (mCheck) return + // 如果属于需要销毁, 则全部不处理 + mOperate?.let { + if (it.isFinishing) { + mStop = true + mCheck = false + mThreadCheckHot = false + return + } + } + // 是否延时检查 + val isPostDelayed = true + when (wifiHotUtils.wifiApState) { + WifiHotUtils.WIFI_AP_STATE_DISABLING -> { + TAG.log_dTag( + message = "Wifi 热点正在关闭" + ) + } + WifiHotUtils.WIFI_AP_STATE_DISABLED -> { + TAG.log_dTag( + message = "Wifi 热点已关闭" + ) + // 开启热点 + val wifiConfiguration = WifiHotUtils.createWifiConfigToAp(mHotSSID, mHotPwd) + wifiHotUtils.startWifiAp(wifiConfiguration) + } + WifiHotUtils.WIFI_AP_STATE_ENABLING -> { + TAG.log_dTag( + message = "Wifi 热点正在打开" + ) + } + WifiHotUtils.WIFI_AP_STATE_ENABLED -> { + TAG.log_dTag( + message = "Wifi 热点已打开" + ) + TAG.log_dTag( + message = "ssid: ${wifiHotUtils.apWifiSSID}, pwd: ${wifiHotUtils.apWifiSSID}" + ) + } + WifiHotUtils.WIFI_AP_STATE_FAILED -> { + TAG.log_dTag( + message = "Wifi热点状态未知" + ) + } + } + // 判断是否延时 0.4 秒进行开启热点 + if (isPostDelayed) { + // 删除上一个任务, 并且重新绑定任务 + hotHandler.removeCallbacks(this) + hotHandler.postDelayed(this, 400) + } else { // 开启热点成功 + hotHandler.sendEmptyMessage(START_WIFISPOT_SUCCESS) + } + } + } + + /** + * 检查热点连接线程 + */ + private val hotCheckThread = object : Thread() { + override fun run() { + while (mThreadCheckHot) { + // 如果属于需要销毁, 则全部不处理 + mOperate?.let { + if (it.isFinishing) { + mStop = true + mCheck = false + mThreadCheckHot = false + return + } + } + // 检查是否连接热点 + hotHandler.sendEmptyMessage(CHECK_HOT_CONN) + try { + sleep(500) + } catch (ignored: Exception) { + } + } + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 开启热点 + * @param ssid Wifi 名 + * @param pwd Wifi 密码 + */ + fun openHotspot( + ssid: String?, + pwd: String? + ) { + TAG.log_dTag( + message = "openHotspot 开启热点 ssid: $ssid, pwd: $pwd" + ) + mHotSSID = ssid + mHotPwd = pwd + // 如果开启了 Wifi 则进行关闭 Wifi + if (wifiUtils.isOpenWifi) { + // 开始进行线程检查 + setWifiCheck(true) + // 开启线程 + Thread(closeWifiThread).start() + } else { // 开启热点 + hotHandler.sendEmptyMessage(CLOSE_WIFI_SUCCESS) + } + } + + /** + * 关闭热点 + */ + fun closeHotspot() { + // 全部不处理 + mStop = true + mCheck = false + mThreadCheckHot = false + // 关闭热点 + wifiHotUtils.closeWifiAp() + } + + /** + * detail: 操作接口 + * @author Ttt + */ + interface Operate { + /** + * 是否需要销毁 + * @return `true` yes, `false` no + */ + val isFinishing: Boolean + } + + companion object { + + // 成功关闭 Wifi 准备开启热点 + private const val CLOSE_WIFI_SUCCESS = 103 + + // 开启热点成功 + private const val START_WIFISPOT_SUCCESS = 104 + + // 检查是否连接热点 + private const val CHECK_HOT_CONN = 105 + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/WifiActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/WifiActivity.kt new file mode 100644 index 0000000000..df18fcdc6f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/wifi/WifiActivity.kt @@ -0,0 +1,371 @@ +package afkt.project.feature.other_function.wifi + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList.wifiButtonValues +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.Manifest +import android.annotation.SuppressLint +import android.net.wifi.WifiConfiguration +import android.os.Build +import android.os.Handler +import android.os.Message +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.log.log_dTag +import dev.expand.engine.permission.permission_request +import dev.receiver.WifiReceiver +import dev.receiver.WifiReceiver.Companion.register +import dev.receiver.WifiReceiver.Companion.setListener +import dev.receiver.WifiReceiver.Companion.unregister +import dev.utils.app.AppUtils +import dev.utils.app.IntentUtils +import dev.utils.app.permission.PermissionUtils +import dev.utils.app.toast.ToastTintUtils +import dev.utils.app.toast.ToastUtils +import dev.utils.app.wifi.WifiHotUtils +import dev.utils.app.wifi.WifiUtils + +/** + * detail: Wifi 相关 ( 热点 ) + * @author Ttt + * Wifi 热点状态监听等可参考 [QuickWifiHotUtils] + */ +@Route(path = RouterPath.OTHER_FUNCTION.WifiActivity_PATH) +class WifiActivity : BaseActivity() { + + // Wifi 工具类 + var wifiUtils = WifiUtils() + + // Wifi 热点工具类 + var wifiHotUtils = WifiHotUtils() + + // 热点名、密码 + var wifiHotSSID = "DevWifiAp" + var wifiHotPwd = "123456789" + + // Android 8.0 开启热点不能多次点击 + var isOpenAPING = false + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onDestroy() { + super.onDestroy() + // 注销监听 + unregister() + } + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(wifiButtonValues) + .setItemCallback(object : DevItemClickCallback() { + @SuppressLint("MissingPermission") + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_WIFI_OPEN -> { + if (wifiUtils.isOpenWifi) { + ToastTintUtils.error("Wifi 已打开") + } else { + showToast(wifiUtils.openWifi(), "打开成功", "打开失败") + } + } + ButtonValue.BTN_WIFI_CLOSE -> { + if (wifiUtils.isOpenWifi) { + showToast(wifiUtils.closeWifi(), "关闭成功", "关闭失败") + } else { + ToastTintUtils.error("Wifi 已关闭") + } + } + ButtonValue.BTN_WIFI_HOT_OPEN -> { + if (wifiHotUtils.isOpenWifiAp) { + ToastTintUtils.error("Wifi 热点已打开") + } else { + if (isOpenAPING) { + ToastUtils.showShort("Wifi 热点开启中") + return + } + // = 8.0 特殊处理 = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + permission_request( + permissions = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + isOpenAPING = true + // 设置热点 Wifi 监听 + wifiHotUtils.setOnWifiAPListener(object : WifiHotUtils.OnWifiAPListener { + override fun onStarted(wifiConfiguration: WifiConfiguration) { + val wifiAp = + "ssid: ${wifiConfiguration.SSID}, pwd: ${wifiConfiguration.preSharedKey}" + TAG.log_dTag( + message = wifiAp + ) + ToastTintUtils.success(wifiAp) + // 表示操作结束 + isOpenAPING = false + } + + override fun onStopped() { + TAG.log_dTag( + message = "关闭热点" + ) + // 表示操作结束 + isOpenAPING = false + } + + override fun onFailed(reason: Int) { + TAG.log_dTag( + message = "热点异常 reason: $reason" + ) + // 表示操作结束 + isOpenAPING = false + } + }) + // 密码必须大于等于 8 位 + val wifiConfiguration = + WifiHotUtils.createWifiConfigToAp( + wifiHotSSID, wifiHotPwd + ) + val success = + wifiHotUtils.startWifiAp(wifiConfiguration) + showToast(success, "打开热点成功", "打开热点失败") + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + ToastTintUtils.warning("开启热点需要定位权限") + } + } + ) + return + } else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { // 7.0 及以下需要 WRITE_SETTINGS 权限 + // 无法进行申请, 只能跳转到权限页面, 让用户开启 + // 获取写入设置权限 , 必须有这个权限, 否则无法开启 + if (!PermissionUtils.isGranted(Manifest.permission.WRITE_SETTINGS)) { + ToastUtils.showShort("开启热点需要修改系统设置权限") + // 如果没有权限则跳转过去 + AppUtils.startActivity( + IntentUtils.getLaunchAppDetailsSettingsIntent( + AppUtils.getPackageName() + ) + ) + return + } + } + // 密码必须大于等于 8 位 + val wifiConfiguration = + WifiHotUtils.createWifiConfigToAp(wifiHotSSID, wifiHotPwd) +// // 如果不需要密码, 则设置为非密码 +// val wifiConfiguration = +// WifiHotUtils.createWifiConfigToAp("TttWifiAp1", null) + // 开启热点 ( Android 7.1 以上特殊处理 ) + val success = wifiHotUtils.startWifiAp(wifiConfiguration) + showToast(success, "打开热点成功", "打开热点失败") + } + } + ButtonValue.BTN_WIFI_HOT_CLOSE -> { + if (wifiHotUtils.isOpenWifiAp) { + val success = wifiHotUtils.closeWifiAp() + showToast(success, "热点关闭成功", "热点关闭失败") + } else { + ToastTintUtils.error("Wifi 热点已关闭") + } + } + ButtonValue.BTN_WIFI_LISTENER_REGISTER -> { + register() + showToast(true, "注册监听成功, 请查看 Logcat") + } + ButtonValue.BTN_WIFI_LISTENER_UNREGISTER -> { + unregister() + showToast(true, "注销监听成功") + } + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + override fun initListener() { + super.initListener() + + // 设置监听事件 + setListener(object : WifiReceiver.Listener { + override fun onWifiSwitch(isOpenWifi: Boolean) { // Wifi 开关状态 + TAG.log_dTag( + message = "Wifi 是否打开: $isOpenWifi" + ) + } + + override fun onIntoTrigger() { + TAG.log_dTag( + message = "触发回调通知 ( 每次进入都通知 )" + ) + } + + override fun onTrigger(what: Int) { + when (what) { + WifiReceiver.WIFI_SCAN_FINISH -> { + TAG.log_dTag( + message = "startScan() 扫描附近 Wifi 结束触发" + ) + } + WifiReceiver.WIFI_RSSI_CHANGED -> { + TAG.log_dTag( + message = "已连接的 Wifi 强度发生变化" + ) + } + WifiReceiver.WIFI_ERROR_AUTHENTICATING -> { + TAG.log_dTag( + message = "Wifi 认证错误 ( 密码错误等 )" + ) + } + WifiReceiver.WIFI_ERROR_UNKNOWN -> { + TAG.log_dTag( + message = "连接错误 ( 其他错误 )" + ) + } + WifiReceiver.WIFI_STATE_ENABLED -> { + TAG.log_dTag( + message = "Wifi 已打开" + ) + } + WifiReceiver.WIFI_STATE_ENABLING -> { + TAG.log_dTag( + message = "Wifi 正在打开" + ) + } + WifiReceiver.WIFI_STATE_DISABLED -> { + TAG.log_dTag( + message = "Wifi 已关闭" + ) + } + WifiReceiver.WIFI_STATE_DISABLING -> { + TAG.log_dTag( + message = "Wifi 正在关闭" + ) + } + WifiReceiver.WIFI_STATE_UNKNOWN -> { + TAG.log_dTag( + message = "Wifi 状态未知" + ) + } + WifiReceiver.CONNECTED -> { + TAG.log_dTag( + message = "Wifi 连接成功" + ) + } + WifiReceiver.CONNECTING -> { + TAG.log_dTag( + message = "Wifi 连接中" + ) + } + WifiReceiver.DISCONNECTED -> { + TAG.log_dTag( + message = "Wifi 连接失败、断开" + ) + } + WifiReceiver.SUSPENDED -> { + TAG.log_dTag( + message = "Wifi 暂停、延迟" + ) + } + WifiReceiver.UNKNOWN -> { + TAG.log_dTag( + message = "Wifi 未知" + ) + } + } + } + + override fun onTrigger( + what: Int, + message: Message? + ) { // Wifi 在连接过程的状态返回 + val ssid = message?.obj as? String + when (what) { + WifiReceiver.CONNECTED -> { + TAG.log_dTag( + message = "连接 Wifi 成功: $ssid" + ) + } + WifiReceiver.CONNECTING -> { + TAG.log_dTag( + message = "连接 Wifi 中: $ssid" + ) + } + WifiReceiver.DISCONNECTED -> { + TAG.log_dTag( + message = "连接 Wifi 断开" + ) + } + WifiReceiver.SUSPENDED -> { + TAG.log_dTag( + message = "连接 Wifi 暂停、延迟" + ) + } + WifiReceiver.UNKNOWN -> { + TAG.log_dTag( + message = "连接 Wifi 状态未知" + ) + } + } + } + }) + } + + // ========== + // = 检测任务 = + // ========== + + // 检查热点状态 + private val CHECK_HOTSOPT_STATE = 100 + + @SuppressLint("HandlerLeak") + var handler = object : Handler() { + override fun handleMessage(msg: Message) { + super.handleMessage(msg) + when (msg.what) { + // 判断是否打开了热点, 如果开启了则进行关闭 + CHECK_HOTSOPT_STATE -> { + if (wifiHotUtils.closeWifiApCheck(true)) { + // 进行延迟检查 + Handler().postDelayed({ + // 防止出现意外, 再次关闭 + if (wifiHotUtils.closeWifiApCheck(true)) { + try { + // 堵塞 1.5 秒 + Thread.sleep(1500) + } catch (ignored: Exception) { + } + } + // 防止页面已经关闭 + if (!isFinishing) { + // 打开 Wifi + wifiUtils.openWifi() + } + }, 1500) + } else { // 如果没有开启热点, 则判断是否开启 Wifi + // 判断是否开启 Wifi + if (!wifiUtils.isOpenWifi) { + wifiUtils.openWifi() + } + } + } + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/AdapterEditsActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/AdapterEditsActivity.kt new file mode 100644 index 0000000000..7837c5dbe6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/AdapterEditsActivity.kt @@ -0,0 +1,83 @@ +package afkt.project.feature.ui_effect.adapter_edits + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.EvaluateItem +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.expand.engine.log.log_dTag +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.toast.ToastTintUtils +import dev.widget.decoration.linear.FirstLinearColorItemDecoration + +/** + * detail: Adapter Item EditText 输入监听 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.AdapterEditsActivity_PATH) +class AdapterEditsActivity : BaseActivity() { + + // 适配器 + lateinit var adapter: EditsAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val view = QuickHelper.get(BaseTextView(this)) + .setText("提交") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(13.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { + val builder = StringBuilder() + for (item in adapter.dataList) { + builder + .append("\nevaluateContent: ").append(item.evaluateContent) + .append("\nevaluateLevel: ").append(item.evaluateLevel) + .append(DevFinal.SYMBOL.NEW_LINE) + } + TAG.log_dTag( + message = builder.toString() + ) + ToastTintUtils.success("数据已打印, 请查看 Logcat") + }.getView() + toolbar?.addView(view) + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + .setBackgroundColor(ResourceUtils.getColor(R.color.color_33)) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..5) lists.add(EvaluateItem()) + // 默认清空第一条数据内容 + lists[0].evaluateContent = "" + + // 初始化布局管理器、适配器 + adapter = EditsAdapter(lists) + adapter.bindAdapter(binding.vidRv) + + QuickHelper.get(binding.vidRv) + .removeAllItemDecoration() + .addItemDecoration( + FirstLinearColorItemDecoration( + true, ResourceUtils.getDimension(R.dimen.dp_10) + ) + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/EditsAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/EditsAdapter.kt new file mode 100644 index 0000000000..b3110b9f92 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/adapter_edits/EditsAdapter.kt @@ -0,0 +1,93 @@ +package afkt.project.feature.ui_effect.adapter_edits + +import afkt.project.R +import afkt.project.databinding.AdapterItemEditsBinding +import afkt.project.model.bean.EvaluateItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.common.StringUtils + +/** + * detail: Item EditText 输入监听 Adapter + * @author Ttt + */ +class EditsAdapter(data: List) : DevDataAdapterExt>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_item_edits) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + + // ========== + // = 商品信息 = + // ========== + + val commodity = item.commodityItem + + // 商品名 + holder.binding.vidNameTv.text = commodity.commodityName + + // 商品价格 + holder.binding.vidPriceTv.text = + commodity.commodityPrice.toPriceString()?.toRMBSubZeroAndDot() + + // 商品图片 + holder.binding.vidPicIv.display( + source = commodity.commodityPicture?.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + // 评星等级 + val ratingBar = holder.binding.vidRatingbar + ratingBar.setOnRatingChangeListener { _, rating, _ -> + item.evaluateLevel = rating + } + // 设置评星等级 + ratingBar.rating = item.evaluateLevel + + // ========== + // = 输入监听 = + // ========== + + // 评价内容字数 + val numberTv = holder.binding.vidNumberTv + // 计算已经输入的内容长度 + numberTv.text = "${120 - StringUtils.length(item.evaluateContent)}" + // 绑定监听事件 + mTextWatcherAssist.bindListener( + item.evaluateContent, + position, + holder.binding.vidContentEt + ) { charSequence, _, pos, _ -> + try { + // 保存评价内容 + getDataItem(pos).evaluateContent = charSequence.toString() + } catch (ignored: Exception) { + } + try { + // 计算已经输入的内容长度 + numberTv.text = "${120 - StringUtils.length(item.evaluateContent)}" + } catch (ignored: Exception) { + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureActivity.kt new file mode 100644 index 0000000000..782bfdd46f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureActivity.kt @@ -0,0 +1,155 @@ +package afkt.project.feature.ui_effect.capture_picture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCapturePictureBinding +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.graphics.Bitmap +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.CapturePictureUtils +import dev.utils.app.ListenerUtils +import dev.utils.common.FileUtils + +/** + * detail: CapturePictureUtils 截图工具类 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.CapturePictureActivity_PATH) +class CapturePictureActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_capture_picture + + override fun initValue() { + super.initValue() + + // ========== + // = 截图方法 = + // ========== + +// | snapshotWithStatusBar | 获取当前屏幕截图, 包含状态栏 ( 顶部灰色 TitleBar 高度, 没有设置 android:theme 的 NoTitleBar 时会显示 ) | +// | snapshotWithoutStatusBar | 获取当前屏幕截图, 不包含状态栏 ( 如果 android:theme 全屏, 则截图无状态栏 ) | +// | enableSlowWholeDocumentDraw | 关闭 WebView 优化 | +// | snapshotByWebView | 截图 WebView | +// | snapshotByView | 通过 View 绘制为 Bitmap | +// | snapshotByViewCache | 通过 View Cache 绘制为 Bitmap | +// | snapshotByLinearLayout | 通过 LinearLayout 绘制为 Bitmap | +// | snapshotByFrameLayout | 通过 FrameLayout 绘制为 Bitmap | +// | snapshotByRelativeLayout | 通过 RelativeLayout 绘制为 Bitmap | +// | snapshotByScrollView | 通过 ScrollView 绘制为 Bitmap | +// | snapshotByHorizontalScrollView | 通过 HorizontalScrollView 绘制为 Bitmap | +// | snapshotByNestedScrollView | 通过 NestedScrollView 绘制为 Bitmap | +// | snapshotByListView | 通过 ListView 绘制为 Bitmap | +// | snapshotByGridView | 通过 GridView 绘制为 Bitmap | +// | snapshotByRecyclerView | 通过 RecyclerView 绘制为 Bitmap | + } + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidScreenBtn, binding.vidScreen1Btn, + binding.vidLinearBtn, binding.vidScrollBtn, + binding.vidListBtn, binding.vidGridBtn, + binding.vidRecyBtn, binding.vidWebviewBtn + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_screen_btn -> { + saveBitmap( + "screen.jpg", + CapturePictureUtils.snapshotWithStatusBar(mActivity) + ) + } + R.id.vid_screen1_btn -> { + saveBitmap( + "screen1.jpg", + CapturePictureUtils.snapshotWithoutStatusBar(mActivity) + ) + } + R.id.vid_linear_btn -> { + // snapshotByLinearLayout、snapshotByFrameLayout、snapshotByRelativeLayout + // 以上方法都是使用 snapshotByView + saveBitmap( + "linear.jpg", + CapturePictureUtils.snapshotByLinearLayout(binding.vidLl) + ) + } + R.id.vid_scroll_btn -> { + // snapshotByScrollView、snapshotByHorizontalScrollView、snapshotByNestedScrollView + saveBitmap( + "scroll.jpg", + CapturePictureUtils.snapshotByNestedScrollView(binding.vidNsv) + ) + } + R.id.vid_list_btn -> { + routerActivity( + ButtonValue( + 1, "CapturePictureUtils ListView 截图", + RouterPath.UI_EFFECT.CapturePictureListActivity_PATH + ) + ) + } + R.id.vid_grid_btn -> { + routerActivity( + ButtonValue( + 2, "CapturePictureUtils GridView 截图", + RouterPath.UI_EFFECT.CapturePictureGridActivity_PATH + ) + ) + } + R.id.vid_recy_btn -> { + routerActivity( + ButtonValue( + 3, "CapturePictureUtils RecyclerView 截图", + RouterPath.UI_EFFECT.CapturePictureRecyActivity_PATH + ) + ) + } + R.id.vid_webview_btn -> { + routerActivity( + ButtonValue( + 4, "CapturePictureUtils WebView 截图", + RouterPath.UI_EFFECT.CapturePictureWebActivity_PATH + ) + ) + } + } + } + + private fun saveBitmap( + fileName: String, + bitmap: Bitmap? + ) { + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + fileName + ), + DevSource.create( + bitmap + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureGridActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureGridActivity.kt new file mode 100644 index 0000000000..25d00cb060 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureGridActivity.kt @@ -0,0 +1,116 @@ +package afkt.project.feature.ui_effect.capture_picture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCapturePictureGridBinding +import afkt.project.databinding.AdapterCapturePictureBinding +import afkt.project.model.bean.AdapterBean +import afkt.project.model.bean.AdapterBean.Companion.newAdapterBeanList +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.base.widget.BaseTextView +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.CapturePictureUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.FileUtils + +/** + * detail: CapturePictureUtils GridView 截图 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.CapturePictureGridActivity_PATH) +class CapturePictureGridActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_capture_picture_grid + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 截图按钮 + val view = QuickHelper.get(BaseTextView(this)) + .setText("截图") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { v: View? -> + val bitmap = CapturePictureUtils.snapshotByGridView(binding.vidGv) +// // 保存 ListView 一样效果 +// bitmap = CapturePictureUtils.snapshotByGridView( +// binding.vidGv, +// Bitmap.Config.ARGB_8888, +// true +// ) + + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + "grid.jpg" + ), + DevSource.create( + bitmap + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + + val lists = newAdapterBeanList(15) + // 设置适配器 + binding.vidGv.adapter = object : BaseAdapter() { + override fun getCount(): Int { + return lists.size + } + + override fun getItem(position: Int): AdapterBean { + return lists[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val adapterBean = getItem(position) + // 初始化 View 设置 TextView + val itemBinding = AdapterCapturePictureBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ViewHelper.get() + .setText(adapterBean.title, itemBinding.vidTitleTv) + .setText(adapterBean.content, itemBinding.vidContentTv) + return itemBinding.root + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureListActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureListActivity.kt new file mode 100644 index 0000000000..6ed0be64a5 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureListActivity.kt @@ -0,0 +1,108 @@ +package afkt.project.feature.ui_effect.capture_picture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCapturePictureListBinding +import afkt.project.databinding.AdapterCapturePictureBinding +import afkt.project.model.bean.AdapterBean +import afkt.project.model.bean.AdapterBean.Companion.newAdapterBeanList +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.base.widget.BaseTextView +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.CapturePictureUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.FileUtils + +/** + * detail: CapturePictureUtils ListView 截图 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.CapturePictureListActivity_PATH) +class CapturePictureListActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_capture_picture_list + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 截图按钮 + val view = QuickHelper.get(BaseTextView(this)) + .setText("截图") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + "list.jpg" + ), + DevSource.create( + CapturePictureUtils.snapshotByListView(binding.vidLv) + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + + val lists = newAdapterBeanList(15) + // 设置适配器 + binding.vidLv.adapter = object : BaseAdapter() { + override fun getCount(): Int { + return lists.size + } + + override fun getItem(position: Int): AdapterBean { + return lists[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val adapterBean = getItem(position) + // 初始化 View 设置 TextView + val itemBinding = AdapterCapturePictureBinding.inflate( + LayoutInflater.from(parent.context), parent, false + ) + ViewHelper.get() + .setText(adapterBean.title, itemBinding.vidTitleTv) + .setText(adapterBean.content, itemBinding.vidContentTv) + return itemBinding.root + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureRecyActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureRecyActivity.kt new file mode 100644 index 0000000000..20afb707c1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureRecyActivity.kt @@ -0,0 +1,118 @@ +package afkt.project.feature.ui_effect.capture_picture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCapturePictureRecyBinding +import afkt.project.databinding.AdapterCapturePictureBinding +import afkt.project.model.bean.AdapterBean +import afkt.project.model.bean.AdapterBean.Companion.newAdapterBeanList +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.StaggeredGridLayoutManager +import com.alibaba.android.arouter.facade.annotation.Route +import dev.adapter.DevDataAdapterExt +import dev.base.DevSource +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.base.widget.BaseTextView +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.CapturePictureUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.FileUtils + +/** + * detail: CapturePictureUtils RecyclerView 截图 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.CapturePictureRecyActivity_PATH) +class CapturePictureRecyActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_capture_picture_recy + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 截图按钮 + val view = QuickHelper.get(BaseTextView(this)) + .setText("截图") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { + // 支持三种布局 GridLayoutManager、LinearLayoutManager、StaggeredGridLayoutManager + // 以及对于的横、竖屏效果截图 + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + "recy.jpg" + ), + DevSource.create( + CapturePictureUtils.snapshotByRecyclerView(binding.vidRv) + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + +// binding.vidRv.layoutManager = LinearLayoutManager(this) +// binding.vidRv.layoutManager = +// LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false) +// +// binding.vidRv.layoutManager = GridLayoutManager(this, 3) +// binding.vidRv.layoutManager = +// GridLayoutManager(this, 3, GridLayoutManager.HORIZONTAL, false) + + binding.vidRv.layoutManager = + StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL) +// binding.vidRv.layoutManager = +// StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.HORIZONTAL) + + binding.vidRv.adapter = + object : DevDataAdapterExt>() { + + init { + setDataList(newAdapterBeanList(15), false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_capture_picture) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setText(item.title, holder.binding.vidTitleTv) + .setText(item.content, holder.binding.vidContentTv) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureWebActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureWebActivity.kt new file mode 100644 index 0000000000..4a159d3c87 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/capture_picture/CapturePictureWebActivity.kt @@ -0,0 +1,73 @@ +package afkt.project.feature.ui_effect.capture_picture + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityCapturePictureWebBinding +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.base.widget.BaseTextView +import dev.engine.DevEngine +import dev.engine.storage.OnDevInsertListener +import dev.engine.storage.StorageItem +import dev.engine.storage.StorageResult +import dev.utils.app.CapturePictureUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.FileUtils + +/** + * detail: CapturePictureUtils WebView 截图 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.CapturePictureWebActivity_PATH) +class CapturePictureWebActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_capture_picture_web + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 关闭 WebView 优化 + CapturePictureUtils.enableSlowWholeDocumentDraw() + // 截图按钮 + val view = QuickHelper.get(BaseTextView(this)) + .setText("截图") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { + DevEngine.getStorage()?.insertImageToExternal( + StorageItem.createExternalItem( + "web.jpg" + ), + DevSource.create( + CapturePictureUtils.snapshotByWebView(binding.vidWv) + ), + object : OnDevInsertListener { + override fun onResult( + result: StorageResult, + params: StorageItem?, + source: DevSource? + ) { + showToast( + result.isSuccess(), + "保存成功\n${FileUtils.getAbsolutePath(result.getFile())}", + "保存失败" + ) + } + } + ) + }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + // 加载网页 + binding.vidWv.loadUrl("https://www.csdn.net/") + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabItem.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabItem.kt new file mode 100644 index 0000000000..07af212351 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabItem.kt @@ -0,0 +1,12 @@ +package afkt.project.feature.ui_effect.common + +/** + * detail: Tab 模型类 + * @author Ttt + */ +class TabItem( + // 标题 + val title: String, + // 类型 + val type: Int +) \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabLayoutAssist.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabLayoutAssist.kt new file mode 100644 index 0000000000..b0f271c114 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/TabLayoutAssist.kt @@ -0,0 +1,200 @@ +package afkt.project.feature.ui_effect.common + +import afkt.project.R +import android.view.LayoutInflater +import android.view.View +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import dev.base.widget.BaseTextView +import dev.utils.app.HandlerUtils +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: TabLayout 辅助类 + * @author Ttt + */ +class TabLayoutAssist private constructor( + // TabLayout + private var mTabLayout: TabLayout +) { + + // 数据源 + private val mListTabs = mutableListOf() + + // Tab 切换事件 + private var mListener: TabChangeListener? = null + + companion object { + + /** + * 获取 TabLayout 辅助类 + * @param tabLayout [TabLayout] + * @return [TabLayoutAssist] + */ + @kotlin.jvm.JvmStatic + operator fun get(tabLayout: TabLayout?): TabLayoutAssist? { + return tabLayout?.let { return TabLayoutAssist(it) } + } + } + + init { + // Tab 切换选择事件 + mTabLayout.addOnTabSelectedListener(object : OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab) { + updateTabTextView(tab, true) + // 触发回调 + mListener?.onTabChange(mListTabs[tab.position], tab.position) + } + + override fun onTabUnselected(tab: TabLayout.Tab) { + updateTabTextView(tab, false) + } + + override fun onTabReselected(tab: TabLayout.Tab) {} + }) + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取选中的 Tab 索引 + * @return 选中的 Tab 索引 + */ + val selectedTabPosition: Int + get() = mTabLayout.selectedTabPosition + + /** + * 获取 Tab 总数 + * @return Tab 总数 + */ + val tabCount: Int + get() = mTabLayout.tabCount + + /** + * 设置选中索引 + * @param position 选中索引 + * @return [TabLayoutAssist] + */ + fun setSelect(position: Int): TabLayoutAssist { + return setSelect(position, true) + } + + /** + * 设置选中 + * @param position 选中索引 + * @param isScroll 是否滑动 + * @return [TabLayoutAssist] + */ + fun setSelect( + position: Int, + isScroll: Boolean + ): TabLayoutAssist { + mTabLayout.getTabAt(position)?.let { tab -> + tab.select() + // 切换选中状态 + updateTabTextView(tab, true) + } + // 滑动处理 + if (isScroll) scrollTab(selectedTabPosition) + return this + } + + /** + * 设置数据操作 + * @param listTabs Tab Item 集合 + * @return [TabLayoutAssist] + */ + fun setListTabs( + listTabs: List?, + listener: TabChangeListener? + ): TabLayoutAssist { + this.mListener = listener + this.mListTabs.clear() + listTabs?.let { this.mListTabs.addAll(it) } + // 初始化操作 + addTabViews() + return this + } + + // ============== + // = Tab 切换事件 = + // ============== + + /** + * detail: Tab 切换事件 + * @author Ttt + */ + interface TabChangeListener { + fun onTabChange( + tabItem: TabItem, + pos: Int + ) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 添加 Tab View 集合 + */ + private fun addTabViews() { + mTabLayout.removeAllTabs() + // 循环添加 Tab + for (i in 0 until mListTabs.size) { + val tab = mTabLayout.newTab() + tab.customView = createTabView(i) + mTabLayout.addTab(tab) + } + } + + /** + * 获取单个 TabView + * @param position 索引 + * @return Tab [View] + */ + private fun createTabView(position: Int): View { + val view = LayoutInflater.from(mTabLayout.context).inflate(R.layout.tab_item_view, null) + val textView = view.findViewById(R.id.vid_tv) + textView.text = mListTabs[position].title + return view + } + + /** + * 改变 Tab TextView 选中状态 + * @param tab Tab + * @param isSelect 是否选中 + */ + private fun updateTabTextView( + tab: TabLayout.Tab?, + isSelect: Boolean + ) { + tab?.customView?.let { view -> + ViewHelper.get() + .setBold(isSelect, view.findViewById(R.id.vid_tv)) + .setVisibilitys(isSelect, view.findViewById(R.id.vid_line_view)) + } + } + + /** + * 滑动 Tab 处理 + * @param pos 需要滑动的索引 + */ + private fun scrollTab(pos: Int) { + // 延时移动 + HandlerUtils.postRunnable({ + var x = 0 + // 循环遍历 + for (i in 1 until pos) { + mTabLayout.getTabAt(i)?.customView?.let { + // 累加宽度 + x += it.width + } + } + // 开始移动位置 + mTabLayout.scrollTo(x, 0) + }, 100) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/UIEffectActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/UIEffectActivity.kt new file mode 100644 index 0000000000..ca9dc71540 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/common/UIEffectActivity.kt @@ -0,0 +1,305 @@ +package afkt.project.feature.ui_effect.common + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityUiEffectBinding +import afkt.project.feature.ui_effect.common.TabLayoutAssist.TabChangeListener +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.View +import androidx.core.content.ContextCompat +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.expand.engine.log.log_dTag +import dev.utils.app.* +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.ArrayUtils + +/** + * detail: 常见 UI、GradientDrawable 效果等 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.UIEffectActivity_PATH) +class UIEffectActivity : BaseActivity() { + + // 当前选中的索引 + private var selectTabIndex = -1 + + override fun baseLayoutId(): Int = R.layout.activity_ui_effect + + override fun initValue() { + super.initValue() + + // 默认选中 + ViewHelper.get() + .setSelected(true, binding.vid10Tv) + .setSelected(false, binding.vid11Tv) + changeTab1(binding.vid20Tv, binding.vid21Tv) + changeTab2(binding.vid31Tv, binding.vid30Tv) + + // 动态设置 + StateListUtils.newSelector(R.drawable.btn_pressed, R.drawable.btn_normal) + .also { binding.vid40Btn.background = it } + + binding.vid40Btn.setTextColor( + StateListUtils.createColorStateList( + R.color.black, R.color.white + ) + ) + + // 默认就设置背景色 + ShapeUtils.newShape() + .setCornerRadiusLeft(10.0F) + .setColor(Color.BLACK) + .drawable.also { binding.vid41Btn.background = it } + + // 设置点击效果 + val drawable1 = ShapeUtils.newShape(10F, ResourceUtils.getColor(R.color.black)) + .setStroke(5, ResourceUtils.getColor(R.color.darkorange)).drawable + val drawable2 = ShapeUtils.newShape(10F, ResourceUtils.getColor(R.color.sky_blue)) + .setStroke(5, ResourceUtils.getColor(R.color.gray)).drawable + + StateListUtils.newSelector(drawable2, drawable1) + .also { binding.vid41Btn.background = it } + + binding.vid41Btn.setTextColor( + StateListUtils.createColorStateList( + R.color.red, R.color.white + ) + ) + + // ========== + // = 渐变效果 = + // ========== + + ShapeUtils.newShape( + GradientDrawable.Orientation.BR_TL, + intArrayOf(Color.RED, Color.BLUE, Color.GREEN) + ).setDrawable(binding.vid50View) + + val colors = IntArray(3) + colors[0] = ContextCompat.getColor(this, R.color.black) + colors[1] = ContextCompat.getColor(this, R.color.sky_blue) + colors[2] = ContextCompat.getColor(this, R.color.orange) + val drawable = ShapeUtils.newShape(GradientDrawable.Orientation.BR_TL, colors).drawable +// drawable.gradientType = GradientDrawable.LINEAR_GRADIENT // 线性渐变, 这是默认设置 +// drawable.gradientType = GradientDrawable.RADIAL_GRADIENT // 放射性渐变, 以开始色为中心 + drawable.gradientType = GradientDrawable.SWEEP_GRADIENT // 扫描线式的渐变 + binding.vid60View.background = drawable + + // ============================ + // = HorizontalScrollView Tab = + // ============================ + + // Tab 数据 + val listTabs = mutableListOf() + listTabs.add(TabItem("全部", 0)) + listTabs.add(TabItem("待付款", 1)) + listTabs.add(TabItem("待发货", 2)) + listTabs.add(TabItem("待收货", 3)) + listTabs.add(TabItem("待评价", 4)) + listTabs.add(TabItem("已取消", 5)) + listTabs.add(TabItem("已完成", 6)) + listTabs.add(TabItem("已关闭", 7)) + listTabs.add(TabItem("售后", 8)) + + // 循环添加 Tab + for (i in 0 until listTabs.size) { + val tabItem = listTabs[i] + val view = QuickHelper.get(BaseTextView(this)) + .setText(tabItem.title) + .setTextColors(ResourceUtils.getColor(R.color.black)) + .setPadding(30, 30, 30, 30) + .setOnClick { + ViewHelper.get() + .setBold( + false, + ViewUtils.getChildAt(binding.vid70Ll, selectTabIndex) + ) + .setTextColors( + ResourceUtils.getColor(R.color.black), + ViewUtils.getChildAt( + binding.vid70Ll, selectTabIndex + ) + ) + .setBold(true, ViewUtils.getChildAt(binding.vid70Ll, i)) + .setTextColors( + ResourceUtils.getColor(R.color.red), + ViewUtils.getChildAt(binding.vid70Ll, i) + ) + // 修改索引 + selectTabIndex = i + // 滑动 Tab 处理 + scrollTab(i) + }.getView() + binding.vid70Ll.addView(view) + } + ViewUtils.getChildAt(binding.vid70Ll).performClick() + + // ================= + // = TabLayout Tab = + // ================= + + TabLayoutAssist[binding.vid80Tl]?.apply { + setListTabs(listTabs, object : TabChangeListener { + override fun onTabChange( + tabItem: TabItem, + pos: Int + ) { + TAG.log_dTag( + message = "TabItem: %s, pos: %s", + args = arrayOf(tabItem.title, pos) + ) + // 设置选中 + setSelect(pos) + } + }).setSelect(tabCount - 1) + } + + TabLayoutAssist[binding.vid90Tl]?.apply { + setListTabs(ArrayUtils.asList(ArrayUtils.subArray(listTabs.toTypedArray(), 0, 3)), + object : TabChangeListener { + override fun onTabChange( + tabItem: TabItem, + pos: Int + ) { + TAG.log_dTag( + message = "TabItem: %s, pos: %s", + args = arrayOf(tabItem.title, pos) + ) + } + } + ).setSelect(0) + } + } + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vid10Tv, binding.vid11Tv, + binding.vid20Tv, binding.vid21Tv, + binding.vid30Tv, binding.vid31Tv + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_1_0_tv -> { + ViewHelper.get() + .setSelected(true, binding.vid10Tv) + .setSelected(false, binding.vid11Tv) + } + R.id.vid_1_1_tv -> { + ViewHelper.get() + .setSelected(true, binding.vid11Tv) + .setSelected(false, binding.vid10Tv) + } + R.id.vid_2_0_tv -> changeTab1(binding.vid20Tv, binding.vid21Tv) + R.id.vid_2_1_tv -> changeTab1(binding.vid21Tv, binding.vid20Tv) + R.id.vid_3_0_tv -> changeTab2(binding.vid30Tv, binding.vid31Tv) + R.id.vid_3_1_tv -> changeTab2(binding.vid31Tv, binding.vid30Tv) + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 切换 Tab + * @param clickTab 点击 Tab + * @param unClickTab 未点击 Tab + */ + private fun changeTab1( + clickTab: BaseTextView, + unClickTab: BaseTextView + ) { + // 判断点击的是左边还是右边 + if (clickTab.id == R.id.vid_2_0_tv) { // 点击左边 + clickTab.setBackgroundResource(R.drawable.shape_tab_left_pressed) + unClickTab.setBackgroundResource(R.drawable.shape_tab_right_selector) + } else { + clickTab.setBackgroundResource(R.drawable.shape_tab_right_pressed) + unClickTab.setBackgroundResource(R.drawable.shape_tab_left_selector) + } + // = 字体效果 = + // 选中变白色 + clickTab.setTextColor(resources.getColor(R.color.white)) + // 未选中增加点击效果 + unClickTab.setTextColor(ResourceUtils.getColorStateList(R.color.selector_tab_text_color)) + } + + /** + * 切换 Tab + * @param clickTab 点击 Tab + * @param unClickTab 未点击 Tab + */ + private fun changeTab2( + clickTab: BaseTextView, + unClickTab: BaseTextView + ) { + // 判断点击的是左边还是右边 + if (clickTab.id == R.id.vid_3_0_tv) { // 点击左边 + // 设置选中颜色 + ShapeUtils.newShape().setCornerRadiusLeft(10F) + .setColor(ResourceUtils.getColor(R.color.sky_blue)) + .setDrawable(clickTab) + + // 设置未选中颜色 + val drawable1 = ShapeUtils.newShape() + .setCornerRadiusRight(10F) + .setColor(ResourceUtils.getColor(R.color.sky_blue)) + .drawable + val drawable2 = ShapeUtils.newShape() + .setCornerRadiusRight(10F) + .setColor(ResourceUtils.getColor(R.color.color_33)) + .drawable + unClickTab.background = StateListUtils.newSelector(drawable1, drawable2) + } else { + // 设置选中颜色 + ShapeUtils.newShape().setCornerRadiusRight(10F) + .setColor(ResourceUtils.getColor(R.color.sky_blue)) + .setDrawable(clickTab) + + // 设置未选中颜色 + val drawable1 = ShapeUtils.newShape() + .setCornerRadiusLeft(10F) + .setColor(ResourceUtils.getColor(R.color.sky_blue)) + .drawable + val drawable2 = ShapeUtils.newShape() + .setCornerRadiusLeft(10F) + .setColor(ResourceUtils.getColor(R.color.color_33)) + .drawable + unClickTab.background = StateListUtils.newSelector(drawable1, drawable2) + } + // = 字体效果 = + // 选中变白色 + clickTab.setTextColor(resources.getColor(R.color.white)) + // 设置未选中颜色 + unClickTab.setTextColor(StateListUtils.createColorStateList(R.color.white, R.color.red)) + } + + /** + * 滑动 Tab 处理 + * @param position 需要滑动的索引 + */ + private fun scrollTab(position: Int) { + // 延时移动 + HandlerUtils.postRunnable({ + var x = 0 + // 循环遍历 + for (i in 1 until position) { + binding.vid70Ll.getChildAt(i)?.apply { + // 累加宽度 + x += width + } + } + // 开始移动位置 + binding.vid70Sv.scrollTo(x, 0) + }, 50) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxLayoutManagerActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxLayoutManagerActivity.kt new file mode 100644 index 0000000000..839951af85 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxLayoutManagerActivity.kt @@ -0,0 +1,75 @@ +package afkt.project.feature.ui_effect.flexbox_layout + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexboxLayoutManager +import com.google.android.flexbox.JustifyContent +import dev.base.widget.BaseTextView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils +import java.util.* + +/** + * detail: Flexbox LayoutManager + * @author Ttt + * Google FlexboxLayout 推荐使用该库, 支持 RecyclerView ( FlexboxLayoutManager ) + * @see https://github.com/google/flexbox-layout + * Android 可伸缩布局 FlexboxLayout ( 支持 RecyclerView 集成 ) + * @see https://juejin.im/post/58d1035161ff4b00603ca9c4 + */ +@Route(path = RouterPath.UI_EFFECT.FlexboxLayoutManagerActivity_PATH) +class FlexboxLayoutManagerActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding( + 0, 0, AppSize.dp2px(5F), + AppSize.dp2px(5F) + ) + + val view = QuickHelper.get(BaseTextView(this)) + .setText("刷新") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.red)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { initValue() }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 1..20) { + val text = ChineseUtils.randomWord(RandomUtils.getRandom(8)) + + RandomUtils.getRandomLetters(RandomUtils.getRandom(8)) + val randomText = + i.toString() + "." + RandomUtils.getRandom(text.toCharArray(), text.length) + lists.add(randomText) + } + + val layoutManager = FlexboxLayoutManager(this) + layoutManager.flexDirection = FlexDirection.ROW + layoutManager.justifyContent = JustifyContent.FLEX_START + + binding.vidRv.layoutManager = layoutManager + FlexboxTextAdapter(lists).bindAdapter(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxTextAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxTextAdapter.kt new file mode 100644 index 0000000000..1d131bbf09 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/flexbox_layout/FlexboxTextAdapter.kt @@ -0,0 +1,33 @@ +package afkt.project.feature.ui_effect.flexbox_layout + +import afkt.project.R +import afkt.project.databinding.AdapterFlexboxTextBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Text Adapter + * @author Ttt + */ +class FlexboxTextAdapter(data: MutableList) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_flexbox_text) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTv.text = getDataItem(position) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVActivity.kt new file mode 100644 index 0000000000..7d7a4d2ceb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVActivity.kt @@ -0,0 +1,155 @@ +package afkt.project.feature.ui_effect.gpu + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityGpuFilterBinding +import afkt.project.feature.ui_effect.gpu.GPUFilterUtils.getFilterBitmap +import afkt.project.feature.ui_effect.gpu.GPUFilterUtils.getGPUImageToneCurveFilter +import afkt.project.feature.ui_effect.gpu.bean.ACVFileBean +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.content.Intent +import android.graphics.Bitmap +import android.view.View +import android.widget.AdapterView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.DevEngine +import dev.expand.engine.log.log_eTag +import dev.utils.app.HandlerUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ScreenUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.image.ImageUtils +import dev.utils.common.ScaleUtils + +/** + * detail: GPU ACV 文件滤镜效果 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.GPUFilterACVActivity_PATH) +class GPUFilterACVActivity : BaseActivity() { + + // 适配器 + private lateinit var gpuFilterACVAdapter: GPUFilterACVAdapter + + // ACV 文件集合 + private val listACVFiles = mutableListOf() + + // 图片 Bitmap + private var selectBitmap: Bitmap? = null + + companion object { + // 滤镜线程 + private var filterThread: Runnable? = null + } + + override fun baseLayoutId(): Int = R.layout.activity_gpu_filter + + override fun onDestroy() { + super.onDestroy() + filterThread = null + } + + override fun initValue() { + super.initValue() + + // 设置滤镜线程 + filterThread = Runnable { setFilter() } + + // 初始化数据 + listACVFiles.add(ACVFileBean("August", "filter/August.acv")) + listACVFiles.add(ACVFileBean("Darker", "filter/Darker.acv")) + listACVFiles.add(ACVFileBean("Dream", "filter/Dream.acv")) + listACVFiles.add(ACVFileBean("Fornature", "filter/Fornature.acv")) + listACVFiles.add(ACVFileBean("Greens", "filter/Greens.acv")) + listACVFiles.add(ACVFileBean("Miami", "filter/Miami.acv")) + + // 设置适配器 + binding.vidGallery.adapter = GPUFilterACVAdapter(this, listACVFiles).also { + gpuFilterACVAdapter = it + } + binding.vidGallery.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View, + position: Int, + id: Long + ) { + gpuFilterACVAdapter.setSelectPosition(position) + // 延迟一会进行滤镜 + HandlerUtils.removeRunnable(filterThread) + HandlerUtils.postRunnable(filterThread, 500) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + // 默认选中第一个 + binding.vidGallery.setSelection(0) + } + + override fun initListener() { + super.initListener() + binding.vidSelectBtn.setOnClickListener { + mActivity?.let { activity -> + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + } + } + } + + // ========== + // = 图片回传 = + // ========== + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + // 判断是否属于图片选择 + if (resultCode == RESULT_OK && intent != null) { + // 获取图片地址 + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + // 获取图片 Bitmap + selectBitmap = ImageUtils.decodeStream(ResourceUtils.openInputStream(imgUri)) + // 设置图片滤镜 + setFilter() + } + } + + // ========== + // = 滤镜处理 = + // ========== + + /** + * 设置滤镜效果 + */ + private fun setFilter() { + try { + if (selectBitmap == null) return + // 获取选中的滤镜 + val position = binding.vidGallery.selectedItemPosition + // 获取滤镜文件实体类 + val acvFileBean = gpuFilterACVAdapter.getItem(position) + // 设置滤镜效果 + val gpuFilter = getGPUImageToneCurveFilter(ResourceUtils.open(acvFileBean.acvPath)) + val bitmapFilter = getFilterBitmap(this, selectBitmap, gpuFilter) + bitmapFilter?.let { + val wh = ScaleUtils.calcScaleToWidthI( + ScreenUtils.getScreenWidth().toDouble(), + it.width.toDouble(), it.height.toDouble() + ) + QuickHelper.get(binding.vidIv) + .setWidthHeight(wh[0], wh[1]) + .setImageBitmap(it) + } + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "setFilter" + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVAdapter.kt new file mode 100644 index 0000000000..0ea94fe05d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterACVAdapter.kt @@ -0,0 +1,81 @@ +package afkt.project.feature.ui_effect.gpu + +import afkt.project.R +import afkt.project.feature.ui_effect.gpu.bean.ACVFileBean +import android.content.Context +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.Gallery +import dev.base.widget.BaseTextView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: GPU ACV 文件滤镜效果适配器 + * @author Ttt + */ +class GPUFilterACVAdapter( + // Context + private val context: Context, + // ACV 文件集合 + private val listACVFiles: List +) : BaseAdapter() { + + // 当前选中索引 + private var selectPosition = -1 + + override fun getCount(): Int { + return listACVFiles.size + } + + override fun getItem(position: Int): ACVFileBean { + return listACVFiles[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup? + ): View { + return createTextView(position) + } + + /** + * 设置选中索引 + * @param selectPosition 选中索引 + */ + fun setSelectPosition(selectPosition: Int) { + this.selectPosition = selectPosition + notifyDataSetChanged() + } + + /** + * 创建 TextView + * @param position 对应索引 + * @return [BaseTextView] + */ + private fun createTextView(position: Int): BaseTextView { + val acvFileBean = getItem(position) + val isSelect = (selectPosition == position) + val width = AppSize.dp2px(100F) + val layoutParams = Gallery.LayoutParams( + width, Gallery.LayoutParams.MATCH_PARENT + ) + // 初始化 View + return QuickHelper.get(BaseTextView(context)) + .setText(acvFileBean.acvName) + .setBold(isSelect) + .setGravity(Gravity.CENTER) + .setTextColors(ResourceUtils.getColor(if (isSelect) R.color.red else R.color.black)) + .setTextSizeBySp(if (isSelect) 18.0F else 13.0F) + .setLayoutParams(layoutParams) + .getView() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterActivity.kt new file mode 100644 index 0000000000..7974f30f21 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterActivity.kt @@ -0,0 +1,144 @@ +package afkt.project.feature.ui_effect.gpu + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityGpuFilterBinding +import afkt.project.feature.ui_effect.gpu.GPUFilterUtils.getFilterBitmap +import afkt.project.feature.ui_effect.gpu.bean.FilterItem.Companion.createFilterForType +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.content.Intent +import android.graphics.Bitmap +import android.view.View +import android.widget.AdapterView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.DevEngine +import dev.expand.engine.log.log_eTag +import dev.utils.app.HandlerUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ScreenUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.image.ImageUtils +import dev.utils.common.ScaleUtils + +/** + * detail: GPU 滤镜效果 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.GPUFilterActivity_PATH) +class GPUFilterActivity : BaseActivity() { + + // 适配器 + lateinit var gpuFilterAdapter: GPUFilterAdapter + + // 图片 Bitmap + private var selectBitmap: Bitmap? = null + + companion object { + // 滤镜线程 + var filterThread: Runnable? = null + } + + override fun baseLayoutId(): Int = R.layout.activity_gpu_filter + + override fun onDestroy() { + super.onDestroy() + filterThread = null + } + + override fun initValue() { + super.initValue() + + // 设置滤镜线程 + filterThread = Runnable { setFilter() } + + // 设置适配器 + binding.vidGallery.adapter = GPUFilterAdapter(this).also { + gpuFilterAdapter = it + } + binding.vidGallery.onItemSelectedListener = + object : AdapterView.OnItemSelectedListener { + override fun onItemSelected( + parent: AdapterView<*>?, + view: View, + position: Int, + id: Long + ) { + gpuFilterAdapter.setSelectPosition(position) + // 延迟一会进行滤镜 + HandlerUtils.removeRunnable(filterThread) + HandlerUtils.postRunnable(filterThread, 500) + } + + override fun onNothingSelected(parent: AdapterView<*>?) {} + } + // 默认选中第一个 + binding.vidGallery.setSelection(0) + } + + override fun initListener() { + super.initListener() + binding.vidSelectBtn.setOnClickListener { + mActivity?.let { activity -> + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + } + } + } + + // ========== + // = 图片回传 = + // ========== + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + // 判断是否属于图片选择 + if (resultCode == RESULT_OK && intent != null) { + // 获取图片地址 + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + // 获取图片 Bitmap + selectBitmap = ImageUtils.decodeStream(ResourceUtils.openInputStream(imgUri)) + // 设置图片滤镜 + setFilter() + } + } + + // ========== + // = 滤镜处理 = + // ========== + + /** + * 设置滤镜效果 + */ + private fun setFilter() { + try { + if (selectBitmap == null) return + // 获取选中的滤镜 + val position = binding.vidGallery.selectedItemPosition + // 获取滤镜 Item + val filterItem = gpuFilterAdapter.getItem(position) + // 设置滤镜效果 + val bitmapFilter = getFilterBitmap( + this, selectBitmap, createFilterForType(filterItem.filterType) + ) + bitmapFilter?.let { + val wh = ScaleUtils.calcScaleToWidthI( + ScreenUtils.getScreenWidth().toDouble(), + it.width.toDouble(), it.height.toDouble() + ) + QuickHelper.get(binding.vidIv) + .setWidthHeight(wh[0], wh[1]) + .setImageBitmap(it) + } + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "setFilter" + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterAdapter.kt new file mode 100644 index 0000000000..94433912be --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterAdapter.kt @@ -0,0 +1,78 @@ +package afkt.project.feature.ui_effect.gpu + +import afkt.project.R +import afkt.project.feature.ui_effect.gpu.bean.FilterItem +import android.content.Context +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.Gallery +import dev.base.widget.BaseTextView +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: GPU 滤镜效果适配器 + * @author Ttt + */ +class GPUFilterAdapter( + private val context: Context +) : BaseAdapter() { + + // 当前选中索引 + private var selectPosition = -1 + + override fun getCount(): Int { + return FilterItem.FILTER_LISTS.size + } + + override fun getItem(position: Int): FilterItem { + return FilterItem.FILTER_LISTS[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup? + ): View { + return createTextView(position) + } + + /** + * 设置选中索引 + * @param selectPosition 选中索引 + */ + fun setSelectPosition(selectPosition: Int) { + this.selectPosition = selectPosition + notifyDataSetChanged() + } + + /** + * 创建 TextView + * @param position 对应索引 + * @return [BaseTextView] + */ + private fun createTextView(position: Int): BaseTextView { + val filterItem = getItem(position) + val isSelect = (selectPosition == position) + val width = AppSize.dp2px(100F) + val layoutParams = Gallery.LayoutParams( + width, Gallery.LayoutParams.MATCH_PARENT + ) + // 初始化 View + return QuickHelper.get(BaseTextView(context)) + .setText(filterItem.filterName) + .setBold(isSelect) + .setGravity(Gravity.CENTER) + .setTextColors(ResourceUtils.getColor(if (isSelect) R.color.red else R.color.black)) + .setTextSizeBySp(if (isSelect) 18.0F else 13.0F) + .setLayoutParams(layoutParams) + .getView() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterUtils.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterUtils.kt new file mode 100644 index 0000000000..3efafe4c38 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/GPUFilterUtils.kt @@ -0,0 +1,106 @@ +package afkt.project.feature.ui_effect.gpu + +import android.content.Context +import android.graphics.Bitmap +import dev.expand.engine.log.log_eTag +import dev.utils.common.CloseUtils +import jp.co.cyberagent.android.gpuimage.GPUImage +import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter +import jp.co.cyberagent.android.gpuimage.filter.GPUImageToneCurveFilter +import java.io.InputStream + +/** + * detail: GPU 滤镜工具类 + * @author Ttt + */ +object GPUFilterUtils { + + // 日志 TAG + private val TAG = GPUFilterUtils::class.java.simpleName + + /** + * 获取 GPU Image 滤镜配置对象 + * @param inputStream [InputStream] + * @return [GPUImageToneCurveFilter] + */ + @JvmStatic + fun getGPUImageToneCurveFilter(inputStream: InputStream?): GPUImageToneCurveFilter? { + if (inputStream != null) { + try { + // 读取 PhotoShop acv 文件 + val filter = GPUImageToneCurveFilter() + filter.setFromCurveFileInputStream(inputStream) + return filter + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "getGPUImageToneCurveFilter" + ) + } finally { + CloseUtils.closeIOQuietly(inputStream) + } + } + return null + } + + /** + * 获取滤镜后的 Bitmap + * @param gpuImage [GPUImage] + * @param gpuImageFilter [GPUImageFilter] + * @return 滤镜后的 Bitmap + */ + @JvmStatic + fun getFilterBitmap( + gpuImage: GPUImage?, + gpuImageFilter: GPUImageFilter? + ): Bitmap? { + if (gpuImage != null && gpuImageFilter != null) { + gpuImage.setFilter(gpuImageFilter) + return gpuImage.bitmapWithFilterApplied + } + return null + } + + /** + * 获取滤镜后的 Bitmap + * @param context [Context] + * @param bitmap [Bitmap] + * @param gpuImageFilter [GPUImageFilter] + * @return 滤镜后的 Bitmap + */ + @JvmStatic + fun getFilterBitmap( + context: Context?, + bitmap: Bitmap?, + gpuImageFilter: GPUImageFilter? + ): Bitmap? { + if (context != null && bitmap != null && gpuImageFilter != null) { + val gpuImage = GPUImage(context) + gpuImage.setImage(bitmap) + gpuImage.setFilter(gpuImageFilter) + return gpuImage.bitmapWithFilterApplied + } + return null + } + + /** + * 获取滤镜后的 Bitmap + * @param gpuImage [GPUImage] + * @param bitmap [Bitmap] + * @param gpuImageFilter [GPUImageFilter] + * @return 滤镜后的 Bitmap + */ + @JvmStatic + fun getFilterBitmap( + gpuImage: GPUImage?, + bitmap: Bitmap?, + gpuImageFilter: GPUImageFilter? + ): Bitmap? { + if (gpuImage != null && bitmap != null && gpuImageFilter != null) { + gpuImage.setImage(bitmap) + gpuImage.setFilter(gpuImageFilter) + return gpuImage.bitmapWithFilterApplied + } + return null + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/ACVFileBean.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/ACVFileBean.kt new file mode 100644 index 0000000000..3faf3dd7e6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/ACVFileBean.kt @@ -0,0 +1,12 @@ +package afkt.project.feature.ui_effect.gpu.bean + +/** + * detail: ACV 文件实体类 + * @author Ttt + */ +class ACVFileBean( + // ACV 名 + val acvName: String, + // 文件地址 + val acvPath: String +) \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/FilterItem.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/FilterItem.kt new file mode 100644 index 0000000000..9acd64bb02 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/gpu/bean/FilterItem.kt @@ -0,0 +1,365 @@ +package afkt.project.feature.ui_effect.gpu.bean + +import android.graphics.PointF +import dev.expand.engine.log.log_eTag +import jp.co.cyberagent.android.gpuimage.filter.* +import java.util.* + +/** + * detail: 滤镜类型 Item + * @author Ttt + */ +class FilterItem( + // 滤镜名 + val filterName: String, + // 滤镜类型 + val filterType: FilterType +) { + /** + * 滤镜类型枚举类 + */ + enum class FilterType { + CONTRAST, + GRAYSCALE, + SHARPEN, + SEPIA, + SOBEL_EDGE_DETECTION, + THREE_X_THREE_CONVOLUTION, + FILTER_GROUP, + EMBOSS, + POSTERIZE, + GAMMA, + BRIGHTNESS, + INVERT, + HUE, + PIXELATION, + SATURATION, + EXPOSURE, + HIGHLIGHT_SHADOW, + MONOCHROME, + OPACITY, + RGB, + WHITE_BALANCE, + VIGNETTE, + TONE_CURVE, + BLEND_COLOR_BURN, + BLEND_COLOR_DODGE, + BLEND_DARKEN, + BLEND_DIFFERENCE, + BLEND_DISSOLVE, + BLEND_EXCLUSION, + BLEND_SOURCE_OVER, + BLEND_HARD_LIGHT, + BLEND_LIGHTEN, + BLEND_ADD, + BLEND_DIVIDE, + BLEND_MULTIPLY, + BLEND_OVERLAY, + BLEND_SCREEN, + BLEND_ALPHA, + BLEND_COLOR, + BLEND_HUE, + BLEND_SATURATION, + BLEND_LUMINOSITY, + BLEND_LINEAR_BURN, + BLEND_SOFT_LIGHT, + BLEND_SUBTRACT, + BLEND_CHROMA_KEY, + BLEND_NORMAL, + LOOKUP_AMATORKA, + GAUSSIAN_BLUR, + CROSSHATCH, + BOX_BLUR, + CGA_COLORSPACE, + DILATION, + KUWAHARA, + RGB_DILATION, + SKETCH, + TOON, + SMOOTH_TOON, + BULGE_DISTORTION, + GLASS_SPHERE, + HAZE, + LAPLACIAN, + NON_MAXIMUM_SUPPRESSION, + SPHERE_REFRACTION, + SWIRL, + WEAK_PIXEL_INCLUSION, + FALSE_COLOR, + COLOR_BALANCE, + LEVELS_FILTER_MIN, + BILATERAL_BLUR, + HALFTONE, + TRANSFORM2D + } + + companion object { + + // 日志 TAG + private val TAG = FilterItem::class.java.simpleName + + // 滤镜类型集合 + var FILTER_LISTS = mutableListOf() + + /** + * 创建 GPU Image Filter + * @param type 滤镜类型 + * @return [GPUImageFilter] + */ + @JvmStatic + fun createFilterForType(type: FilterType?): GPUImageFilter? { + return when (type) { + FilterType.CONTRAST -> GPUImageContrastFilter(2.0F) + FilterType.GAMMA -> GPUImageGammaFilter(2.0F) + FilterType.INVERT -> GPUImageColorInvertFilter() + FilterType.PIXELATION -> GPUImagePixelationFilter() + FilterType.HUE -> GPUImageHueFilter(90.0F) + FilterType.BRIGHTNESS -> GPUImageBrightnessFilter(1.5F) + FilterType.GRAYSCALE -> GPUImageGrayscaleFilter() + FilterType.SEPIA -> GPUImageSepiaToneFilter() + FilterType.SHARPEN -> { + val sharpness = GPUImageSharpenFilter() + sharpness.setSharpness(2.0F) + sharpness + } + FilterType.SOBEL_EDGE_DETECTION -> GPUImageSobelEdgeDetectionFilter() + FilterType.THREE_X_THREE_CONVOLUTION -> { + val convolution = GPUImage3x3ConvolutionFilter() + convolution.setConvolutionKernel( + floatArrayOf( + -1.0F, 0.0F, 1.0F, + -2.0F, 0.0F, 2.0F, + -1.0F, 0.0F, 1.0F + ) + ) + convolution + } + FilterType.EMBOSS -> GPUImageEmbossFilter() + FilterType.POSTERIZE -> GPUImagePosterizeFilter() + FilterType.FILTER_GROUP -> { + val filters = LinkedList() + filters.add(GPUImageContrastFilter()) + filters.add(GPUImageDirectionalSobelEdgeDetectionFilter()) + filters.add(GPUImageGrayscaleFilter()) + GPUImageFilterGroup(filters) + } + FilterType.SATURATION -> GPUImageSaturationFilter(1.0F) + FilterType.EXPOSURE -> GPUImageExposureFilter(0.0F) + FilterType.HIGHLIGHT_SHADOW -> GPUImageHighlightShadowFilter(0.0F, 1.0F) + FilterType.MONOCHROME -> GPUImageMonochromeFilter( + 1.0F, + floatArrayOf(0.6F, 0.45F, 0.3F, 1.0F) + ) + FilterType.OPACITY -> GPUImageOpacityFilter(1.0F) + FilterType.RGB -> GPUImageRGBFilter(1.0F, 1.0F, 1.0F) + FilterType.WHITE_BALANCE -> GPUImageWhiteBalanceFilter(5000.0F, 0.0F) + FilterType.VIGNETTE -> { + val centerPoint = PointF() + centerPoint.x = 0.5F + centerPoint.y = 0.5F + GPUImageVignetteFilter(centerPoint, floatArrayOf(0.0F, 0.0F, 0.0F), 0.3F, 0.75F) + } + FilterType.TONE_CURVE -> GPUImageToneCurveFilter() + FilterType.BLEND_DIFFERENCE -> createBlendFilter( + GPUImageDifferenceBlendFilter::class.java + ) + FilterType.BLEND_SOURCE_OVER -> createBlendFilter( + GPUImageSourceOverBlendFilter::class.java + ) + FilterType.BLEND_COLOR_BURN -> createBlendFilter( + GPUImageColorBurnBlendFilter::class.java + ) + FilterType.BLEND_COLOR_DODGE -> createBlendFilter( + GPUImageColorDodgeBlendFilter::class.java + ) + FilterType.BLEND_DARKEN -> createBlendFilter( + GPUImageDarkenBlendFilter::class.java + ) + FilterType.BLEND_DISSOLVE -> createBlendFilter( + GPUImageDissolveBlendFilter::class.java + ) + FilterType.BLEND_EXCLUSION -> createBlendFilter( + GPUImageExclusionBlendFilter::class.java + ) + FilterType.BLEND_HARD_LIGHT -> createBlendFilter( + GPUImageHardLightBlendFilter::class.java + ) + FilterType.BLEND_LIGHTEN -> createBlendFilter( + GPUImageLightenBlendFilter::class.java + ) + FilterType.BLEND_ADD -> createBlendFilter( + GPUImageAddBlendFilter::class.java + ) + FilterType.BLEND_DIVIDE -> createBlendFilter( + GPUImageDivideBlendFilter::class.java + ) + FilterType.BLEND_MULTIPLY -> createBlendFilter( + GPUImageMultiplyBlendFilter::class.java + ) + FilterType.BLEND_OVERLAY -> createBlendFilter( + GPUImageOverlayBlendFilter::class.java + ) + FilterType.BLEND_SCREEN -> createBlendFilter( + GPUImageScreenBlendFilter::class.java + ) + FilterType.BLEND_ALPHA -> createBlendFilter( + GPUImageAlphaBlendFilter::class.java + ) + FilterType.BLEND_COLOR -> createBlendFilter( + GPUImageColorBlendFilter::class.java + ) + FilterType.BLEND_HUE -> createBlendFilter( + GPUImageHueBlendFilter::class.java + ) + FilterType.BLEND_SATURATION -> createBlendFilter( + GPUImageSaturationBlendFilter::class.java + ) + FilterType.BLEND_LUMINOSITY -> createBlendFilter( + GPUImageLuminosityBlendFilter::class.java + ) + FilterType.BLEND_LINEAR_BURN -> createBlendFilter( + GPUImageLinearBurnBlendFilter::class.java + ) + FilterType.BLEND_SOFT_LIGHT -> createBlendFilter( + GPUImageSoftLightBlendFilter::class.java + ) + FilterType.BLEND_SUBTRACT -> createBlendFilter( + GPUImageSubtractBlendFilter::class.java + ) + FilterType.BLEND_CHROMA_KEY -> createBlendFilter( + GPUImageChromaKeyBlendFilter::class.java + ) + FilterType.BLEND_NORMAL -> createBlendFilter( + GPUImageNormalBlendFilter::class.java + ) + FilterType.LOOKUP_AMATORKA -> GPUImageLookupFilter() + FilterType.GAUSSIAN_BLUR -> GPUImageGaussianBlurFilter() + FilterType.CROSSHATCH -> GPUImageCrosshatchFilter() + FilterType.BOX_BLUR -> GPUImageBoxBlurFilter() + FilterType.CGA_COLORSPACE -> GPUImageCGAColorspaceFilter() + FilterType.DILATION -> GPUImageDilationFilter() + FilterType.KUWAHARA -> GPUImageKuwaharaFilter() + FilterType.RGB_DILATION -> GPUImageRGBDilationFilter() + FilterType.SKETCH -> GPUImageSketchFilter() + FilterType.TOON -> GPUImageToonFilter() + FilterType.SMOOTH_TOON -> GPUImageSmoothToonFilter() + FilterType.BULGE_DISTORTION -> GPUImageBulgeDistortionFilter() + FilterType.GLASS_SPHERE -> GPUImageGlassSphereFilter() + FilterType.HAZE -> GPUImageHazeFilter() + FilterType.LAPLACIAN -> GPUImageLaplacianFilter() + FilterType.NON_MAXIMUM_SUPPRESSION -> GPUImageNonMaximumSuppressionFilter() + FilterType.SPHERE_REFRACTION -> GPUImageSphereRefractionFilter() + FilterType.SWIRL -> GPUImageSwirlFilter() + FilterType.WEAK_PIXEL_INCLUSION -> GPUImageWeakPixelInclusionFilter() + FilterType.FALSE_COLOR -> GPUImageFalseColorFilter() + FilterType.COLOR_BALANCE -> GPUImageColorBalanceFilter() + FilterType.LEVELS_FILTER_MIN -> { + val levelsFilter = GPUImageLevelsFilter() + levelsFilter.setMin(0.0F, 3.0F, 1.0F) + levelsFilter + } + FilterType.HALFTONE -> GPUImageHalftoneFilter() + FilterType.BILATERAL_BLUR -> GPUImageBilateralBlurFilter() + FilterType.TRANSFORM2D -> GPUImageTransformFilter() + else -> throw IllegalStateException("No filter of that type!") + } + } + + /** + * 创建内置混合滤镜效果 + * @param filterClass Class extends GPUImageTwoInputFilter + * @return [GPUImageFilter] + */ + private fun createBlendFilter(filterClass: Class): GPUImageFilter? { + try { + return filterClass.newInstance() + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "createBlendFilter" + ) + } + return null + } + + init { + FILTER_LISTS.add(FilterItem("Contrast", FilterType.CONTRAST)) + FILTER_LISTS.add(FilterItem("Invert", FilterType.INVERT)) + FILTER_LISTS.add(FilterItem("Pixelation", FilterType.PIXELATION)) + FILTER_LISTS.add(FilterItem("Hue", FilterType.HUE)) + FILTER_LISTS.add(FilterItem("Gamma", FilterType.GAMMA)) + FILTER_LISTS.add(FilterItem("Brightness", FilterType.BRIGHTNESS)) + FILTER_LISTS.add(FilterItem("Sepia", FilterType.SEPIA)) + FILTER_LISTS.add(FilterItem("Grayscale", FilterType.GRAYSCALE)) + FILTER_LISTS.add(FilterItem("Sharpness", FilterType.SHARPEN)) + FILTER_LISTS.add(FilterItem("Sobel Edge Detection", FilterType.SOBEL_EDGE_DETECTION)) + FILTER_LISTS.add(FilterItem("3x3 Convolution", FilterType.THREE_X_THREE_CONVOLUTION)) + FILTER_LISTS.add(FilterItem("Emboss", FilterType.EMBOSS)) + FILTER_LISTS.add(FilterItem("Posterize", FilterType.POSTERIZE)) + FILTER_LISTS.add(FilterItem("Grouped filters", FilterType.FILTER_GROUP)) + FILTER_LISTS.add(FilterItem("Saturation", FilterType.SATURATION)) + FILTER_LISTS.add(FilterItem("Exposure", FilterType.EXPOSURE)) + FILTER_LISTS.add(FilterItem("Highlight Shadow", FilterType.HIGHLIGHT_SHADOW)) + FILTER_LISTS.add(FilterItem("Monochrome", FilterType.MONOCHROME)) + FILTER_LISTS.add(FilterItem("Opacity", FilterType.OPACITY)) + FILTER_LISTS.add(FilterItem("RGB", FilterType.RGB)) + FILTER_LISTS.add(FilterItem("White Balance", FilterType.WHITE_BALANCE)) + FILTER_LISTS.add(FilterItem("Vignette", FilterType.VIGNETTE)) + FILTER_LISTS.add(FilterItem("ToneCurve", FilterType.TONE_CURVE)) + FILTER_LISTS.add(FilterItem("Blend (Difference)", FilterType.BLEND_DIFFERENCE)) + FILTER_LISTS.add(FilterItem("Blend (Source Over)", FilterType.BLEND_SOURCE_OVER)) + FILTER_LISTS.add(FilterItem("Blend (Color Burn)", FilterType.BLEND_COLOR_BURN)) + FILTER_LISTS.add(FilterItem("Blend (Color Dodge)", FilterType.BLEND_COLOR_DODGE)) + FILTER_LISTS.add(FilterItem("Blend (Darken)", FilterType.BLEND_DARKEN)) + FILTER_LISTS.add(FilterItem("Blend (Dissolve)", FilterType.BLEND_DISSOLVE)) + FILTER_LISTS.add(FilterItem("Blend (Exclusion)", FilterType.BLEND_EXCLUSION)) + FILTER_LISTS.add(FilterItem("Blend (Hard Light)", FilterType.BLEND_HARD_LIGHT)) + FILTER_LISTS.add(FilterItem("Blend (Lighten)", FilterType.BLEND_LIGHTEN)) + FILTER_LISTS.add(FilterItem("Blend (Add)", FilterType.BLEND_ADD)) + FILTER_LISTS.add(FilterItem("Blend (Divide)", FilterType.BLEND_DIVIDE)) + FILTER_LISTS.add(FilterItem("Blend (Multiply)", FilterType.BLEND_MULTIPLY)) + FILTER_LISTS.add(FilterItem("Blend (Overlay)", FilterType.BLEND_OVERLAY)) + FILTER_LISTS.add(FilterItem("Blend (Screen)", FilterType.BLEND_SCREEN)) + FILTER_LISTS.add(FilterItem("Blend (Alpha)", FilterType.BLEND_ALPHA)) + FILTER_LISTS.add(FilterItem("Blend (Color)", FilterType.BLEND_COLOR)) + FILTER_LISTS.add(FilterItem("Blend (Hue)", FilterType.BLEND_HUE)) + FILTER_LISTS.add(FilterItem("Blend (Saturation)", FilterType.BLEND_SATURATION)) + FILTER_LISTS.add(FilterItem("Blend (Luminosity)", FilterType.BLEND_LUMINOSITY)) + FILTER_LISTS.add(FilterItem("Blend (Linear Burn)", FilterType.BLEND_LINEAR_BURN)) + FILTER_LISTS.add(FilterItem("Blend (Soft Light)", FilterType.BLEND_SOFT_LIGHT)) + FILTER_LISTS.add(FilterItem("Blend (Subtract)", FilterType.BLEND_SUBTRACT)) + FILTER_LISTS.add(FilterItem("Blend (Chroma Key)", FilterType.BLEND_CHROMA_KEY)) + FILTER_LISTS.add(FilterItem("Blend (Normal)", FilterType.BLEND_NORMAL)) + FILTER_LISTS.add(FilterItem("Lookup (Amatorka)", FilterType.LOOKUP_AMATORKA)) + FILTER_LISTS.add(FilterItem("Gaussian Blur", FilterType.GAUSSIAN_BLUR)) + FILTER_LISTS.add(FilterItem("Crosshatch", FilterType.CROSSHATCH)) + FILTER_LISTS.add(FilterItem("Box Blur", FilterType.BOX_BLUR)) + FILTER_LISTS.add(FilterItem("CGA Color Space", FilterType.CGA_COLORSPACE)) + FILTER_LISTS.add(FilterItem("Dilation", FilterType.DILATION)) + FILTER_LISTS.add(FilterItem("Kuwahara", FilterType.KUWAHARA)) + FILTER_LISTS.add(FilterItem("RGB Dilation", FilterType.RGB_DILATION)) + FILTER_LISTS.add(FilterItem("Sketch", FilterType.SKETCH)) + FILTER_LISTS.add(FilterItem("Toon", FilterType.TOON)) + FILTER_LISTS.add(FilterItem("Smooth Toon", FilterType.SMOOTH_TOON)) + FILTER_LISTS.add(FilterItem("Halftone", FilterType.HALFTONE)) + FILTER_LISTS.add(FilterItem("Bulge Distortion", FilterType.BULGE_DISTORTION)) + FILTER_LISTS.add(FilterItem("Glass Sphere", FilterType.GLASS_SPHERE)) + FILTER_LISTS.add(FilterItem("Haze", FilterType.HAZE)) + FILTER_LISTS.add(FilterItem("Laplacian", FilterType.LAPLACIAN)) + FILTER_LISTS.add( + FilterItem( + "Non Maximum Suppression", + FilterType.NON_MAXIMUM_SUPPRESSION + ) + ) + FILTER_LISTS.add(FilterItem("Sphere Refraction", FilterType.SPHERE_REFRACTION)) + FILTER_LISTS.add(FilterItem("Swirl", FilterType.SWIRL)) + FILTER_LISTS.add(FilterItem("Weak Pixel Inclusion", FilterType.WEAK_PIXEL_INCLUSION)) + FILTER_LISTS.add(FilterItem("False Color", FilterType.FALSE_COLOR)) + FILTER_LISTS.add(FilterItem("Color Balance", FilterType.COLOR_BALANCE)) + FILTER_LISTS.add(FilterItem("Levels Min (Mid Adjust)", FilterType.LEVELS_FILTER_MIN)) + FILTER_LISTS.add(FilterItem("Bilateral Blur", FilterType.BILATERAL_BLUR)) + FILTER_LISTS.add(FilterItem("Transform (2-D)", FilterType.TRANSFORM2D)) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetActivity.kt new file mode 100644 index 0000000000..eb3257dbd7 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetActivity.kt @@ -0,0 +1,102 @@ +package afkt.project.feature.ui_effect.material + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityBottomSheetBinding +import afkt.project.model.item.RouterPath +import android.view.View +import android.widget.LinearLayout +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.android.material.bottomsheet.BottomSheetBehavior +import dev.expand.engine.log.log_dTag +import dev.utils.app.ViewUtils + +/** + * detail: Material BottomSheet + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.BottomSheetActivity_PATH) +class BottomSheetActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_bottom_sheet + + private lateinit var mBehavior: BottomSheetBehavior + + override fun initValue() { + super.initValue() + mBehavior = BottomSheetBehavior.from(binding.vidSheetLl) + } + + override fun initListener() { + super.initListener() + + mBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged( + bottomSheet: View, + newState: Int + ) { + when (newState) { + BottomSheetBehavior.STATE_COLLAPSED -> { + // 折叠状态, bottom sheets 只在底部显示一部分布局 + // 显示高度可以通过 app:behavior_peekHeight 设置 + TAG.log_dTag( + message = "STATE_COLLAPSED" + ) + + ViewUtils.setVisibility(false, binding.vidBgView) + } + BottomSheetBehavior.STATE_DRAGGING -> { + // 过渡状态, 此时用户正在向上或者向下拖动 bottom sheet + TAG.log_dTag( + message = "STATE_DRAGGING" + ) + + ViewUtils.setVisibility(true, binding.vidBgView) + } + BottomSheetBehavior.STATE_EXPANDED -> { + // 完全展开的状态 + TAG.log_dTag( + message = "STATE_EXPANDED" + ) + + ViewUtils.setVisibility(true, binding.vidBgView) + } + BottomSheetBehavior.STATE_HIDDEN -> { + // 隐藏状态, 默认是 false 可通过 app:behavior_hideable 属性设置是否能隐藏 + TAG.log_dTag( + message = "STATE_HIDDEN" + ) + + ViewUtils.setVisibility(false, binding.vidBgView) + } + BottomSheetBehavior.STATE_SETTLING -> { + // 视图从脱离手指自由滑动到最终停下的这一小段时间 + TAG.log_dTag( + message = "STATE_SETTLING" + ) + } + } + } + + override fun onSlide( + bottomSheet: View, + slideOffset: Float + ) { + } + }) + +// // 手动设置状态 +// // 展开 +// mBehavior.state = BottomSheetBehavior.STATE_EXPANDED +// // 折叠 +// mBehavior.state = BottomSheetBehavior.STATE_COLLAPSED +// // 隐藏 +// mBehavior.state = BottomSheetBehavior.STATE_HIDDEN + +// // Bottom Sheet 是否允许隐藏 +// // xml 设置 +// app:behavior_hideable="false" +// // 代码设置 +// mBehavior.isHideable = false + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialog.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialog.kt new file mode 100644 index 0000000000..af05e5ebfb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialog.kt @@ -0,0 +1,50 @@ +package afkt.project.feature.ui_effect.material + +import afkt.project.R +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dev.mvvm.utils.size.AppSize +import dev.utils.app.ResourceUtils + +/** + * detail: Material BottomSheetDialog + * @author Ttt + */ +class BottomSheetDialog : BottomSheetDialogFragment() { + + private lateinit var mBehavior: BottomSheetBehavior + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.dialog_bottom_sheet, container, false) + } + + override fun onStart() { + super.onStart() + + val mDialog = dialog as? BottomSheetDialog? + val bottomSheet = + mDialog?.delegate?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + bottomSheet?.apply { +// val layoutParams = layoutParams as CoordinatorLayout.LayoutParams +// // 最大高度 +// layoutParams.height = (ScreenUtils.getScreenHeight() * 0.7F).toInt() + + setBackgroundColor(ResourceUtils.getColor(R.color.transparent)) + + mBehavior = BottomSheetBehavior.from(this) + // 默认显示高度 + mBehavior.peekHeight = AppSize.dp2px(290F) + mBehavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialogActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialogActivity.kt new file mode 100644 index 0000000000..50f2fe061c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/BottomSheetDialogActivity.kt @@ -0,0 +1,26 @@ +package afkt.project.feature.ui_effect.material + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.model.item.RouterPath +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import com.alibaba.android.arouter.facade.annotation.Route + +/** + * detail: Material BottomSheetDialog + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.BottomSheetDialogActivity_PATH) +class BottomSheetDialogActivity : BaseActivity() { + + override fun isViewBinding(): Boolean = false + + override fun baseLayoutId(): Int = R.layout.activity_bottom_sheet_dialog + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + BottomSheetDialog().show(supportFragmentManager, "BottomSheetDialog") + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ChipActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ChipActivity.kt new file mode 100644 index 0000000000..e46d3959e4 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ChipActivity.kt @@ -0,0 +1,73 @@ +package afkt.project.feature.ui_effect.material + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityChipBinding +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.android.material.chip.Chip +import dev.base.widget.BaseTextView +import dev.utils.app.ResourceUtils +import dev.utils.app.StateListUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.ColorUtils +import dev.utils.common.RandomUtils +import java.util.* + +/** + * detail: Material Chip、ChipGroups、ChipDrawable + * @author Ttt + * Google Chips + * @see https://material.io/components/chips + * Android : Chip、ChipGroups、ChipDrawable + * @see https://blog.csdn.net/north1989/article/details/81878653 + * 注意事项: + * Activity 需要设置为 Theme.MaterialComponents 主题 + */ +@Route(path = RouterPath.UI_EFFECT.ChipActivity_PATH) +class ChipActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_chip + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val view = QuickHelper.get(BaseTextView(this)) + .setText("刷新") + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.red)) + .setTextSizeBySp(15.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick { initValue() }.getView() + toolbar?.addView(view) + } + + override fun initValue() { + super.initValue() + + binding.vidGroup.removeAllViews() + + for (i in 1..20) { + val text = ChineseUtils.randomWord(RandomUtils.getRandom(8)) + + RandomUtils.getRandomLetters(RandomUtils.getRandom(5)) + val randomText = + i.toString() + "." + RandomUtils.getRandom(text.toCharArray(), text.length) + + val chip = ViewUtils.inflate(this, R.layout.include_chip) as? Chip + chip?.run { + // 随机颜色 + val pressed = ColorUtils.getRandomColorString() + val normal = ColorUtils.getRandomColorString() + + chip.text = randomText + chip.chipBackgroundColor = StateListUtils.createColorStateList(pressed, normal) + binding.vidGroup.addView(this) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ShapeableImageViewActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ShapeableImageViewActivity.kt new file mode 100644 index 0000000000..51af6b15fd --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/material/ShapeableImageViewActivity.kt @@ -0,0 +1,20 @@ +package afkt.project.feature.ui_effect.material + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.model.item.RouterPath +import androidx.viewbinding.ViewBinding +import com.alibaba.android.arouter.facade.annotation.Route + +/** + * detail: Material ShapeableImageView + * @author Ttt + * 描边需设置 padding 大小为描边宽度一半, 否则显示不全 + */ +@Route(path = RouterPath.UI_EFFECT.ShapeableImageViewActivity_PATH) +class ShapeableImageViewActivity : BaseActivity() { + + override fun isViewBinding(): Boolean = false + + override fun baseLayoutId(): Int = R.layout.activity_shapeable_image_view +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectActivity.kt new file mode 100644 index 0000000000..71d7cde9db --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectActivity.kt @@ -0,0 +1,182 @@ +package afkt.project.feature.ui_effect.multi_select + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ui_effect.multi_select.MultiSelectAdapter.OnSelectListener +import afkt.project.model.bean.CommodityItem +import afkt.project.model.bean.CommodityItem.Companion.newCommodityItem +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.expand.engine.log.log_dTag +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.toast.ToastTintUtils +import dev.widget.decoration.linear.FirstLinearColorItemDecoration + +/** + * detail: 多选辅助类 MultiSelectAssist + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.MultiSelectActivity_PATH) +class MultiSelectActivity : BaseActivity() { + + // 适配器 + lateinit var adapter: MultiSelectAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // 增加 Toolbar 按钮 + addToolbarButton() + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + .setBackgroundColor(ResourceUtils.getColor(R.color.color_33)) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..14) lists.add(newCommodityItem()) + + // 初始化布局管理器、适配器 + adapter = MultiSelectAdapter(lists) + .setSelectListener(object : OnSelectListener { + override fun onClickSelect( + position: Int, + now: Boolean + ) { + val item = adapter.getDataItem(position) + TAG.log_dTag( + message = "新状态: %s, 商品名: %s", + args = arrayOf(now, item?.commodityName) + ) + } + }) + adapter.bindAdapter(binding.vidRv) + + QuickHelper.get(binding.vidRv) + .removeAllItemDecoration() + .addItemDecoration( + FirstLinearColorItemDecoration( + true, ResourceUtils.getDimension(R.dimen.dp_10) + ) + ) + } + + // ============= + // = 增加按钮处理 = + // ============= + + // 编辑按钮 + private var editView: BaseTextView? = null + + // 取消编辑按钮 + private var cancelView: BaseTextView? = null + + // 确定按钮 + private var confirmView: BaseTextView? = null + + // 全选按钮 + private var allSelectView: BaseTextView? = null + + // 非全选按钮 + private var unAllSelectView: BaseTextView? = null + + // 反选按钮 + private var inverseSelectView: BaseTextView? = null + + /** + * 增加 Toolbar 按钮 + */ + private fun addToolbarButton() { + toolbar?.addView(createTextView("编辑") { + adapter.isEditState = true + // 显示其他按钮、隐藏编辑按钮 + ViewUtils.toggleVisibilitys( + arrayOf( + cancelView, + confirmView, + allSelectView, + unAllSelectView, + inverseSelectView + ), editView + ) + }.also { editView = it }) + + toolbar?.addView(createTextView("取消") { + adapter.setEditState(false).clearSelectAll() + // 显示编辑按钮、隐藏其他按钮 + ViewUtils.toggleVisibilitys( + editView, + cancelView, + confirmView, + allSelectView, + unAllSelectView, + inverseSelectView + ) + }.also { cancelView = it }) + + toolbar?.addView(createTextView("确定") { + val builder = StringBuilder() + builder.append("是否全选: ").append(adapter.isSelectAll) + builder.append("\n是否选中: ").append(adapter.isSelect) + builder.append("\n选中数量: ").append(adapter.selectSize) + builder.append("\n总数: ").append(adapter.dataCount) + ToastTintUtils.normal(builder.toString()) + }.also { confirmView = it }) + + toolbar?.addView(createTextView("全选") { + adapter.selectAll() + }.also { + allSelectView = it + }) + + toolbar?.addView(createTextView("非全选") { + adapter.clearSelectAll() + }.also { + unAllSelectView = it + }) + + toolbar?.addView(createTextView("反选") { + adapter.inverseSelect() + }.also { + inverseSelectView = it + }) + + // 显示编辑按钮 + ViewUtils.setVisibility(true, editView) + } + + /** + * 创建 TextView + * @param text 文案 + * @param onClickListener 点击事件 + * @return [BaseTextView] + */ + private fun createTextView( + text: String, + onClickListener: View.OnClickListener + ): BaseTextView { + return QuickHelper.get(BaseTextView(this)) + .setVisibilitys(false) // 默认隐藏 + .setText(text) + .setBold() + .setTextColors(ResourceUtils.getColor(R.color.white)) + .setTextSizeBySp(13.0F) + .setPaddingLeft(30) + .setPaddingRight(30) + .setOnClick(onClickListener) + .getView() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectAdapter.kt new file mode 100644 index 0000000000..9eaa97821c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/multi_select/MultiSelectAdapter.kt @@ -0,0 +1,121 @@ +package afkt.project.feature.ui_effect.multi_select + +import afkt.project.R +import afkt.project.databinding.AdapterMultiSelectBinding +import afkt.project.model.bean.CommodityItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.View +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt2 +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.ViewUtils +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: 多选 Adapter + * @author Ttt + */ +class MultiSelectAdapter(data: List) : + DevDataAdapterExt2>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_multi_select) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + + // 商品信息 + ViewHelper.get() + .setText(item?.commodityName, holder.binding.vidNameTv) + .setText( + item?.commodityPrice?.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 商品图片 + holder.binding.vidPicIv.display( + source = item?.commodityPicture?.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + + // ========== + // = 多选处理 = + // ========== + + val key = getMultiSelectKey(item, position) + val selectIGView = holder.binding.vidIv + // 是否显示编辑按钮、以及是否选中 + ViewHelper.get().setVisibilitys(isEditState, selectIGView) + .setSelected(mMultiSelectMap.isSelectKey(key), selectIGView) + .setOnClick(View.OnClickListener { + if (!isEditState) return@OnClickListener + // 反选处理 + mMultiSelectMap.toggle(key, item) + // 设置是否选中 + ViewUtils.setSelected(mMultiSelectMap.isSelectKey(key), selectIGView) + // 触发回调 + selectListener?.onClickSelect(position, mMultiSelectMap.isSelectKey(key)) + }, holder.itemView.findViewById(R.id.vid_ll)) + } + + // ======= + // = 多选 = + // ======= + + override fun getMultiSelectKey( + item: CommodityItem?, + position: Int + ): String { + return position.toString() + } + + // ============= + // = 操作监听事件 = + // ============= + + // 选择事件通知事件 + private var selectListener: OnSelectListener? = null + + /** + * 设置选择事件通知事件 + * @param selectListener [OnSelectListener] + * @return [MultiSelectAdapter] + */ + fun setSelectListener(selectListener: OnSelectListener?): MultiSelectAdapter { + this.selectListener = selectListener + return this + } + + /** + * detail: 选择事件通知事件 + * @author Ttt + */ + interface OnSelectListener { + + /** + * 点击选中切换 + * @param position 对应的索引 + * @param now 新的状态 + */ + fun onClickSelect( + position: Int, + now: Boolean + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteActivity.kt new file mode 100644 index 0000000000..79d85dc970 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteActivity.kt @@ -0,0 +1,82 @@ +package afkt.project.feature.ui_effect.palette + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityPaletteBinding +import afkt.project.model.item.RouterPath +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.android.material.tabs.TabLayoutMediator +import dev.utils.app.BarUtils + +/** + * detail: Palette 调色板 + * @author Ttt + * Android Palette 基本使用 + * @see https://www.jianshu.com/p/d3c13eb700a4 + * Android Material Design 系列之 Palette 开发详解 + * @see https://blog.csdn.net/jaynm/article/details/107076754 + */ +@Route(path = RouterPath.UI_EFFECT.PaletteActivity_PATH) +class PaletteActivity : BaseActivity() { + + private val viewModel by viewModels() + + override fun baseLayoutId(): Int = R.layout.activity_palette + + override fun initValue() { + super.initValue() + + viewModel.paletteColor.observe(this) { + it.vibrantSwatch?.run { + binding.vidTl.setBackgroundColor(rgb) + toolbar?.let { bar -> + bar.setBackgroundColor(rgb) + BarUtils.addMarginTopEqualStatusBarHeight(bar) + } + BarUtils.setStatusBarColor(mActivity, rgb) + } + } + + val list = mutableListOf() + list.add(newPaletteFragment(1)) + list.add(newPaletteFragment(2)) + list.add(newPaletteFragment(3)) + list.add(newPaletteFragment(4)) + list.add(newPaletteFragment(5)) + + binding.vidVp.adapter = MyPagerAdapter(this, list) + } + + override fun initListener() { + super.initListener() + + binding.vidVp.registerOnPageChangeCallback(object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + viewModel.postItemPosition(position + 1) + } + }) + + // TabLayout 与 ViewPager2 联动 + TabLayoutMediator( + binding.vidTl, binding.vidVp + ) { tab, position -> + tab.text = "Wallpaper-${position}" + }.attach() + } + + class MyPagerAdapter( + val activity: FragmentActivity, + val list: MutableList + ) : FragmentStateAdapter(activity) { + + override fun getItemCount(): Int = list.size + + override fun createFragment(position: Int): Fragment = list[position] + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteFragment.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteFragment.kt new file mode 100644 index 0000000000..f94287690d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteFragment.kt @@ -0,0 +1,141 @@ +package afkt.project.feature.ui_effect.palette + +import afkt.project.R +import afkt.project.base.app.BaseFragment +import afkt.project.databinding.FragmentPaletteBinding +import android.graphics.Bitmap +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.fragment.app.activityViewModels +import androidx.palette.graphics.Palette +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.image.ImageUtils +import dev.utils.common.ColorUtils +import java.lang.ref.WeakReference + +fun newPaletteFragment(position: Int) = PaletteFragment().apply { + arguments = bundleOf(DevFinal.STR.POSITION to position) +} + +class PaletteFragment : BaseFragment() { + + private var palette: Palette? = null + + private var position: Int = 0 + + private var wallpaper: WeakReference? = null + + private val viewModel by activityViewModels() + + override fun baseContentId(): Int = R.layout.fragment_palette + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + position = arguments?.getInt(DevFinal.STR.POSITION) ?: 1 + loadPalette() + } + + override fun initObserve() { + super.initObserve() + + viewModel.itemPosition.observe(this) { index -> + if (index == position) { + palette?.let { viewModel.postPalette(it) } + } + } + } + + private fun loadPalette() { + if (palette == null) { + Palette.from(getBitmap()).generate { + palette = it + refreshUI() + } + } else { + refreshUI() + } + } + + private fun getBitmap(): Bitmap { + var bitmap = wallpaper?.get() + if (bitmap != null) return bitmap + val rawId = ResourceUtils.getRawId("wallpaper_${position}") + val stream = ResourceUtils.openRawResource(rawId) + bitmap = ImageUtils.decodeStream(stream) + wallpaper = WeakReference(bitmap) + return bitmap + } + + private fun refreshUI() { + palette?.let { + if (position == viewModel.itemPosition.value) { + viewModel.postPalette(it) + } + // 设置图片 + binding.vidBgIv.setImageBitmap(getBitmap()) + +// // 获取某种特性颜色的样品 +// it.vibrantSwatch?.run { +// // 谷歌推荐 : 图片的整体的颜色 rgb 的混合值 ( 主色调 ) +// val rgb: Int = rgb +// // 谷歌推荐 : 图片中间的文字颜色 +// val bodyTextColor: Int = bodyTextColor +// // 谷歌推荐 : 作为标题的颜色 ( 有一定的和图片的对比度的颜色值 ) +// val titleTextColor: Int = titleTextColor +// // 颜色向量 +// val hsl: FloatArray = hsl +// // 分析该颜色在图片中所占的像素多少值 +// val population: Int = population +// } + + // 获取最活跃的颜色信息 ( 也可以说整个图片出现最多的颜色 ) + it.vibrantSwatch?.run { + QuickHelper.get(binding.vidTv1) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + // 获取活跃明亮的颜色信息 + it.lightVibrantSwatch?.run { + QuickHelper.get(binding.vidTv2) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + // 获取活跃深色的颜色信息 + it.darkVibrantSwatch?.run { + QuickHelper.get(binding.vidTv3) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + // 获取柔和的颜色信息 + it.mutedSwatch?.run { + QuickHelper.get(binding.vidTv4) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + // 获取柔和明亮的颜色信息 + it.lightMutedSwatch?.run { + QuickHelper.get(binding.vidTv5) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + // 获取柔和深色的颜色信息 + it.darkMutedSwatch?.run { + QuickHelper.get(binding.vidTv6) + .setBackgroundColor(rgb) + .setText(ColorUtils.intToRgbString(rgb).uppercase()) + .setTextColors(bodyTextColor) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteViewModel.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteViewModel.kt new file mode 100644 index 0000000000..338279093b --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/palette/PaletteViewModel.kt @@ -0,0 +1,22 @@ +package afkt.project.feature.ui_effect.palette + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.palette.graphics.Palette + +class PaletteViewModel : ViewModel(), + DefaultLifecycleObserver { + + val itemPosition = MutableLiveData() + + val paletteColor = MutableLiveData() + + fun postItemPosition(value: Int) { + itemPosition.value = value + } + + fun postPalette(value: Palette) { + paletteColor.value = value + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeCreateActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeCreateActivity.kt new file mode 100644 index 0000000000..c2c7fb9eb6 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeCreateActivity.kt @@ -0,0 +1,94 @@ +package afkt.project.feature.ui_effect.qrcode + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityQrcodeCreateBinding +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.content.Intent +import android.graphics.Bitmap +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.engine.DevEngine +import dev.engine.barcode.BarCodeData +import dev.mvvm.utils.size.AppSize +import dev.utils.app.* +import dev.utils.app.image.ImageUtils +import dev.utils.common.StringUtils +import dev.utils.common.ThrowableUtils + +/** + * detail: 创建二维码 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.QRCodeCreateActivity_PATH) +class QRCodeCreateActivity : BaseActivity() { + + // 图片 Bitmap + private var selectBitmap: Bitmap? = null + + override fun baseLayoutId(): Int = R.layout.activity_qrcode_create + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidCreateBtn, binding.vidSelectBtn + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_create_btn -> { + val text = EditTextUtils.getText(binding.vidContentEt) + // 判断是否存在内容 + if (StringUtils.isEmpty(text)) { + showToast(false, "请输入生成二维码内容") + return + } + val size = AppSize.dp2px(200F) + // 创建二维码 + DevEngine.getBarCode().encodeBarCode( + BarCodeData[text, size].setIcon(selectBitmap) + ) { success, bitmap, error -> + if (success) { + HandlerUtils.postRunnable { + ImageViewUtils.setImageBitmap( + binding.vidIv, + bitmap + ) + } + } else { + showToast(false, ThrowableUtils.getThrowable(error)) + } + } + } + R.id.vid_select_btn -> { + mActivity?.let { activity -> + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + } + } + } + } + + // ========== + // = 图片回传 = + // ========== + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + // 判断是否属于图片选择 + if (resultCode == RESULT_OK && intent != null) { + // 获取图片地址 + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + // 获取图片 Bitmap + selectBitmap = ImageUtils.decodeStream(ResourceUtils.openInputStream(imgUri)) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeImageActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeImageActivity.kt new file mode 100644 index 0000000000..e68077bff1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeImageActivity.kt @@ -0,0 +1,143 @@ +package afkt.project.feature.ui_effect.qrcode + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityQrcodeImageBinding +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.content.Intent +import android.graphics.Bitmap +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.DevSource +import dev.engine.DevEngine +import dev.engine.barcode.BarCodeResult +import dev.engine.barcode.listener.BarCodeDecodeCallback +import dev.engine.image.listener.BitmapListener +import dev.expand.engine.image.loadBitmap +import dev.mvvm.utils.toSource +import dev.utils.DevFinal +import dev.utils.app.* +import dev.utils.app.toast.ToastTintUtils +import dev.utils.common.StringUtils +import dev.utils.common.ThrowableUtils +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch + +/** + * detail: 二维码图片解析 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.QRCodeImageActivity_PATH) +class QRCodeImageActivity : BaseActivity() { + + // 图片 Bitmap + private var selectBitmap: Bitmap? = null + + override fun baseLayoutId(): Int = R.layout.activity_qrcode_image + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidSelectBtn, binding.vidTv + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_select_btn -> { + mActivity?.let { activity -> + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + } + } + R.id.vid_tv -> { + val text = TextViewUtils.getText(binding.vidTv) + if (StringUtils.isEmpty(text)) return + // 复制到剪切板 + ClipboardUtils.copyText(text) + // 进行提示 + ToastTintUtils.success(ResourceUtils.getString(R.string.str_copy_suc) + " -> " + text) + } + } + } + + // ========== + // = 图片回传 = + // ========== + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + // 判断是否属于图片选择 + if (resultCode == RESULT_OK && intent != null) { + MainScope().launch { + // 获取图片地址 + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + + mActivity?.loadBitmap( + source = imgUri?.toSource(), config = null, + listener = object : BitmapListener() { + override fun onStart(source: DevSource) {} + override fun onResponse( + source: DevSource, + value: Bitmap + ) { + // 获取图片 Bitmap + selectBitmap = value + // 解析图片 + DevEngine.getBarCode().decodeBarCode( + selectBitmap, object : BarCodeDecodeCallback { + override fun onResult( + success: Boolean, + result: BarCodeResult?, + error: Throwable? + ) { + HandlerUtils.postRunnable { + if (success) { + val builder = StringBuilder() + .append("二维码解析数据: \n") + .append( + StringUtils.checkValue( + DevFinal.SYMBOL.NULL, + result?.getResultData() + ) + ) + TextViewUtils.setText( + binding.vidTv, + builder.toString() + ) + } else { + TextViewUtils.setText( + binding.vidTv, "图片非二维码 / 识别失败\n" + + ThrowableUtils.getThrowableStackTrace( + error + ) + ) + } + } + } + } + ) + } + + override fun onFailure( + source: DevSource, + throwable: Throwable + ) { + TextViewUtils.setText( + binding.vidTv, "图片非二维码 / 识别失败\n" + + ThrowableUtils.getThrowableStackTrace(throwable) + ) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeScanActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeScanActivity.kt new file mode 100644 index 0000000000..c5578e7481 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/QRCodeScanActivity.kt @@ -0,0 +1,289 @@ +package afkt.project.feature.ui_effect.qrcode + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityScanShapeBinding +import afkt.project.feature.dev_widget.scan_shape.ScanShapeUtils +import afkt.project.feature.ui_effect.qrcode.zxing.DecodeConfig +import afkt.project.feature.ui_effect.qrcode.zxing.DecodeResult +import afkt.project.feature.ui_effect.qrcode.zxing.Operate +import afkt.project.feature.ui_effect.qrcode.zxing.ZXingDecodeAssist +import afkt.project.model.item.RouterPath +import afkt.project.utils.createGalleryConfig +import android.Manifest +import android.content.Intent +import android.graphics.Rect +import android.hardware.Camera +import android.os.Bundle +import android.os.Handler +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.zxing.Result +import dev.engine.DevEngine +import dev.engine.barcode.BarCodeResult +import dev.engine.barcode.listener.BarCodeDecodeCallback +import dev.engine.permission.IPermissionEngine +import dev.expand.engine.log.log_dTag +import dev.expand.engine.log.log_eTag +import dev.expand.engine.permission.permission_request +import dev.utils.app.HandlerUtils +import dev.utils.app.ListenerUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.assist.BeepVibrateAssist +import dev.utils.app.assist.InactivityTimerAssist +import dev.utils.app.camera.camera1.FlashlightUtils +import dev.utils.app.image.ImageUtils +import dev.widget.ui.ScanShapeView + +/** + * detail: 二维码扫描解析 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.QRCodeScanActivity_PATH) +class QRCodeScanActivity : BaseActivity() { + + // 无操作计时辅助类 + private var mInactivityTimerAssist = InactivityTimerAssist(this) + + // 扫描成功响声 + 震动 + private var mBeepVibrateAssist = BeepVibrateAssist(this, R.raw.dev_beep) + + override fun baseLayoutId(): Int = R.layout.activity_scan_shape + + override fun onDestroy() { + // 销毁处理 + binding.vidSsv.destroy() + // 结束计时 + mInactivityTimerAssist.onDestroy() + super.onDestroy() + } + + override fun onResume() { + super.onResume() + // 开始动画 + binding.vidSsv.startAnim() + // 开始计时 + mInactivityTimerAssist.onResume() + // 准备扫描 + zxingDecodeAssist.onResume(binding.vidSurface) + } + + override fun onPause() { + super.onPause() + // 停止动画 + binding.vidSsv.stopAnim() + // 暂停计时 + mInactivityTimerAssist.onPause() + // 暂停扫描 + zxingDecodeAssist.onPause() + } + + override fun initValue() { + super.initValue() + + // 设置扫描类型 + ScanShapeUtils.refShape(binding.vidSsv, ScanShapeView.Shape.Square) + // 显示图片识别按钮 + ViewUtils.setVisibility(true, findViewById(R.id.vid_image_iv)) + } + + override fun initListener() { + super.initListener() + ListenerUtils.setOnClicks( + this, + binding.vidFlashlightIv, + binding.vidSquareIv, + binding.vidHexagonIv, + binding.vidAnnulusIv, + binding.vidImageIv + ) + } + + override fun onClick(v: View) { + super.onClick(v) + when (v.id) { + R.id.vid_flashlight_iv -> { + if (!FlashlightUtils.isFlashlightEnable()) { + showToast(false, "暂不支持开启手电筒") + return + } + // 设置手电筒开关 + zxingDecodeAssist.setFlashlight( + !ViewUtils.isSelected(binding.vidFlashlightIv) + ) + } + R.id.vid_square_iv -> { + ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Square + ) + } + R.id.vid_hexagon_iv -> { + ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Hexagon + ) + } + R.id.vid_annulus_iv -> { + ScanShapeUtils.refShape( + binding.vidSsv, + ScanShapeView.Shape.Annulus + ) + } + R.id.vid_image_iv -> { + mActivity?.let { activity -> + // 打开图片选择器 + DevEngine.getMedia()?.openGallery(activity, activity.createGalleryConfig()) + } + } + } + } + + // ========== + // = 图片回传 = + // ========== + + override fun onActivityResult( + requestCode: Int, + resultCode: Int, + intent: Intent? + ) { + super.onActivityResult(requestCode, resultCode, intent) + // 判断是否属于图片选择 + if (resultCode == RESULT_OK && intent != null) { + // 获取图片地址 + val imgUri = DevEngine.getMedia()?.getSingleSelectorUri(intent, false) + // 获取图片 Bitmap + val selectBitmap = ImageUtils.decodeStream(ResourceUtils.openInputStream(imgUri)) + // 解析图片 + DevEngine.getBarCode().decodeBarCode( + selectBitmap, object : BarCodeDecodeCallback { + override fun onResult( + success: Boolean, + result: BarCodeResult?, + error: Throwable? + ) { + HandlerUtils.postRunnable { + if (success) { + mDecodeResult.handleDecode(result?.getResult(), Bundle()) + } else { + showToast(false, "图片非二维码 / 识别失败") + } + } + } + } + ) + } + } + + // =============== + // = 二维码识别相关 = + // =============== + + private val zxingDecodeAssist: ZXingDecodeAssist by lazy { + ZXingDecodeAssist(mOperate, mDecodeConfig, mDecodeResult) + } + + // 解码结果回调 + private val mDecodeResult = object : DecodeResult { + override fun handleDecode( + result: Result?, + bundle: Bundle? + ) { + val resultStr = BarCodeResult(result).getResultData() + // 提示解析成功声音 + mBeepVibrateAssist.playBeepSoundAndVibrate() + // 打印结果 + TAG.log_dTag( + message = "handleDecode result: $resultStr" + ) + showToast(true, "二维码内容: $resultStr") + + // 以下代码只是为了解决停留在此页面可以一直扫码, 实际扫码成功应该回传 + zxingDecodeAssist.captureHandler()?.let { + // 延迟重置, 否则手机一直震动 ( 扫描成功, 重置后又解析成功连续触发 ) + HandlerUtils.postRunnable({ + it.restartPreviewAndDecode() + }, 1000) + } + } + } + + // 扫描相关操作接口 + private val mOperate = object : Operate { + override fun cameraPermission() { + permission_request( + permissions = arrayOf( + Manifest.permission.CAMERA + ), + callback = object : IPermissionEngine.Callback { + override fun onGranted() { + zxingDecodeAssist.startPreview(binding.vidSurface) + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + } + } + ) + } + + override fun switchFlashlight(state: Boolean) { + ViewUtils.setSelected(state, binding.vidFlashlightIv) + } + } + + // 解码配置 + private val mDecodeConfig = object : DecodeConfig { + // 是否出现异常 + private var tryError = false + + // 扫描区域 + private var mCropRect: Rect? = null + + override fun isError(): Boolean { + return tryError + } + + override fun setError( + isError: Boolean, + error: Throwable? + ) { + // 记录是否发生异常 + tryError = isError + // 打印日志 + TAG.log_eTag( + throwable = error, + message = "setError" + ) + } + + override fun getHandler(): Handler? { + return zxingDecodeAssist.captureHandler() + } + + override fun getPreviewSize(): Camera.Size? { + return zxingDecodeAssist.cameraPreviewSize() + } + + override fun isCropRect(): Boolean = false + + override fun getCropRect(): Rect? { + // 优化专门识别指定区域需 isCropRect() 返回 true + mCropRect?.let { + val rectF = binding.vidSsv.region + val rect = Rect() + rect.left = rectF.left.toInt() + rect.top = rectF.top.toInt() + rect.right = rectF.right.toInt() + rect.bottom = rectF.bottom.toInt() + mCropRect = rect + } + return mCropRect + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode.kt new file mode 100644 index 0000000000..f3ebb298a7 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode.kt @@ -0,0 +1,365 @@ +package afkt.project.feature.ui_effect.qrcode.zxing + +import android.graphics.Bitmap +import android.graphics.Rect +import android.hardware.Camera +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.os.Message +import androidx.annotation.IntDef +import com.google.zxing.* +import com.google.zxing.common.HybridBinarizer +import dev.expand.engine.log.log_dTag +import java.io.ByteArrayOutputStream +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import java.util.* +import java.util.concurrent.CountDownLatch + +const val WHAT_QUIT = 100 +const val WHAT_DECODE = 101 +const val WHAT_DECODE_FAILED = 102 +const val WHAT_DECODE_SUCCEEDED = 103 +const val WHAT_RESTART_PREVIEW = 104 + +/** + * detail: 解码配置 + * @author Ttt + */ +interface DecodeConfig { + + // 获取处理 Handler + fun getHandler(): Handler? + + // 是否裁减 + fun isCropRect(): Boolean + + // 裁减区域 + fun getCropRect(): Rect? + + // 获取预览大小 + fun getPreviewSize(): Camera.Size? + + // 判断是否出现异常 + fun isError(): Boolean + + /** + * 设置异常 + * @param isError 是否异常 + * @param error 异常信息 + */ + fun setError( + isError: Boolean, + error: Throwable? + ) + + companion object { + // 条形码数据 Key + const val BARCODE_BITMAP = "barcode_bitmap" + } +} + +/** + * detail: 解码结果回调 + * @author Ttt + */ +interface DecodeResult { + /** + * 处理解码 ( 解码成功 ) + * @param result 识别数据 [Result] + * @param bundle [Bundle] + */ + fun handleDecode( + result: Result?, + bundle: Bundle? + ) +} + +/** + * detail: 解码类型 + * @author Ttt + */ +object DecodeFormat { + + // 1D 解码 + private val PRODUCT_FORMATS: Set + private val INDUSTRIAL_FORMATS: Set + private val ONE_D_FORMATS: MutableSet + + // 二维码解码 + private val QR_CODE_FORMATS: Set + + init { + // 1D 解码类型 + PRODUCT_FORMATS = EnumSet.of( + BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.EAN_13, + BarcodeFormat.EAN_8, BarcodeFormat.RSS_14, BarcodeFormat.RSS_EXPANDED + ) + INDUSTRIAL_FORMATS = EnumSet.of( + BarcodeFormat.CODE_39, BarcodeFormat.CODE_93, + BarcodeFormat.CODE_128, BarcodeFormat.ITF, BarcodeFormat.CODABAR + ) + ONE_D_FORMATS = EnumSet.copyOf(PRODUCT_FORMATS) + ONE_D_FORMATS.addAll(INDUSTRIAL_FORMATS) + // 二维码解码类型 + QR_CODE_FORMATS = EnumSet.of(BarcodeFormat.QR_CODE) + } + + /** + * 获取二维码解码类型集合 + * @return 二维码解码类型集合 + */ + @JvmStatic + val qrCodeFormats: Collection + get() = QR_CODE_FORMATS + + /** + * 获取 1D 解码类型集合 + * @return 1D 解码类型集合 + */ + @JvmStatic + val barCodeFormats: Collection + get() = ONE_D_FORMATS + + // 1D 条形码 + const val BARCODE = 0X100 + + // 二维码 + const val QRCODE = 0X200 + + // 全部识别 + const val ALL = 0X300 + + @IntDef(BARCODE, QRCODE, ALL) + @Retention(RetentionPolicy.SOURCE) + annotation class DecodeMode +} + +/** + * detail: 解码线程 + * @author Ttt + */ +class DecodeThread( + // 解码配置 + private val mDecodeConfig: DecodeConfig, + // 解码类型 + @DecodeFormat.DecodeMode decodeMode: Int +) : Thread() { + + // 解码参数 + private val mHints: MutableMap = EnumMap(DecodeHintType::class.java) + + // 并发线程等待 + private val mHandlerInitLatch = CountDownLatch(1) + + // 解码处理 Handler ( DecodeHandler ) + private var mHandler: DecodeHandler? = null + + init { + val decodeFormats = mutableListOf() + decodeFormats.addAll(EnumSet.of(BarcodeFormat.AZTEC)) + decodeFormats.addAll(EnumSet.of(BarcodeFormat.PDF_417)) + when (decodeMode) { + DecodeFormat.BARCODE -> { + decodeFormats.addAll(DecodeFormat.barCodeFormats) + } + DecodeFormat.QRCODE -> { + decodeFormats.addAll(DecodeFormat.qrCodeFormats) + } + DecodeFormat.ALL -> { + decodeFormats.addAll(DecodeFormat.barCodeFormats) + decodeFormats.addAll(DecodeFormat.qrCodeFormats) + } + } + // 保存解码类型 + mHints[DecodeHintType.POSSIBLE_FORMATS] = decodeFormats + } + + /** + * 获取解码 Handler + * @return [DecodeHandler] + */ + val handler: DecodeHandler? + get() { + try { + mHandlerInitLatch.await() + } catch (ignored: InterruptedException) { + } + return mHandler + } + + override fun run() { + Looper.prepare() + mHandler = DecodeHandler(mDecodeConfig, mHints) + mHandlerInitLatch.countDown() + Looper.loop() + } +} + +/** + * detail: 解码处理 + * @author Ttt + */ +class DecodeHandler( + // 解码配置对象 + private val mDecodeConfig: DecodeConfig, + // 解码参数 + hints: Map +) : Handler() { + + // 日志 TAG + private val TAG = DecodeHandler::class.java.simpleName + + // 是否运行中 + private var mRunning = true + + // 读取图像数据对象 + private val mMultiFormatReader = MultiFormatReader() + + init { + mMultiFormatReader.setHints(hints) + } + + override fun handleMessage(message: Message) { + // 如果非运行中, 则不处理 + if (!mRunning) return + // 判断类型 + if (message.what == WHAT_DECODE) { + // 解码图像数据 + decode(message.obj as ByteArray, message.arg1, message.arg2) + } else if (message.what == WHAT_QUIT) { + mRunning = false + Looper.myLooper()?.quit() + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 处理缩略图 + * @param source [PlanarYUVLuminanceSource] + * @param bundle 存储数据 [Bundle] + */ + private fun bundleThumbnail( + source: PlanarYUVLuminanceSource?, + bundle: Bundle + ) { + source?.let { it -> + val pixels = it.renderThumbnail() + val width = it.thumbnailWidth + val height = it.thumbnailHeight + val bitmap = + Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.ARGB_8888) + val out = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.JPEG, 50, out) + bundle.putByteArray(DecodeConfig.BARCODE_BITMAP, out.toByteArray()) + } + } + + /** + * 对取景器矩形内的数据进行解码, 并计算其占用的时间 + * 为了效率从一个解码到下一个重复使用相同的读取器对象 + * @param data The YUV preview frame. + * @param width The width of the preview frame. + * @param height The height of the preview frame. + */ + private fun decode( + data: ByteArray, + width: Int, + height: Int + ) { + mDecodeConfig.getHandler() ?: return + // 获取 Camera 预览大小 + val size = mDecodeConfig.getPreviewSize() ?: return + // 这里需要将获取的 data 翻转, 因为相机默认拿的的横屏的数据 + val rotatedData = ByteArray(data.size) + for (y in 0 until size.height) { + for (x in 0 until size.width) { + rotatedData[x * size.height + size.height - y - 1] = data[x + y * size.width] + } + } + // 宽高也要调整 + val tmp = size.width + size.width = size.height + size.height = tmp + + // 处理数据 + var rawResult: Result? = null + val source = buildLuminanceSource(rotatedData, size.width, size.height) + if (source != null) { + try { + val bitmap = BinaryBitmap(HybridBinarizer(source)) + rawResult = mMultiFormatReader.decodeWithState(bitmap) + } catch (re: ReaderException) { + // continue + } finally { + mMultiFormatReader.reset() + } + } + val handler = mDecodeConfig.getHandler() + if (rawResult != null) { + TAG.log_dTag( + message = "解析成功, 发送数据" + ) + if (handler != null) { + val message = Message.obtain(handler, WHAT_DECODE_SUCCEEDED, rawResult) + val bundle = Bundle() + bundleThumbnail(source, bundle) + message.data = bundle + message.sendToTarget() + } + } else { + TAG.log_dTag( + message = "解析失败" + ) + if (handler != null) { + val message = Message.obtain(handler, WHAT_DECODE_FAILED) + message.sendToTarget() + } + } + } + + /** + * A factory method to build the appropriate LuminanceSource object based on + * the format of the preview buffers, as described by Camera.Parameters. + * @param data A preview frame. + * @param width The width of the image. + * @param height The height of the image. + * @return A PlanarYUVLuminanceSource instance. + */ + private fun buildLuminanceSource( + data: ByteArray?, + width: Int, + height: Int + ): PlanarYUVLuminanceSource { + TAG.log_dTag( + message = "buildLuminanceSource 解析摄像头数据" + ) + // 判断是否裁减 + return if (mDecodeConfig.isCropRect() && mDecodeConfig.getCropRect() != null) { + // 判断是否出现异常 + if (mDecodeConfig.isError()) { + PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false) + } else { + try { + // 获取裁减识别区域 + val rect = mDecodeConfig.getCropRect() as Rect + PlanarYUVLuminanceSource( + data, width, height, + rect.left, rect.top, rect.width(), rect.height(), false + ) + } catch (e: Exception) { // 出现异常时, 使用全屏解析, 不解析指定识别区域 + // 设置异常 + mDecodeConfig.setError(true, e) + // 全屏处理 + PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false) + } + } + } else { + PlanarYUVLuminanceSource(data, width, height, 0, 0, width, height, false) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode_assist.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode_assist.kt new file mode 100644 index 0000000000..3a105ff6d1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/qrcode/zxing/zxing_decode_assist.kt @@ -0,0 +1,416 @@ +package afkt.project.feature.ui_effect.qrcode.zxing + +import afkt.project.feature.ui_effect.qrcode.zxing.DecodeFormat.DecodeMode +import android.Manifest +import android.hardware.Camera +import android.os.Handler +import android.os.Message +import android.view.SurfaceHolder +import android.view.SurfaceView +import com.google.zxing.Result +import dev.expand.engine.log.log_dTag +import dev.expand.engine.log.log_eTag +import dev.utils.app.camera.camera1.CameraAssist +import dev.utils.app.camera.camera1.CameraAssist.PreviewNotify +import dev.utils.app.camera.camera1.CameraUtils +import dev.utils.app.permission.PermissionUtils + +/** + * detail: 预览回调 + * @author Ttt + */ +class PreviewCallback( + // 显示的大小 + private val mSize: Camera.Size? +) : Camera.PreviewCallback { + + // 日志 TAG + private val TAG = PreviewCallback::class.java.simpleName + + // 处理 Handler + private var mPreviewHandler: Handler? = null + + // 处理 what + private var mWhat = 0 + + /** + * 设置 Handler、what + * @param previewHandler 通知处理 Handler + * @param what 通知 What + * @return [PreviewCallback] + */ + fun setHandler( + previewHandler: Handler?, + what: Int + ): PreviewCallback { + mPreviewHandler = previewHandler + mWhat = what + return this + } + + override fun onPreviewFrame( + data: ByteArray, + camera: Camera + ) { + if (mSize != null && mPreviewHandler != null) { + mPreviewHandler?.apply { + val message = obtainMessage(mWhat, mSize.width, mSize.height, data) + message.sendToTarget() + mPreviewHandler = null + } + } else { + TAG.log_dTag( + message = "Got preview callback, but no handler or resolution available" + ) + } + } +} + +/** + * detail: 捕获预览画面处理 Handler + * @author Ttt + */ +class CaptureHandler( + // 解码配置 + decodeConfig: DecodeConfig, + // 解码类型 + decodeMode: Int, + // Camera 辅助类 + private val mCameraAssist: CameraAssist, + // 数据回调 + private val mPreviewCallback: PreviewCallback?, + // 解码结果回调 + private val mDecodeResult: DecodeResult +) : Handler() { + + // 日志 TAG + private val TAG = CaptureHandler::class.java.simpleName + + // 识别状态 + private var mState = State.SUCCESS + + // 解码线程 + private val mDecodeThread = DecodeThread(decodeConfig, decodeMode) + + init { + // 启动解码线程 + mDecodeThread.start() + // 开始预览 + mCameraAssist.startPreview() + // 设置预览解码线程 + restartPreviewAndDecode() + } + + /** + * detail: 内部枚举状态值 + * @author Ttt + */ + private enum class State { + PREVIEW, + SUCCESS, + DONE + } + + override fun handleMessage(message: Message) { + if (message.what == WHAT_RESTART_PREVIEW) { + restartPreviewAndDecode() + } else if (message.what == WHAT_DECODE_SUCCEEDED) { // 解析成功 + TAG.log_dTag( + message = "解析成功" + ) + mState = State.SUCCESS + val bundle = message.data + mDecodeResult.handleDecode(message.obj as Result, bundle) + } else if (message.what == WHAT_DECODE_FAILED) { // 解析失败 ( 解析不出来触发 ) + TAG.log_dTag( + message = "解析失败" + ) + // 表示预览中 + mState = State.PREVIEW + // 设置预览解码线程 + requestPreviewFrame(mDecodeThread.handler, WHAT_DECODE) + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 设置预览帧数据监听 + * @param handler 解码 Handler [DecodeHandler] + * @param message 解码消息 + */ + @Synchronized + private fun requestPreviewFrame( + handler: DecodeHandler?, + message: Int + ) { + TAG.log_dTag( + message = "requestPreviewFrame" + ) + val theCamera = mCameraAssist.camera + // 不为 null 并且预览中才处理 + if (theCamera != null && mCameraAssist.isPreviewing) { + mPreviewCallback?.setHandler(handler, message) + theCamera.setOneShotPreviewCallback(mPreviewCallback) + } + } + + /** + * 重新设置预览以及解码处理 + */ + fun restartPreviewAndDecode() { + TAG.log_dTag( + message = "restartPreviewAndDecode" + ) + if (mState == State.SUCCESS) { + mState = State.PREVIEW + // 设置请求预览页面 + requestPreviewFrame(mDecodeThread.handler, WHAT_DECODE) + } + } + + /** + * 同步退出解析处理 + */ + fun quitSynchronously() { + TAG.log_dTag( + message = "退出扫描" + ) + // 表示状态为默认 + mState = State.DONE + // 停止预览 + mCameraAssist.stopPreview() + val quit = Message.obtain(mDecodeThread.handler, WHAT_QUIT) + quit.sendToTarget() + try { + // 进行处理解析数据 + mDecodeThread.join(200L) + } catch (ignored: InterruptedException) { + } + // 移除堵塞在队列的消息 + removeMessages(WHAT_DECODE_SUCCEEDED) + removeMessages(WHAT_DECODE_FAILED) + } +} + +/** + * detail: 扫描相关操作接口 + * @author Ttt + */ +interface Operate { + + /** + * Camera 权限申请 + */ + fun cameraPermission() + + /** + * 手电筒开关状态通知 + * @param state Boolean + */ + fun switchFlashlight(state: Boolean) +} + +/** + * detail: ZXing 解码辅助类 + * @author Ttt + */ +class ZXingDecodeAssist( + // 扫描相关操作接口 + val mOperate: Operate, + // 解码配置 + val mDecodeConfig: DecodeConfig, + // 解码结果回调 + val mDecodeResult: DecodeResult +) { + + // 日志 TAG + private val TAG = ZXingDecodeAssist::class.java.simpleName + + // 解码类型 + @DecodeMode + private val DECODE_MODE = DecodeFormat.ALL + + // 摄像头辅助类 + private val mCameraAssist = CameraAssist() + + // 预览回调 + private var mPreviewCallback: PreviewCallback? = null + + // 数据解析 Handler + private var mCaptureHandler: CaptureHandler? = null + + private val mHolderCallback: SurfaceHolder.Callback = object : SurfaceHolder.Callback { + override fun surfaceCreated(holder: SurfaceHolder) { + // Camera 权限申请 + mOperate.cameraPermission() + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + // 关闭手电筒 + setFlashlight(false) + // 停止预览 + stopPreview() + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 停止预览 + */ + fun stopPreview() { + try { + // 停止预览 + mCameraAssist.stopPreview() + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "stopPreview" + ) + } + } + + /** + * 开始预览 + * @param view SurfaceView + * 在获取 [Operate.cameraPermission] 权限成功后调用 + */ + fun startPreview(view: SurfaceView?) { + if (PermissionUtils.isGranted(Manifest.permission.CAMERA)) { + try { + // 打开摄像头 + val camera = CameraUtils.open() + camera.setDisplayOrientation(90) // 设置竖屏显示 + mCameraAssist.camera = camera + // 设置监听 + mCameraAssist.setPreviewNotify(object : PreviewNotify { + override fun stopPreviewNotify() { + TAG.log_dTag( + message = "停止预览通知" + ) + mPreviewCallback?.setHandler(null, 0) + } + + override fun startPreviewNotify() { + TAG.log_dTag( + message = "开始预览通知" + ) + } + }) + // 获取预览大小 + val size = mCameraAssist.cameraResolution + // 设置预览大小, 需要这样设置, 开闪光灯才不会闪烁 + val parameters = camera.parameters + parameters.setPreviewSize(size.width, size.height) + camera.parameters = parameters + // 打开摄像头 + mCameraAssist.openDriver(view?.holder) + // 初始化预览回调 + mPreviewCallback = PreviewCallback(size) + // 初始化获取 Handler + if (mCaptureHandler == null) { + // 初始化捕获预览画面处理 Handler + mCaptureHandler = CaptureHandler( + mDecodeConfig, DECODE_MODE, + mCameraAssist, mPreviewCallback, + mDecodeResult + ) + } + } catch (e: Exception) { + TAG.log_eTag( + throwable = e, + message = "checkPermission startPreview" + ) + } + } + } + + // ============= + // = 生命周期调用 = + // ============= + + fun onPause() { + mCaptureHandler?.let { + it.quitSynchronously() + mCaptureHandler = null + } + // 停止预览 + stopPreview() + } + + fun onResume(view: SurfaceView?) { + // 清空重新初始化 + mCaptureHandler = null + // 绑定 SurfaceHolder Callback + view?.holder?.apply { + try { + removeCallback(mHolderCallback) + } catch (ignored: Exception) { + } + addCallback(mHolderCallback) + } + } + + // ============ + // = 手电筒处理 = + // ============ + + /** + * 设置手电筒开关 + * @param open 是否打开 + */ + fun setFlashlight(open: Boolean) { + if (open) { + mCameraAssist.setFlashlightOn() + } else { + mCameraAssist.setFlashlightOff() + } + mOperate.switchFlashlight(open) + } + + /** + * 切换手电筒开关 + */ + fun toggleFlashlight() { + setFlashlight(!mCameraAssist.isFlashlightOn) + } + + // = + + /** + * 获取捕获预览画面处理 Handler + * @return [CaptureHandler] + */ + fun captureHandler(): CaptureHandler? { + return mCaptureHandler + } + + /** + * 获取 Camera 辅助类 + * @return [CameraAssist] + */ + fun cameraAssist(): CameraAssist { + return mCameraAssist + } + + /** + * 获取 Camera 预览大小 + * @return [Camera.Size] + */ + fun cameraPreviewSize(): Camera.Size? { + return mCameraAssist.previewSize + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/RecyConcatAdapterActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/RecyConcatAdapterActivity.kt new file mode 100644 index 0000000000..e5a6a740be --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/RecyConcatAdapterActivity.kt @@ -0,0 +1,76 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ui_effect.recy_adapter.HeaderFooterItem +import afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter.* +import afkt.project.feature.ui_effect.recy_adapter.createMainData +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.ConcatAdapter +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.helper.quick.QuickHelper +import java.util.* + +/** + * detail: RecyclerView - ConcatAdapter + * @author Ttt + * @see https://mp.weixin.qq.com/s/QTaz45aLucX9mivVMbCZPQ + * @see https://zhuanlan.zhihu.com/p/275635988 + */ +@Route(path = RouterPath.UI_EFFECT.RecyConcatAdapterActivity_PATH) +class RecyConcatAdapterActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + + convertAdapter() + } + + private fun convertAdapter() { + // 头部适配器 + val headerAdapter = HeaderFooterConcatAdapter( + arrayListOf( + HeaderFooterItem("Header"), + HeaderFooterItem("Header2") + ) + ) + + // 底部适配器 + val footerAdapter = HeaderFooterConcatAdapter( + arrayListOf(HeaderFooterItem("Footer")) + ) + + val mainData = createMainData() + + // Banner 广告图适配器 + val bannerAdapter = BannerConcatAdapter(this, mainData.bannerLists) + + // 商品、商品评价适配器 + val commodityAdapter = CommodityConcatAdapter(mainData.commodityLists) + + // ShapeableImageView 效果适配器 + val shapeableAdapter = ShapeableImageConcatAdapter(mainData.shapeableImageLists) + + // 文章适配器 + val articleAdapter = ArticleConcatAdapter(mainData.articleLists) + + // 拼接适配器并设置 + val concatAdapter = ConcatAdapter( + headerAdapter, + bannerAdapter, + commodityAdapter, + shapeableAdapter, + articleAdapter, + footerAdapter + ) + binding.vidRv.adapter = concatAdapter + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ArticleConcatAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ArticleConcatAdapter.kt new file mode 100644 index 0000000000..62c04ce1cd --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ArticleConcatAdapter.kt @@ -0,0 +1,44 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatArticleBinding +import afkt.project.feature.ui_effect.recy_adapter.ArticleBean1 +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Article Adapter + * @author Ttt + */ +class ArticleConcatAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_article) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setText(item.title, holder.binding.vidTitleTv) + .setText(item.content, holder.binding.vidContentTv) + .setImageBitmap(item.pictures, holder.binding.vidIv) + .setBackgroundColor(item.background, holder.itemView) + } + + override fun getItemViewType(position: Int): Int { + return R.layout.adapter_concat_article + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/BannerConcatAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/BannerConcatAdapter.kt new file mode 100644 index 0000000000..172d7eb3dd --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/BannerConcatAdapter.kt @@ -0,0 +1,93 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatBannerBinding +import afkt.project.databinding.AdapterConcatBannerImageBinding +import afkt.project.feature.ui_effect.recy_adapter.BannerBean +import afkt.project.utils.IMAGE_ROUND_10 +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.youth.banner.adapter.BannerAdapter +import com.youth.banner.indicator.CircleIndicator +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource + +/** + * detail: Banner Adapter + * @author Ttt + */ +class BannerConcatAdapter( + private val owner: LifecycleOwner, + private val bannerLists: List +) : RecyclerView.Adapter() { + + var context: Context? = null + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): ItemHolder { + context = parent.context + + return ItemHolder( + AdapterConcatBannerBinding.inflate( + LayoutInflater.from(context), + parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: ItemHolder, + position: Int + ) { + holder.binding.vidBanner.setAdapter( + object : BannerAdapter(bannerLists) { + override fun onCreateHolder( + parent: ViewGroup, + viewType: Int + ): BannerViewHolder { + return BannerViewHolder( + AdapterConcatBannerImageBinding.inflate( + LayoutInflater.from(parent.context), + parent, false + ) + ) + } + + override fun onBindView( + holder: BannerViewHolder, + data: BannerBean, + position: Int, + size: Int + ) { + holder.binding.vidIv.display( + source = data.imageUrl.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } + } + ).addBannerLifecycleObserver(owner) + .indicator = CircleIndicator(context) + } + + override fun getItemCount(): Int { + return 1 + } + + override fun getItemViewType(position: Int): Int { + return R.layout.adapter_concat_banner + } + + class ItemHolder(val binding: AdapterConcatBannerBinding) : RecyclerView.ViewHolder( + binding.root + ) + + class BannerViewHolder(val binding: AdapterConcatBannerImageBinding) : RecyclerView.ViewHolder( + binding.root + ) +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/CommodityConcatAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/CommodityConcatAdapter.kt new file mode 100644 index 0000000000..2f12e0c622 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/CommodityConcatAdapter.kt @@ -0,0 +1,130 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterItemEditsBinding +import afkt.project.databinding.AdapterMultiSelectBinding +import afkt.project.feature.ui_effect.recy_adapter.CommodityBean +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.adapter.DevDataAdapter +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Commodity、Evaluate Adapter + * @author Ttt + */ +class CommodityConcatAdapter(data: List) : DevDataAdapter() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { + parentContext(parent) + if (viewType == R.layout.adapter_item_edits) { + // 商品评价类型 + return CommodityEvaluateHolder( + AdapterItemEditsBinding.inflate( + LayoutInflater.from(context), + parent, false + ) + ) + } + // 商品类型 + return CommodityHolder( + AdapterMultiSelectBinding.inflate( + LayoutInflater.from(context), + parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int + ) { + // 统一设置背景 + ViewHelper.get().setBackgroundColor( + ResourceUtils.getColor(R.color.color_33), + holder.itemView + ) + + val item = getDataItem(position) + if (holder is CommodityHolder) { + ViewHelper.get() + // 是否显示编辑按钮 + .setVisibilitys(false, holder.binding.vidIv) + // 判断是否显示边距 + .setVisibilitys(position == 0, holder.binding.vidLineView) + // 商品名 + .setText(item.commodityName, holder.binding.vidNameTv) + // 商品价格 + .setText( + item.commodityPrice.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 商品图片 + holder.binding.vidPicIv.display( + source = item.commodityPicture.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + + } else if (holder is CommodityEvaluateHolder) { + ViewHelper.get() + // 判断是否显示边距 + .setVisibilitys(position == 0, holder.binding.vidLineView) + // 商品名 + .setText(item.commodityName, holder.binding.vidNameTv) + // 商品价格 + .setText( + item.commodityPrice.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 评价内容 + .setText(item.evaluateContent, holder.binding.vidContentEt) + // 禁止点击评价输入框 + .setEnabled(false, holder.binding.vidContentEt) + + // 商品图片 + holder.binding.vidPicIv.display( + source = item.commodityPicture.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + // 评星等级 + val ratingBar = holder.binding.vidRatingbar + ratingBar.setOnRatingChangeListener { _, rating, _ -> + item.evaluateLevel = rating + } + // 设置评星等级 + ratingBar.rating = item.evaluateLevel + } + } + + override fun getItemViewType(position: Int): Int { + if (getDataItem(position).isEvaluateCommodity) { + // 商品评价类型 + return R.layout.adapter_item_edits + } + // 商品类型 + return R.layout.adapter_multi_select + } + + class CommodityHolder(val binding: AdapterMultiSelectBinding) : RecyclerView.ViewHolder( + binding.root + ) + + class CommodityEvaluateHolder(val binding: AdapterItemEditsBinding) : RecyclerView.ViewHolder( + binding.root + ) +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/HeaderFooterConcatAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/HeaderFooterConcatAdapter.kt new file mode 100644 index 0000000000..f2925fc4b5 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/HeaderFooterConcatAdapter.kt @@ -0,0 +1,38 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatHeaderFooterBinding +import afkt.project.feature.ui_effect.recy_adapter.HeaderFooterItem +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Header、Footer Adapter + * @author Ttt + */ +class HeaderFooterConcatAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_header_footer) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + holder.binding.vidTitleTv.text = getDataItem(position).title + } + + override fun getItemViewType(position: Int): Int { + return R.layout.adapter_concat_header_footer + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ShapeableImageConcatAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ShapeableImageConcatAdapter.kt new file mode 100644 index 0000000000..3bbbd81ef2 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_concat/adapter/ShapeableImageConcatAdapter.kt @@ -0,0 +1,81 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_concat.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatShapeableImageBinding +import afkt.project.feature.ui_effect.recy_adapter.ShapeableImageBean +import android.view.ViewGroup +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.RelativeCornerSize +import com.google.android.material.shape.RoundedCornerTreatment +import com.google.android.material.shape.ShapeAppearanceModel +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.toSource +import dev.utils.app.ResourceUtils + +/** + * detail: ShapeableImage Adapter + * @author Ttt + */ +class ShapeableImageConcatAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_shapeable_image) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + holder.binding.vidIv.display( + source = item.imageUrl.toSource(), + ) + + when (item.type) { + 1 -> { // 圆形 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners(RoundedCornerTreatment()) + .setAllCornerSizes(RelativeCornerSize(0.5F)) + .build() + } + 2 -> { // 圆角 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners( + CornerFamily.ROUNDED, + ResourceUtils.getDimension(R.dimen.dp_30) + ) + .build() + } + 3 -> { // 水滴形 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners( + CornerFamily.ROUNDED, + ResourceUtils.getDimension(R.dimen.dp_25) + ) + .setTopRightCornerSize(RelativeCornerSize(0.7F)) + .setTopLeftCornerSize(RelativeCornerSize(0.7F)) + .build() + } + 4 -> { // 叶子形状 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setTopRightCorner(CornerFamily.ROUNDED, RelativeCornerSize(0.5F)) + .setBottomLeftCorner(CornerFamily.ROUNDED, RelativeCornerSize(0.5F)) + .build() + } + } + } + + override fun getItemViewType(position: Int): Int { + return R.layout.adapter_concat_shapeable_image + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/RecyMultiTypeAdapterActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/RecyMultiTypeAdapterActivity.kt new file mode 100644 index 0000000000..389a240f7c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/RecyMultiTypeAdapterActivity.kt @@ -0,0 +1,66 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ui_effect.recy_adapter.* +import afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter.* +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import com.drakeet.multitype.MultiTypeAdapter +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: RecyclerView MultiType Adapter + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.RecyMultiTypeAdapterActivity_PATH) +class RecyMultiTypeAdapterActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + + convertAdapter() + } + + private fun convertAdapter() { + val mainData = createMainData() + // 转换 Item 模型 + val lists = convertMainDataItem(mainData) + // 添加头部 + lists.add(0, HeaderFooterItem("Header")) + lists.add(1, HeaderFooterItem("Header2")) + // 添加底部 + lists.add(HeaderFooterItem("Footer")) + + val adapter = MultiTypeAdapter(items = lists) + // 头部、底部适配器 + adapter.register(HeaderFooterItem::class.java, HeaderFooterItemViewBinder()) + // Banner 广告图适配器 + adapter.register(BannerBeanItem::class.java, BannerItemViewBinder(this)) + // 商品适配器 + adapter.register(CommodityBeanItem::class.java, CommodityItemViewBinder()) + // 商品评价适配器 + adapter.register(CommodityEvaluateBeanItem::class.java, CommodityEvaluateItemViewBinder()) + // ShapeableImageView 效果适配器 + adapter.register(ShapeableImageBeanItem::class.java, ShapeableImageItemViewBinder()) + // 文章适配器 + adapter.register(ArticleBean1Item::class.java, ArticleItemViewBinder()) + // 一级分类适配器 + adapter.register(ClassifyBeanItem1::class.java, Classify1ItemViewBinder()) + // 二级分类适配器 + adapter.register(ClassifyBeanItem2::class.java, Classify2ItemViewBinder()) + // 三级分类适配器 + adapter.register(ClassifyBeanItem3::class.java, Classify3ItemViewBinder()) + + // 绑定适配器 + binding.vidRv.adapter = adapter + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ArticleItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ArticleItemViewBinder.kt new file mode 100644 index 0000000000..0f299cec42 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ArticleItemViewBinder.kt @@ -0,0 +1,38 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatArticleBinding +import afkt.project.feature.ui_effect.recy_adapter.ArticleBean1Item +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Article Adapter + * @author Ttt + */ +class ArticleItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_article) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: ArticleBean1Item + ) { + val itemObj = item.obj + + ViewHelper.get() + .setText(itemObj.title, holder.binding.vidTitleTv) + .setText(itemObj.content, holder.binding.vidContentTv) + .setImageBitmap(itemObj.pictures, holder.binding.vidIv) + .setBackgroundColor(itemObj.background, holder.itemView) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/BannerItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/BannerItemViewBinder.kt new file mode 100644 index 0000000000..48bc551051 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/BannerItemViewBinder.kt @@ -0,0 +1,78 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatBannerBinding +import afkt.project.databinding.AdapterConcatBannerImageBinding +import afkt.project.feature.ui_effect.recy_adapter.BannerBean +import afkt.project.feature.ui_effect.recy_adapter.BannerBeanItem +import afkt.project.utils.IMAGE_ROUND_10 +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.RecyclerView +import com.drakeet.multitype.ItemViewBinder +import com.youth.banner.adapter.BannerAdapter +import com.youth.banner.indicator.CircleIndicator +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource + +/** + * detail: Banner Adapter + * @author Ttt + */ +class BannerItemViewBinder( + private val owner: LifecycleOwner +) : ItemViewBinder>() { + + var context: Context? = null + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + context = parent.context + return newBindingViewHolder(parent, R.layout.adapter_concat_banner) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: BannerBeanItem + ) { + holder.binding.vidBanner.setAdapter( + object : BannerAdapter(item.obj) { + override fun onCreateHolder( + parent: ViewGroup, + viewType: Int + ): BannerViewHolder { + return BannerViewHolder( + AdapterConcatBannerImageBinding.inflate( + LayoutInflater.from(parent.context), + parent, false + ) + ) + } + + override fun onBindView( + holder: BannerViewHolder, + data: BannerBean, + position: Int, + size: Int + ) { + holder.binding.vidIv.display( + source = data.imageUrl.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } + } + ).addBannerLifecycleObserver(owner) + .indicator = CircleIndicator(context) + } + + class BannerViewHolder(val binding: AdapterConcatBannerImageBinding) : RecyclerView.ViewHolder( + binding.root + ) +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify1ItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify1ItemViewBinder.kt new file mode 100644 index 0000000000..9e92d3d9ed --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify1ItemViewBinder.kt @@ -0,0 +1,38 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatClassifyBinding +import afkt.project.feature.ui_effect.recy_adapter.ClassifyBeanItem1 +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: 一级分类 Adapter + * @author Ttt + */ +class Classify1ItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_classify) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: ClassifyBeanItem1 + ) { + val itemObj = item.obj + + QuickHelper.get(holder.binding.vidTitleTv) + .setText(itemObj.name) + .setBackgroundColor(itemObj.background) + .setPaddingLeft(ResourceUtils.getDimensionInt(R.dimen.dp_20)) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify2ItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify2ItemViewBinder.kt new file mode 100644 index 0000000000..aba61bf62d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify2ItemViewBinder.kt @@ -0,0 +1,38 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatClassifyBinding +import afkt.project.feature.ui_effect.recy_adapter.ClassifyBeanItem2 +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: 二级分类 Adapter + * @author Ttt + */ +class Classify2ItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_classify) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: ClassifyBeanItem2 + ) { + val itemObj = item.obj + + QuickHelper.get(holder.binding.vidTitleTv) + .setText(itemObj.name) + .setBackgroundColor(itemObj.background) + .setPaddingLeft(ResourceUtils.getDimensionInt(R.dimen.dp_40)) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify3ItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify3ItemViewBinder.kt new file mode 100644 index 0000000000..b3b7fe2b0e --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/Classify3ItemViewBinder.kt @@ -0,0 +1,38 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatClassifyBinding +import afkt.project.feature.ui_effect.recy_adapter.ClassifyBeanItem3 +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: 三级分类 Adapter + * @author Ttt + */ +class Classify3ItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_classify) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: ClassifyBeanItem3 + ) { + val itemObj = item.obj + + QuickHelper.get(holder.binding.vidTitleTv) + .setText(itemObj.name) + .setBackgroundColor(itemObj.background) + .setPaddingLeft(ResourceUtils.getDimensionInt(R.dimen.dp_60)) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityEvaluateItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityEvaluateItemViewBinder.kt new file mode 100644 index 0000000000..7c219f5abb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityEvaluateItemViewBinder.kt @@ -0,0 +1,73 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterItemEditsBinding +import afkt.project.feature.ui_effect.recy_adapter.CommodityEvaluateBeanItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Commodity Evaluate Adapter + * @author Ttt + */ +class CommodityEvaluateItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_item_edits) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: CommodityEvaluateBeanItem + ) { + // 统一设置背景 + ViewHelper.get().setBackgroundColor( + ResourceUtils.getColor(R.color.color_33), + holder.itemView + ) + + val itemObj = item.obj + + ViewHelper.get() + // 判断是否显示边距 + .setVisibilitys(itemObj.isFirst, holder.binding.vidLineView) + // 商品名 + .setText(itemObj.commodityName, holder.binding.vidNameTv) + // 商品价格 + .setText( + itemObj.commodityPrice.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 评价内容 + .setText(itemObj.evaluateContent, holder.binding.vidContentEt) + // 禁止点击评价输入框 + .setEnabled(false, holder.binding.vidContentEt) + + // 商品图片 + holder.binding.vidPicIv.display( + source = itemObj.commodityPicture.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + // 评星等级 + val ratingBar = holder.binding.vidRatingbar + ratingBar.setOnRatingChangeListener { _, rating, _ -> + itemObj.evaluateLevel = rating + } + // 设置评星等级 + ratingBar.rating = itemObj.evaluateLevel + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityItemViewBinder.kt new file mode 100644 index 0000000000..305c910031 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/CommodityItemViewBinder.kt @@ -0,0 +1,63 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterMultiSelectBinding +import afkt.project.feature.ui_effect.recy_adapter.CommodityBeanItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Commodity Adapter + * @author Ttt + */ +class CommodityItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_multi_select) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: CommodityBeanItem + ) { + // 统一设置背景 + ViewHelper.get().setBackgroundColor( + ResourceUtils.getColor(R.color.color_33), + holder.itemView + ) + + val itemObj = item.obj + + ViewHelper.get() + // 是否显示编辑按钮 + .setVisibilitys(false, holder.binding.vidIv) + // 判断是否显示边距 + .setVisibilitys(itemObj.isFirst, holder.binding.vidLineView) + // 商品名 + .setText(itemObj.commodityName, holder.binding.vidNameTv) + // 商品价格 + .setText( + itemObj.commodityPrice.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 商品图片 + holder.binding.vidPicIv.display( + source = itemObj.commodityPicture.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/HeaderFooterItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/HeaderFooterItemViewBinder.kt new file mode 100644 index 0000000000..12c1a6f165 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/HeaderFooterItemViewBinder.kt @@ -0,0 +1,31 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatHeaderFooterBinding +import afkt.project.feature.ui_effect.recy_adapter.HeaderFooterItem +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: Header、Footer Adapter + * @author Ttt + */ +class HeaderFooterItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_header_footer) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: HeaderFooterItem + ) { + holder.binding.vidTitleTv.text = item.title + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ShapeableImageItemViewBinder.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ShapeableImageItemViewBinder.kt new file mode 100644 index 0000000000..6e84ed5a10 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/adapter_multitype/adapter/ShapeableImageItemViewBinder.kt @@ -0,0 +1,73 @@ +package afkt.project.feature.ui_effect.recy_adapter.adapter_multitype.adapter + +import afkt.project.R +import afkt.project.databinding.AdapterConcatShapeableImageBinding +import afkt.project.feature.ui_effect.recy_adapter.ShapeableImageBeanItem +import android.view.LayoutInflater +import android.view.ViewGroup +import com.drakeet.multitype.ItemViewBinder +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.RelativeCornerSize +import com.google.android.material.shape.RoundedCornerTreatment +import com.google.android.material.shape.ShapeAppearanceModel +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.toSource +import dev.utils.app.ResourceUtils + +/** + * detail: ShapeableImage Adapter + * @author Ttt + */ +class ShapeableImageItemViewBinder : ItemViewBinder>() { + + override fun onCreateViewHolder( + inflater: LayoutInflater, + parent: ViewGroup + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_concat_shapeable_image) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + item: ShapeableImageBeanItem + ) { + val itemObj = item.obj + holder.binding.vidIv.display( + source = itemObj.imageUrl.toSource() + ) + when (itemObj.type) { + 1 -> { // 圆形 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners(RoundedCornerTreatment()) + .setAllCornerSizes(RelativeCornerSize(0.5F)) + .build() + } + 2 -> { // 圆角 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners( + CornerFamily.ROUNDED, + ResourceUtils.getDimension(R.dimen.dp_30) + ) + .build() + } + 3 -> { // 水滴形 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setAllCorners( + CornerFamily.ROUNDED, + ResourceUtils.getDimension(R.dimen.dp_25) + ) + .setTopRightCornerSize(RelativeCornerSize(0.7F)) + .setTopLeftCornerSize(RelativeCornerSize(0.7F)) + .build() + } + 4 -> { // 叶子形状 + holder.binding.vidIv.shapeAppearanceModel = ShapeAppearanceModel.builder() + .setTopRightCorner(CornerFamily.ROUNDED, RelativeCornerSize(0.5F)) + .setBottomLeftCorner(CornerFamily.ROUNDED, RelativeCornerSize(0.5F)) + .build() + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/ItemSlideAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/ItemSlideAdapter.kt new file mode 100644 index 0000000000..20cf81c217 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/ItemSlideAdapter.kt @@ -0,0 +1,52 @@ +package afkt.project.feature.ui_effect.recy_adapter.item_slide + +import afkt.project.R +import afkt.project.databinding.AdapterMultiSelectBinding +import afkt.project.model.bean.CommodityItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: Item Slide Adapter + * @author Ttt + */ +class ItemSlideAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_multi_select) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setText(item.commodityName, holder.binding.vidNameTv) + .setText( + item.commodityPrice.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 商品图片 + holder.binding.vidIv.display( + source = item.commodityPicture?.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/RecyItemSlideActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/RecyItemSlideActivity.kt new file mode 100644 index 0000000000..9682fd0c26 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_slide/RecyItemSlideActivity.kt @@ -0,0 +1,118 @@ +package afkt.project.feature.ui_effect.recy_adapter.item_slide + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.CommodityItem +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.widget.decoration.linear.FirstLinearColorItemDecoration +import java.util.* + +/** + * detail: RecyclerView 滑动删除、上下滑动 + * @author Ttt + * RecyclerView 实现拖拽排序和侧滑删除 + * @see http://www.imooc.com/article/80640 + * RecyclerView 扩展 + * @see https://www.jianshu.com/p/c769f4ed298f + */ +@Route(path = RouterPath.UI_EFFECT.RecyItemSlideActivity_PATH) +class RecyItemSlideActivity : BaseActivity() { + + private lateinit var itemSlideAdapter: ItemSlideAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + .setBackgroundColor(ResourceUtils.getColor(R.color.color_33)) + } + + override fun initValue() { + super.initValue() + val lists = mutableListOf() + for (i in 0..39) lists.add(CommodityItem.newCommodityItem()) + + // 初始化布局管理器、适配器 + itemSlideAdapter = ItemSlideAdapter(lists) + itemSlideAdapter.bindAdapter(binding.vidRv) + + QuickHelper.get(binding.vidRv) + .removeAllItemDecoration() + .addItemDecoration( + FirstLinearColorItemDecoration( + true, ResourceUtils.getDimension(R.dimen.dp_10) + ) + ) + + val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() { + /** + * 获取动作标识 + * 动作标识分 : dragFlags 和 swipeFlags + * dragFlags : 列表滚动方向的动作标识 ( 如竖直列表就是上和下, 水平列表就是左和右 ) + * wipeFlags : 与列表滚动方向垂直的动作标识 ( 如竖直列表就是左和右, 水平列表就是上和下 ) + */ + override fun getMovementFlags( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder + ): Int { + // 如果你不想上下拖动, 可以将 dragFlags = 0 + val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN + + // 如果你不想左右滑动, 可以将 swipeFlags = 0 + val swipeFlags = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT + + // 最终的动作标识 ( flags ) 必须要用 makeMovementFlags() 方法生成 + return makeMovementFlags(dragFlags, swipeFlags) + } + + /** + * 是否开启 item 长按拖拽功能 + */ + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + val fromPosition = viewHolder.bindingAdapterPosition + val toPosition = target.bindingAdapterPosition + Collections.swap(itemSlideAdapter.dataList, fromPosition, toPosition) + itemSlideAdapter.notifyItemMoved(fromPosition, toPosition) + return true + } + + /** + * 当 item 侧滑出去时触发 ( 竖直列表是侧滑, 水平列表是竖滑 ) + * @param viewHolder + * @param direction 滑动的方向 + */ + override fun onSwiped( + viewHolder: RecyclerView.ViewHolder, + direction: Int + ) { + val position = viewHolder.bindingAdapterPosition + if (direction == ItemTouchHelper.LEFT || direction == ItemTouchHelper.RIGHT) { + itemSlideAdapter.removeDataAt(position) + itemSlideAdapter.notifyItemRemoved(position) + +// // 例如有特殊需求, 需弹窗确认 +// // 可以先触发调用 +// itemSlideAdapter.notifyItemChanged(position) +// // 接着弹窗, 确认要删除才移除对应 position + } + } + }) + itemTouchHelper.attachToRecyclerView(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyActivity.kt new file mode 100644 index 0000000000..653031cd04 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyActivity.kt @@ -0,0 +1,113 @@ +package afkt.project.feature.ui_effect.recy_adapter.item_sticky + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.item.RouterPath +import android.view.View +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import com.gavin.com.library.PowerfulStickyDecoration +import com.gavin.com.library.StickyDecoration +import com.gavin.com.library.listener.GroupListener +import com.gavin.com.library.listener.PowerGroupListener +import dev.expand.engine.log.log_dTag +import dev.mvvm.utils.size.AppSize +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.TextViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils +import java.util.* + +/** + * detail: RecyclerView 吸附效果 + * @author Ttt + * RecyclerView 实现顶部吸附效果 + * @see https://github.com/Gavin-ZYX/StickyDecoration + */ +@Route(path = RouterPath.UI_EFFECT.ItemStickyActivity_PATH) +class ItemStickyActivity : BaseActivity() { + + // 适配器 + lateinit var itemStickyAdapter: ItemStickyAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + + // ==================== + // = 使用自定义悬浮 View = + // ==================== + + val listener = object : PowerGroupListener { + override fun getGroupName(position: Int): String { + return itemStickyAdapter.getDataItem(position).timeTile + } + + override fun getGroupView(position: Int): View? { + TAG.log_dTag( + message = position.toString() + ) + val view = layoutInflater.inflate(R.layout.adapter_sticky_view, null, false) + TextViewUtils.setText( + view.findViewById(R.id.vid_title_tv), + getGroupName(position) + ) + return view + } + } + + val decoration1 = PowerfulStickyDecoration.Builder + .init(listener) + .setGroupHeight(AppSize.dp2px(50F)) +// // 重置 span ( 注意 : 使用 GridLayoutManager 时必须调用 ) +// .resetSpan(mRecyclerView, (GridLayoutManager) manager) + .build() + + // =============== + // = 默认悬浮 View = + // =============== + + val groupListener = GroupListener { position -> + itemStickyAdapter.getDataItem(position).timeTile + } + + val decoration = StickyDecoration.Builder.init(groupListener) + .setGroupBackground(ResourceUtils.getColor(R.color.color_f7)) + .setGroupTextColor(ResourceUtils.getColor(R.color.color_33)) + .setGroupTextSize(AppSize.sp2px(15.0F)) + .setTextSideMargin(AppSize.dp2px(10.0F)) + .build() + + // 初始化布局管理器、适配器 + itemStickyAdapter = ItemStickyAdapter(list) + binding.vidRv.addItemDecoration(decoration) + itemStickyAdapter.bindAdapter(binding.vidRv) + } + + private val list: List + get() { + val lists = mutableListOf() + var time = System.currentTimeMillis() + for (i in 0..9) { + val number = RandomUtils.getRandom(4, 10) + time -= DevFinal.TIME.DAY_MS * number + for (y in 0..number) { + lists.add( + ItemStickyBean( + ChineseUtils.randomWord(RandomUtils.getRandom(3, 12)), + time + ) + ) + } + } + return lists + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyAdapter.kt new file mode 100644 index 0000000000..c73e0640a2 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyAdapter.kt @@ -0,0 +1,35 @@ +package afkt.project.feature.ui_effect.recy_adapter.item_sticky + +import afkt.project.R +import afkt.project.databinding.AdapterItemStickyBinding +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder + +/** + * detail: 吸附 Item 预览 View Adapter + * @author Ttt + */ +class ItemStickyAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_item_sticky) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + holder.binding.vidTitleTv.text = item.title + holder.binding.vidTimeTv.text = item.timeFormat + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyBean.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyBean.kt new file mode 100644 index 0000000000..c33ee7a5ee --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/item_sticky/ItemStickyBean.kt @@ -0,0 +1,37 @@ +package afkt.project.feature.ui_effect.recy_adapter.item_sticky + +import afkt.project.model.bean.AdapterBean +import dev.utils.DevFinal +import dev.utils.common.DateUtils + +/** + * detail: Item 实体类 + * @author Ttt + */ +class ItemStickyBean( + // 标题 + title: String, + // 时间 + time: Long +) : AdapterBean(title, "") { + + // 时间格式化 + val timeFormat: String + + // 吸附标题 + val timeTile: String + + init { + val format = DevFinal.TIME.yyyyMMdd_POINT + // 进行格式化 + timeFormat = DateUtils.formatTime(time, format) + // 获取当前时间 + val currentTime = DateUtils.getDateNow(format) + // 设置标题 + timeTile = if (currentTime == timeFormat) { + "今日" + } else { + DateUtils.formatTime(time, DevFinal.TIME.ZH_MMdd) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapActivity.kt new file mode 100644 index 0000000000..acde3c33c7 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapActivity.kt @@ -0,0 +1,48 @@ +package afkt.project.feature.ui_effect.recy_adapter.linear_snap + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.ItemBean +import afkt.project.model.bean.ItemBean.Companion.newItemBean +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: LinearSnapHelper - RecyclerView + * @author Ttt + * LinearSnapHelper : 滑动多页居中显示, 类似 Gallery + */ +@Route(path = RouterPath.UI_EFFECT.LinearSnapActivity_PATH) +class LinearSnapActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..9) lists.add(newItemBean()) + + // 初始化布局管理器、适配器 + binding.vidRv.layoutManager = + LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) // VERTICAL + LinearSnapAdapter(lists).bindAdapter(binding.vidRv) + + val helper = LinearSnapHelper() + helper.attachToRecyclerView(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapAdapter.kt new file mode 100644 index 0000000000..0df428d76a --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapAdapter.kt @@ -0,0 +1,48 @@ +package afkt.project.feature.ui_effect.recy_adapter.linear_snap + +import afkt.project.R +import afkt.project.databinding.AdapterLinearSnapBinding +import afkt.project.model.bean.ItemBean +import afkt.project.utils.IMAGE_ROUND_10 +import android.view.ViewGroup +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: RecyclerView Gallery 效果 Adapter + * @author Ttt + */ +class LinearSnapAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_linear_snap) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setText(item.title, holder.binding.vidTitleTv) + .setText(item.subtitle, holder.binding.vidSubtitleTv) + .setText(item.timeFormat, holder.binding.vidTimeTv) + + holder.binding.vidIv.display( + source = item.imageUrl?.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXActivity.kt new file mode 100644 index 0000000000..c7e7fddd59 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXActivity.kt @@ -0,0 +1,94 @@ +package afkt.project.feature.ui_effect.recy_adapter.linear_snap + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.ItemBean +import afkt.project.model.bean.ItemBean.Companion.newItemBean +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.expand.engine.log.log_dTag +import dev.utils.app.ListViewUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: LinearSnapHelper - 无限滑动 + * @author Ttt + * LinearSnapHelper : 滑动多页居中显示, 类似 Gallery + */ +@Route(path = RouterPath.UI_EFFECT.LinearSnapMAXActivity_PATH) +class LinearSnapMAXActivity : BaseActivity() { + + lateinit var adapter: LinearSnapMAXAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..9) lists.add(newItemBean()) + + // 初始化布局管理器、适配器 + adapter = LinearSnapMAXAdapter(lists) + binding.vidRv.layoutManager = + LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) // VERTICAL + adapter.bindAdapter(binding.vidRv) + val helper = LinearSnapHelper() + helper.attachToRecyclerView(binding.vidRv) + val size = lists.size + // 滑动到中间 ( 无滑动过程 ) + (binding.vidRv.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset( + size * 100 - 1, + 10 + ) + // 复位到中间 + ListViewUtils.smoothScrollToPosition(binding.vidRv, size * 100 + 1) + } + + override fun initListener() { + super.initListener() + binding.vidRv.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int + ) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + val layoutManager = recyclerView.layoutManager + if (layoutManager is LinearLayoutManager) { + // 获取最后一个可见 view 的位置 + val lastItemPosition = layoutManager.findLastVisibleItemPosition() + // 获取第一个可见 view 的位置 + val firstItemPosition = layoutManager.findFirstVisibleItemPosition() + // 获取居中索引 + val currentPosition = (lastItemPosition + firstItemPosition) / 2 + // 真实索引 + val index = adapter.getRealIndex(currentPosition) + TAG.log_dTag( + message = "%s - %s 当前显示索引: %s - %s", + args = arrayOf( + lastItemPosition, + firstItemPosition, + currentPosition, + index + ) + ) + } + } + } + }) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXAdapter.kt new file mode 100644 index 0000000000..f28ebbd6f4 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/linear_snap/LinearSnapMAXAdapter.kt @@ -0,0 +1,74 @@ +package afkt.project.feature.ui_effect.recy_adapter.linear_snap + +import afkt.project.R +import afkt.project.databinding.AdapterLinearSnapBinding +import afkt.project.model.bean.ItemBean +import afkt.project.utils.IMAGE_ROUND_10 +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewBindingVH +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: RecyclerView Gallery 效果 Adapter + * @author Ttt + */ +class LinearSnapMAXAdapter(data: List) : DevDataAdapter() { + + init { + setDataList(data, false) + } + + /** + * 获取真实索引 + * @param position 当前索引 + * @return Data 真实索引 + */ + fun getRealIndex(position: Int): Int { + val size = dataSize + return if (size != 0) position % size else 0 + } + + override fun getItemCount(): Int { + return Int.MAX_VALUE + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { +// return newBindingViewHolder( +// parent, R.layout.adapter_linear_snap +// ) + return DevBaseViewBindingVH.create( + AdapterLinearSnapBinding::class.java, + parent, R.layout.adapter_linear_snap + ) + } + + override fun onBindViewHolder( + viewHolder: RecyclerView.ViewHolder, + position: Int + ) { + val size = dataSize + if (size != 0) { + val holder = viewHolder as DevBaseViewBindingVH + val index = position % size + val itemBean = getDataItem(index) + ViewHelper.get() + .setText(itemBean.title, holder.binding.vidTitleTv) + .setText(itemBean.subtitle, holder.binding.vidSubtitleTv) + .setText(itemBean.timeFormat, holder.binding.vidTimeTv) + .setText("$position - $index", holder.binding.vidIndexTv) + + holder.binding.vidIv.display( + source = itemBean.imageUrl?.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapActivity.kt new file mode 100644 index 0000000000..5e0712625c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapActivity.kt @@ -0,0 +1,47 @@ +package afkt.project.feature.ui_effect.recy_adapter.pager_snap + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.ItemBean +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.PagerSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: PagerSnapHelper - RecyclerView + * @author Ttt + * PagerSnapHelper : 每次滑动一页居中显示, 类似 ViewPager + */ +@Route(path = RouterPath.UI_EFFECT.PagerSnapActivity_PATH) +class PagerSnapActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..9) lists.add(ItemBean.newItemBeanPager()) + + // 初始化布局管理器、适配器 + binding.vidRv.layoutManager = + LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) // VERTICAL + PagerSnapAdapter(lists).bindAdapter(binding.vidRv) + + val helper = PagerSnapHelper() + helper.attachToRecyclerView(binding.vidRv) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapAdapter.kt new file mode 100644 index 0000000000..8e49cd9681 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapAdapter.kt @@ -0,0 +1,55 @@ +package afkt.project.feature.ui_effect.recy_adapter.pager_snap + +import afkt.project.R +import afkt.project.databinding.AdapterPagerSnapBinding +import afkt.project.model.bean.ItemBean +import afkt.project.utils.IMAGE_ROUND_10 +import android.view.ViewGroup +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewDataBindingVH +import dev.base.adapter.newDataBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource + +/** + * detail: RecyclerView ViewPager 效果 Adapter + * @author Ttt + */ +class PagerSnapAdapter(data: List) : DevDataAdapter>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewDataBindingVH { + return newDataBindingViewHolder(parent, R.layout.adapter_pager_snap) + } + + override fun onBindViewHolder( + holder: DevBaseViewDataBindingVH, + position: Int + ) { +// holder.binding.setVariable(afkt.project.BR.item, getDataItem(position)) + holder.binding.item = getDataItem(position) + } + + companion object { + @JvmStatic + @BindingAdapter("imageUrl") + fun bindImageUrl( + view: ImageView?, + imageUrl: String? + ) { + view?.display( + source = imageUrl?.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXActivity.kt new file mode 100644 index 0000000000..4d663952d7 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXActivity.kt @@ -0,0 +1,92 @@ +package afkt.project.feature.ui_effect.recy_adapter.pager_snap + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.ItemBean +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.PagerSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.alibaba.android.arouter.facade.annotation.Route +import dev.expand.engine.log.log_dTag +import dev.utils.app.ListViewUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: PagerSnapHelper - 无限滑动 + * @author Ttt + * PagerSnapHelper : 每次滑动一页居中显示, 类似 ViewPager + */ +@Route(path = RouterPath.UI_EFFECT.PagerSnapMAXActivity_PATH) +class PagerSnapMAXActivity : BaseActivity() { + + private lateinit var adapter: PagerSnapMAXAdapter + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..9) lists.add(ItemBean.newItemBeanPager()) + + // 初始化布局管理器、适配器 + adapter = PagerSnapMAXAdapter(lists) + binding.vidRv.layoutManager = + LinearLayoutManager(this, RecyclerView.HORIZONTAL, false) // VERTICAL + adapter.bindAdapter(binding.vidRv) + val helper = PagerSnapHelper() + helper.attachToRecyclerView(binding.vidRv) + val size = lists.size + // 滑动到中间 ( 无滑动过程 ) + (binding.vidRv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset( + size * 100 - 1, 10 + ) + // 复位到中间 + ListViewUtils.smoothScrollToPosition(binding.vidRv, size * 100 + 1) + } + + override fun initListener() { + super.initListener() + binding.vidRv.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged( + recyclerView: RecyclerView, + newState: Int + ) { + super.onScrollStateChanged(recyclerView, newState) + if (newState == RecyclerView.SCROLL_STATE_IDLE) { + val layoutManager = recyclerView.layoutManager + if (layoutManager is LinearLayoutManager) { + // 获取最后一个可见 view 的位置 + val lastItemPosition = layoutManager.findLastVisibleItemPosition() + // 获取第一个可见 view 的位置 + val firstItemPosition = layoutManager.findFirstVisibleItemPosition() + // 获取居中索引 + val currentPosition = (lastItemPosition + firstItemPosition) / 2 + // 真实索引 + val index = adapter.getRealIndex(currentPosition) + TAG.log_dTag( + message = "%s - %s 当前显示索引: %s - %s", + args = arrayOf( + lastItemPosition, + firstItemPosition, + currentPosition, + index + ) + ) + } + } + } + }) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXAdapter.kt new file mode 100644 index 0000000000..0ba1f89b70 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/pager_snap/PagerSnapMAXAdapter.kt @@ -0,0 +1,80 @@ +package afkt.project.feature.ui_effect.recy_adapter.pager_snap + +import afkt.project.R +import afkt.project.databinding.AdapterPagerSnapBinding +import afkt.project.model.bean.ItemBean +import afkt.project.utils.IMAGE_ROUND_10 +import android.view.ViewGroup +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewDataBindingVH +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toSource + +/** + * detail: RecyclerView Gallery 效果 Adapter + * @author Ttt + */ +class PagerSnapMAXAdapter(data: List) : DevDataAdapter() { + + init { + setDataList(data, false) + } + + /** + * 获取真实索引 + * @param position 当前索引 + * @return Data 真实索引 + */ + fun getRealIndex(position: Int): Int { + val size = dataSize + return if (size != 0) position % size else 0 + } + + override fun getItemCount(): Int { + return Int.MAX_VALUE + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { +// return newDataBindingViewHolder( +// parent, R.layout.adapter_pager_snap +// ) + return DevBaseViewDataBindingVH.create( + parent, R.layout.adapter_pager_snap + ) + } + + override fun onBindViewHolder( + viewHolder: RecyclerView.ViewHolder, + position: Int + ) { + val size = dataSize + if (size != 0) { + val holder = viewHolder as DevBaseViewDataBindingVH + val index = position % size + + holder.binding.page = "$position - $index" + holder.binding.item = getDataItem(index) + } + } + + companion object { + @JvmStatic + @BindingAdapter("imageUrl") + fun bindImageUrl( + view: ImageView?, + imageUrl: String? + ) { + view?.display( + source = imageUrl?.toSource(), + config = IMAGE_ROUND_10.toImageConfig() + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/recy_concat.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/recy_concat.kt new file mode 100644 index 0000000000..e3cc5ede8f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/recy_adapter/recy_concat.kt @@ -0,0 +1,434 @@ +package afkt.project.feature.ui_effect.recy_adapter + +import android.graphics.Bitmap +import dev.utils.app.ResourceUtils +import dev.utils.app.image.ImageUtils +import dev.utils.common.ChineseUtils +import dev.utils.common.ColorUtils +import dev.utils.common.RandomUtils + +// ===================== +// = 模拟 - 后台返回实体类 = +// ===================== + +/** + * detail: 首页信息实体类 + * @author Ttt + */ +class MainBean( + val bannerLists: List, + val classifyLists: List, + val commodityLists: List, + val shapeableImageLists: List, + val articleLists: List +) + +/** + * detail: Banner 实体类 + * @author Ttt + */ +class BannerBean( + val id: String, + val imageUrl: String +) + +/** + * detail: 分类实体类 + * @author Ttt + */ +class ClassifyBean( + // 分类 id + val id: String, + // 分类名 + val name: String, + // 父类 id + val rootId: String, + // 背景颜色 + val background: Int, + // 子类数据 + var subLists: List? = null +) + +/** + * detail: 商品实体类 + * @author Ttt + */ +class CommodityBean( + // 商品名 + val commodityName: String, + // 商品图片 + val commodityPicture: String, + // 商品价格 + val commodityPrice: Double, + // 商品类型 ( 是否评价商品 ) + val isEvaluateCommodity: Boolean = false, + // 商品评价等级 + var evaluateLevel: Float = 0F, + // 商品评价内容 + var evaluateContent: String? = "", + // 是否首个向上边距 ( MultiType 使用 ) + var isFirst: Boolean = false +) + +/** + * detail: ShapeableImageView 实体类 + * @author Ttt + */ +class ShapeableImageBean( + val type: Int, + val imageUrl: String +) + +/** + * detail: 文章实体类 + * @author Ttt + */ +class ArticleBean1( + // 文章标题 + val title: String, + // 配图 + val pictures: Bitmap?, + // 文章内容 + val content: String, + // 背景颜色 + val background: Int +) + +// ============================== +// = 配合 ConcatAdapter 转换 Item = +// ============================== + +class BannerBeanItem( + val obj: List +) + +// 一级分类 +class ClassifyBeanItem1( + val obj: ClassifyBean +) + +// 二级分类 +class ClassifyBeanItem2( + val obj: ClassifyBean +) + +// 三级分类 +class ClassifyBeanItem3( + val obj: ClassifyBean +) + +class CommodityBeanItem( + val obj: CommodityBean +) + +class CommodityEvaluateBeanItem( + val obj: CommodityBean +) + +class ShapeableImageBeanItem( + val obj: ShapeableImageBean +) + +class ArticleBean1Item( + val obj: ArticleBean1 +) + +class HeaderFooterItem( + val title: String +) + +// ============= +// = 创建数据方法 = +// ============= + +fun createMainData(): MainBean { + return MainBean( + createBannerLists(), + createClassifyLists(), + createCommodityLists(), + createShapeableImageLists(), + createArticleLists(), + ) +} + +private fun createBannerLists(): List { + return ArrayList().apply { + for (position in 1 until 5) { + add( + BannerBean( + id = position.toString(), + imageUrl = String.format( + "https://picsum.photos/id/%s/800/400", + RandomUtils.getRandom(10, 20) + ) + ) + ) + } + } +} + +private fun createClassifyLists(): List { + return ArrayList().apply { + // 主分类背景色 + val level0Bg = ColorUtils.getRandomColor() + // 二级分类背景色 + val level1Bg = ColorUtils.getRandomColor() + // 三级分类背景色 + val level2Bg = ColorUtils.getRandomColor() + + add( + // 添加一级分类 + ClassifyBean( + id = "0-0", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = "0", + background = level0Bg, + // 创建二级分类 + subLists = randomClassifyList( + 1, "0-0", level1Bg, 4 + ) + ) + ) + + add( + // 添加一级分类 + ClassifyBean( + id = "0-1", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = "0", + background = level0Bg, + ) + ) + + add( + // 添加一级分类 + ClassifyBean( + id = "0-2", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = "0", + background = level0Bg, + // 创建二级分类 + subLists = randomClassifyList( + 1, "0-2", level1Bg, 2 + ) + ) + ) + + // 创建一级分类 + val temp = ClassifyBean( + id = "0-3", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = "0", + background = level0Bg, + // 创建二级分类 + subLists = randomClassifyList( + 1, "0-3", level1Bg, 5 + ) + ) + + // 创建三级分类 + temp.subLists?.let { + for (i in it.indices) { + it[i].subLists = randomClassifyList( + 2, "1-$i", level2Bg, + RandomUtils.getRandom(1, 4) + ) + } + } + add(temp) + + add( + // 添加一级分类 + ClassifyBean( + id = "0-4", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = "0", + background = level0Bg, + // 创建二级分类 + subLists = randomClassifyList( + 1, "0-4", level1Bg, 1 + ) + ) + ) + } +} + +private fun createCommodityLists(): List { + return ArrayList().apply { + + // ============= + // = 添加普通商品 = + // ============= + + for (position in 1 until 5) { + add( + CommodityBean( + // 商品名 + commodityName = ChineseUtils.randomWord(RandomUtils.getRandom(5, 40)), + // 商品图片 + commodityPicture = "https://picsum.photos/20${RandomUtils.getRandom(0, 10)}", + // 商品价格 + commodityPrice = RandomUtils.nextDoubleRange(15.1, 79.3), + ) + ) + } + + // ============= + // = 添加评价商品 = + // ============= + + for (position in 1 until 5) { + add( + CommodityBean( + // 商品名 + commodityName = ChineseUtils.randomWord(RandomUtils.getRandom(5, 40)), + // 商品图片 + commodityPicture = "https://picsum.photos/20${RandomUtils.getRandom(0, 10)}", + // 商品价格 + commodityPrice = RandomUtils.nextDoubleRange(15.1, 79.3), + // 商品类型 ( 是否评价商品 ) + isEvaluateCommodity = true, + // 商品评价等级 + evaluateLevel = RandomUtils.getRandom(6).toFloat(), + // 商品评价内容 + evaluateContent = ChineseUtils.randomWord(RandomUtils.getRandom(12, 60)) + ) + ) + } + + // ============= + // = 随机打乱顺序 = + // ============= + + this.shuffle() + } +} + +private fun createShapeableImageLists(): List { + return ArrayList().apply { + add( + ShapeableImageBean( + type = 1, // 圆形 + imageUrl = "https://picsum.photos/id/11/400" + ) + ) + add( + ShapeableImageBean( + type = 2, // 圆角 + imageUrl = "https://picsum.photos/id/12/400" + ) + ) + add( + ShapeableImageBean( + type = 3, // 水滴形 + imageUrl = "https://picsum.photos/id/13/400" + ) + ) + add( + ShapeableImageBean( + type = 4, // 叶子形状 + imageUrl = "https://picsum.photos/id/14/400" + ) + ) + } +} + +private fun createArticleLists(): List { + return ArrayList().apply { + for (position in 1 until 3) { + add( + ArticleBean1( + title = "第${ + ChineseUtils.numberToCHN( + position.toString(), false + ) + }篇: ${ChineseUtils.randomWord(5)}", + pictures = randomBitmap(position), + content = ChineseUtils.randomWord(RandomUtils.getRandom(50, 100)), + background = ColorUtils.getRandomColor() + ) + ) + } + } +} + +// ========== +// = 临时方法 = +// ========== + +private fun randomBitmap(position: Int): Bitmap? { + val rawId = ResourceUtils.getRawId("wallpaper_${position}") + val stream = ResourceUtils.openRawResource(rawId) + return ImageUtils.decodeStream(stream) +} + +private fun randomClassifyList( + // 分类级别 + level: Int, + // 父类 id + rootId: String, + // 背景颜色 + background: Int, + // 随机数量 + count: Int +): List { + return ArrayList().apply { + for (position in 1 until count) { + add( + ClassifyBean( + id = "$level-$position", + name = ChineseUtils.randomWord(RandomUtils.getRandom(5, 15)), + rootId = rootId, + background = background, + subLists = null + ) + ) + } + } +} + +// ============ +// = 转换 Item = +// ============ + +fun convertMainDataItem(mainData: MainBean): ArrayList { + val lists = ArrayList() + + lists.add(BannerBeanItem(mainData.bannerLists)) + + var isFirst = false + + mainData.commodityLists.forEach { + if (!isFirst) { + isFirst = true + it.isFirst = true + } + if (it.isEvaluateCommodity) { + lists.add(CommodityEvaluateBeanItem(it)) + } else { + lists.add(CommodityBeanItem(it)) + } + } + + mainData.shapeableImageLists.forEach { + lists.add(ShapeableImageBeanItem(it)) + } + + mainData.articleLists.forEach { + lists.add(ArticleBean1Item(it)) + } + + mainData.classifyLists.forEach { it1 -> + // 一级分类 + lists.add(ClassifyBeanItem1(it1)) + // 二级分类 + it1.subLists?.forEach { it2 -> + lists.add(ClassifyBeanItem2(it2)) + // 三级分类 + it2.subLists?.forEach { it3 -> + lists.add(ClassifyBeanItem3(it3)) + } + } + } + return lists +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAddAnimActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAddAnimActivity.kt new file mode 100644 index 0000000000..498bc09eb1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAddAnimActivity.kt @@ -0,0 +1,59 @@ +package afkt.project.feature.ui_effect.shop_cart_anim + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.model.bean.CommodityItem +import afkt.project.model.bean.CommodityItem.Companion.newCommodityItem +import afkt.project.model.item.RouterPath +import android.os.Bundle +import android.view.ViewGroup +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.widget.decoration.linear.FirstLinearColorItemDecoration + +/** + * detail: 购物车加入动画 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.ShopCartAddAnimActivity_PATH) +class ShopCartAddAnimActivity : BaseActivity() { + + // 购物车悬浮对外公开类 + lateinit var shopCartFloating: ShopCartFloating + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val parent = binding.vidRv.parent as? ViewGroup + // 根布局处理 + QuickHelper.get(parent).setPadding(0) + .setBackgroundColor(ResourceUtils.getColor(R.color.color_33)) + + // 创建购物车悬浮 + shopCartFloating = ShopCartFloating(this) + } + + override fun initValue() { + super.initValue() + + val lists = mutableListOf() + for (i in 0..14) lists.add(newCommodityItem()) + + // 初始化布局管理器、适配器 + ShopCartAnimAdapter(lists).setClickListener { + shopCartFloating.executeAnim(it) + }.bindAdapter(binding.vidRv) + + QuickHelper.get(binding.vidRv) + .removeAllItemDecoration() + .addItemDecoration( + FirstLinearColorItemDecoration( + true, ResourceUtils.getDimension(R.dimen.dp_10) + ) + ) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAnimAdapter.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAnimAdapter.kt new file mode 100644 index 0000000000..c3e521ee36 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartAnimAdapter.kt @@ -0,0 +1,70 @@ +package afkt.project.feature.ui_effect.shop_cart_anim + +import afkt.project.R +import afkt.project.databinding.AdapterItemShopCartAnimBinding +import afkt.project.model.bean.CommodityItem +import afkt.project.utils.IMAGE_ROUND_3 +import android.view.View +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt +import dev.base.adapter.DevBaseViewBindingVH +import dev.base.adapter.newBindingViewHolder +import dev.expand.engine.image.display +import dev.mvvm.utils.image.toImageConfig +import dev.mvvm.utils.toPriceString +import dev.mvvm.utils.toRMBSubZeroAndDot +import dev.mvvm.utils.toSource +import dev.utils.app.helper.view.ViewHelper + +/** + * detail: 购物车动画 Adapter + * @author Ttt + */ +class ShopCartAnimAdapter(data: List) : + DevDataAdapterExt>() { + + init { + setDataList(data, false) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewBindingVH { + return newBindingViewHolder(parent, R.layout.adapter_item_shop_cart_anim) + } + + override fun onBindViewHolder( + holder: DevBaseViewBindingVH, + position: Int + ) { + val item = getDataItem(position) + + // 商品信息 + ViewHelper.get() + .setText(item?.commodityName, holder.binding.vidNameTv) + .setText( + item?.commodityPrice?.toPriceString()?.toRMBSubZeroAndDot(), + holder.binding.vidPriceTv + ) + // 商品图片 + holder.binding.vidPicIv.display( + source = item?.commodityPicture?.toSource(), + config = IMAGE_ROUND_3.toImageConfig() + ) + // 点击加入购物车 + ViewHelper.get() + .setOnClick({ + mClick?.onClick(it) + }, holder.binding.vidAddIv) + } + + // = + + private var mClick: View.OnClickListener? = null + + fun setClickListener(click: View.OnClickListener): ShopCartAnimAdapter { + mClick = click + return this + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartFloating.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartFloating.kt new file mode 100644 index 0000000000..339da9b4ac --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/shop_cart_anim/ShopCartFloating.kt @@ -0,0 +1,372 @@ +package afkt.project.feature.ui_effect.shop_cart_anim + +import afkt.project.R +import afkt.project.databinding.IncludeBottomShopCartFloatingAnimViewBinding +import afkt.project.databinding.IncludeBottomShopCartFloatingBinding +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.TypeEvaluator +import android.animation.ValueAnimator +import android.app.Activity +import android.graphics.Point +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.animation.AnimationSet +import android.view.animation.ScaleAnimation +import android.widget.FrameLayout +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import dev.assist.NumberControlAssist +import dev.base.number.INumberListener +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.assist.floating.FloatingWindowManagerAssist2 +import dev.utils.app.assist.floating.IFloatingActivity +import dev.utils.app.assist.floating.IFloatingOperate +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.ArrayUtils + +/** + * detail: 购物车悬浮对外公开类 + * @author Ttt + * 搭配 [IFloatingActivity]、[FloatingWindowManagerAssist2] 使用, 简化代码以及使用流程 + * 而不是在每个布局内最外层加 FrameLayout 使用 include 插入布局 + */ +class ShopCartFloating(private val activity: AppCompatActivity) { + + // 数量控制辅助类 + private val numberControl = NumberControlAssist(0, Int.MAX_VALUE).setNumberListener( + object : INumberListener { + override fun onPrepareChanged( + isAdd: Boolean, + curNumber: Int, + afterNumber: Int + ): Boolean { + return true + } + + override fun onNumberChanged( + isAdd: Boolean, + curNumber: Int + ) { + } + } + ) + + // 购物车悬浮 View + private val binding: IncludeBottomShopCartFloatingBinding by lazy { + IncludeBottomShopCartFloatingBinding.inflate( + activity.layoutInflater, + null, false + ).apply { + numberControl.currentNumber = 0 + // 设置购买数量 + QuickHelper.get(vidCartNumberTv) + .setText(numberControl.currentNumber.toString()) + .setVisibilitys(true) + } + } + + // 悬浮窗生命周期处理类 + private val floatingLifecycle = FloatingLifecycle(activity, binding) + + // 购物车动画 + private val shopCartAnimation = ShopCartAnimation() + + /** + * 执行添加购物车动画 + * @param view View + */ + fun executeAnim(view: View) { + numberControl.addNumber().apply { + val numberTxt = if (currentNumber > 99) "99+" else currentNumber.toString() + // 设置购买数量 + QuickHelper.get(binding.vidCartNumberTv) + .setText(numberTxt) + } + // 开始动画 + shopCartAnimation.startAnim( + view, binding.vidCartFl, binding, activity + ) + } +} + +// ================================================== +// = IFloatingActivity、FloatingWindowManagerAssist2 = +// ================================================== + +/** + * detail: 悬浮窗生命周期处理类 + * @author Ttt + */ +class FloatingLifecycle( + private val activity: AppCompatActivity, + private val binding: IncludeBottomShopCartFloatingBinding +) : DefaultLifecycleObserver, + IFloatingActivity { + + init { + activity.lifecycle.addObserver(this) + } + + // ===================== + // = IFloatingActivity = + // ===================== + + override fun getAttachActivity(): Activity { + return activity + } + + override fun getMapFloatingKey(): String { + return this.toString() + } + + override fun getMapFloatingView(): View { + return InnerUtils.instance.createFloatingView(binding.root) + } + + override fun getMapFloatingViewLayoutParams(): ViewGroup.LayoutParams { + return InnerUtils.instance.createLayoutParams(this) + } + + // ============================ + // = DefaultLifecycleObserver = + // ============================ + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + // 添加悬浮窗 View + InnerUtils.instance.addFloatingView(this) + } + + override fun onDestroy(owner: LifecycleOwner) { + super.onDestroy(owner) + // 移除悬浮窗 View + InnerUtils.instance.removeFloatingView(this) + } +} + +/** + * detail: 悬浮窗工具类 + * @author Ttt + */ +internal class InnerUtils private constructor() : IFloatingOperate { + + companion object { + + val instance: InnerUtils by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + InnerUtils() + } + } + + // 悬浮窗管理辅助类 + private val mAssist = object : FloatingWindowManagerAssist2() { + override fun updateViewLayout( + floatingActivity: IFloatingActivity, + view: View + ) { + instance.updateViewLayout(floatingActivity, view) + } + } + + /** + * 创建悬浮 View + * @param view 悬浮窗 View + * @return FloatingView + */ + fun createFloatingView(view: View): View { + return view + } + + /** + * 创建悬浮 View LayoutParams + * @param floatingActivity 悬浮窗辅助类接口 + * @return ViewGroup.LayoutParams + */ + fun createLayoutParams(floatingActivity: IFloatingActivity): ViewGroup.LayoutParams { + return FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ).apply { + // 靠左下 + gravity = Gravity.START or Gravity.BOTTOM + // 设置边距 + setMargins( + 0, 0, 0, + ResourceUtils.getDimensionInt(R.dimen.dp_70) + ) + } + } + + // ==================== + // = IFloatingOperate = + // ==================== + + override fun removeFloatingView(floatingActivity: IFloatingActivity): Boolean { + return mAssist.removeFloatingView(floatingActivity) + } + + override fun addFloatingView(floatingActivity: IFloatingActivity): Boolean { + return mAssist.addFloatingView(floatingActivity) + } + + override fun removeAllFloatingView() { + mAssist.removeAllFloatingView() + } + + override fun updateViewLayout( + floatingActivity: IFloatingActivity, + view: View + ) { + } + + override fun isNeedsAdd(): Boolean { + return mAssist.isNeedsAdd + } + + override fun setNeedsAdd(needsAdd: Boolean) { + mAssist.isNeedsAdd = needsAdd + } +} + +// ============ +// = 购物车动画 = +// ============ + +class ShopCartAnimation { + + fun startAnim( + startView: View, + endView: View, + binding: IncludeBottomShopCartFloatingBinding, + activity: AppCompatActivity + ) { + // 开始位置 - 起点位置 + val startPoints = ViewUtils.getLocationInWindow(startView) + // 准备前往 - 结束位置 + val endPoints = ViewUtils.getLocationInWindow(endView) + // 悬浮 View - 用于获取宽高 + val rootView = binding.root + + startPoints[1] -= startView.height + // 动画 View - 小红点 + val animBinding = IncludeBottomShopCartFloatingAnimViewBinding.inflate( + activity.layoutInflater, ViewUtils.getContentView(activity), true + ) + QuickHelper.get(animBinding.root) + .setMargin(startPoints[0], startPoints[1], 0, 0) + // 待执行动画 View + val animView = animBinding.root + + // 创建动画并执行 + createAnimAndExecute( + animView, endPoints[0], endPoints[1], + startPoints[0], startPoints[1], + rootView, object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + // 动画结束抖动数量 + val zoomTime = 100L + val zoomAnim = ScaleAnimation( + 1.0F, 1.1F, 1.0F, 1.1F, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F + ) + zoomAnim.duration = zoomTime + val zoomOutAnim = ScaleAnimation( + 1.1F, 1.0F, 1.1F, 1.0F, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F + ) + zoomOutAnim.duration = zoomTime + val animatorSet = AnimationSet(false) + animatorSet.addAnimation(zoomAnim) + animatorSet.addAnimation(zoomOutAnim) + // 开始动画 + QuickHelper.get(binding.vidCartNumberTv) + .clearAnimation().startAnimation(animatorSet) + } + } + ) + } + + // ========== + // = 动画代码 = + // ========== + + /** + * 创建动画并执行 + * @param view 待执行 View + * @param toX 前往的 X + * @param toY 前往的 Y + * @param cX 当前的 X + * @param cY 当前的 Y + * @param rootView 悬浮 View + * @param listener 动画结束触发 + */ + private fun createAnimAndExecute( + view: View, + toX: Int, + toY: Int, + cX: Int, + cY: Int, + rootView: View, + listener: AnimatorListenerAdapter + ) { + val wh = ViewUtils.getWidthHeight(rootView) + // 结尾偏移值 + val endOffsetX = ArrayUtils.get(wh, 0) / 2 + val endOffsetY = -ArrayUtils.get(wh, 1) / 3 + // 中间点偏移值 + val controlOffsetX = ResourceUtils.getDimensionInt(R.dimen.dp_50) + val controlOffsetY = ResourceUtils.getDimensionInt(R.dimen.dp_200) + // 开始、结束位置 + val startPosition = Point(cX, cY) + val endPosition = Point(toX + endOffsetX, toY + endOffsetY) + // 中间控制点 + val pointX = (startPosition.x + endPosition.x) / 2 - controlOffsetX + val pointY = startPosition.y - controlOffsetY + val controlPoint = Point(pointX, pointY) + // 开始执行属性动画 + val valueAnimator = ValueAnimator.ofObject( + BezierCurve2(controlPoint), startPosition, endPosition + ) + valueAnimator.duration = 5000 + valueAnimator.start() + valueAnimator.addUpdateListener { valueAnimator -> + val point = valueAnimator.animatedValue as Point + view.x = point.x.toFloat() + view.y = point.y.toFloat() + } + // 添加动画结束监听 + valueAnimator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + super.onAnimationEnd(animation) + ViewUtils.removeSelfFromParent(view) + // 触发事件 + listener.onAnimationEnd(animation) + } + }) + } + + /** + * 贝塞尔曲线 ( 二阶抛物线 ) + * controlPoint 是中间的转折点 + * startValue 是起始的位置 + * endValue 是结束的位置 + */ + private class BezierCurve2(private val controlPoint: Point) : TypeEvaluator { + override fun evaluate( + t: Float, + startValue: Point, + endValue: Point + ): Point { + val x = + ((1 - t) * (1 - t) * startValue.x + 2 * t * (1 - t) * controlPoint.x + t * t * endValue.x).toInt() + val y = + ((1 - t) * (1 - t) * startValue.y + 2 * t * (1 - t) * controlPoint.y + t * t * endValue.y).toInt() + return Point(x, y) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/status_bar/StatusBarActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/status_bar/StatusBarActivity.kt new file mode 100644 index 0000000000..9e09d2348b --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/status_bar/StatusBarActivity.kt @@ -0,0 +1,47 @@ +package afkt.project.feature.ui_effect.status_bar + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityStatusBarBinding +import afkt.project.model.item.RouterPath +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import com.alibaba.android.arouter.facade.annotation.Route +import dev.utils.app.BarUtils +import dev.utils.app.ResourceUtils + +/** + * detail: 点击 显示/隐藏 ( 状态栏 ) + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.StatusBarActivity_PATH) +class StatusBarActivity : BaseActivity() { + + // 判断是否显示 + private var display = true + + override fun baseLayoutId(): Int = R.layout.activity_status_bar + + override fun initValue() { + super.initValue() + + // 想要实现点击, 显示状态栏图标, 再次点击切换不显示, 并且整体不会上下移动 + // 需要先设置 Activity Theme => android:Theme.Light.NoTitleBar + // 第二就是 Activity 最外层布局 view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) + + // 设置状态栏 View 高度 + val layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, BarUtils.getStatusBarHeight() + ) + val statusView = View(this) + statusView.setBackgroundColor(ResourceUtils.getColor(R.color.colorPrimary)) + statusView.layoutParams = layoutParams + contentAssist.rootLinear?.addView(statusView, 0) + // 设置全屏显示, 但是会被状态栏覆盖 + contentAssist.rootLinear?.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + binding.vidToggleBtn.setOnClickListener { // 设置是否显示 + BarUtils.setStatusBarVisibility(mActivity, !display.also { display = !it }) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_calc/TextCalcActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_calc/TextCalcActivity.kt new file mode 100644 index 0000000000..be5200b5d9 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_calc/TextCalcActivity.kt @@ -0,0 +1,67 @@ +package afkt.project.feature.ui_effect.text_calc + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityTextCalcBinding +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.view.View +import com.alibaba.android.arouter.facade.annotation.Route +import dev.base.widget.BaseTextView +import dev.utils.app.TextViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.toast.ToastTintUtils +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +/** + * detail: 计算字体宽度、高度 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.TextCalcActivity_PATH) +class TextCalcActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_text_calc + + override fun initValue() { + super.initValue() + for (i in 0..14) { + val text = ChineseUtils.randomWord(RandomUtils.getRandom(100)) + + RandomUtils.getRandomLetters(RandomUtils.getRandom(20)) + + val randomText = RandomUtils.getRandom(text.toCharArray(), text.length) + val view = QuickHelper.get(BaseTextView(this)) + .setPadding(30) + .setMarginTop(40) + .setMarginBottom(20) + .setTextColors(Color.BLACK) + .setTextSizeBySp(RandomUtils.getRandom(13, 20).toFloat()) + .setBold(RandomUtils.nextBoolean()) + .setText(randomText).setOnClick { v -> + val textView = v as BaseTextView + val text = textView.text.toString() + val builder = StringBuilder() + builder.append("字体总数: ").append(text.length) + builder.append("\n字体高度: ").append(TextViewUtils.getTextHeight(textView)) + builder.append("\n偏移高度: ") + .append(TextViewUtils.getTextTopOffsetHeight(textView)) + builder.append("\n字体宽度: ").append(TextViewUtils.getTextWidth(textView)) + builder.append("\n字体大小: ").append(textView.textSize) + builder.append("\n计算字体大小: ").append( + TextViewUtils.reckonTextSizeByHeight( + TextViewUtils.getTextHeight(textView) + ) + ) + builder.append("\n计算行数: ").append( + TextViewUtils.calcTextLine( + textView, + textView.measuredWidth.toFloat() + ) + ) + val content = builder.toString() + ToastTintUtils.normal(content) + }.getView() + binding.vidLl.addView(view) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_view/TextViewActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_view/TextViewActivity.kt new file mode 100644 index 0000000000..b6183f2e5f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/text_view/TextViewActivity.kt @@ -0,0 +1,19 @@ +package afkt.project.feature.ui_effect.text_view + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.model.item.RouterPath +import androidx.viewbinding.ViewBinding +import com.alibaba.android.arouter.facade.annotation.Route + +/** + * detail: 两个 TextView 显示效果 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.TextViewActivity_PATH) +class TextViewActivity : BaseActivity() { + + override fun isViewBinding(): Boolean = false + + override fun baseLayoutId(): Int = R.layout.activity_textview +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/toast_tint/ToastTintActivity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/toast_tint/ToastTintActivity.kt new file mode 100644 index 0000000000..3eb4a2bbaf --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/toast_tint/ToastTintActivity.kt @@ -0,0 +1,114 @@ +package afkt.project.feature.ui_effect.toast_tint + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.BaseViewRecyclerviewBinding +import afkt.project.feature.ButtonAdapter +import afkt.project.model.item.ButtonList +import afkt.project.model.item.ButtonValue +import afkt.project.model.item.RouterPath +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.text.TextUtils.TruncateAt +import com.alibaba.android.arouter.facade.annotation.Route +import dev.callback.DevItemClickCallback +import dev.utils.app.ResourceUtils +import dev.utils.app.toast.ToastTintUtils +import utils_use.toast.ToastTintUse + +/** + * detail: ToastTint ( 着色美化 Toast ) + * @author Ttt + * [ToastTintUse] + */ +@Route(path = RouterPath.UI_EFFECT.ToastTintActivity_PATH) +class ToastTintActivity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.base_view_recyclerview + + override fun initValue() { + super.initValue() + + // 初始化布局管理器、适配器 + ButtonAdapter(ButtonList.toastButtonValues) + .setItemCallback(object : DevItemClickCallback() { + override fun onItemClick( + buttonValue: ButtonValue, + param: Int + ) { + when (buttonValue.type) { + ButtonValue.BTN_TOAST_TINT_SUCCESS -> ToastTintUtils.success("Success Style Toast") + ButtonValue.BTN_TOAST_TINT_ERROR -> ToastTintUtils.error("Error Style Toast") + ButtonValue.BTN_TOAST_TINT_INFO -> ToastTintUtils.info("Info Style Toast") + ButtonValue.BTN_TOAST_TINT_NORMAL -> ToastTintUtils.normal("Normal Style Toast") + ButtonValue.BTN_TOAST_TINT_WARNING -> ToastTintUtils.warning("Warning Style Toast") + ButtonValue.BTN_TOAST_TINT_CUSTOM_STYLE -> ToastTintUtils.custom( + TempStyle(), "Custom Style Toast", + ResourceUtils.getDrawable(R.mipmap.icon_launcher_round) + ) + else -> ToastTintUtils.warning("未处理 ${buttonValue.text} 事件") + } + } + }).bindAdapter(binding.vidRv) + } + + /** + * 自定义实现样式 + * [ToastTintUtils.SuccessStyle] + * [ToastTintUtils.ErrorStyle] + * [ToastTintUtils.InfoStyle] + * [ToastTintUtils.WarningStyle] + * [ToastTintUtils.NormalStyle] + * [ToastTintUtils.DefaultStyle] + */ + private class TempStyle : ToastTintUtils.Style { + + /** + * 文本颜色 + */ + override fun getTextColor(): Int = Color.WHITE + + /** + * 字体大小 + */ + override fun getTextSize(): Float = 16F + + /** + * 背景着色颜色 + */ + override fun getBackgroundTintColor(): Int = 0 + + /** + * 背景图片 + */ + override fun getBackground(): Drawable? = null + + /** + * 最大行数 + */ + override fun getMaxLines(): Int = 0 + + /** + * Ellipsize 效果 + */ + override fun getEllipsize(): TruncateAt? = null + + /** + * 字体样式 + * return Typeface.create("sans-serif-condensed", Typeface.NORMAL) + */ + override fun getTypeface(): Typeface? = null + + /** + * 获取图标着色颜色 + */ + override fun getTintIconColor(): Int = Color.WHITE + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return `true` yes, `false` no + */ + override fun isTintIcon(): Boolean = false + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/PagerFragment.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/PagerFragment.kt new file mode 100644 index 0000000000..2373791d1c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/PagerFragment.kt @@ -0,0 +1,50 @@ +package afkt.project.feature.ui_effect.view_pager2 + +import afkt.project.R +import afkt.project.base.app.BaseFragment +import afkt.project.databinding.FragmentPagerBinding +import afkt.project.ui.widget.VerticalScrollView +import android.graphics.Bitmap +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import dev.utils.DevFinal +import dev.utils.app.ResourceUtils +import dev.utils.app.image.ImageUtils +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +fun newPagerFragment(position: Int) = PagerFragment().apply { + arguments = bundleOf(DevFinal.STR.POSITION to position) +} + +class PagerFragment : BaseFragment() { + + private var position: Int = 0 + + override fun baseContentId(): Int = R.layout.fragment_pager + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + position = arguments?.getInt(DevFinal.STR.POSITION) ?: 1 + + /** + * 竖屏需要使用 [VerticalScrollView] + */ + val titleText = "${position}.${ChineseUtils.randomWord(5)}" + + binding.vidPrefaceTv.text = ChineseUtils.randomWord(RandomUtils.getRandom(30, 100)) + binding.vidTitleTv.text = titleText + binding.vidContentTv.text = ChineseUtils.randomWord(400) + binding.vidIv.setImageBitmap(getBitmap()) + } + + private fun getBitmap(): Bitmap { + val rawId = ResourceUtils.getRawId("wallpaper_${position}") + val stream = ResourceUtils.openRawResource(rawId) + return ImageUtils.decodeStream(stream) + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/ViewPager2Activity.kt b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/ViewPager2Activity.kt new file mode 100644 index 0000000000..7101b14330 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/feature/ui_effect/view_pager2/ViewPager2Activity.kt @@ -0,0 +1,98 @@ +package afkt.project.feature.ui_effect.view_pager2 + +import afkt.project.R +import afkt.project.base.app.BaseActivity +import afkt.project.databinding.ActivityViewpager2Binding +import afkt.project.model.item.RouterPath +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.alibaba.android.arouter.facade.annotation.Route +import com.google.android.material.tabs.TabLayoutMediator +import dev.utils.app.ResourceUtils + +/** + * detail: ViewPager2 + * @author Ttt + */ +@Route(path = RouterPath.UI_EFFECT.ViewPager2Activity_PATH) +class ViewPager2Activity : BaseActivity() { + + override fun baseLayoutId(): Int = R.layout.activity_viewpager2 + + override fun initValue() { + super.initValue() + + val list = mutableListOf() + list.add(newPagerFragment(1)) + list.add(newPagerFragment(2)) + list.add(newPagerFragment(3)) + list.add(newPagerFragment(4)) + + binding.vidVp.adapter = MyPagerAdapter(this, list) + } + + override fun initListener() { + super.initListener() + binding.vidTl.setTabTextColors( + ResourceUtils.getColor(R.color.black), + ResourceUtils.getColor(R.color.white) + ) + + // TabLayout 与 ViewPager2 联动 + TabLayoutMediator( + binding.vidTl, binding.vidVp + ) { tab, position -> + tab.text = "Pager-${position + 1}" + }.attach() + } + + class MyPagerAdapter( + val activity: FragmentActivity, + val list: MutableList + ) : FragmentStateAdapter(activity) { + + override fun getItemCount(): Int = list.size + + override fun createFragment(position: Int): Fragment = list[position] + } + + // ======== + // = Menu = + // ======== + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_pager, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.vid_menu_ltr -> { + binding.vidTl.layoutDirection = View.LAYOUT_DIRECTION_LTR + binding.vidVp.layoutDirection = View.LAYOUT_DIRECTION_LTR + } + R.id.vid_menu_rtl -> { + binding.vidTl.layoutDirection = View.LAYOUT_DIRECTION_RTL + binding.vidVp.layoutDirection = View.LAYOUT_DIRECTION_RTL + } + R.id.vid_menu_horizontal -> { + binding.vidVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL + } + R.id.vid_menu_vertical -> { + binding.vidVp.orientation = ViewPager2.ORIENTATION_VERTICAL + } + R.id.vid_menu_reset -> { + binding.vidTl.layoutDirection = View.LAYOUT_DIRECTION_LTR + binding.vidVp.layoutDirection = View.LAYOUT_DIRECTION_LTR + binding.vidVp.orientation = ViewPager2.ORIENTATION_HORIZONTAL + } + } + binding.vidVp.adapter?.notifyDataSetChanged() + return true + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Article.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Article.kt new file mode 100644 index 0000000000..dd6e42d837 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Article.kt @@ -0,0 +1,27 @@ +package afkt.project.model.bean + +/** + * detail: 文章信息实体类 + * @author Ttt + */ +class ArticleBean { + @JvmField + var data: DataBean? = null + + class DataBean { + var size = 0 + + @JvmField + var datas: List? = null + + class ListBean { + var id = 0 + var link: String? = null + var niceDate: String? = null + var niceShareDate: String? = null + var author: String? = null + var title: String? = null + var type = 0 + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Beans.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Beans.kt new file mode 100644 index 0000000000..c8fa831adb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Beans.kt @@ -0,0 +1,105 @@ +package afkt.project.model.bean + +import dev.utils.DevFinal +import dev.utils.common.ChineseUtils +import dev.utils.common.DateUtils +import dev.utils.common.RandomUtils + +/** + * detail: 适配器实体类 + * @author Ttt + */ +open class AdapterBean( + // 标题 + val title: String, + // 内容 + val content: String, +) { + + companion object { + + /** + * 创建适配器实体类 + * @param position 索引 + * @return [AdapterBean] + */ + private fun newAdapterBean(position: Int): AdapterBean { + val number = RandomUtils.getRandom(10, 100) + (10 + position / 3) * 3 + val content = "${position + 1}." + ChineseUtils.randomWord( + RandomUtils.getRandom(number) + ) + return AdapterBean( + title = ChineseUtils.randomWord(2), + content = content + ) + } + + /** + * 获取适配器实体类集合 + * @param count 集合总数 + * @return 适配器实体类集合 + */ + fun newAdapterBeanList(count: Int): List { + val lists = mutableListOf() + for (i in 0 until count) { + lists.add(newAdapterBean(i)) + } + return lists + } + } +} + +/** + * detail: Item 实体类 + * @author Ttt + */ +class ItemBean( + // 标题 + val title: String, + // 副标题 + val subtitle: String, + // 内容 + val content: String, + // 图片路径 + var imageUrl: String?, + // 时间 + val timeFormat: String +) { + + companion object { + + /** + * 创建 Item 实体类 ( 正方形 ) + * @return [ItemBean] + */ + fun newItemBean(): ItemBean { + val time = System.currentTimeMillis() - RandomUtils.nextLongRange( + DevFinal.TIME.MINUTE_MS, + DevFinal.TIME.DAY_MS + ) + return ItemBean( + title = ChineseUtils.randomWord(RandomUtils.getRandom(5, 10)), + subtitle = ChineseUtils.randomWord(RandomUtils.getRandom(5, 10)), + content = ChineseUtils.randomWord(RandomUtils.getRandom(30, 60)), + imageUrl = String.format( + "https://picsum.photos/id/%s/500", + RandomUtils.getRandom(1, 50) + ), + timeFormat = DateUtils.formatTime(time, DevFinal.TIME.yyyyMMdd_POINT) + ) + } + + /** + * 创建 Item 实体类 ( 长方形 ) + * @return [ItemBean] + */ + fun newItemBeanPager(): ItemBean { + return newItemBean().apply { + imageUrl = String.format( + "https://picsum.photos/id/%s/1080/1920", + RandomUtils.getRandom(1, 50) + ) + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Items.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Items.kt new file mode 100644 index 0000000000..64676c1b9d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/bean/Items.kt @@ -0,0 +1,61 @@ +package afkt.project.model.bean + +import dev.utils.common.ChineseUtils +import dev.utils.common.RandomUtils + +/** + * detail: 评价 Item + * @author Ttt + */ +class EvaluateItem { + + // 商品评价等级 + @JvmField + var evaluateLevel: Float + + // 商品评价内容 + @JvmField + var evaluateContent: String + + // 存储对象 + var commodityItem: CommodityItem + + init { + val text = + ChineseUtils.randomWord(RandomUtils.getRandom(50)) + RandomUtils.getRandomLetters( + RandomUtils.getRandom(10) + ) + val randomText = RandomUtils.getRandom(text.toCharArray(), text.length) + evaluateContent = randomText + evaluateLevel = RandomUtils.getRandom(6).toFloat() + commodityItem = CommodityItem.newCommodityItem() + } +} + +/** + * detail: 商品 Item + * @author Ttt + */ +class CommodityItem( + // 商品名 + val commodityName: String? = null, + // 商品图片 + val commodityPicture: String? = null, + // 商品价格 + val commodityPrice: Double +) { + + companion object { + /** + * 创建商品评价实体类 + * @return [CommodityItem] + */ + fun newCommodityItem(): CommodityItem { + return CommodityItem( + commodityName = ChineseUtils.randomWord(RandomUtils.getRandom(5, 40)), + commodityPicture = "https://picsum.photos/20${RandomUtils.getRandom(0, 10)}", + commodityPrice = RandomUtils.nextDoubleRange(15.1, 79.3) + ) + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonList.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonList.kt new file mode 100644 index 0000000000..719d20a36d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonList.kt @@ -0,0 +1,973 @@ +package afkt.project.model.item + +/** + * detail: Button List + * @author Ttt + */ +object ButtonList { + + // ========== + // = 获取集合 = + // ========== + + /** + * 获取 Main Button Value 集合 + * @return [List] + */ + val mainButtonValues = mutableListOf( + ButtonValue.MODULE_FRAMEWORK.buttonOf( + "Framework 架构", + RouterPath.ModuleActivity_PATH + ), + ButtonValue.MODULE_LIB.buttonOf( + "Lib 框架", + RouterPath.ModuleActivity_PATH + ), + ButtonValue.MODULE_UI.buttonOf( + "UI 效果", + RouterPath.ModuleActivity_PATH + ), + ButtonValue.MODULE_OTHER.buttonOf( + "其他功能", + RouterPath.ModuleActivity_PATH + ), + ButtonValue.MODULE_DEV_WIDGET.buttonOf( + "DevWidget UI 库", + RouterPath.ModuleActivity_PATH + ), + ButtonValue.MODULE_DEV_ENVIRONMENT.buttonOf( + "DevEnvironment 环境配置切换库", + RouterPath.DEV_LIBS.DevEnvironmentLibActivity_PATH + ), + ButtonValue.MODULE_DEV_ASSIST_ENGINE.buttonOf( + "DevAssist Engine 实现", + RouterPath.DEV_LIBS.DevAssistEngineActivity_PATH + ), + ButtonValue.MODULE_DEV_HTTP_CAPTURE.buttonOf( + "DevHttpCapture OkHttp 抓包工具库", + RouterPath.DEV_LIBS.DevHttpCaptureActivity_PATH + ), + ButtonValue.MODULE_DEV_SKU.buttonOf( + "DevSKU 商品 SKU 组合封装实现", + RouterPath.DEV_LIBS.DevSKUActivity_PATH + ) + ) + + /** + * 获取 Module 功能 Button Value 集合 + * @param type Module Type + * @return [List] + */ + fun getModuleButtonValues(type: Int): List { + when (type) { + ButtonValue.MODULE_FRAMEWORK -> return moduleFrameworkButtonValues + ButtonValue.MODULE_LIB -> return moduleLibButtonValues + ButtonValue.MODULE_UI -> return moduleUIButtonValues + ButtonValue.MODULE_OTHER -> return moduleOtherButtonValues + ButtonValue.MODULE_DEV_WIDGET -> return moduleDevWidgetButtonValues + } + return emptyList() + } + + /** + * 获取 Button Item 集合 + * @param type button Type + * @return [List] + */ + fun getButtonValues(type: Int): List { + when (type) { + ButtonValue.BTN_VIEW_ASSIST -> return viewAssistButtonValues + ButtonValue.BTN_LINEAR_ITEM_DECORATION -> return linearItemDecorationButtonValues + ButtonValue.BTN_GRID_ITEM_DECORATION -> return gridItemDecorationButtonValues + } + return emptyList() + } + + // ============= + // = Framework = + // ============= + + /** + * 获取 Framework Module Button Value 集合 + * @return [List] + */ + private val moduleFrameworkButtonValues = mutableListOf( + ButtonValue.BTN_MVP.buttonOf( + "MVP", + RouterPath.FRAMEWORK.ArticleMVPActivity_PATH + ), + ButtonValue.BTN_MVVM.buttonOf( + "MVVM", + RouterPath.FRAMEWORK.ArticleMVVMActivity_PATH + ) + ) + + // ======= + // = Lib = + // ======= + + /** + * 获取 Lib Module Button Value 集合 + * @return [List] + */ + private val moduleLibButtonValues = mutableListOf( + ButtonValue.BTN_GREEN_DAO.buttonOf( + "GreenDAO", + RouterPath.EMPTY + ), + ButtonValue.BTN_ROOM.buttonOf( + "Room", + RouterPath.EMPTY + ), + ButtonValue.BTN_DATA_STORE.buttonOf( + "DataStore", + RouterPath.LIB_FRAME.DataStoreActivity_PATH + ), + ButtonValue.BTN_EVENT_BUS.buttonOf( + "EventBusUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_GLIDE.buttonOf( + "GlideUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_IMAGE_LOADER.buttonOf( + "ImageLoaderUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_GSON.buttonOf( + "GsonUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_FASTJSON.buttonOf( + "FastjsonUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_ZXING.buttonOf( + "ZXingUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_PICTURE_SELECTOR.buttonOf( + "PictureSelectorUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_OKGO.buttonOf( + "OkGoUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_LUBAN.buttonOf( + "LubanUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_MMKV.buttonOf( + "MMKVUtils", + RouterPath.EMPTY + ), + ButtonValue.BTN_WORK_MANAGER.buttonOf( + "WorkManagerUtils", + RouterPath.EMPTY + ) + ) + + // ====== + // = UI = + // ====== + + /** + * 获取 UI Module Button Value 集合 + * @return [List] + */ + private val moduleUIButtonValues = mutableListOf( + ButtonValue.BTN_TOAST_TINT.buttonOf( + "ToastTint ( 着色美化 Toast )", + RouterPath.UI_EFFECT.ToastTintActivity_PATH + ), + ButtonValue.BTN_UI_EFFECT.buttonOf( + "常见 UI、GradientDrawable 效果等", + RouterPath.UI_EFFECT.UIEffectActivity_PATH + ), + ButtonValue.BTN_STATUS_BAR.buttonOf( + "点击 显示/隐藏 ( 状态栏 )", + RouterPath.UI_EFFECT.StatusBarActivity_PATH + ), + ButtonValue.BTN_TEXT_CALC.buttonOf( + "计算字体宽度、高度", + RouterPath.UI_EFFECT.TextCalcActivity_PATH + ), + ButtonValue.BTN_ADAPTER_EDITS.buttonOf( + "Adapter Item EditText 输入监听", + RouterPath.UI_EFFECT.AdapterEditsActivity_PATH + ), + ButtonValue.BTN_MULTI_SELECT.buttonOf( + "多选辅助类 MultiSelectAssist", + RouterPath.UI_EFFECT.MultiSelectActivity_PATH + ), + ButtonValue.BTN_GPU_ACV.buttonOf( + "GPU ACV 文件滤镜效果", + RouterPath.UI_EFFECT.GPUFilterACVActivity_PATH + ), + ButtonValue.BTN_GPU_FILTER.buttonOf( + "GPU 滤镜效果", + RouterPath.UI_EFFECT.GPUFilterActivity_PATH + ), + ButtonValue.BTN_QRCODE_CREATE.buttonOf( + "创建二维码", + RouterPath.UI_EFFECT.QRCodeCreateActivity_PATH + ), + ButtonValue.BTN_QRCODE_IMAGE.buttonOf( + "二维码图片解析", + RouterPath.UI_EFFECT.QRCodeImageActivity_PATH + ), + ButtonValue.BTN_QRCODE_SCAN.buttonOf( + "二维码扫描解析", + RouterPath.UI_EFFECT.QRCodeScanActivity_PATH + ), + ButtonValue.BTN_CAPTURE_PICTURE.buttonOf( + "CapturePictureUtils 截图工具类", + RouterPath.UI_EFFECT.CapturePictureActivity_PATH + ), + ButtonValue.BTN_TEXTVIEW.buttonOf( + "两个 TextView 显示效果", + RouterPath.UI_EFFECT.TextViewActivity_PATH + ), + ButtonValue.BTN_ITEM_STICKY.buttonOf( + "RecyclerView 吸附效果", + RouterPath.UI_EFFECT.ItemStickyActivity_PATH + ), + ButtonValue.BTN_RECY_ITEM_SLIDE.buttonOf( + "RecyclerView 滑动删除、上下滑动", + RouterPath.UI_EFFECT.RecyItemSlideActivity_PATH + ), + ButtonValue.BTN_RECY_LINEAR_SNAP.buttonOf( + "LinearSnapHelper - RecyclerView", + RouterPath.UI_EFFECT.LinearSnapActivity_PATH + ), + ButtonValue.BTN_RECY_LINEAR_SNAP_MAX.buttonOf( + "LinearSnapHelper - 无限滑动", + RouterPath.UI_EFFECT.LinearSnapMAXActivity_PATH + ), + ButtonValue.BTN_RECY_PAGER_SNAP.buttonOf( + "PagerSnapHelper - RecyclerView", + RouterPath.UI_EFFECT.PagerSnapActivity_PATH + ), + ButtonValue.BTN_RECY_PAGER_SNAP_MAX.buttonOf( + "PagerSnapHelper - 无限滑动", + RouterPath.UI_EFFECT.PagerSnapMAXActivity_PATH + ), + ButtonValue.BTN_SHAPEABLE_IMAGE_VIEW.buttonOf( + "Material ShapeableImageView", + RouterPath.UI_EFFECT.ShapeableImageViewActivity_PATH + ), + ButtonValue.BTN_BOTTOM_SHEET.buttonOf( + "Material BottomSheet", + RouterPath.UI_EFFECT.BottomSheetActivity_PATH + ), + ButtonValue.BTN_BOTTOM_SHEET_DIALOG.buttonOf( + "Material BottomSheetDialog", + RouterPath.UI_EFFECT.BottomSheetDialogActivity_PATH + ), + ButtonValue.BTN_PALETTE.buttonOf( + "Palette 调色板", + RouterPath.UI_EFFECT.PaletteActivity_PATH + ), + ButtonValue.BTN_FLEXBOX_LAYOUTMANAGER.buttonOf( + "Flexbox LayoutManager", + RouterPath.UI_EFFECT.FlexboxLayoutManagerActivity_PATH + ), + ButtonValue.BTN_CHIP.buttonOf( + "Material Chip、ChipGroups、ChipDrawable", + RouterPath.UI_EFFECT.ChipActivity_PATH + ), + ButtonValue.BTN_VIEWPAGER2.buttonOf( + "ViewPager2", + RouterPath.UI_EFFECT.ViewPager2Activity_PATH + ), + ButtonValue.BTN_RECYCLERVIEW_CONCATADAPTER.buttonOf( + "RecyclerView - ConcatAdapter", + RouterPath.UI_EFFECT.RecyConcatAdapterActivity_PATH + ), + ButtonValue.BTN_RECYCLERVIEW_MULTITYPE_ADAPTER.buttonOf( + "RecyclerView MultiType Adapter", + RouterPath.UI_EFFECT.RecyMultiTypeAdapterActivity_PATH + ), + ButtonValue.BTN_SHOP_CARD_ADD_ANIM.buttonOf( + "购物车加入动画", + RouterPath.UI_EFFECT.ShopCartAddAnimActivity_PATH + ) + ) + + /** + * 获取 Toast Button Value 集合 + * @return [List] + */ + val toastButtonValues = mutableListOf( + ButtonValue.BTN_TOAST_TINT_SUCCESS.buttonOf( + "Toast Success", + RouterPath.EMPTY + ), + ButtonValue.BTN_TOAST_TINT_ERROR.buttonOf( + "Toast Error", + RouterPath.EMPTY + ), + ButtonValue.BTN_TOAST_TINT_INFO.buttonOf( + "Toast Info", + RouterPath.EMPTY + ), + ButtonValue.BTN_TOAST_TINT_NORMAL.buttonOf( + "Toast Normal", + RouterPath.EMPTY + ), + ButtonValue.BTN_TOAST_TINT_WARNING.buttonOf( + "Toast Warning", + RouterPath.EMPTY + ), + ButtonValue.BTN_TOAST_TINT_CUSTOM_STYLE.buttonOf( + "Toast Custom Style", + RouterPath.EMPTY + ) + ) + + // ========== + // = 其他功能 = + // ========== + + /** + * 获取 Other Module Button Value 集合 + * @return [List] + */ + private val moduleOtherButtonValues = mutableListOf( + ButtonValue.BTN_LISTENER.buttonOf( + "事件 / 广播监听 ( 网络状态、屏幕旋转等 )", + RouterPath.OTHER_FUNCTION.ListenerActivity_PATH + ), + ButtonValue.BTN_NOTIFICATION_SERVICE.buttonOf( + "通知栏监听服务 ( NotificationService )", + RouterPath.OTHER_FUNCTION.NotificationServiceActivity_PATH + ), + ButtonValue.BTN_ACCESSIBILITY_SERVICE.buttonOf( + "无障碍监听服务 ( AccessibilityListenerService )", + RouterPath.OTHER_FUNCTION.AccessibilityListenerServiceActivity_PATH + ), + ButtonValue.BTN_WIFI.buttonOf( + "Wifi 相关 ( 热点 )", + RouterPath.OTHER_FUNCTION.WifiActivity_PATH + ), + ButtonValue.BTN_FUNCTION.buttonOf( + "铃声、震动、通知栏等功能", + RouterPath.OTHER_FUNCTION.FunctionActivity_PATH + ), + ButtonValue.BTN_TIMER.buttonOf( + "TimerManager 定时器工具类", + RouterPath.OTHER_FUNCTION.TimerActivity_PATH + ), + ButtonValue.BTN_CACHE.buttonOf( + "DevCache 缓存工具类", + RouterPath.OTHER_FUNCTION.CacheActivity_PATH + ), + ButtonValue.BTN_LOGGER.buttonOf( + "DevLogger 日志工具类", + RouterPath.OTHER_FUNCTION.LoggerActivity_PATH + ), + ButtonValue.BTN_FILE_RECORD.buttonOf( + "日志、异常文件记录保存", + RouterPath.OTHER_FUNCTION.FileRecordActivity_PATH + ), + ButtonValue.BTN_CRASH.buttonOf( + "奔溃日志捕获", + RouterPath.OTHER_FUNCTION.CrashCatchActivity_PATH + ), + ButtonValue.BTN_PATH.buttonOf( + "路径信息", + RouterPath.OTHER_FUNCTION.PathActivity_PATH + ), + ButtonValue.BTN_WEBVIEW.buttonOf( + "WebView 辅助类", + RouterPath.OTHER_FUNCTION.WebViewActivity_PATH + ), + ButtonValue.BTN_ACTIVITY_RESULT_API.buttonOf( + "Activity Result API", + RouterPath.OTHER_FUNCTION.ActivityResultAPIActivity_PATH + ), + ButtonValue.BTN_ACTIVITY_RESULT_CALLBACK.buttonOf( + "startActivityForResult Callback", + RouterPath.OTHER_FUNCTION.ActivityResultCallbackActivity_PATH + ), + ButtonValue.BTN_ADD_CONTACT.buttonOf( + "添加联系人", + RouterPath.OTHER_FUNCTION.AddContactActivity_PATH + ), + ButtonValue.BTN_WALLPAPER.buttonOf( + "手机壁纸", + RouterPath.OTHER_FUNCTION.WallpaperActivity_PATH + ), + ButtonValue.BTN_FLOATING_WINDOW_MANAGER.buttonOf( + "悬浮窗管理辅助类 ( 需权限 )", + RouterPath.OTHER_FUNCTION.FloatingWindowManagerActivity_PATH + ), + ButtonValue.BTN_FLOATING_WINDOW_MANAGER2.buttonOf( + "悬浮窗管理辅助类 ( 无需权限依赖 Activity )", + RouterPath.OTHER_FUNCTION.FloatingWindowManager2Activity_PATH + ) + ) + + /** + * 获取 Listener Button Value 集合 + * @return [List] + */ + @JvmStatic + val listenerButtonValues = mutableListOf( + ButtonValue.BTN_WIFI_LISTENER.buttonOf( + "Wifi 监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_NETWORK_LISTENER.buttonOf( + "网络监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_PHONE_LISTENER.buttonOf( + "电话监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_SMS_LISTENER.buttonOf( + "短信监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIME_LISTENER.buttonOf( + "时区、时间监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_SCREEN_LISTENER.buttonOf( + "屏幕监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_ROTA_LISTENER.buttonOf( + "屏幕旋转监听 ( 重力传感器 )", + RouterPath.EMPTY + ), + ButtonValue.BTN_ROTA2_LISTENER.buttonOf( + "屏幕旋转监听 ( OrientationEventListener )", + RouterPath.EMPTY + ), + ButtonValue.BTN_BATTERY_LISTENER.buttonOf( + "电量监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_APP_STATE_LISTENER.buttonOf( + "应用状态监听", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Notification Service Button Value 集合 + * @return [List] + */ + @JvmStatic + val notificationServiceButtonValues = mutableListOf( + ButtonValue.BTN_NOTIFICATION_SERVICE_CHECK.buttonOf( + "检查是否开启", + RouterPath.EMPTY + ), + ButtonValue.BTN_NOTIFICATION_SERVICE_REGISTER.buttonOf( + "开始监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_NOTIFICATION_SERVICE_UNREGISTER.buttonOf( + "注销监听", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Accessibility Listener Service Button Value 集合 + * @return [List] + */ + @JvmStatic + val accessibilityListenerServiceButtonValues = mutableListOf( + ButtonValue.BTN_ACCESSIBILITY_SERVICE_CHECK.buttonOf( + "检查是否开启", + RouterPath.EMPTY + ), + ButtonValue.BTN_ACCESSIBILITY_SERVICE_REGISTER.buttonOf( + "开始监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_ACCESSIBILITY_SERVICE_UNREGISTER.buttonOf( + "注销监听", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Wifi Button Value 集合 + * @return [List] + */ + @JvmStatic + val wifiButtonValues = mutableListOf( + ButtonValue.BTN_WIFI_OPEN.buttonOf( + "打开 Wifi", + RouterPath.EMPTY + ), + ButtonValue.BTN_WIFI_CLOSE.buttonOf( + "关闭 Wifi", + RouterPath.EMPTY + ), + ButtonValue.BTN_WIFI_HOT_OPEN.buttonOf( + "打开 Wifi 热点", + RouterPath.EMPTY + ), + ButtonValue.BTN_WIFI_HOT_CLOSE.buttonOf( + "关闭 Wifi 热点", + RouterPath.EMPTY + ), + ButtonValue.BTN_WIFI_LISTENER_REGISTER.buttonOf( + "注册 Wifi 监听", + RouterPath.EMPTY + ), + ButtonValue.BTN_WIFI_LISTENER_UNREGISTER.buttonOf( + "注销 Wifi 监听", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Function Button Value 集合 + * @return [List] + */ + @JvmStatic + val functionButtonValues = mutableListOf( + ButtonValue.BTN_FUNCTION_VIBRATE.buttonOf( + "震动", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_BEEP.buttonOf( + "铃声 - 播放一小段音频", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_NOTIFICATION_CHECK.buttonOf( + "是否存在通知权限", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_NOTIFICATION_OPEN.buttonOf( + "开启通知权限", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_NOTIFICATION.buttonOf( + "通知消息", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_NOTIFICATION_REMOVE.buttonOf( + "移除消息", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_HOME.buttonOf( + "回到桌面", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_FLASHLIGHT_OPEN.buttonOf( + "打开手电筒", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_FLASHLIGHT_CLOSE.buttonOf( + "关闭手电筒", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_SHORTCUT_CHECK.buttonOf( + "是否创建桌面快捷方式", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_SHORTCUT_CREATE.buttonOf( + "创建桌面快捷方式", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_SHORTCUT_DELETE.buttonOf( + "删除桌面快捷方式", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_MEMORY_PRINT.buttonOf( + "打印内存信息", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_DEVICE_PRINT.buttonOf( + "打印设备信息", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_APP_DETAILS_SETTINGS.buttonOf( + "跳转到 APP 设置详情页面", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_GPS_SETTINGS.buttonOf( + "打开 GPS 设置界面", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_WIRELESS_SETTINGS.buttonOf( + "打开网络设置界面", + RouterPath.EMPTY + ), + ButtonValue.BTN_FUNCTION_SYS_SETTINGS.buttonOf( + "跳转到系统设置页面", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Timer Button Value 集合 + * @return [List] + */ + @JvmStatic + val timerButtonValues = mutableListOf( + ButtonValue.BTN_TIMER_START.buttonOf( + "启动定时器", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIMER_STOP.buttonOf( + "停止定时器", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIMER_RESTART.buttonOf( + "重新启动定时器", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIMER_CHECK.buttonOf( + "定时器是否启动", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIMER_GET.buttonOf( + "获取定时器", + RouterPath.EMPTY + ), + ButtonValue.BTN_TIMER_GET_NUMBER.buttonOf( + "获取运行次数", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Cache Button Value 集合 + * @return [List] + */ + @JvmStatic + val cacheButtonValues = mutableListOf( + ButtonValue.BTN_CACHE_STRING.buttonOf( + "存储字符串", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_STRING_TIME.buttonOf( + "存储有效期字符串", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_STRING_GET.buttonOf( + "获取字符串", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_BEAN.buttonOf( + "存储实体类", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_BEAN_TIME.buttonOf( + "存储有效期实体类", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_BEAN_GET.buttonOf( + "获取实体类", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_FILE.buttonOf( + "存储到指定位置", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_FILE_GET.buttonOf( + "获取指定位置缓存数据", + RouterPath.EMPTY + ), + ButtonValue.BTN_CACHE_CLEAR.buttonOf( + "清除全部数据", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Logger Button Value 集合 + * @return [List] + */ + @JvmStatic + val loggerButtonValues = mutableListOf( + ButtonValue.BTN_LOGGER_PRINT.buttonOf( + "打印日志", + RouterPath.EMPTY + ), + ButtonValue.BTN_LOGGER_TIME.buttonOf( + "打印日志耗时测试", + RouterPath.EMPTY + ) + ) + + /** + * 获取 File Record Button Value 集合 + * @return [List] + */ + @JvmStatic + val fileRecordButtonValues = mutableListOf( + ButtonValue.BTN_FILE_RECORD_UTILS.buttonOf( + "FileRecordUtils 工具类", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Crash Record Button Value 集合 + * @return [List] + */ + val crashButtonValues = mutableListOf( + ButtonValue.BTN_CRASH_CLICK_CATCH.buttonOf( + "点击崩溃捕获信息", + RouterPath.EMPTY + ) + ) + + /** + * 获取 Path Button Value 集合 + * @return [List] + */ + val pathButtonValues = mutableListOf( + ButtonValue.BTN_PATH_INTERNAL.buttonOf( + "内部存储路径", + RouterPath.EMPTY + ), + ButtonValue.BTN_PATH_APP_EXTERNAL.buttonOf( + "应用外部存储路径", + RouterPath.EMPTY + ), + ButtonValue.BTN_PATH_SDCARD.buttonOf( + "外部存储路径 ( SDCard )", + RouterPath.EMPTY + ) + ) + + /** + * 获取悬浮窗 Button Value 集合 + * @return [List] + */ + val floatingWindowButtonValues = mutableListOf( + ButtonValue.BTN_OPEN_FLOATING_WINDOW.buttonOf( + "打开悬浮窗", + RouterPath.EMPTY + ), + ButtonValue.BTN_CLOSE_FLOATING_WINDOW.buttonOf( + "关闭悬浮窗", + RouterPath.EMPTY + ) + ) + + // ================== + // = DevWidget UI 库 = + // ================== + + /** + * 获取 DevWidget Module Button Value 集合 + * @return [List] + */ + private val moduleDevWidgetButtonValues = mutableListOf( + ButtonValue.BTN_VIEW_PAGER.buttonOf( + "ViewPager 滑动监听、控制滑动", + RouterPath.DEV_WIDGET.ViewPagerActivity_PATH + ), + ButtonValue.BTN_CUSTOM_PROGRESS_BAR.buttonOf( + "自定义 ProgressBar 样式 View", + RouterPath.DEV_WIDGET.ProgressBarActivity_PATH + ), + ButtonValue.BTN_SCAN_VIEW.buttonOf( + "自定义扫描 View ( QRCode、AR )", + RouterPath.DEV_WIDGET.ScanShapeActivity_PATH + ), + ButtonValue.BTN_WRAP_VIEW.buttonOf( + "自动换行 View", + RouterPath.DEV_WIDGET.WrapActivity_PATH + ), + ButtonValue.BTN_SIGN_VIEW.buttonOf( + "签名 View", + RouterPath.DEV_WIDGET.SignActivity_PATH + ), + ButtonValue.BTN_LINE_VIEW.buttonOf( + "换行监听 View", + RouterPath.DEV_WIDGET.LineActivity_PATH + ), + ButtonValue.BTN_LIKE_VIEW.buttonOf( + "自定义点赞效果 View", + RouterPath.DEV_WIDGET.FlowLikeActivity_PATH + ), + ButtonValue.BTN_CORNER_LABEL_VIEW.buttonOf( + "自定义角标 View", + RouterPath.DEV_WIDGET.CornerLabelActivity_PATH + ), + ButtonValue.BTN_VIEW_ASSIST.buttonOf( + "View 填充辅助类", + RouterPath.ButtonItemActivity_PATH + ), + ButtonValue.BTN_FLIP_CARD_VIEW.buttonOf( + "翻转卡片 View", + RouterPath.DEV_WIDGET.FlipCardActivity_PATH + ), + ButtonValue.BTN_WAVE_VIEW.buttonOf( + "波浪 View", + RouterPath.DEV_WIDGET.WaveViewActivity_PATH + ), + ButtonValue.BTN_LINEAR_ITEM_DECORATION.buttonOf( + "Linear Color ItemDecoration", + RouterPath.ButtonItemActivity_PATH + ), + ButtonValue.BTN_GRID_ITEM_DECORATION.buttonOf( + "Grid Color ItemDecoration", + RouterPath.ButtonItemActivity_PATH + ) + ) + + /** + * 获取 ViewAssist Button Value 集合 + * @return [List] + */ + private val viewAssistButtonValues = mutableListOf( + ButtonValue.BTN_VIEW_ASSIST_RECYCLER.buttonOf( + "RecyclerView ( loading )", + RouterPath.DEV_WIDGET.ViewAssistRecyclerViewLoadActivity_PATH + ), + ButtonValue.BTN_VIEW_ASSIST_ERROR.buttonOf( + "Error ( failed )", + RouterPath.DEV_WIDGET.ViewAssistActivity_PATH + ), + ButtonValue.BTN_VIEW_ASSIST_EMPTY.buttonOf( + "Empty ( data )", + RouterPath.DEV_WIDGET.ViewAssistActivity_PATH + ), + ButtonValue.BTN_VIEW_ASSIST_CUSTOM.buttonOf( + "Custom Type", + RouterPath.DEV_WIDGET.ViewAssistActivity_PATH + ) + ) + + /** + * 获取 Linear Color ItemDecoration Button Value 集合 + * @return [List] + */ + private val linearItemDecorationButtonValues = mutableListOf( + ButtonValue.BTN_LINEAR_ITEM_VERTICAL.buttonOf( + "Linear Vertical ItemDecoration", + RouterPath.DEV_WIDGET.LinearColorItemDecorationActivity_PATH + ), + ButtonValue.BTN_LINEAR_ITEM_HORIZONTAL.buttonOf( + "Linear Horizontal ItemDecoration", + RouterPath.DEV_WIDGET.LinearColorItemDecorationActivity_PATH + ) + ) + + /** + * 获取 Grid Color ItemDecoration Button Value 集合 + * @return [List] + */ + private val gridItemDecorationButtonValues = mutableListOf( + ButtonValue.BTN_GRID_ITEM_VERTICAL.buttonOf( + "Grid Vertical ItemDecoration", + RouterPath.DEV_WIDGET.GridColorItemDecorationActivity_PATH + ), + ButtonValue.BTN_GRID_ITEM_HORIZONTAL.buttonOf( + "Grid Horizontal ItemDecoration", + RouterPath.DEV_WIDGET.GridColorItemDecorationActivity_PATH + ) + ) + + // ============================== + // = DevEnvironment 环境配置切换库 = + // ============================== + + /** + * 获取 DevEnvironment Module Button Value 集合 + * @return [List] + */ + @JvmStatic + val moduleDevEnvironmentButtonValues = mutableListOf( + ButtonValue.BTN_DEV_ENVIRONMENT.buttonOf( + "环境配置切换", + RouterPath.EMPTY + ), + ButtonValue.BTN_USE_CUSTOM.buttonOf( + "使用自定义配置", + RouterPath.EMPTY + ) + ) + + // ======================== + // = DevAssist Engine 实现 = + // ======================== + + /** + * 获取 DevAssist Engine Module Button Value 集合 + * @return [List] + */ + @JvmStatic + val moduleDevAssistEngineButtonValues = mutableListOf( + ButtonValue.BTN_ENGINE_ANALYTICS.buttonOf( + "Analytics Engine 数据统计 ( 埋点 )", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_CACHE.buttonOf( + "Cache Engine 有效期键值对缓存", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_IMAGE_COMPRESS.buttonOf( + "Image Compress Engine 图片压缩", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_IMAGE.buttonOf( + "Image Engine 图片加载、下载、转格式等", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_JSON.buttonOf( + "JSON Engine", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_KEYVALUE.buttonOf( + "KeyValue Engine 键值对存储", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_LOG.buttonOf( + "Log Engine 日志打印", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_MEDIA_SELECTOR.buttonOf( + "Media Selector Engine 多媒体资源选择", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_PERMISSION.buttonOf( + "Permission Engine 权限申请", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_PUSH.buttonOf( + "Push Engine 推送平台处理", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_SHARE.buttonOf( + "Share Engine 分享平台处理", + RouterPath.EMPTY + ), + ButtonValue.BTN_ENGINE_STORAGE.buttonOf( + "Storage Engine 外部、内部文件存储", + RouterPath.EMPTY + ) + ) + + // ============================= + // = DevSKU 商品 SKU 组合封装实现 = + // ============================= + + /** + * 获取 DevSKU Module Button Value 集合 + * @return [List] + */ + @JvmStatic + val moduleDevSKUButtonValues = mutableListOf( + ButtonValue.BTN_SKU_DIALOG.buttonOf( + "显示商品 SKU Dialog", + RouterPath.EMPTY + ) + ) +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonValue.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonValue.kt new file mode 100644 index 0000000000..706e4ced98 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/item/ButtonValue.kt @@ -0,0 +1,612 @@ +package afkt.project.model.item + +/** + * detail: 扩展函数 + * @author Ttt + */ +fun Int.buttonOf( + text: String, + path: String +): ButtonValue { + return ButtonValue( + this, text, path + ) +} + +/** + * detail: Button Value 实体类 + * @author Ttt + */ +class ButtonValue( + // 按钮类型 + val type: Int, + // 文案 + val text: String, + // ARouter Path + val path: String +) { + + companion object { + + // ======= + // = 常量 = + // ======= + + private const val BASE = 1001 + + // 架构 + const val MODULE_FRAMEWORK = BASE + 10000 + + // Lib + const val MODULE_LIB = BASE + 20000 + + // UI + const val MODULE_UI = BASE + 30000 + + // 其他功能 + const val MODULE_OTHER = BASE + 40000 + + // DevWidget UI 库 + const val MODULE_DEV_WIDGET = BASE + 50000 + + // DevEnvironment 环境配置切换库 + const val MODULE_DEV_ENVIRONMENT = BASE + 60000 + + // DevAssist Engine 实现 + const val MODULE_DEV_ASSIST_ENGINE = BASE + 70000 + + // DevHttpCapture OkHttp 抓包工具库 + const val MODULE_DEV_HTTP_CAPTURE = BASE + 80000 + + // DevSKU 商品 SKU 组合封装实现 + const val MODULE_DEV_SKU = BASE + 90000 + + // ============= + // = Framework = + // ============= + + // MVP + const val BTN_MVP = MODULE_FRAMEWORK + + // MVVM + const val BTN_MVVM = BTN_MVP + 100 + + // ======= + // = Lib = + // ======= + + // EventBusUtils + const val BTN_EVENT_BUS = MODULE_LIB + + // GreenDAO + const val BTN_GREEN_DAO = MODULE_LIB + 1 + + // Room + const val BTN_ROOM = MODULE_LIB + 2 + + // DataStore + const val BTN_DATA_STORE = MODULE_LIB + 3 + + // GlideUtils + const val BTN_GLIDE = MODULE_LIB + 4 + + // ImageLoaderUtils + const val BTN_IMAGE_LOADER = MODULE_LIB + 5 + + // GsonUtils + const val BTN_GSON = MODULE_LIB + 6 + + // FastjsonUtils + const val BTN_FASTJSON = MODULE_LIB + 7 + + // ZXingUtils + const val BTN_ZXING = MODULE_LIB + 8 + + // PictureSelectorUtils + const val BTN_PICTURE_SELECTOR = MODULE_LIB + 9 + + // OkGoUtils + const val BTN_OKGO = MODULE_LIB + 10 + + // LubanUtils + const val BTN_LUBAN = MODULE_LIB + 11 + + // MMKVUtils + const val BTN_MMKV = MODULE_LIB + 12 + + // WorkManagerUtils + const val BTN_WORK_MANAGER = MODULE_LIB + 13 + + // ====== + // = UI = + // ====== + + // ToastTint ( 着色美化 Toast ) + const val BTN_TOAST_TINT = MODULE_UI + + // Toast Success + const val BTN_TOAST_TINT_SUCCESS = BTN_TOAST_TINT + 1 + + // Toast Error + const val BTN_TOAST_TINT_ERROR = BTN_TOAST_TINT + 2 + + // Toast Info + const val BTN_TOAST_TINT_INFO = BTN_TOAST_TINT + 3 + + // Toast Normal + const val BTN_TOAST_TINT_NORMAL = BTN_TOAST_TINT + 4 + + // Toast Warning + const val BTN_TOAST_TINT_WARNING = BTN_TOAST_TINT + 5 + + // Toast Custom Style + const val BTN_TOAST_TINT_CUSTOM_STYLE = BTN_TOAST_TINT + 6 + + // 常见 UI、GradientDrawable 效果等 + const val BTN_UI_EFFECT = MODULE_UI + 100 + + // 点击 显示/隐藏 ( 状态栏 ) + const val BTN_STATUS_BAR = MODULE_UI + 200 + + // 计算字体宽度、高度 + const val BTN_TEXT_CALC = MODULE_UI + 300 + + // Adapter Item EditText 输入监听 + const val BTN_ADAPTER_EDITS = MODULE_UI + 400 + + // 多选辅助类 MultiSelectAssist + const val BTN_MULTI_SELECT = MODULE_UI + 500 + + // GPU ACV 文件滤镜效果 + const val BTN_GPU_ACV = MODULE_UI + 600 + + // GPU 滤镜效果 + const val BTN_GPU_FILTER = MODULE_UI + 700 + + // 创建二维码 + const val BTN_QRCODE_CREATE = MODULE_UI + 800 + + // 二维码图片解析 + const val BTN_QRCODE_IMAGE = MODULE_UI + 900 + + // 二维码扫描解析 + const val BTN_QRCODE_SCAN = MODULE_UI + 1000 + + // CapturePictureUtils 截图工具类 + const val BTN_CAPTURE_PICTURE = MODULE_UI + 1100 + + // 两个 TextView 显示效果 + const val BTN_TEXTVIEW = MODULE_UI + 1200 + + // RecyclerView 吸附效果 + const val BTN_ITEM_STICKY = MODULE_UI + 1300 + + // RecyclerView 滑动删除、上下滑动 + const val BTN_RECY_ITEM_SLIDE = MODULE_UI + 1400 + + // LinearSnapHelper - RecyclerView + const val BTN_RECY_LINEAR_SNAP = MODULE_UI + 1500 + + // LinearSnapHelper - 无限滑动 + const val BTN_RECY_LINEAR_SNAP_MAX = MODULE_UI + 1600 + + // PagerSnapHelper - RecyclerView + const val BTN_RECY_PAGER_SNAP = MODULE_UI + 1700 + + // PagerSnapHelper - 无限滑动 + const val BTN_RECY_PAGER_SNAP_MAX = MODULE_UI + 1800 + + // Material ShapeableImageView + const val BTN_SHAPEABLE_IMAGE_VIEW = MODULE_UI + 1900 + + // Material BottomSheet + const val BTN_BOTTOM_SHEET = MODULE_UI + 2000 + + // Material BottomSheetDialog + const val BTN_BOTTOM_SHEET_DIALOG = MODULE_UI + 2100 + + // Palette 调色板 + const val BTN_PALETTE = MODULE_UI + 2200 + + // Flexbox LayoutManager + const val BTN_FLEXBOX_LAYOUTMANAGER = MODULE_UI + 2300 + + // Material Chip、ChipGroups、ChipDrawable + const val BTN_CHIP = MODULE_UI + 2400 + + // ViewPager2 + const val BTN_VIEWPAGER2 = MODULE_UI + 2500 + + // RecyclerView - ConcatAdapter + const val BTN_RECYCLERVIEW_CONCATADAPTER = MODULE_UI + 2600 + + // RecyclerView MultiType Adapter + const val BTN_RECYCLERVIEW_MULTITYPE_ADAPTER = MODULE_UI + 2700 + + // 购物车加入动画 + const val BTN_SHOP_CARD_ADD_ANIM = MODULE_UI + 2800 + + // ========== + // = 其他功能 = + // ========== + + // 事件 / 广播监听 ( 网络状态、屏幕旋转等 ) + const val BTN_LISTENER = MODULE_OTHER + + // Wifi 监听 + const val BTN_WIFI_LISTENER = BTN_LISTENER + 1 + + // 网络监听 + const val BTN_NETWORK_LISTENER = BTN_LISTENER + 2 + + // 电话监听 + const val BTN_PHONE_LISTENER = BTN_LISTENER + 3 + + // 短信监听 + const val BTN_SMS_LISTENER = BTN_LISTENER + 4 + + // 时区、时间监听 + const val BTN_TIME_LISTENER = BTN_LISTENER + 5 + + // 屏幕监听 + const val BTN_SCREEN_LISTENER = BTN_LISTENER + 6 + + // 屏幕旋转监听 ( 重力传感器 ) + const val BTN_ROTA_LISTENER = BTN_LISTENER + 7 + + // 屏幕旋转监听 ( OrientationEventListener ) + const val BTN_ROTA2_LISTENER = BTN_LISTENER + 8 + + // 电量监听 + const val BTN_BATTERY_LISTENER = BTN_LISTENER + 9 + + // 应用状态监听 + const val BTN_APP_STATE_LISTENER = BTN_LISTENER + 10 + + // 通知栏监听服务 ( NotificationService ) + const val BTN_NOTIFICATION_SERVICE = MODULE_OTHER + 100 + + // 检查是否开启 + const val BTN_NOTIFICATION_SERVICE_CHECK = BTN_NOTIFICATION_SERVICE + 1 + + // 开始监听 + const val BTN_NOTIFICATION_SERVICE_REGISTER = BTN_NOTIFICATION_SERVICE + 2 + + // 注销监听 + const val BTN_NOTIFICATION_SERVICE_UNREGISTER = BTN_NOTIFICATION_SERVICE + 3 + + // 无障碍监听服务 ( AccessibilityListenerService ) + const val BTN_ACCESSIBILITY_SERVICE = MODULE_OTHER + 200 + + // 检查是否开启 + const val BTN_ACCESSIBILITY_SERVICE_CHECK = BTN_ACCESSIBILITY_SERVICE + 1 + + // 开始监听 + const val BTN_ACCESSIBILITY_SERVICE_REGISTER = BTN_ACCESSIBILITY_SERVICE + 2 + + // 注销监听 + const val BTN_ACCESSIBILITY_SERVICE_UNREGISTER = BTN_ACCESSIBILITY_SERVICE + 3 + + // Wifi 相关 ( 热点 ) + const val BTN_WIFI = MODULE_OTHER + 300 + + // 打开 Wifi + const val BTN_WIFI_OPEN = BTN_WIFI + 1 + + // 关闭 Wifi + const val BTN_WIFI_CLOSE = BTN_WIFI + 2 + + // 打开 Wifi 热点 + const val BTN_WIFI_HOT_OPEN = BTN_WIFI + 3 + + // 关闭 Wifi 热点 + const val BTN_WIFI_HOT_CLOSE = BTN_WIFI + 4 + + // 注册 Wifi 监听 + const val BTN_WIFI_LISTENER_REGISTER = BTN_WIFI + 5 + + // 注销 Wifi 监听 + const val BTN_WIFI_LISTENER_UNREGISTER = BTN_WIFI + 6 + + // 铃声、震动、通知栏等功能 + const val BTN_FUNCTION = MODULE_OTHER + 400 + + // 震动 + const val BTN_FUNCTION_VIBRATE = BTN_FUNCTION + 1 + + // 铃声 - 播放一小段音频 + const val BTN_FUNCTION_BEEP = BTN_FUNCTION + 2 + + // 是否存在通知权限 + const val BTN_FUNCTION_NOTIFICATION_CHECK = BTN_FUNCTION + 3 + + // 开启通知权限 + const val BTN_FUNCTION_NOTIFICATION_OPEN = BTN_FUNCTION + 4 + + // 通知消息 + const val BTN_FUNCTION_NOTIFICATION = BTN_FUNCTION + 5 + + // 移除消息 + const val BTN_FUNCTION_NOTIFICATION_REMOVE = BTN_FUNCTION + 6 + + // 回到桌面 + const val BTN_FUNCTION_HOME = BTN_FUNCTION + 7 + + // 打开手电筒 + const val BTN_FUNCTION_FLASHLIGHT_OPEN = BTN_FUNCTION + 8 + + // 关闭手电筒 + const val BTN_FUNCTION_FLASHLIGHT_CLOSE = BTN_FUNCTION + 9 + + // 是否创建桌面快捷方式 + const val BTN_FUNCTION_SHORTCUT_CHECK = BTN_FUNCTION + 10 + + // 创建桌面快捷方式 + const val BTN_FUNCTION_SHORTCUT_CREATE = BTN_FUNCTION + 11 + + // 删除桌面快捷方式 + const val BTN_FUNCTION_SHORTCUT_DELETE = BTN_FUNCTION + 12 + + // 打印内存信息 + const val BTN_FUNCTION_MEMORY_PRINT = BTN_FUNCTION + 13 + + // 打印设备信息 + const val BTN_FUNCTION_DEVICE_PRINT = BTN_FUNCTION + 14 + + // 跳转到 APP 设置详情页面 + const val BTN_FUNCTION_APP_DETAILS_SETTINGS = BTN_FUNCTION + 15 + + // 打开 GPS 设置界面 + const val BTN_FUNCTION_GPS_SETTINGS = BTN_FUNCTION + 16 + + // 打开网络设置界面 + const val BTN_FUNCTION_WIRELESS_SETTINGS = BTN_FUNCTION + 17 + + // 跳转到系统设置页面 + const val BTN_FUNCTION_SYS_SETTINGS = BTN_FUNCTION + 18 + + // TimerManager 定时器工具类 + const val BTN_TIMER = MODULE_OTHER + 500 + + // 启动定时器 + const val BTN_TIMER_START = BTN_TIMER + 1 + + // 停止定时器 + const val BTN_TIMER_STOP = BTN_TIMER + 2 + + // 重新启动定时器 + const val BTN_TIMER_RESTART = BTN_TIMER + 3 + + // 定时器是否启动 + const val BTN_TIMER_CHECK = BTN_TIMER + 4 + + // 获取定时器 + const val BTN_TIMER_GET = BTN_TIMER + 5 + + // 获取运行次数 + const val BTN_TIMER_GET_NUMBER = BTN_TIMER + 6 + + // DevCache 缓存工具类 + const val BTN_CACHE = MODULE_OTHER + 600 + + // 存储字符串 + const val BTN_CACHE_STRING = BTN_CACHE + 1 + + // 存储有效期字符串 + const val BTN_CACHE_STRING_TIME = BTN_CACHE + 2 + + // 获取字符串 + const val BTN_CACHE_STRING_GET = BTN_CACHE + 3 + + // 存储实体类 + const val BTN_CACHE_BEAN = BTN_CACHE + 4 + + // 存储有效期实体类 + const val BTN_CACHE_BEAN_TIME = BTN_CACHE + 5 + + // 获取实体类 + const val BTN_CACHE_BEAN_GET = BTN_CACHE + 6 + + // 存储到指定位置 + const val BTN_CACHE_FILE = BTN_CACHE + 7 + + // 获取指定位置缓存数据 + const val BTN_CACHE_FILE_GET = BTN_CACHE + 8 + + // 清除全部数据 + const val BTN_CACHE_CLEAR = BTN_CACHE + 9 + + // DevLogger 日志工具类 + const val BTN_LOGGER = MODULE_OTHER + 700 + + // 打印日志 + const val BTN_LOGGER_PRINT = BTN_LOGGER + 1 + + // 打印日志耗时测试 + const val BTN_LOGGER_TIME = BTN_LOGGER + 2 + + // 日志、异常文件记录保存 + const val BTN_FILE_RECORD = MODULE_OTHER + 800 + + // FileRecordUtils 工具类 + const val BTN_FILE_RECORD_UTILS = BTN_FILE_RECORD + 1 + + // 奔溃日志捕获 + const val BTN_CRASH = MODULE_OTHER + 900 + + // 点击崩溃捕获信息 + const val BTN_CRASH_CLICK_CATCH = BTN_CRASH + 1 + + // 路径信息 + const val BTN_PATH = MODULE_OTHER + 1000 + + // 内部存储路径 + const val BTN_PATH_INTERNAL = BTN_PATH + 1 + + // 应用外部存储路径 + const val BTN_PATH_APP_EXTERNAL = BTN_PATH + 2 + + // 外部存储路径 ( SDCard ) + const val BTN_PATH_SDCARD = BTN_PATH + 3 + + // WebView 辅助类 + const val BTN_WEBVIEW = MODULE_OTHER + 1100 + + // Activity Result API + const val BTN_ACTIVITY_RESULT_API = MODULE_OTHER + 1200 + + // startActivityForResult Callback + const val BTN_ACTIVITY_RESULT_CALLBACK = MODULE_OTHER + 1300 + + // 添加联系人 + const val BTN_ADD_CONTACT = MODULE_OTHER + 1400 + + // 手机壁纸 + const val BTN_WALLPAPER = MODULE_OTHER + 1500 + + // 悬浮窗管理辅助类 ( 需权限 ) + const val BTN_FLOATING_WINDOW_MANAGER = MODULE_OTHER + 1600 + + // 打开悬浮窗 + const val BTN_OPEN_FLOATING_WINDOW = BTN_FLOATING_WINDOW_MANAGER + 1 + + // 关闭悬浮窗 + const val BTN_CLOSE_FLOATING_WINDOW = BTN_FLOATING_WINDOW_MANAGER + 2 + + // 悬浮窗管理辅助类 ( 无需权限依赖 Activity ) + const val BTN_FLOATING_WINDOW_MANAGER2 = MODULE_OTHER + 1700 + + // ================== + // = DevWidget UI 库 = + // ================== + + // DevWidget + private const val BTN_DEV_WIDGET = MODULE_DEV_WIDGET + + // ViewPager 滑动监听、控制滑动 + const val BTN_VIEW_PAGER = BTN_DEV_WIDGET + 100 + + // 自定义 ProgressBar 样式 View + const val BTN_CUSTOM_PROGRESS_BAR = BTN_DEV_WIDGET + 200 + + // 自定义扫描 View ( QRCode、AR ) + const val BTN_SCAN_VIEW = BTN_DEV_WIDGET + 300 + + // 自动换行 View + const val BTN_WRAP_VIEW = BTN_DEV_WIDGET + 400 + + // 签名 View + const val BTN_SIGN_VIEW = BTN_DEV_WIDGET + 500 + + // 换行监听 View + const val BTN_LINE_VIEW = BTN_DEV_WIDGET + 600 + + // 自定义点赞效果 View + const val BTN_LIKE_VIEW = BTN_DEV_WIDGET + 700 + + // 自定义角标 View + const val BTN_CORNER_LABEL_VIEW = BTN_DEV_WIDGET + 800 + + // View 填充辅助类 + const val BTN_VIEW_ASSIST = BTN_DEV_WIDGET + 900 + + // RecyclerView ( loading ) + const val BTN_VIEW_ASSIST_RECYCLER = BTN_VIEW_ASSIST + 1 + + // Error ( failed ) + const val BTN_VIEW_ASSIST_ERROR = BTN_VIEW_ASSIST + 2 + + // Empty ( data ) + const val BTN_VIEW_ASSIST_EMPTY = BTN_VIEW_ASSIST + 3 + + // Custom Type + const val BTN_VIEW_ASSIST_CUSTOM = BTN_VIEW_ASSIST + 4 + + // 翻转卡片 View + const val BTN_FLIP_CARD_VIEW = BTN_DEV_WIDGET + 1000 + + // 波浪 View + const val BTN_WAVE_VIEW = BTN_DEV_WIDGET + 1100 + + // Linear Color ItemDecoration + const val BTN_LINEAR_ITEM_DECORATION = BTN_DEV_WIDGET + 1200 + + // Linear Vertical ItemDecoration + const val BTN_LINEAR_ITEM_VERTICAL = BTN_LINEAR_ITEM_DECORATION + 1 + + // Linear Horizontal ItemDecoration + const val BTN_LINEAR_ITEM_HORIZONTAL = BTN_LINEAR_ITEM_DECORATION + 2 + + // Grid Color ItemDecoration + const val BTN_GRID_ITEM_DECORATION = BTN_DEV_WIDGET + 1300 + + // Grid Vertical ItemDecoration + const val BTN_GRID_ITEM_VERTICAL = BTN_GRID_ITEM_DECORATION + 1 + + // Grid Horizontal ItemDecoration + const val BTN_GRID_ITEM_HORIZONTAL = BTN_GRID_ITEM_DECORATION + 2 + + // ============================== + // = DevEnvironment 环境配置切换库 = + // ============================== + + // 环境配置切换 + const val BTN_DEV_ENVIRONMENT = MODULE_DEV_ENVIRONMENT + + // 使用自定义配置 + const val BTN_USE_CUSTOM = BTN_DEV_ENVIRONMENT + 100 + + // ======================== + // = DevAssist Engine 实现 = + // ======================== + + // DevAssist Engine + private const val BTN_ENGINE = MODULE_DEV_ASSIST_ENGINE + + // Analytics Engine 数据统计 ( 埋点 ) + const val BTN_ENGINE_ANALYTICS = BTN_ENGINE + 100 + + // Cache Engine 有效期键值对缓存 + const val BTN_ENGINE_CACHE = BTN_ENGINE + 200 + + // Image Compress Engine 图片压缩 + const val BTN_ENGINE_IMAGE_COMPRESS = BTN_ENGINE + 300 + + // Image Engine 图片加载、下载、转格式等 + const val BTN_ENGINE_IMAGE = BTN_ENGINE + 400 + + // JSON Engine + const val BTN_ENGINE_JSON = BTN_ENGINE + 500 + + // KeyValue Engine 键值对存储 + const val BTN_ENGINE_KEYVALUE = BTN_ENGINE + 600 + + // Log Engine 日志打印 + const val BTN_ENGINE_LOG = BTN_ENGINE + 700 + + // Media Selector Engine 多媒体资源选择 + const val BTN_ENGINE_MEDIA_SELECTOR = BTN_ENGINE + 800 + + // Permission Engine 权限申请 + const val BTN_ENGINE_PERMISSION = BTN_ENGINE + 9000 + + // Push Engine 推送平台处理 + const val BTN_ENGINE_PUSH = BTN_ENGINE + 1000 + + // Share Engine 分享平台处理 + const val BTN_ENGINE_SHARE = BTN_ENGINE + 1100 + + // Storage Engine 外部、内部文件存储 + const val BTN_ENGINE_STORAGE = BTN_ENGINE + 1200 + + // ============================= + // = DevSKU 商品 SKU 组合封装实现 = + // ============================= + + // DevSKU + const val BTN_DEV_SKU = MODULE_DEV_SKU + + // 显示商品 SKU Dialog + const val BTN_SKU_DIALOG = BTN_DEV_SKU + 100 + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/model/item/RouterPath.kt b/application/DevUtilsApp/src/main/java/afkt/project/model/item/RouterPath.kt new file mode 100644 index 0000000000..b81dbc1463 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/model/item/RouterPath.kt @@ -0,0 +1,205 @@ +package afkt.project.model.item + +/** + * detail: ARouter Path + * @author Ttt + */ +object RouterPath { + + // ================= + // = 默认通用、根目录 = + // ================= + + const val EMPTY = "" + + const val ModuleActivity_PATH = "/activity/ModuleActivity" + + const val ButtonItemActivity_PATH = "/activity/ButtonItemActivity" + + // ================== + // = 按功能模块列表循序 = + // ================== + + /** + * Framework 架构 + */ + object FRAMEWORK { + + const val ArticleMVPActivity_PATH = "/activity/ArticleMVPActivity" + + const val ArticleMVVMActivity_PATH = "/activity/ArticleMVVMActivity" + } + + /** + * Lib 框架 + */ + object LIB_FRAME { + + const val DataStoreActivity_PATH = "/activity/DataStoreActivity" + } + + /** + * UI 效果 + */ + object UI_EFFECT { + + const val ToastTintActivity_PATH = "/activity/ToastTintActivity" + + const val UIEffectActivity_PATH = "/activity/UIEffectActivity" + + const val StatusBarActivity_PATH = "/activity/StatusBarActivity" + + const val TextCalcActivity_PATH = "/activity/TextCalcActivity" + + const val AdapterEditsActivity_PATH = "/activity/AdapterEditsActivity" + + const val MultiSelectActivity_PATH = "/activity/MultiSelectActivity" + + const val GPUFilterACVActivity_PATH = "/activity/GPUFilterACVActivity" + + const val GPUFilterActivity_PATH = "/activity/GPUFilterActivity" + + const val QRCodeCreateActivity_PATH = "/activity/QRCodeCreateActivity" + + const val QRCodeImageActivity_PATH = "/activity/QRCodeImageActivity" + + const val QRCodeScanActivity_PATH = "/activity/QRCodeScanActivity" + + const val CapturePictureActivity_PATH = "/activity/CapturePictureActivity" + + const val CapturePictureListActivity_PATH = "/activity/CapturePictureListActivity" + + const val CapturePictureGridActivity_PATH = "/activity/CapturePictureGridActivity" + + const val CapturePictureRecyActivity_PATH = "/activity/CapturePictureRecyActivity" + + const val CapturePictureWebActivity_PATH = "/activity/CapturePictureWebActivity" + + const val TextViewActivity_PATH = "/activity/TextViewActivity" + + const val ItemStickyActivity_PATH = "/activity/ItemStickyActivity" + + const val RecyItemSlideActivity_PATH = "/activity/RecyItemSlideActivity" + + const val LinearSnapActivity_PATH = "/activity/LinearSnapActivity" + + const val LinearSnapMAXActivity_PATH = "/activity/LinearSnapMAXActivity" + + const val PagerSnapActivity_PATH = "/activity/PagerSnapActivity" + + const val PagerSnapMAXActivity_PATH = "/activity/PagerSnapMAXActivity" + + const val ShapeableImageViewActivity_PATH = "/activity/ShapeableImageViewActivity" + + const val BottomSheetActivity_PATH = "/activity/BottomSheetActivity" + + const val BottomSheetDialogActivity_PATH = "/activity/BottomSheetDialogActivity" + + const val PaletteActivity_PATH = "/activity/PaletteActivity" + + const val FlexboxLayoutManagerActivity_PATH = "/activity/FlexboxLayoutManagerActivity" + + const val ChipActivity_PATH = "/activity/ChipActivity" + + const val ViewPager2Activity_PATH = "/activity/ViewPager2Activity" + + const val RecyConcatAdapterActivity_PATH = "/activity/RecyConcatAdapterActivity" + + const val RecyMultiTypeAdapterActivity_PATH = "/activity/RecyMultiTypeAdapterActivity" + + const val ShopCartAddAnimActivity_PATH = "/activity/ShopCartAddAnimActivity" + } + + /** + * 其他功能 + */ + object OTHER_FUNCTION { + + const val ListenerActivity_PATH = "/activity/ListenerActivity" + + const val NotificationServiceActivity_PATH = "/activity/NotificationServiceActivity" + + const val AccessibilityListenerServiceActivity_PATH = + "/activity/AccessibilityListenerServiceActivity" + + const val WifiActivity_PATH = "/activity/WifiActivity" + + const val FunctionActivity_PATH = "/activity/FunctionActivity" + + const val TimerActivity_PATH = "/activity/TimerActivity" + + const val CacheActivity_PATH = "/activity/CacheActivity" + + const val LoggerActivity_PATH = "/activity/LoggerActivity" + + const val FileRecordActivity_PATH = "/activity/FileRecordActivity" + + const val CrashCatchActivity_PATH = "/activity/CrashCatchActivity" + + const val PathActivity_PATH = "/activity/PathActivity" + + const val WebViewActivity_PATH = "/activity/WebViewActivity" + + const val ActivityResultAPIActivity_PATH = "/activity/ActivityResultAPIActivity" + + const val ActivityResultCallbackActivity_PATH = "/activity/ActivityResultCallbackActivity" + + const val AddContactActivity_PATH = "/activity/AddContactActivity" + + const val WallpaperActivity_PATH = "/activity/WallpaperActivity" + + const val FloatingWindowManagerActivity_PATH = "/activity/FloatingWindowManagerActivity" + + const val FloatingWindowManager2Activity_PATH = "/activity/FloatingWindowManager2Activity" + } + + /** + * DevWidget UI 库 + */ + object DEV_WIDGET { + + const val ViewPagerActivity_PATH = "/activity/ViewPagerActivity" + + const val ProgressBarActivity_PATH = "/activity/ProgressBarActivity" + + const val ScanShapeActivity_PATH = "/activity/ScanShapeActivity" + + const val WrapActivity_PATH = "/activity/WrapActivity" + + const val SignActivity_PATH = "/activity/SignActivity" + + const val LineActivity_PATH = "/activity/LineActivity" + + const val FlowLikeActivity_PATH = "/activity/FlowLikeActivity" + + const val CornerLabelActivity_PATH = "/activity/CornerLabelActivity" + + const val ViewAssistRecyclerViewLoadActivity_PATH = + "/activity/ViewAssistRecyclerViewLoadActivity" + + const val ViewAssistActivity_PATH = "/activity/ViewAssistActivity" + + const val FlipCardActivity_PATH = "/activity/FlipCardActivity" + + const val WaveViewActivity_PATH = "/activity/WaveViewActivity" + + const val LinearColorItemDecorationActivity_PATH = + "/activity/LinearColorItemDecorationActivity" + + const val GridColorItemDecorationActivity_PATH = "/activity/GridColorItemDecorationActivity" + } + + /** + * Dev Libs + */ + object DEV_LIBS { + + const val DevEnvironmentLibActivity_PATH = "/activity/DevEnvironmentLibActivity" + + const val DevAssistEngineActivity_PATH = "/activity/DevAssistEngineActivity" + + const val DevHttpCaptureActivity_PATH = "/activity/DevHttpCaptureActivity" + + const val DevSKUActivity_PATH = "/activity/DevSKUActivity" + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/ui/SlideImageView.java b/application/DevUtilsApp/src/main/java/afkt/project/ui/SlideImageView.java new file mode 100644 index 0000000000..725e7a7ad8 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/ui/SlideImageView.java @@ -0,0 +1,527 @@ +package afkt.project.ui; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +/** + * detail: 滑动 ImageView ( 未调整优化 ) + * @author Ttt + * https://blog.csdn.net/qq_18833399/article/details/60958658 + * https://blog.csdn.net/csdnwangzhan/article/details/51597142 + * https://blog.csdn.net/nugongahou110/article/details/50668925 + * https://blog.csdn.net/qq_30716173/article/details/51460627 + * https://yq.aliyun.com/ziliao/131530 + * https://blog.csdn.net/zjutkz/article/details/46553017 + * https://blog.csdn.net/u010335298/article/details/52278609 + * https://github.com/LuckyJayce/LargeImage + */ +public class SlideImageView + extends AppCompatImageView { + + // 判断是否允许滑动 => 如果可以滑动 isNeedSlide = true, 该参数限制才有效 + private boolean isAllowSlide = true; + // 是否需要滑动 + private boolean isNeedSlide; + // View 宽度, 高度 + private int viewWidth, viewHeight; + // 需要绘制的图片的区域 + private final Rect srcRect = new Rect(); + // 绘制的区域 + private final RectF dstRectF = new RectF(); + // 画笔 + private final Paint paint = new Paint(); + // 已经滑动过的高度 + private float slideHeight; + // 绘制的 Bitmap + private Bitmap drawBitmap; + // 设置计算倍数 + private float scale = 1.0F; + + { + // 初始化画笔 + paint.setAntiAlias(true); // 抗锯齿 + paint.setDither(true); // 防抖动 + paint.setColor(Color.WHITE); + paint.setStrokeWidth(1.0F); + } + + public SlideImageView(Context context) { + super(context); + } + + public SlideImageView( + Context context, + @Nullable AttributeSet attrs + ) { + super(context, attrs); + } + + public SlideImageView( + Context context, + @Nullable AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + // = 优化处理, 防止多次执行 onMeasure 计算处理 = + // 预计宽度, 小于预计的宽度才处理 + private int estimateWidth = -1; + // 计算次数, 计算多次后, 就不处理 + private int measureCount = -1; + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int specSize = MeasureSpec.getSize(widthMeasureSpec); + viewWidth = getPaddingLeft() + getPaddingRight() + specSize; + specSize = MeasureSpec.getSize(heightMeasureSpec); + viewHeight = getPaddingTop() + getPaddingBottom() + specSize; + // 防止多次调用 onMeasure, 内部缩放 resizeImage 导致卡顿 + if (estimateWidth == -1 || viewWidth <= estimateWidth) { + // 计算总数大于 1 表示允许计算 + if (measureCount != 0) { + // 获取设置的 View + Drawable drawable = getDrawable(); + // 防止为 null + if (drawable != null) { + try { + // 根据图片宽度为基准, 缩放图片大小 + drawBitmap = resizeImage(((BitmapDrawable) drawable).getBitmap(), viewWidth); + measureCount--; + } catch (Exception e) { + drawBitmap = null; + } + // 防止为 null + if (drawBitmap != null) { + // 判断图片高度, 是否超过 View 的高度 => scale 倍 + if (drawBitmap.getHeight() > scale * viewHeight) { + // 需要滑动 + setNeedSlide(true); + } else { + // 不需要滑动 + setNeedSlide(false); + srcRect.left = 0; + srcRect.top = 0; + srcRect.right = drawBitmap.getWidth(); + srcRect.bottom = drawBitmap.getHeight(); + // 如果高度大于 View 的高度, 则以高度为基准 ( 铺满高度, 宽度跟随比例缩放 ) + if (drawBitmap.getHeight() > viewHeight) { + // 生成新的图片 ( 高度铺满, 宽度按比例缩放 ) + drawBitmap = resizeImageH(drawBitmap, viewHeight); + } else { // 图片高度 小于 View 高度, 则计算多出来的差距 + float space = (viewHeight - drawBitmap.getHeight()); + dstRectF.left = 0; + dstRectF.top = space; + dstRectF.right = viewWidth; + dstRectF.bottom = viewHeight - space; + } + } + } + } + } + } + setMeasuredDimension(viewWidth, viewHeight); + } + + @Override + protected void onDraw(Canvas canvas) { + if (drawBitmap != null) { + // 绘制 View + canvas.drawBitmap(drawBitmap, (viewWidth - drawBitmap.getWidth()) / 2, slideHeight, paint); + } + } + + // 触摸操作的坐标 + private float lastX, lastY; + + @Override + public boolean onTouchEvent(MotionEvent event) { + // 如果不符合要求, 也不做处理 + if (!isNeedSlide) { + return super.onTouchEvent(event); + } + // 如果不允许滑动, 则不处理 + if (!isAllowSlide) { + return super.onTouchEvent(event); + } + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: // 按下 + lastX = event.getX(); + lastY = event.getY(); + break; + case MotionEvent.ACTION_MOVE: // 移动 + float moveX = event.getX(); + if (moveX - lastX > 50) { + // 判断为左右滑动 + return super.onTouchEvent(event); + } + // 获取移动上下值 + float moveY = event.getY(); + // 计算滑动距离 + float distance = moveY - lastY; + // 设置最后滑动的值 + lastY = moveY; + // 滑动的位置累加 + slideHeight += distance; + // 如果属于到顶了, 还一直向上滑, 则设置为 0 + if (slideHeight >= 0) { + slideHeight = 0; + } + // 计算滑动的距离 ( 需要取反, 因为向下滑为负数 ) + float calcDistance = (-1) * (drawBitmap.getHeight() - viewHeight); + // 向下滑为负数 + if (slideHeight <= calcDistance) { + slideHeight = calcDistance; + } + // 重新绘制 + postInvalidate(); + break; + default: + break; + } + return true; + } + + /** + * 重新计算大小, 以宽度为基准 + */ + public Bitmap resizeImage( + Bitmap bitmap, + int w + ) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + float scaleWidth = ((float) w) / width; + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleWidth); + return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); + } + + /** + * 重新计算大小, 以高度为基准 + */ + public Bitmap resizeImageH( + Bitmap bitmap, + int h + ) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + float scaleWidth = ((float) h) / height; + Matrix matrix = new Matrix(); + matrix.postScale(scaleWidth, scaleWidth); + return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true); + } + + /** + * 获取设置的预计宽度 + */ + public int getEstimateWidth() { + return estimateWidth; + } + + /** + * 设置预计高度 + */ + public SlideImageView setEstimateWidth(int estimateWidth) { + this.estimateWidth = estimateWidth; + return this; + } + + /** + * 获取计算次数 + */ + public int getMeasureCount() { + return measureCount; + } + + /** + * 设置计算次数 + */ + public SlideImageView setMeasureCount(int measureCount) { + this.measureCount = measureCount; + return this; + } + + // = + + /** + * 是否需要滑动 -> 图片是否符合要求能够滑动 + */ + public boolean isNeedSlide() { + return isNeedSlide; + } + + /** + * 设置是否需要滑动 + */ + public SlideImageView setNeedSlide(boolean needSlide) { + isNeedSlide = needSlide; + return this; + } + + /** + * 是否允许滑动 + */ + public boolean isAllowSlide() { + return isAllowSlide; + } + + /** + * 设置是否允许滑动 + */ + public SlideImageView setAllowSlide(boolean allowSlide) { + isAllowSlide = allowSlide; + return this; + } + + /** + * 获取比例 + */ + public float getScale() { + return scale; + } + + /** + * 设置计算比例 + */ + public SlideImageView setScale(float scale) { + this.scale = scale; + return this; + } + + // = 动画相关 = + + private final Handler handler = new Handler(); + // 动画滑动距离 + private float slideLength = 10F; + // 滑动速度 -> 时间 + private long slideSpeed = 100L; + // 检测时间 + private long checkTime = 20L; + // 是否滑动到底部 + private boolean isScrollBottom = true; + // 是否开启动画 + private boolean isStartAnim = false; + // 是否关闭动画 + private boolean isStopAnim = false; + + // 动画线程 + private final Runnable animRunnable = new Runnable() { + @Override + public void run() { + // 如果关闭了动画, 则不处理 + if (isStopAnim) { + return; + } + // 防止 图片 为 null + if (drawBitmap == null) { + // 进行延时处理 + handler.postDelayed(this, checkTime); + return; + } + // 计算滑动比例 + calcSlideScale(); + // 向下滑动 + if (isScrollBottom) { + slideHeight -= slideLength; + } else { // 向上滑动 + slideHeight += slideLength; + } + // 判断是否滑动到顶部 + if (slideHeight >= 0) { + slideHeight = 0; + // 表示准备滑动到底部 + isScrollBottom = true; + } + // 计算滑动的距离 + float calcDistance = (-1) * (drawBitmap.getHeight() - viewHeight); + // 向下滑为负数 + if (slideHeight <= calcDistance) { + slideHeight = calcDistance; + // 表示向上滑动 + isScrollBottom = false; + } + if (isStopAnim) { + return; + } + // 重新绘制 + postInvalidate(); + // 进行延时处理 + handler.postDelayed(this, slideSpeed); + } + }; + + /** + * 启动动画 + */ + public void startAnim() { + // 如果没启动则允许启动 + if (!isStartAnim) { + // 表示开启了动画 + isStartAnim = true; + // 表示没有关闭动画 + isStopAnim = false; + // 开启动画 + new Thread(animRunnable).start(); + } + } + + /** + * 关闭动画 + */ + public void stopAnim() { + // 表示关闭动画 + isStopAnim = true; + // 表示非启动动画 + isStartAnim = false; + } + + // = + + /** + * 是否启动了动画 + */ + public boolean isStartAnim() { + return isStartAnim; + } + + /** + * 获取滑动长度 + */ + public float getSlideLength() { + return slideLength; + } + + /** + * 设置滑动长度 + */ + public SlideImageView setSlideLength(float slideLength) { + this.slideLength = slideLength; + return this; + } + + /** + * 获取滑动速度 - 时间 + */ + public long getSlideSpeed() { + return slideSpeed; + } + + /** + * 设置滑动速度 - 时间 + */ + public SlideImageView setSlideSpeed(long slideSpeed) { + this.slideSpeed = slideSpeed; + return this; + } + + /** + * 获取检测时间 + */ + public long getCheckTime() { + return checkTime; + } + + /** + * 设置检测时间 + */ + public void setCheckTime(long checkTime) { + this.checkTime = checkTime; + } + + // = + + /** + * 是否向下滑动 + */ + public boolean isScrollBottom() { + return isScrollBottom; + } + + /** + * 设置滑动方向 + * @param scrollBottom 是否向下滑动 + */ + public SlideImageView setScrollDirection(boolean scrollBottom) { + isScrollBottom = scrollBottom; + return this; + } + + /** + * 获取当前滑动的高度 + */ + public float getSlideHeight() { + return slideHeight; + } + + /** + * 设置当前滑动的高度 + */ + public SlideImageView setSlideHeight(float slideHeight) { + this.slideHeight = slideHeight; + return this; + } + + // 设置滑动比例 + private float slideCalcScale = -1F; + + /** + * 设置滑动比例 + */ + public SlideImageView setSlideHeightScale(float scale) { + slideCalcScale = scale; + return this; + } + + /** + * 计算滑动比例 + */ + private void calcSlideScale() { + if (slideCalcScale != -1F) { + // 临时处理 + float tScalc = slideCalcScale; + // 清空比例 + slideCalcScale = -1F; + // 超过限制, 则默认为 1 倍 + if (tScalc > 1.0F) { + tScalc = 1.0F; + } else if (tScalc < 0F) { + tScalc = 0F; // 默认为 0 倍 + } + // 计算滑动的距离 + float calcDistance = (-1) * (drawBitmap.getHeight() - viewHeight) * tScalc; + // 设置新的滑动距离 + slideHeight = calcDistance; +// // 判断是否滑动到顶部 +// if (slideHeight >= 0) { +// slideHeight = 0; +//// // 表示准备滑动到底部 +//// isScrollBottom = true; +// } +// // 向下滑为负数 +// if (slideHeight <= calcDistance) { +// slideHeight = calcDistance; +//// // 表示向上滑动 +//// isScrollBottom = false; +// } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/AutoGridView.java b/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/AutoGridView.java new file mode 100644 index 0000000000..dc553b1162 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/AutoGridView.java @@ -0,0 +1,140 @@ +package afkt.project.ui.widget; + +import android.content.Context; +import android.content.res.Configuration; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.GridView; +import android.widget.ListAdapter; + +public class AutoGridView + extends GridView { + + private static final String TAG = AutoGridView.class.getSimpleName(); + private int numColumnsID; + private int previousFirstVisible; + private int numColumns = 1; + + public AutoGridView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initialize(attrs); + } + + public AutoGridView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initialize(attrs); + } + + public AutoGridView(Context context) { + super(context); + } + + /** + * Sets the numColumns based on the attributeset + */ + private void initialize(AttributeSet attrs) { + // Read numColumns out of the AttributeSet + int count = attrs.getAttributeCount(); + if (count > 0) { + for (int i = 0; i < count; i++) { + String name = attrs.getAttributeName(i); + if (name != null && name.equals("numColumns")) { + // Update columns + this.numColumnsID = attrs.getAttributeResourceValue(i, 1); + updateColumns(); + break; + } + } + } + Log.d(TAG, "numColumns set to: " + numColumns); + } + + /** + * Reads the amount of columns from the resource file and + * updates the "numColumns" variable + */ + private void updateColumns() { + this.numColumns = getContext().getResources().getInteger(numColumnsID); + } + + @Override + public void setNumColumns(int numColumns) { + this.numColumns = numColumns; + super.setNumColumns(numColumns); + + Log.d(TAG, "setSelection --> " + previousFirstVisible); + setSelection(previousFirstVisible); + } + + @Override + protected void onLayout( + boolean changed, + int leftPos, + int topPos, + int rightPos, + int bottomPos + ) { + super.onLayout(changed, leftPos, topPos, rightPos, bottomPos); + setHeights(); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + updateColumns(); + setNumColumns(this.numColumns); + } + + @Override + protected void onScrollChanged( + int newHorizontal, + int newVertical, + int oldHorizontal, + int oldVertical + ) { + // Check if the first visible position has changed due to this scroll + int firstVisible = getFirstVisiblePosition(); + if (previousFirstVisible != firstVisible) { + // Update position, and update heights + previousFirstVisible = firstVisible; + setHeights(); + } + + super.onScrollChanged(newHorizontal, newVertical, oldHorizontal, oldVertical); + } + + private void setHeights() { + ListAdapter adapter = getAdapter(); + + if (adapter != null) { + for (int i = 0; i < getChildCount(); i += numColumns) { + // Determine the maximum height for this row + int maxHeight = 0; + for (int j = i; j < i + numColumns; j++) { + View view = getChildAt(j); + if (view != null && view.getHeight() > maxHeight) { + maxHeight = view.getHeight(); + } + } + //Log.d(TAG, "Max height for row #" + i/numColumns + ": " + maxHeight); + + // Set max height for each element in this row + if (maxHeight > 0) { + for (int j = i; j < i + numColumns; j++) { + View view = getChildAt(j); + if (view != null && view.getHeight() != maxHeight) { + view.setMinimumHeight(maxHeight); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/VerticalScrollView.java b/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/VerticalScrollView.java new file mode 100644 index 0000000000..83239ba27a --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/ui/widget/VerticalScrollView.java @@ -0,0 +1,84 @@ +package afkt.project.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.core.widget.NestedScrollView; + +public class VerticalScrollView + extends NestedScrollView { + + private int mScrollY; + private int mLastMotionY; + private boolean mClampedY; + + public VerticalScrollView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public VerticalScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onOverScrolled( + int scrollX, + int scrollY, + boolean clampedX, + boolean clampedY + ) { + mScrollY = scrollY; + mClampedY = clampedY; + super.onOverScrolled(scrollX, scrollY, clampedX, clampedY); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + final int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { + final int y = (int) ev.getY(); + final int yDiff = y - mLastMotionY; + // 8 为阈值, 可自行定义 ( 这里为方便用的魔法值 ) + if (yDiff > 8) { // 向下滑动 + mLastMotionY = y; + if (mScrollY == 0 && mClampedY) { + // ScrollView 已经滑到顶部了, 再向下滑动, 那就不处理了, 让父控件决定是否拦截事件 + getParent().requestDisallowInterceptTouchEvent(false); + } else { + getParent().requestDisallowInterceptTouchEvent(true); + } + } else if (yDiff < -8) { // 向上滑动 + mLastMotionY = y; + if (mScrollY > 0 && mClampedY) { + getParent().requestDisallowInterceptTouchEvent(false); + } else { + getParent().requestDisallowInterceptTouchEvent(true); + } + } + break; + } + case MotionEvent.ACTION_DOWN: { + // 先告知父控件不要拦截事件, 我先处理 + getParent().requestDisallowInterceptTouchEvent(true); + mLastMotionY = (int) ev.getY(); + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + // 还原 + getParent().requestDisallowInterceptTouchEvent(false); + break; + } + return super.dispatchTouchEvent(ev); + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/use_demo/DevRetrofitCoroutinesDemo.kt b/application/DevUtilsApp/src/main/java/afkt/project/use_demo/DevRetrofitCoroutinesDemo.kt new file mode 100644 index 0000000000..6bc50597da --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/use_demo/DevRetrofitCoroutinesDemo.kt @@ -0,0 +1,289 @@ +package afkt.project.use_demo + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import dev.DevHttpCapture +import dev.capture.CallbackInterceptor +import dev.capture.CaptureInfo +import dev.capture.IHttpCaptureEnd +import dev.engine.DevEngine +import dev.retrofit.* +import dev.utils.LogPrintUtils +import dev.utils.common.StringUtils +import okhttp3.OkHttpClient +import okhttp3.RequestBody +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path +import java.util.* + +// 日志 TAG +const val TAG_L = "DevRetrofitCoroutinesDemo" + +/** + * detail: 服务器接口 API Service + * @author Ttt + */ +interface APIService { + + @GET("xxx") + suspend fun loadArticleList(@Path("page") page: Int): ArticleResponse // or BaseResponse> + + @POST("xxx") + suspend fun uploadImage(@Body body: RequestBody): UploadBean // or BaseResponse>() +} + +/** + * detail: Retrofit API Service + * @author Ttt + */ +object RetrofitAPI { + + private val retrofit: Retrofit + + private val apiService: APIService + + init { + // ==================== + // = OkHttpClient 配置 = + // ==================== + + val builder = OkHttpClient.Builder() + + // 使用 DevHttpCapture 库进行 Http 拦截回调 ( 抓包数据存储 ) + DevHttpCapture.addInterceptor(builder, "ModuleName") + + // 使用 DevHttpCapture 库进行 Http 拦截回调 ( 不进行抓包数据存储 ) + builder.addInterceptor(CallbackInterceptor(endCall = object : IHttpCaptureEnd { + override fun callEnd(info: CaptureInfo) { + LogPrintUtils.jsonTag( + TAG_L, DevEngine.getJSON().toJson(info) + ) + } + })) + + // ================ + // = Retrofit 配置 = + // ================ + + retrofit = Retrofit.Builder() + // Gson 解析 + .addConverterFactory(GsonConverterFactory.create()) + // OkHttpClient + .client(builder.build()) + // 服务器地址 + .baseUrl("https://baseUrl") + .build() + + apiService = retrofit.create(APIService::class.java) + } + + fun api(): APIService { + return apiService + } +} + +// ======== +// = 实体类 = +// ======== + +/** + * detail: 统一响应实体类 + * @author Ttt + */ +open class BaseResponse : Base.Response { + + private var customData: T? = null + private var customCode: String? = null + private var customMessage: String? = null + + // ================= + // = Base.Response = + // ================= + + override fun getData(): T? { + return customData + } + + override fun getCode(): String? { + return customCode + } + + override fun getMessage(): String? { + return customMessage + } + + override fun isSuccess(): Boolean { + return customCode?.let { code -> + // 自定义 code 为 200 表示请求成功 ( 后台定义 ) + StringUtils.equals(code, "200") + } ?: false + } +} + +// ============= +// = 非基础实体类 = +// ============= + +/** + * detail: 上传图片结果 + * @author Ttt + */ +class UploadBean : BaseResponse>() + +/** + * detail: 文章数据响应类 ( 可不定义, 只是为了方便理解、展示 ) + * @author Ttt + * data 映射实体类为 List + */ +class ArticleResponse : BaseResponse>() + +/** + * detail: 文章实体类 + * @author Ttt + */ +data class ArticleBean( + val content: String?, + val cover: String? +) + +// =================== +// = DevRetrofit 使用 = +// =================== + +/** + * 模拟在 Activity 下使用 + * 总的请求方法分为以下两种 + * execute Request + * execute Response Request + * 区别在于 Response 是直接使用内部封装的 Base.Result 以及 ResultCallback 进行回调通知等 + * 不管什么扩展函数方式请求, 最终都是执行 request.kt 中的 finalExecute、finalExecuteResponse 方法 + */ +class TestActivity : AppCompatActivity() { + + // 封装 Base Notify.Callback + abstract class BaseCallback : Notify.Callback() + + // 封装 Notify.ResultCallback 简化代码 + abstract class BaseResultCallback : Notify.ResultCallback>() + + // LiveData + private val _articleLiveData = MutableLiveData() + val articleLiveData: LiveData = _articleLiveData + + private fun request() { + // 加载文章列表方式一 + simpleLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : Notify.Callback() { + override fun onSuccess( + uuid: UUID, + data: ArticleResponse? + ) { + // 请求成功 + } + + override fun onError( + uuid: UUID, + error: Throwable? + ) { + // 请求异常 + } + } + ) + // 加载文章列表方式一 ( 使用封装 Callback ) + simpleLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, ArticleCallback() + ) + + // 加载文章列表方式二 + simpleLaunchExecuteResponseRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : Notify.ResultCallback, ArticleResponse>() { + override fun onSuccess( + uuid: UUID, + data: Base.Result, ArticleResponse> + ) { + + } + } + ) + + // 加载文章列表方式二 ( 使用 BaseResultCallback ) + simpleLaunchExecuteResponseRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : BaseResultCallback>() { + override fun onSuccess( + uuid: UUID, + data: Base.Result, BaseResponse>> + ) { + + } + } + ) + + // 加载文章列表方式三 + liveDataLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, + liveData = _articleLiveData, + usePostValue = false // default true + ) + + // 加载文章列表方式四 ( 可自行添加额外流程等 ) + launchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, + start = { + + }, + success = { + + }, + error = { + + }, + finish = { + + }, + callback = ArticleCallback() + ) + } + + private class ArticleCallback : Notify.Callback() { + override fun onSuccess( + uuid: UUID, + data: ArticleResponse? + ) { + // 请求成功 + } + + override fun onError( + uuid: UUID, + error: Throwable? + ) { + // 请求异常 + } + + override fun onStart(uuid: UUID) { + super.onStart(uuid) + // 开始请求 + } + + override fun onFinish(uuid: UUID) { + super.onFinish(uuid) + // 请求结束 + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/utils/image_config.kt b/application/DevUtilsApp/src/main/java/afkt/project/utils/image_config.kt new file mode 100644 index 0000000000..6a3d2cfa0d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/utils/image_config.kt @@ -0,0 +1,93 @@ +package afkt.project.utils + +import dev.engine.image.ImageConfig +import dev.mvvm.DevMVVM +import dev.mvvm.utils.size.AppSize + +// ================================ +// = dev.engine.image.ImageConfig = +// ================================ + +// ============ +// = 使用方式一 = +// ============ + +private val IMAGE_ROUND = ImageConfig.create().apply { + setTransform(ImageConfig.TRANSFORM_ROUNDED_CORNERS) + setScaleType(ImageConfig.SCALE_NONE) +} + +//val IMAGE_DEFAULT_CROP = ImageConfig.create().apply { +// setScaleType(ImageConfig.SCALE_CENTER_CROP) +//} +// +//val IMAGE_ROUND_3 = ImageConfig.create(IMAGE_ROUND).apply { +// setRoundedCornersRadius( +// AppSize.dp2px(3F) +// ) +//} + +// ============ +// = 使用方式二 = +// ============ + +// IMAGE_KEY.toImageConfig() => ImageConfig +const val IMAGE_DEFAULT_CROP = "IMAGE_DEFAULT_CROP" +const val IMAGE_DEFAULT_FIX = "IMAGE_DEFAULT_FIX" +const val IMAGE_ROUND_3 = "IMAGE_ROUND_3" +const val IMAGE_ROUND_10 = "IMAGE_ROUND_10" +const val IMAGE_ROUND_CROP_10 = "IMAGE_ROUND_CROP_10" +const val IMAGE_ROUND_FIX_10 = "IMAGE_ROUND_FIX_10" + +/** + * 初始化 App ImageConfig 创建器 + */ +fun initAppImageConfigCreator() { + DevMVVM.setImageCreator { key, param -> + when (key) { + IMAGE_DEFAULT_CROP -> { + ImageConfig.create().apply { + setScaleType(ImageConfig.SCALE_CENTER_CROP) + } + } + IMAGE_DEFAULT_FIX -> { + ImageConfig.create().apply { + setScaleType(ImageConfig.SCALE_FIT_CENTER) + } + } + IMAGE_ROUND_3 -> { + ImageConfig.create(IMAGE_ROUND).apply { + setRoundedCornersRadius( + AppSize.dp2px(3F) + ) + } + } + IMAGE_ROUND_10 -> { + ImageConfig.create(IMAGE_ROUND).apply { + setRoundedCornersRadius( + AppSize.dp2px(10F) + ) + } + } + IMAGE_ROUND_CROP_10 -> { + ImageConfig.create(IMAGE_ROUND).apply { + setRoundedCornersRadius( + AppSize.dp2px(10F) + ) + setScaleType(ImageConfig.SCALE_CENTER_CROP) + } + } + IMAGE_ROUND_FIX_10 -> { + ImageConfig.create(IMAGE_ROUND).apply { + setRoundedCornersRadius( + AppSize.dp2px(10F) + ) + setScaleType(ImageConfig.SCALE_FIT_CENTER) + } + } + else -> { + null + } + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/afkt/project/utils/media_config_create.kt b/application/DevUtilsApp/src/main/java/afkt/project/utils/media_config_create.kt new file mode 100644 index 0000000000..4aa2bdfb7d --- /dev/null +++ b/application/DevUtilsApp/src/main/java/afkt/project/utils/media_config_create.kt @@ -0,0 +1,166 @@ +package afkt.project.utils + +import android.app.Activity +import android.content.Context +import android.widget.ImageView +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.luck.lib.camerax.SimpleCameraX +import com.luck.picture.lib.basic.PictureSelector +import com.luck.picture.lib.config.SelectMimeType +import com.luck.picture.lib.engine.ImageEngine +import com.luck.picture.lib.utils.ActivityCompatHelper +import dev.engine.media.IMediaEngine +import dev.engine.media.MediaConfig +import dev.other.R +import dev.utils.DevFinal +import dev.utils.app.PathUtils +import dev.utils.common.FileUtils + +// ============================= +// = IMediaEngine.EngineConfig = +// ============================= + +fun Activity.createGalleryConfig(): IMediaEngine.EngineConfig { + // 初始化图片配置 + val selector = PictureSelector.create(this) + .openGallery(SelectMimeType.ofImage()) + .setImageEngine(LuckImageEngineImpl.createEngine()) + .setCameraInterceptListener { fragment, cameraMode, requestCode -> + fragment?.let { itFrag -> + val dirPath = PathUtils.getAppExternal().getAppCachePath(DevFinal.STR.SANDBOX) + FileUtils.createFolder(dirPath) + + // ========== + // = 点击拍照 = + // ========== + + val camera = SimpleCameraX.of() + camera.isAutoRotation(true) + camera.setCameraMode(cameraMode) + camera.setVideoFrameRate(25) + camera.setVideoBitRate(3 * 1024 * 1024) + camera.isDisplayRecordChangeTime(true) + camera.isManualFocusCameraPreview(false) + camera.isZoomCameraPreview(true) + camera.setOutputPathDir(dirPath) + camera.setImageEngine { context, url, imageView -> + Glide.with(context).load(url).into(imageView) + } + camera.start(this, itFrag, requestCode) + } + } + .setMaxSelectNum(1).isGif(false) + return MediaConfig().setLibCustomConfig(selector) +} + +/** + * detail: PictureSelector 相册图片加载引擎 + * @author luck + */ +class LuckImageEngineImpl private constructor() : ImageEngine { + + private object Holder { + val instance = LuckImageEngineImpl() + } + + companion object { + fun createEngine(): LuckImageEngineImpl { + return Holder.instance + } + } + + // =============== + // = ImageEngine = + // =============== + + /** + * 加载图片 + * @param context Context + * @param url 资源 URL + * @param imageView 图片承载控件 + */ + override fun loadImage( + context: Context, + url: String?, + imageView: ImageView + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .load(url) + .into(imageView) + } + + override fun loadImage( + context: Context, + imageView: ImageView, + url: String?, + maxWidth: Int, + maxHeight: Int + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .load(url) + .override(maxWidth, maxHeight) + .into(imageView) + } + + /** + * 加载相册目录封面 + * @param context Context + * @param url 图片路径 + * @param imageView 图片承载控件 + */ + override fun loadAlbumCover( + context: Context, + url: String?, + imageView: ImageView + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .asBitmap() + .load(url) + .override(180, 180) + .sizeMultiplier(0.5f) + .transform(CenterCrop(), RoundedCorners(8)) + .placeholder(R.drawable.ps_image_placeholder) + .into(imageView) + } + + /** + * 加载图片列表图片 + * @param context Context + * @param url 图片路径 + * @param imageView 图片承载控件 + */ + override fun loadGridImage( + context: Context, + url: String?, + imageView: ImageView + ) { + if (!ActivityCompatHelper.assertValidRequest(context)) { + return + } + Glide.with(context) + .load(url) + .override(200, 200) + .centerCrop() + .placeholder(R.drawable.ps_image_placeholder) + .into(imageView) + } + + override fun pauseRequests(context: Context) { + Glide.with(context).pauseRequests() + } + + override fun resumeRequests(context: Context) { + Glide.with(context).resumeRequests() + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/cache/CacheUse.java b/application/DevUtilsApp/src/main/java/utils_use/cache/CacheUse.java new file mode 100644 index 0000000000..0381b1d1c9 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/cache/CacheUse.java @@ -0,0 +1,88 @@ +package utils_use.cache; + +import java.io.File; +import java.io.Serializable; + +import dev.utils.app.PathUtils; +import dev.utils.app.cache.DevCache; + +import static dev.expand.engine.log.LogKt.log_dTag; + +/** + * detail: 缓存使用方法 + * @author Ttt + */ +public final class CacheUse { + + private CacheUse() { + } + + // 日志 TAG + private static final String TAG = CacheUse.class.getSimpleName(); + + /** + * 缓存使用方法 + */ + private void cacheUse() { + // 初始化 + CacheVo cacheVo = new CacheVo("测试持久化"); + // 打印信息 + log_dTag(TAG, null, "保存前: %s", cacheVo.toString()); + // 保存数据 + DevCache.newCache().put("ctv", cacheVo, -1); + // 重新获取 + CacheVo ctv = (CacheVo) DevCache.newCache().getSerializable("ctv"); + // 打印获取后的数据 + log_dTag(TAG, null, "保存后: %s", ctv.toString()); + // 设置保存有效时间 5秒 + DevCache.newCache().put("ctva", new CacheVo("测试有效时间"), 1); + + // 保存到指定文件夹下 + DevCache.newCache( + new File(PathUtils.getSDCard().getSDCardPath(), "Cache").getAbsolutePath() + ).put("key", "保存数据", -1); + + // 延迟后 + new Thread(() -> { + try { + // 延迟 1.5 已经过期再去获取 + Thread.sleep(1500); + // 获取数据 + CacheVo ctva = (CacheVo) DevCache.newCache().getSerializable("ctva"); + // 判断是否过期 + log_dTag(TAG, null, "是否过期: %s", (ctva == null)); + } catch (Exception ignored) { + } + }).start(); + } + + /** + * detail: 缓存实体类 + * @author Ttt + */ + static class CacheVo + implements Serializable { + + String name; + + long time; + + public CacheVo(String name) { + this.name = name; + this.time = System.currentTimeMillis(); + } + + public CacheVo( + String name, + long time + ) { + this.name = name; + this.time = time; + } + + @Override + public String toString() { + return "name: " + name + ", time: " + time; + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/logger/LogTools.java b/application/DevUtilsApp/src/main/java/utils_use/logger/LogTools.java new file mode 100644 index 0000000000..dbe67c5952 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/logger/LogTools.java @@ -0,0 +1,78 @@ +package utils_use.logger; + +/** + * detail: 日志工具类 + * @author Ttt + *
+ *     这个类的主要作用是把方法封装好, 然后实现传入实体类, 或者几个参数, 返回处理后的 String
+ *     不用在代码中, 拼接 String, 然后打印日志, 这样代码维护起来方便快捷, 都是 1-2 句代码实现具体功能调用
+ * 
+ */ +public class LogTools { + + /** + * 获取分享信息实体类数据 + * @param sMsgVo 分享实体类对象 + * @return + */ + public static String getShareMsgVoData(TestData.ShareMsgVo sMsgVo) { + StringBuilder builder = new StringBuilder(); + try { + if (sMsgVo != null) { + builder.append("打印分享信息实体类数据"); + builder.append("\n分享标题: ").append(sMsgVo.sTitle); + builder.append("\n分享文本: ").append(sMsgVo.sText); + builder.append("\n分享的图片路径: ").append(sMsgVo.sImagePath); + builder.append("\n标题网络链接: ").append(sMsgVo.sTitleUrl); + } else { + builder.append("sMsgVo 为 null"); + } + } catch (Exception ignored) { + } + return builder.toString(); + } + + /** + * 获取用户信息实体类数据 + * @param uInfoVo 用户信息实体类对象 + * @return + */ + public static String getUserInfoVoData(TestData.UserInfoVo uInfoVo) { + StringBuilder builder = new StringBuilder(); + try { + if (uInfoVo != null) { + builder.append("打印用户信息实体类数据"); + builder.append("\n用户名: ").append(uInfoVo.uName); + builder.append("\n用户密码: ").append(uInfoVo.uPwd); + builder.append("\n用户年龄: ").append(uInfoVo.uAge); + } else { + builder.append("uInfoVo 为 null"); + } + } catch (Exception ignored) { + } + return builder.toString(); + } + + /** + * 获取零散参数数据 + * @param uName 用户名字 + * @param sTitle 分享标题 + * @param uAge 用户年龄 + * @return + */ + public static String getScatteredData( + String uName, + String sTitle, + int uAge + ) { + StringBuilder builder = new StringBuilder(); + try { + builder.append("打印零散参数数据"); + builder.append("\nuName: ").append(uName); + builder.append("\nsTitle: ").append(sTitle); + builder.append("\nuAge: ").append(uAge); + } catch (Exception ignored) { + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/logger/LoggerUse.java b/application/DevUtilsApp/src/main/java/utils_use/logger/LoggerUse.java new file mode 100644 index 0000000000..c1782e462f --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/logger/LoggerUse.java @@ -0,0 +1,230 @@ +package utils_use.logger; + +import android.util.Log; + +import dev.utils.app.logger.DevLogger; +import dev.utils.app.logger.LogConfig; +import dev.utils.app.logger.LogLevel; +import dev.utils.common.DevCommonUtils; + +/** + * detail: 日志使用方法 + * @author Ttt + */ +public final class LoggerUse { + + private LoggerUse() { + } + + // 日志 TAG + private static final String LOG_TAG = LoggerUse.class.getSimpleName(); + + // ======= + // = 配置 = + // ======= + + /** + * 日志配置相关 + */ + private void logConfig() { + // = 在 BaseApplication 中调用 = + // 初始化日志配置 + LogConfig logConfig = new LogConfig(); + // 堆栈方法总数 ( 显示经过的方法 ) + logConfig.methodCount = 3; + // 堆栈方法索引偏移 (0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + logConfig.methodOffset = 0; + // 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + logConfig.outputMethodAll = false; + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + logConfig.displayThreadInfo = false; + // 是否排序日志 ( 格式化后 ) + logConfig.sortLog = false; // 是否美化日志, 边框包围 + // 日志级别 + logConfig.logLevel = LogLevel.DEBUG; + // 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + logConfig.tag = "BaseLog"; + // 进行初始化配置, 这样设置后, 默认全部日志都使用改配置, 特殊使用 DevLogger.other(config).d(xxx); + DevLogger.initialize(logConfig); + } + + // = 使用 = + + /** + * 日志使用方法 + */ + public static void loggerUse() { + // 测试打印 Log 所用时间 + testTime(); + + // 使用日志操作 + tempLog(); + } + + /** + * 测试打印 Log 所用时间 + */ + public static void testTime() { + // 拼接字符串 + StringBuilder builder = new StringBuilder(); + // 日志 TAG + final String tag = "CALC_TIME"; + // = + // 遍历次数 + int count = 100; + // 设置开始时间 + long sTime = System.currentTimeMillis(); + // 开始遍历 + for (int i = 0; i < count; i++) { + Log.d(tag, "A: " + (i + 1)); + } + // 拼接时间信息 + DevCommonUtils.timeRecord(builder, "正常系统 Log 耗时记录", sTime, System.currentTimeMillis()); + + // = + // 设置开始时间 + sTime = System.currentTimeMillis(); + // 开始遍历 + for (int i = 0; i < count; i++) { + // DevLogger.d("B: %s", (i + 1)); + DevLogger.dTag(tag, "B: %s", (i + 1)); + } + // 拼接时间信息 + DevCommonUtils.timeRecord(builder, "\nLogger 耗时记录", sTime, System.currentTimeMillis()); + + // = + // 初始化日志配置 + LogConfig logConfig = new LogConfig(); + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + logConfig.displayThreadInfo = true; + // 是否排序日志 ( 格式化后 ) + logConfig.sortLog = true; + // 日志级别 + logConfig.logLevel = LogLevel.DEBUG; + // 设置开始时间 + sTime = System.currentTimeMillis(); + // 开始遍历 + for (int i = 0; i < count; i++) { + DevLogger.other(logConfig).dTag(tag, "C: %s", (i + 1)); + } + // 拼接时间信息 + DevCommonUtils.timeRecord(builder, "\nLogger 耗时记录 - 使用自定义日志配置", sTime, System.currentTimeMillis()); + // 打印次数 + builder.append("\n\n打印次数: ").append(count); + // 打印耗时信息 + DevLogger.dTag(LOG_TAG, builder.toString()); + } + + /** + * 打印临时日志 + */ + public static void tempLog() { + // = 打印零散数据 = + TestData.ShareMsgVo sMsgVo = new TestData.ShareMsgVo(); + sMsgVo.sTitle = "分享 Blog"; + sMsgVo.sText = null; + sMsgVo.sImagePath = "http://t.jpg"; + sMsgVo.sTitleUrl = "http://www.test.com"; + + TestData.UserInfoVo uInfoVo = new TestData.UserInfoVo(); + uInfoVo.uName = "BlogRecord"; + uInfoVo.uPwd = "log_pwd"; + uInfoVo.uAge = 100; + + // 打印分享数据 + DevLogger.d(LogTools.getShareMsgVoData(sMsgVo)); + // 打印用户数据 + DevLogger.d(LogTools.getUserInfoVoData(uInfoVo)); + // 打印零散数据 + DevLogger.d(LogTools.getScatteredData(uInfoVo.uName, sMsgVo.sTitle, uInfoVo.uAge)); + + // = 打印测试数据 = + // 日志 TAG + final String tag = LOG_TAG; + // = 使用 BaseApplication 默认配置 = + // JSON 数组 + DevLogger.json("[" + TestData.JSON_WITH_NO_LINE_BREAK + "," + TestData.JSON_WITH_NO_LINE_BREAK + "]"); + // JSON 对象 + DevLogger.json(TestData.SMALL_SON_WITH_NO_LINE_BREAK); + // XML 数据 + DevLogger.xml(TestData.XML_DATA); + // = 其他 = + DevLogger.v("测试数据 - v"); + DevLogger.d("测试数据 - d"); + DevLogger.i("测试数据 - i"); + DevLogger.w("测试数据 - w"); + DevLogger.e("错误 - e"); + DevLogger.wtf("测试数据 - wtf"); + // = + DevLogger.vTag(tag, "测试数据 - v"); + DevLogger.vTag(tag, "测试数据 - d"); + try { + Class clazz = Class.forName("asdfasd"); + } catch (ClassNotFoundException e) { + DevLogger.e(e, "发生异常"); + } + // 占位符 ( 其他类型, 一样 ) + DevLogger.d("%s 测试占位符数据 d%s", "1.", "Format"); + // = + DevLogger.dTag(tag, "%s 测试占位符数据 d%s", "1.", "Format"); + + // = 使用自定义临时配置 = + // 自定义配置, 如下使用方式 + // DevLogger.other(logConfig).d(message); + // DevLogger.other(logConfig).dTag(tag, message); + // 打印不换行的日志信息 + DevLogger.other(LogConfig.getDebugLogConfig(tag)).vTag("Temp", "测试数据 - v"); + DevLogger.other(LogConfig.getDebugLogConfig(tag)).d("测试数据 - d"); + DevLogger.other(LogConfig.getDebugLogConfig(tag)).i("测试数据 - i"); + DevLogger.other(LogConfig.getDebugLogConfig(tag)).w("测试数据 - w"); + DevLogger.other(LogConfig.getDebugLogConfig(tag)).e("错误 - e"); + DevLogger.other(LogConfig.getDebugLogConfig(tag)).wtf(tag, "测试数据 - wtf"); + // = + DevLogger.other(LogConfig.getDebugLogConfig(tag, LogLevel.DEBUG)).json(TestData.SMALL_SON_WITH_NO_LINE_BREAK); + + // = + // 初始化日志配置 + LogConfig logConfig = new LogConfig(); + // 堆栈方法总数 ( 显示经过的方法 ) + logConfig.methodCount = 3; + // 堆栈方法索引偏移 (0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + logConfig.methodOffset = 0; + // 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + logConfig.outputMethodAll = false; + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + logConfig.displayThreadInfo = true; + // 是否排序日志 ( 格式化后 ) + logConfig.sortLog = true; + // 日志级别 + logConfig.logLevel = LogLevel.DEBUG; + // 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + logConfig.tag = "SAD"; + // 打印不换行的日志信息 + DevLogger.other(logConfig).e("new Config - e"); + + // = + // 使用方法 + LogConfig tempLogConfig = new LogConfig(); + // 堆栈方法总数 ( 显示经过的方法 ) + tempLogConfig.methodCount = 10; + // 堆栈方法索引偏移 (0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + tempLogConfig.methodOffset = 0; + // 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + tempLogConfig.outputMethodAll = false; + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + tempLogConfig.displayThreadInfo = true; + // 是否排序日志 ( 格式化后 ) + tempLogConfig.sortLog = true; + // 日志级别 + tempLogConfig.logLevel = LogLevel.DEBUG; + // 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + tempLogConfig.tag = "SAD"; + try { + String s = null; + s.indexOf("tempLogConfig"); + } catch (Exception e) { + // 打印不换行的日志信息 + DevLogger.other(tempLogConfig).e(e, "new Config - e"); + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/logger/TestData.java b/application/DevUtilsApp/src/main/java/utils_use/logger/TestData.java new file mode 100644 index 0000000000..10f514c7ce --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/logger/TestData.java @@ -0,0 +1,110 @@ +package utils_use.logger; + +/** + * detail: 测试数据 + * @author Ttt + */ +public class TestData { + + /** + * 分享信息实体类 ( 测试 ) + */ + public static class ShareMsgVo { + + public int sPlatform = 0; // 分享平台 + public String sTitle = null; // title 标题, 印象笔记、邮箱、信息、微信、人人网和 QQ 空间使用 + public String sText = null; // text 是分享文本, 所有平台都需要这个字段 + public String sImagePath = null; // imagePath 是图片的本地路径, Linked-In 以外的平台都支持此参数 + public String sImageUrl = null; // 分享图片路径 (QQ 空间需要 ) + public String sTitleUrl = null; // titleUrl 是标题的网络链接, 仅在人人网和 QQ 空间使用 + // = 不一定使用 = + public String sUrl = null; // url 仅在微信 ( 包括好友和朋友圈 ) 中使用 + public String sComment = null; // 分享的评论, 仅在人人网和 QQ 空间使用 + public String sSite = null; // site 是分享此内容的网站名称, 仅在 QQ 空间使用 + public String sSiteUrl = null; // siteUrl 是分享此内容的网站地址, 仅在 QQ 空间使用 + // = 微信平台分享类型 = + public int weChatATShareType = 0; // 分享类型 shareType(SHARE_IMAGE) ,shareType(SHARE_VIDEO), shareType(SHARE_WEBPAGE) + // 栈索引 ( 用于移除顶部栈 View) + public int sTaskId = -1; // -1 表示不需要移除栈例如单图片分享, 不会添加到栈, 自然也不需要移除 + // 分享模式 + public int sMode = -1; + } + + /** + * 用户信息实体类 ( 测试 ) + */ + public static class UserInfoVo { + + public String uName = null; // 用户名 + public String uPwd = null; // 用户密码 + public int uAge = -1; // 用户年龄 + } + + // = + + public static final String JSON_WITH_LINE_BREAK = "{\"widget\": {\n" + + " \"debug\": \"on\",\n" + + " \"window\": {\n" + + " \"title\": \"Sample Konfabulator Widget\",\n" + + " \"name\": \"main_window\",\n" + + " \"width\": 500,\n" + + " \"height\": 500\n" + + " },\n" + + " \"t.dev.image\": { \n" + + " \"src\": \"Images/Sun.png\",\n" + + " \"name\": \"sun1\",\n" + + " \"hOffset\": 250,\n" + + " \"vOffset\": 250,\n" + + " \"alignment\": \"center\"\n" + + " },\n" + + " \"t.dev.text\": {\n" + + " \"data\": \"Click Here\",\n" + + " \"size\": 36,\n" + + " \"style\": \"bold\",\n" + + " \"name\": \"text1\",\n" + + " \"hOffset\": 250,\n" + + " \"vOffset\": 100,\n" + + " \"alignment\": \"center\",\n" + + " \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" + + " }\n" + + "}} "; + + public static final String JSON_WITH_NO_LINE_BREAK = "{\"widget\": {" + + " \"debug\": \"on\"," + + " \"window\": {" + + " \"title\": \"Sample Konfabulator Widget\"," + + " \"name\": \"main_window\"," + + " \"width\": 500," + + " \"height\": 500" + + " },\n" + + " \"t.dev.image\": { " + + " \"src\": \"Images/Sun.png\"," + + " \"name\": \"sun1\"," + + " \"hOffset\": 250," + + " \"vOffset\": 250," + + " \"alignment\": \"center\"" + + " },\n" + + " \"t.dev.text\": {" + + " \"data\": \"Click Here\"," + + " \"size\": 36," + + " \"style\": \"bold\"," + + " \"name\": \"text1\"," + + " \"hOffset\": 250," + + " \"vOffset\": 100," + + " \"alignment\": \"center\"," + + " \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"" + + " }" + + "}} "; + + public static final String SMALL_SON_WITH_NO_LINE_BREAK = "{\"widget\": {" + + " \"debug\": \"on\"," + + " \"window\": {" + + " \"title\": \"Sample Konfabulator Widget\"," + + " \"name\": \"main_window\"," + + " \"width\": 500," + + " \"height\": 500" + + " }" + + "}} "; + + public static final String XML_DATA = "abcd"; +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/media/MediaUse.java b/application/DevUtilsApp/src/main/java/utils_use/media/MediaUse.java new file mode 100644 index 0000000000..ee086340a0 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/media/MediaUse.java @@ -0,0 +1,130 @@ +package utils_use.media; + +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.view.SurfaceView; + +import afkt.project.R; +import dev.utils.app.PathUtils; +import dev.utils.app.player.DevMediaManager; +import dev.utils.app.player.DevVideoPlayerControl; + +/** + * detail: 多媒体使用方法 + * @author Ttt + */ +public final class MediaUse { + + private MediaUse() { + } + + // 日志 TAG + private static final String TAG = MediaUse.class.getSimpleName(); + + /** + * 多媒体使用方法 + */ + private void mediaUse() { + // 设置 TAG, 打印日志使用 + DevMediaManager.getInstance().setTAG(TAG); + // 设置音量 + DevMediaManager.getInstance().setVolume(50); + // 设置流类型 + DevMediaManager.getInstance().setAudioStreamType(AudioManager.STREAM_MUSIC); + + // 获取播放音量 + DevMediaManager.getInstance().getVolume(); + // 获取当前播放的地址 + DevMediaManager.getInstance().getPlayUri(); + // 获取播放的资源 id + DevMediaManager.getInstance().getPlayRawId(); + // 获取 当前播放时间 + DevMediaManager.getInstance().getCurrentPosition(); + // 获取资源总时间 + DevMediaManager.getInstance().getDuration(); + // 获取播放进度百分比 + DevMediaManager.getInstance().getPlayPercent(); + // 获取 MediaPlayer 对象 + DevMediaManager.getInstance().getMediaPlayer(); + + // 获取播放的视频高度 + DevMediaManager.getInstance().getVideoHeight(); + // 获取播放的视频宽度 + DevMediaManager.getInstance().getVideoWidth(); + + // 是否播放中 + DevMediaManager.getInstance().isPlaying(); + // 停止操作 + DevMediaManager.getInstance().stop(); + // 暂停操作 + DevMediaManager.getInstance().pause(); + + // 设置事件监听 + DevMediaManager.getInstance().setMediaListener(new DevMediaManager.MediaListener() { + @Override + public void onPrepared() { + if (DevMediaManager.getInstance().isNotNullMediaPlayer()) { + // 播放操作 + DevMediaManager.getInstance().getMediaPlayer().start(); + } + } + + @Override + public void onCompletion() { + } + + @Override + public void onBufferingUpdate(int percent) { + } + + @Override + public void onSeekComplete() { + } + + @Override + public boolean onError( + int what, + int extra + ) { + return false; + } + + @Override + public void onVideoSizeChanged( + int width, + int height + ) { + } + }); + + // = + + // 播放音频 + DevMediaManager.getInstance().playPrepareRaw(R.raw.dev_beep); + DevMediaManager.getInstance().playPrepareAssets("a.mp3"); + DevMediaManager.getInstance().playPrepare(PathUtils.getSDCard().getSDCardPath() + "/a.mp3"); + DevMediaManager.getInstance().playPrepare("http://xxx.mp3"); + DevMediaManager.getInstance().playPrepare(new DevMediaManager.MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + mediaPlayer.setDataSource("xxx"); + } + }); // 自由设置信息 + + // = + + SurfaceView surfaceView = null; + // 播放视频 + DevVideoPlayerControl control = new DevVideoPlayerControl(surfaceView); + control.startPlayer(PathUtils.getSDCard().getSDCardPath() + "/video_3.mp4"); + control.startPlayer("http://xxx.mp4"); + control.startPlayer(new DevMediaManager.MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + mediaPlayer.setDataSource("xxx"); + } + }); // 自由设置信息 + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/record/FileRecordUse.java b/application/DevUtilsApp/src/main/java/utils_use/record/FileRecordUse.java new file mode 100644 index 0000000000..00aa704d0c --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/record/FileRecordUse.java @@ -0,0 +1,40 @@ +package utils_use.record; + +import dev.utils.app.PathUtils; +import dev.utils.common.assist.record.FileRecordUtils; +import dev.utils.common.assist.record.RecordConfig; + +/** + * detail: 日志、异常文件记录保存使用方法 + * @author Ttt + */ +public final class FileRecordUse { + + private FileRecordUse() { + } + + /** + * 日志、异常文件记录保存使用方法 + */ + public static void fileRecordUse() { + String storagePath = PathUtils.getAppExternal().getAppCachePath(); + + // 创建文件夹 ( 以秒为存储单位 ) 创建如: HH_23/MM_13/SS_01 对应文件夹, 并存储到该目录下 + RecordConfig config = RecordConfig.get(storagePath, "Main_Module", RecordConfig.TIME.HH); + + // 创建文件夹 ( 以小时为存储单位 ) 创建如: HH_23 对应文件夹, 并存储到该目录下 + RecordConfig config2 = RecordConfig.get(storagePath, "User_Module", RecordConfig.TIME.HH); + + // 存储到 storagePath/FileRecord/yyyy_MM_dd/FolderName/HH_number/MM_number/SS_number/ 内 + FileRecordUtils.record(config, "日志内容"); + + // 保存错误信息 + NullPointerException nullPointerException = new NullPointerException("报错啦, null 异常啊"); + + // 单独异常 + FileRecordUtils.record(config2, nullPointerException); + + // 异常 + 日志 + FileRecordUtils.record(config2, "第一个日志内容", nullPointerException, "其他日志内容"); + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/share/ShareUse.java b/application/DevUtilsApp/src/main/java/utils_use/share/ShareUse.java new file mode 100644 index 0000000000..3a8060d8f1 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/share/ShareUse.java @@ -0,0 +1,54 @@ +package utils_use.share; + +import android.content.Context; + +import dev.utils.app.share.SPUtils; +import dev.utils.app.share.SharedUtils; + +/** + * detail: SharedPreferences 使用方法 + * @author Ttt + */ +public final class ShareUse { + + private ShareUse() { + } + + public static void shareUse(Context context) { + // 具体实现方法 基于 PreferenceImpl 实现 + + // 存在可调用的方法 IPreference + + // SharedUtils 二次分装 SPUtils, 快捷调用 + + SharedUtils.put("aa", "aa"); + SharedUtils.put("ac", 123); + + // =========== + // = SPUtils = + // =========== + + // 想要自定义 模式, 名字等 + SPUtils.getPreference(context).put("aa", 1); + SPUtils.getPreference(context, "xxx").put("aa", 1); + SPUtils.getPreference(context, "xxxxx", Context.MODE_PRIVATE).put("aa", 1); + +// // 默认值如下 +// switch (type) { +// case INTEGER: +// return preferences.getInt(key, -1); +// case FLOAT: +// return preferences.getFloat(key, -1F); +// case BOOLEAN: +// return preferences.getBoolean(key, false); +// case LONG: +// return preferences.getLong(key, -1L); +// case STRING: +// return preferences.getString(key, null); +// case STRING_SET: +// return preferences.getStringSet(key, null); +// default: // 默认取出 String 类型的数据 +// return null; +// } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/snackbar/SnackbarUse.java b/application/DevUtilsApp/src/main/java/utils_use/snackbar/SnackbarUse.java new file mode 100644 index 0000000000..c4aa4e0f63 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/snackbar/SnackbarUse.java @@ -0,0 +1,414 @@ +package utils_use.snackbar; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +import androidx.fragment.app.Fragment; + +import com.google.android.material.snackbar.Snackbar; + +import afkt.project.R; +import dev.utils.app.SnackbarUtils; + +/** + * detail: Snackbar 使用方法 + * @author Ttt + */ +public final class SnackbarUse { + + private SnackbarUse() { + } + + // 日志 TAG + private static final String TAG = SnackbarUse.class.getSimpleName(); + + View view; + Window window; + Fragment fragment; + Activity activity; + View.OnClickListener clickListener; + + int viewId; + View targetView; + TextView newTextView; + + public void snackbarUse() { + + // ========================================== + // = 只能通过以下四种方式 获取 SnackbarUtils 对象 = + // ========================================== + + SnackbarUtils.with(view); + + SnackbarUtils.with(window); + + SnackbarUtils.with(fragment); + + SnackbarUtils.with(activity); + + // ============= + // = 获取相关方法 = + // ============= + + // = 获取 View = + + // 获取 Snackbar 底层 View + View snackbarView = SnackbarUtils.with(view).getSnackbarView(); + + // 获取 Snackbar TextView ( snackbar_text ) - 左侧 文本 TextView + TextView textView = SnackbarUtils.with(view).getTextView(); + + // 获取 Snackbar Action Button ( snackbar_action ) - 右侧 Button + Button actionButton = SnackbarUtils.with(view).getActionButton(); + + // = + + // 获取 Snackbar 对象 + Snackbar snackbar = SnackbarUtils.with(view).getSnackbar(); + + // 获取 View 阴影边距大小 - View 自带阴影 + int shadowMargin = SnackbarUtils.with(view).getShadowMargin(); + + // 获取 是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + boolean autoCalc = SnackbarUtils.with(view).isAutoCalc(); // 只有调用 above / bellow 该属性才有意义 + + // 获取 Snackbar 显示效果样式配置信息 + SnackbarUtils.StyleBuilder styleBuilder = SnackbarUtils.with(view).getStyle(); + + // ============= + // = 设置相关方法 = + // ============= + + // 设置 View 阴影边距大小 + SnackbarUtils.with(view).setShadowMargin(2); + + // 设置是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + SnackbarUtils.with(view).setAutoCalc(true); // 只有调用 above / bellow 该属性才有意义 + + // 设置 Snackbar 显示效果样式 + SnackbarUtils snackbarUtils = SnackbarUtils.with(view).setStyle(style); + + // = 快捷设置指定样式效果的 Snackbar = + + // 设置 Snackbar 显示效果为 当前 SnackbarUtils 对象使用的样式 + Snackbar snackbar1 = SnackbarUtils.with(view).setSnackbarStyle(snackbar); + + // 设置 Snackbar 显示效果为 自定义样式效果 + Snackbar snackbar2 = SnackbarUtils.with(view).setSnackbarStyle(snackbar, style); + + // = 设置 Action Button 文案等 = + + // 设置 Snackbar Action Button(snackbar_action) 文案 + SnackbarUtils.with(view).setAction(R.string.app_name); + + // 设置 Snackbar Action Button(snackbar_action) 文案 - 支持格式化字符串 + SnackbarUtils.with(view).setAction(R.string.app_name, "1", 2); + + // 设置 Snackbar Action Button(snackbar_action) 文案 + SnackbarUtils.with(view).setAction("撤销"); + + // 设置 Snackbar Action Button(snackbar_action) 文案 - 支持格式化字符串 + SnackbarUtils.with(view).setAction("撤销 %s", "3"); + + // 设置 Snackbar Action Button(snackbar_action) 文案以及点击事件 + SnackbarUtils.with(view).setAction(clickListener, R.string.app_name); + + // 设置 Snackbar Action Button(snackbar_action) 文案以及点击事件 + SnackbarUtils.with(view).setAction(clickListener, "撤销"); + + // = 设置 事件相关 = + + // 设置 Snackbar 展示完成 及 隐藏完成 的监听 + SnackbarUtils.with(view).setCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + super.onShown(sb); + // Snackbar 显示 + } + + @Override + public void onDismissed( + Snackbar transientBottomBar, + int event + ) { + super.onDismissed(transientBottomBar, event); + // Snackbar 关闭 + } + }); + + // ========== + // = 操作方法 = + // ========== + + // = 关闭 = + + // 关闭显示 Snackbar + SnackbarUtils.with(view).dismiss(); + + // 关闭显示 Snackbar, 但不销毁 Snackbar + SnackbarUtils.with(view).dismiss(false); + + // = 显示 - 支持 String、R.string.xx 以及格式化字符串 = + + // 显示 Short Snackbar + SnackbarUtils.with(view).showShort("已收藏该消息!"); + + // 显示 Long Snackbar + SnackbarUtils.with(view).showLong("已收藏该消息!"); + + // 显示 Indefinite Snackbar ( 无限时, 一直显示 ) + SnackbarUtils.with(view).showIndefinite("已收藏该消息!"); + + // = 显示区域 = + + // 设置是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + // setAutoCalc 只有调用 above / bellow 该属性才有意义 + + // 设置 Snackbar 显示在指定 View 的上方, 并且向上边距 20 + SnackbarUtils.with(view).above(targetView, 20); + + // 设置 Snackbar 显示在指定 View 的下方, 并且向下边距 5 + SnackbarUtils.with(view).bellow(targetView, 5); + + // 向 Snackbar 布局中添加 View (Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示 ) + SnackbarUtils.with(view).addView(newTextView, 0); + + // 向 Snackbar 布局中添加 View (Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示 ) + SnackbarUtils.with(view).addView(viewId, 1); + + // = 结合使用 = + + // 只有调用了 showXxx, 才会进行设置样式, 并且显示 + SnackbarUtils.with(view) + .addView(viewId, 0) + .setStyle(new NightStyle()) + .setAction(new View.OnClickListener() { + @Override + public void onClick(View v) { + + } + }, "撤销") + .bellow(targetView, 0) + .setCallback(new Snackbar.Callback() { + @Override + public void onDismissed( + Snackbar transientBottomBar, + int event + ) { + } + }).setAutoCalc(true) + .showShort("已收藏该消息!"); + + // = + + // 通过已有样式创建 StyleBuilder 并修改样式效果使用 + SnackbarUtils.StyleBuilder styleBuilder1 = new SnackbarUtils.StyleBuilder(style); + styleBuilder1.setActionColor(Color.RED); + SnackbarUtils.with(view).setStyle(styleBuilder1).showShort("已收藏该消息!"); + + // 修改默认样式中的部分展示效果 + SnackbarUtils snackbarUtils1 = SnackbarUtils.with(view); + SnackbarUtils.StyleBuilder styleBuilder2 = snackbarUtils1.getStyle(); + styleBuilder2.setActionColor(Color.BLACK); + snackbarUtils1.setStyle(styleBuilder2); + } + + /** + * detail: 自定义样式 - 可参照下方实现方法, 进行配置 + * @author Ttt + */ + static class NightStyle + extends SnackbarUtils.Style { + @Override + public int getTextColor() { + return Color.WHITE; + } + + @Override + public float getRootAlpha() { + return 0.5F; + } + } + + /** + * detail: 样式相关 + * @author Ttt + */ + SnackbarUtils.Style style = new SnackbarUtils.Style() { + + // ============ + // = RootView = + // ============ + + /** + * RootView 的重心 + * @return + */ + public int getRootGravity() { + return 0; + } + + /** + * RootView 背景圆角大小 + * @return + */ + public float getRootCornerRadius() { + return 0F; + } + + /** + * RootView 背景着色颜色 + * @return + */ + public int getRootBackgroundTintColor() { + return 0; + } + + /** + * RootView 背景图片 + * @return + */ + public Drawable getRootBackground() { + return null; + } + + /** + * RootView margin 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getRootMargin() { + return null; + } + + /** + * RootView 透明度 + * @return + */ + public float getRootAlpha() { + return 1.0F; + } + + // = snackbar_text TextView 相关 = + + /** + * TextView 的重心 + * @return + */ + public int getTextGravity() { + return 0; + } + + /** + * TextView 文本颜色 + * @return + */ + public int getTextColor() { + return 0; + } + + /** + * TextView 字体大小 + * @return + */ + public float getTextSize() { + return 0F; + } + + /** + * TextView 最大行数 + * @return + */ + public int getTextMaxLines() { + return 0; + } + + /** + * TextView Ellipsize 效果 + * @return + */ + public TextUtils.TruncateAt getTextEllipsize() { + return null; + } + + /** + * TextView 字体样式 + * @return + */ + public Typeface getTextTypeface() { + return null; + } + + /** + * TextView padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getTextPadding() { + return null; + } + + // = snackbar_action Button 相关 = + + /** + * Action Button 的重心 + * @return + */ + public int getActionGravity() { + return 0; + } + + /** + * Action Button 文本颜色 + * @return + */ + public int getActionColor() { + return 0; + } + + /** + * Action Button 字体大小 + * @return + */ + public float getActionSize() { + return 0F; + } + + /** + * Action Button padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getActionPadding() { + return null; + } + + /** + * Action Button 背景圆角大小 + * @return + */ + public float getActionCornerRadius() { + return 0F; + } + + /** + * Action Button 背景着色颜色 + * @return + */ + public int getActionBackgroundTintColor() { + return 0; + } + + /** + * Action Button 背景图片 + * @return + */ + public Drawable getActionBackground() { + return null; + } + }; +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/text/TextCalcUse.java b/application/DevUtilsApp/src/main/java/utils_use/text/TextCalcUse.java new file mode 100644 index 0000000000..08c3542c58 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/text/TextCalcUse.java @@ -0,0 +1,65 @@ +package utils_use.text; + +import android.content.Context; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import dev.utils.app.TextViewUtils; + +import static dev.expand.engine.log.LogKt.log_dTag; + +/** + * detail: 计算字体宽度、高度 + * @author Ttt + */ +public final class TextCalcUse { + + private TextCalcUse() { + } + + // 日志 TAG + private static final String TAG = TextCalcUse.class.getSimpleName(); + + /** + * 计算字体宽度、高度 + */ + protected void textCalcUse(Context context) { + LinearLayout vid_linear = null; + // 打印信息 + for (int i = 0, len = vid_linear.getChildCount(); i < len; i++) { + View view = vid_linear.getChildAt(i); + if (view instanceof TextView) { + printInfo((TextView) view); + } + } + +// // 计算第几位超过宽度 (600) +// int pos = TextViewUtils.calcTextWidth(vid_tv.getPaint(), "测试内容", 600); + + TextView tv = new TextView(context); + // 获取字体高度 + TextViewUtils.getTextHeight(tv); + // 获取字体大小 + TextViewUtils.reckonTextSizeByHeight(90); // 获取字体高度为 90 的字体大小 + } + + // = + + /** + * 打印信息 + */ + private void printInfo(TextView textView) { + StringBuilder builder = new StringBuilder(); + builder.append("内容: ").append(textView.getText().toString()); + builder.append("\n字体总数: ").append(textView.getText().length()); + builder.append("\n字体高度: ").append(TextViewUtils.getTextHeight(textView)); + builder.append("\n偏移高度: ").append(TextViewUtils.getTextTopOffsetHeight(textView)); + builder.append("\n字体宽度: ").append(TextViewUtils.getTextWidth(textView)); + builder.append("\n字体大小: ").append(textView.getTextSize()); + builder.append("\n计算字体大小: ").append(TextViewUtils.reckonTextSizeByHeight(TextViewUtils.getTextHeight(textView))); + builder.append("\n计算行数: ").append(TextViewUtils.calcTextLine(textView, textView.getMeasuredWidth())); + // 打印日志 + log_dTag(TAG, null, builder.toString()); + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/thread/ThreadUse.java b/application/DevUtilsApp/src/main/java/utils_use/thread/ThreadUse.java new file mode 100644 index 0000000000..5104f6808a --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/thread/ThreadUse.java @@ -0,0 +1,41 @@ +package utils_use.thread; + +import dev.utils.common.thread.DevThreadManager; +import dev.utils.common.thread.DevThreadPool; + +/** + * detail: 线程使用方法 + * @author Ttt + */ +public final class ThreadUse { + + private ThreadUse() { + } + + /** + * 线程使用方法 + */ + private void threadUse() { + Runnable runnable = new Runnable() { + @Override + public void run() { + + } + }; + + // = 优先判断 10 个线程数, 的线程池是否存在, 不存在则创建, 存在则复用 = + DevThreadManager.getInstance(10).execute(runnable); + + // 与上面 传入 int 是完全不同的线程池 + DevThreadManager.getInstance("10").execute(runnable); + + // 可以先增加配置 + DevThreadManager.putConfig("QPQP", new DevThreadPool(DevThreadPool.DevThreadPoolType.CALC_CPU)); + // 使用配置的信息 + DevThreadManager.getInstance("QPQP").execute(runnable); + + DevThreadManager.putConfig("QQQQQQ", 10); + // 使用配置的信息 + DevThreadManager.getInstance("QQQQQQ").execute(runnable); + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/toast/DevToastUse.java b/application/DevUtilsApp/src/main/java/utils_use/toast/DevToastUse.java new file mode 100644 index 0000000000..2dfd81defb --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/toast/DevToastUse.java @@ -0,0 +1,232 @@ +package utils_use.toast; + +import android.app.Application; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.View; + +import afkt.project.R; +import dev.utils.app.toast.toaster.DevToast; +import dev.utils.app.toast.toaster.IToast; + +/** + * detail: Toast 使用方法 + * @author Ttt + */ +public final class DevToastUse { + + private DevToastUse() { + } + + // ======= + // = 配置 = + // ======= + + private static final View view = null; + private static final int viewId = 0; + + /** + * Toast 配置相关 + */ + private void toastConfig(Application application) { + // 初始化 Toast - DevUtils 内部已经调用 + DevToast.initialize(application); // 必须调用 + + // 初始化 Toast 样式 - 全局通用 + // DevToast.initStyle(new IToast.Style() {}); // 可以实现 IToast.Style 接口, 参照 DefaultToastStyle + + // 当 Toast 内容为 null 时, 显示的内容 + DevToast.setNullText("text is null"); + + // 是否设置 Handler 显示 Toast - 默认 true, 支持子线程显示 Toast + DevToast.setUseHandler(true); + + // 设置文本长度限制, 超过设置的位数则 为 LENGTH_LONG + DevToast.setTextLength(15); + + // 支持自定义 View - 可不配置, 默认使用系统 Toast View + DevToast.setView(view); + DevToast.setView(viewId); + + // 配置 Toast 过滤, 判断是否显示 Toast、以及内容处理 + // DevToast.initToastFilter(new IToast.Filter() {}); + + // 恢复默认配置 + DevToast.reset(); + } + + // = 使用 = + + /** + * Toast 使用方法 + */ + public static void toastUse() { + // 显示 Toast + DevToast.show(view); + DevToast.show(R.string.app_name); + DevToast.show("Toast"); // initStyle - Toast + + // 使用特殊样式 - 默认统一全局样式, style 则为 这个 Toast 单独为这个样式 + DevToast.style(new TempStyle()).show("tempStyle - Toast"); + + // 获取 当前全局使用的样式 + DevToast.getToastStyle(); + + // 获取默认样式 + DevToast.defaultStyle(); + + // 关闭正在显示的 Toast + DevToast.cancel(); + + // 不同效果, 可通过实现 IToast.Style 自定义样式并初始化 initStyle/style 查看效果 + + // 默认不设置 initStyle, 会使用 defaultStyle + } + + /** + * 自定义实现样式 + */ + private static class TempStyle + implements IToast.Style { + + /** + * Toast 的重心 + * @return + */ + @Override + public int getGravity() { + return 0; + } + + /** + * X 轴偏移 + * @return + */ + @Override + public int getXOffset() { + return 0; + } + + /** + * Y 轴偏移 + * @return + */ + @Override + public int getYOffset() { + return 0; + } + + /** + * 获取水平边距 + * @return + */ + @Override + public int getHorizontalMargin() { + return 0; + } + + /** + * 获取垂直边距 + * @return + */ + @Override + public int getVerticalMargin() { + return 0; + } + + /** + * Toast Z 轴坐标阴影 + * @return + */ + @Override + public int getZ() { + return 0; + } + + /** + * 圆角大小 + * @return + */ + @Override + public float getCornerRadius() { + return 5F; + } + + /** + * 背景着色颜色 + * @return + */ + @Override + public int getBackgroundTintColor() { + return 0xB2000000; + } + + /** + * 背景图片 + * @return + */ + @Override + public Drawable getBackground() { + return null; + } + + // = TextView 相关 = + + /** + * 文本颜色 + * @return + */ + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 字体大小 + * @return + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 最大行数 + * @return + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * Ellipsize 效果 + * @return + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 字体样式 + * @return + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * TextView padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + @Override + public int[] getPadding() { + return new int[]{25, 10, 25, 10}; + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/toast/ToastTintUse.java b/application/DevUtilsApp/src/main/java/utils_use/toast/ToastTintUse.java new file mode 100644 index 0000000000..ff1d7e33dc --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/toast/ToastTintUse.java @@ -0,0 +1,211 @@ +package utils_use.toast; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.Gravity; + +import dev.utils.app.toast.ToastTintUtils; + +/** + * detail: Toast 使用方法 + * @author Ttt + */ +public final class ToastTintUse { + + private ToastTintUse() { + } + + // ======= + // = 配置 = + // ======= + + private static ToastTintUtils.Style style; + private static Drawable iconDrawable; + + /** + * Toast 配置相关 + */ + private void toastConfig() { + // 获取默认样式 + ToastTintUtils.getDefaultStyle(); + + // 获取 Normal 样式 + ToastTintUtils.getNormalStyle(); + // 设置 Normal 样式 + ToastTintUtils.setNormalStyle(style); + + // 获取 Error 样式 + ToastTintUtils.getErrorStyle(); + // 设置 Error 样式 + ToastTintUtils.setErrorStyle(style); + // 获取 Error 样式 小图标 + ToastTintUtils.getErrorDrawable(); + + // 获取 Warning 样式 + ToastTintUtils.getWarningStyle(); + // 设置 Warning 样式 + ToastTintUtils.setWarningStyle(style); + // 获取 Warning 样式 小图标 + ToastTintUtils.getWarningDrawable(); + + // 获取 Success 样式 + ToastTintUtils.getSuccessStyle(); + // 设置 Success 样式 + ToastTintUtils.setSuccessStyle(style); + // 获取 Success 样式 小图标 + ToastTintUtils.getSuccessDrawable(); + + // 获取 Info 样式 + ToastTintUtils.getInfoStyle(); + // 设置 Info 样式 + ToastTintUtils.setInfoStyle(style); + // 获取 Info 样式 小图标 + ToastTintUtils.getInfoDrawable(); + + // 是否使用配置 - 如 Gravity、HorizontalMargin、VerticalMargin + ToastTintUtils.setUseConfig(true); + + // 设置 Gravity + ToastTintUtils.setGravity(Gravity.BOTTOM, 0, 0); + + // 当 Toast 内容为 null 时, 显示的内容 + ToastTintUtils.setNullText("text is null"); + + // 是否设置 Handler 显示 Toast - 默认 true, 支持子线程显示 Toast + ToastTintUtils.setUseHandler(true); + + // 设置 HorizontalMargin、VerticalMargin 边距 + ToastTintUtils.setMargin(0F, 0F); + + // 配置 Toast 过滤, 判断是否显示 Toast、以及内容处理 + // ToastTintUtils.setToastFilter(new ToastTintUtils.Filter() {}); + + // 恢复默认配置 + ToastTintUtils.reset(); + } + + // = 使用 = + + /** + * Toast 使用方法 + */ + public static void toastUse() { + // 显示 Success 样式 Toast + ToastTintUtils.success("Success Style Toast"); + + // 显示 Error 样式 Toast + ToastTintUtils.error("Error Style Toast"); + + // 显示 Info 样式 Toast + ToastTintUtils.info("Info Style Toast"); + + // 显示 Normal 样式 Toast + ToastTintUtils.normal("Normal Style Toast"); + + // 显示 Warning 样式 Toast + ToastTintUtils.warning("Warning Style Toast"); + + // 显示 Custom 样式 Toast + ToastTintUtils.custom(style, "Custom Style Toast"); + + // 显示 Custom 样式 Toast, 自定义小图标 + ToastTintUtils.custom(new TempStyle(), "Custom Style Toast", iconDrawable); + } + + /** + * 自定义实现样式 + * {@link ToastTintUtils.SuccessStyle} + * {@link ToastTintUtils.ErrorStyle} + * {@link ToastTintUtils.InfoStyle} + * {@link ToastTintUtils.WarningStyle} + * {@link ToastTintUtils.NormalStyle} + * {@link ToastTintUtils.DefaultStyle} + */ + private static class TempStyle + implements ToastTintUtils.Style { + + /** + * 文本颜色 + * @return + */ + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 字体大小 + * @return + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 背景着色颜色 + * @return + */ + @Override + public int getBackgroundTintColor() { + return 0; + } + + /** + * 背景图片 + * @return + */ + @Override + public Drawable getBackground() { + return null; + } + + /** + * 最大行数 + * @return + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * Ellipsize 效果 + * @return + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 字体样式 + * @return + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * 获取图标着色颜色 + * @return + */ + @Override + public int getTintIconColor() { + return Color.WHITE; + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return false; + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/java/utils_use/wifi/WifiHotUse.java b/application/DevUtilsApp/src/main/java/utils_use/wifi/WifiHotUse.java new file mode 100644 index 0000000000..aca94e2576 --- /dev/null +++ b/application/DevUtilsApp/src/main/java/utils_use/wifi/WifiHotUse.java @@ -0,0 +1,77 @@ +package utils_use.wifi; + +import android.Manifest; +import android.net.wifi.WifiConfiguration; +import android.os.Build; + +import androidx.annotation.RequiresPermission; + +import dev.utils.app.wifi.WifiHotUtils; + +/** + * detail: Wifi 热点使用方法 + * @author Ttt + */ +public final class WifiHotUse { + + private WifiHotUse() { + } + + /** + * Wifi 热点使用方法 + */ + @RequiresPermission(allOf = { + Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + private void wifiHotUse() { + + // 所需权限 + // + // + // + // + // + // + + final WifiHotUtils wifiHotUtils = new WifiHotUtils(); + + // 有密码 + WifiConfiguration wifiConfiguration = WifiHotUtils.createWifiConfigToAp("WifiHot_AP", "123456789"); + + // 无密码 + wifiConfiguration = WifiHotUtils.createWifiConfigToAp("WifiHot_AP", null); + + // 开启热点 ( 兼容 8.0) 7.1 跳转到热点页面, 需手动开启 ( 但是配置信息使用上面的 WifiConfig) + wifiHotUtils.startWifiAp(wifiConfiguration); + + // 关闭热点 + wifiHotUtils.closeWifiAp(); + + // = 8.0 特殊处理 = + + // 8.0 以后热点是针对应用开启, 并且必须强制使用随机生成的 WifiConfig 信息, 无法替换 + + // 如果应用开启了热点, 然后后台清空内存, 对应的热点会关闭, 应用开启的热点是系统随机的, 不影响系统设置中的热点配置信息 + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + wifiHotUtils.setOnWifiAPListener(new WifiHotUtils.OnWifiAPListener() { + @Override + public void onStarted(WifiConfiguration wifiConfig) { + String ssid = wifiHotUtils.getApWifiSSID(); + String pwd = wifiHotUtils.getApWifiPwd(); + } + + @Override + public void onStopped() { + + } + + @Override + public void onFailed(int reason) { + + } + }); + } + } +} \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-base/layout/base_toolbar.xml b/application/DevUtilsApp/src/main/res-base/layout/base_toolbar.xml new file mode 100644 index 0000000000..3c248d9d48 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-base/layout/base_toolbar.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-base/layout/base_view_button.xml b/application/DevUtilsApp/src/main/res-base/layout/base_view_button.xml new file mode 100644 index 0000000000..df433123d4 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-base/layout/base_view_button.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-base/layout/base_view_recyclerview.xml b/application/DevUtilsApp/src/main/res-base/layout/base_view_recyclerview.xml new file mode 100644 index 0000000000..cfb03a0d9e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-base/layout/base_view_recyclerview.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-base/layout/base_view_textview.xml b/application/DevUtilsApp/src/main/res-base/layout/base_view_textview.xml new file mode 100644 index 0000000000..5df6c834e8 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-base/layout/base_view_textview.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-framework/layout/activity_article_mvvm.xml b/application/DevUtilsApp/src/main/res-framework/layout/activity_article_mvvm.xml new file mode 100644 index 0000000000..f0d3b5aa89 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-framework/layout/activity_article_mvvm.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-framework/layout/adapter_article.xml b/application/DevUtilsApp/src/main/res-framework/layout/adapter_article.xml new file mode 100644 index 0000000000..cffebcea5d --- /dev/null +++ b/application/DevUtilsApp/src/main/res-framework/layout/adapter_article.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_api.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_api.xml new file mode 100644 index 0000000000..daff7bf651 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_api.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_callback.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_callback.xml new file mode 100644 index 0000000000..ff6cdb5780 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_activity_result_callback.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_add_contact.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_add_contact.xml new file mode 100644 index 0000000000..e96ae1599e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_add_contact.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_common_tips.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_common_tips.xml new file mode 100644 index 0000000000..7ed805e6d5 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_common_tips.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_wallpaper.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_wallpaper.xml new file mode 100644 index 0000000000..b503ff97c8 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_wallpaper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/activity_webview.xml b/application/DevUtilsApp/src/main/res-function/layout/activity_webview.xml new file mode 100644 index 0000000000..0cfe54759e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/activity_webview.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-function/layout/layout_floating.xml b/application/DevUtilsApp/src/main/res-function/layout/layout_floating.xml new file mode 100644 index 0000000000..43a0bcfa24 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-function/layout/layout_floating.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-lib/layout/activity_data_store.xml b/application/DevUtilsApp/src/main/res-lib/layout/activity_data_store.xml new file mode 100644 index 0000000000..41a3326e4e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-lib/layout/activity_data_store.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_black.webp b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_black.webp new file mode 100644 index 0000000000..2719f9d81a Binary files /dev/null and b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_black.webp differ diff --git a/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_grey.webp b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_grey.webp new file mode 100644 index 0000000000..826874b614 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_add_grey.webp differ diff --git a/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_close.webp b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_close.webp new file mode 100644 index 0000000000..40c66a5965 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_close.webp differ diff --git a/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_black.webp b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_black.webp new file mode 100644 index 0000000000..9546bcb083 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_black.webp differ diff --git a/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_grey.webp b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_grey.webp new file mode 100644 index 0000000000..50c9bb2ec4 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-sku/drawable-xxhdpi/sku_icon_reduce_grey.webp differ diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_edit_cursor_color_style.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_edit_cursor_color_style.xml new file mode 100644 index 0000000000..49e295d645 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_edit_cursor_color_style.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_gradient_92ba37_bfe076_corners10.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_gradient_92ba37_bfe076_corners10.xml new file mode 100644 index 0000000000..96848afb3e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_gradient_92ba37_bfe076_corners10.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_add_icon.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_add_icon.xml new file mode 100644 index 0000000000..e689eaf78b --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_add_icon.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_reduce_icon.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_reduce_icon.xml new file mode 100644 index 0000000000..cad49ed016 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_selector_reduce_icon.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_f6f6f6_corners6.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_f6f6f6_corners6.xml new file mode 100644 index 0000000000..ec6c08bc2f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_f6f6f6_corners6.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_92ba37_corners6.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_92ba37_corners6.xml new file mode 100644 index 0000000000..819a7fbfed --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_92ba37_corners6.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_f6f6f6_corners6_dash.xml b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_f6f6f6_corners6_dash.xml new file mode 100644 index 0000000000..35cca3c193 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/drawable/sku_shape_stroked_f6f6f6_corners6_dash.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec.xml b/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec.xml new file mode 100644 index 0000000000..66404f3bb1 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec_item.xml b/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec_item.xml new file mode 100644 index 0000000000..30e35350c6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/layout/sku_adapter_spec_item.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/layout/sku_dialog_spec.xml b/application/DevUtilsApp/src/main/res-sku/layout/sku_dialog_spec.xml new file mode 100644 index 0000000000..26cabddaf8 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/layout/sku_dialog_spec.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-sku/values/colors.xml b/application/DevUtilsApp/src/main/res-sku/values/colors.xml new file mode 100644 index 0000000000..fcafc523fa --- /dev/null +++ b/application/DevUtilsApp/src/main/res-sku/values/colors.xml @@ -0,0 +1,11 @@ + + + #f6f6f6 + #f5f9f2 + #bfe076 + #92ba37 + #c7c7c7 + #bdb9b8 + #999999 + #282828 + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_annulus.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_annulus.png new file mode 100644 index 0000000000..dd41baa0ac Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_annulus.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_fan.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_fan.png new file mode 100644 index 0000000000..671be7898b Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_fan.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_leaf.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_leaf.png new file mode 100644 index 0000000000..d2e629240a Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_leaf.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_loading.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_loading.png new file mode 100644 index 0000000000..a87f7803ff Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_electric_loading.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_empty.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_empty.png new file mode 100644 index 0000000000..cbb9654cb3 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_empty.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_error.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_error.png new file mode 100644 index 0000000000..c4d9841234 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_error.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_off.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_off.png new file mode 100644 index 0000000000..afa109ea51 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_off.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_on.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_on.png new file mode 100644 index 0000000000..e2a64180c5 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_flashlight_on.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_hexagon.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_hexagon.png new file mode 100644 index 0000000000..5c29d142e3 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_hexagon.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_image.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_image.png new file mode 100644 index 0000000000..4763316d9a Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_image.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_1.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_1.png new file mode 100644 index 0000000000..110f27bf96 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_1.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_2.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_2.png new file mode 100644 index 0000000000..5678b4c75a Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_2.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_3.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_3.png new file mode 100644 index 0000000000..ad5fcdfbd0 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_3.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_4.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_4.png new file mode 100644 index 0000000000..c8606d1b23 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_4.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_5.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_5.png new file mode 100644 index 0000000000..42684b73c8 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_live_brow_5.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_refresh.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_refresh.png new file mode 100644 index 0000000000..f1c739bf1e Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_refresh.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_square.png b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_square.png new file mode 100644 index 0000000000..1fbd69da67 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/icon_square.png differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/img_commodity.jpg b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/img_commodity.jpg new file mode 100644 index 0000000000..a0d8343312 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui-widget/drawable-xxhdpi/img_commodity.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui-widget/drawable/shape_flashlight_selector.xml b/application/DevUtilsApp/src/main/res-ui-widget/drawable/shape_flashlight_selector.xml new file mode 100644 index 0000000000..a58abd0d52 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/drawable/shape_flashlight_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_corner_label.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_corner_label.xml new file mode 100644 index 0000000000..8cbc13377c --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_corner_label.xml @@ -0,0 +1,319 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flip_card.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flip_card.xml new file mode 100644 index 0000000000..8dbbe1a826 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flip_card.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flow_like.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flow_like.xml new file mode 100644 index 0000000000..5e7f9ad48f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_flow_like.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_grid_item_decoration.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_grid_item_decoration.xml new file mode 100644 index 0000000000..9e4ad9a94c --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_grid_item_decoration.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_line.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_line.xml new file mode 100644 index 0000000000..74ce7b9db3 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_line.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_linear_item_decoration.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_linear_item_decoration.xml new file mode 100644 index 0000000000..6d098a8b1e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_linear_item_decoration.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_progressbar.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_progressbar.xml new file mode 100644 index 0000000000..525a9d5337 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_progressbar.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_scan_shape.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_scan_shape.xml new file mode 100644 index 0000000000..3659abccba --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_scan_shape.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_assist.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_assist.xml new file mode 100644 index 0000000000..334d77b652 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_assist.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_pager.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_pager.xml new file mode 100644 index 0000000000..f7dc948410 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_view_pager.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wave_view.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wave_view.xml new file mode 100644 index 0000000000..e88c73e141 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wave_view.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wrap.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wrap.xml new file mode 100644 index 0000000000..d14fcd64ea --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/activity_wrap.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_horizontal_text.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_horizontal_text.xml new file mode 100644 index 0000000000..ec8b5580ab --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_horizontal_text.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_vertical_text.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_vertical_text.xml new file mode 100644 index 0000000000..ec8b5580ab --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_grid_vertical_text.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_horizontal_text.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_horizontal_text.xml new file mode 100644 index 0000000000..ce5b85edef --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_horizontal_text.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_vertical_text.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_vertical_text.xml new file mode 100644 index 0000000000..c39a011691 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_linear_vertical_text.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_recycler_loading.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_recycler_loading.xml new file mode 100644 index 0000000000..73423f7e1f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/adapter_recycler_loading.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/include_grid_item_decoration_assist.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/include_grid_item_decoration_assist.xml new file mode 100644 index 0000000000..9dcebea94a --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/include_grid_item_decoration_assist.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/include_linear_item_decoration_assist.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/include_linear_item_decoration_assist.xml new file mode 100644 index 0000000000..1549e5c478 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/include_linear_item_decoration_assist.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_content.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_content.xml new file mode 100644 index 0000000000..bb1c9cb19d --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_content.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_custom.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_custom.xml new file mode 100644 index 0000000000..5cafd342fe --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_custom.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_empty.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_empty.xml new file mode 100644 index 0000000000..f950acbed8 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_empty.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_error.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_error.xml new file mode 100644 index 0000000000..f101b047d8 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_error.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading.xml new file mode 100644 index 0000000000..100ea22948 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading2.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading2.xml new file mode 100644 index 0000000000..95ae745b7f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading2.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading3.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading3.xml new file mode 100644 index 0000000000..f948aa8f54 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_loading3.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_failed.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_failed.xml new file mode 100644 index 0000000000..aede1c539d --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_failed.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_loading.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_loading.xml new file mode 100644 index 0000000000..578c042a67 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_assist_recy_loading.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui-widget/layout/view_pager_item_view.xml b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_pager_item_view.xml new file mode 100644 index 0000000000..38b1d9af88 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui-widget/layout/view_pager_item_view.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/color/selector_tab_text_color.xml b/application/DevUtilsApp/src/main/res-ui/color/selector_tab_text_color.xml new file mode 100644 index 0000000000..6b23d2ee3f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/color/selector_tab_text_color.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper.png new file mode 100644 index 0000000000..4b58162f37 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper2.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper2.png new file mode 100644 index 0000000000..8bad8a86b4 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/bg_wallpaper2.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_normal.9.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_normal.9.png new file mode 100644 index 0000000000..c12879e80b Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_normal.9.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_pressed.9.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_pressed.9.png new file mode 100644 index 0000000000..211649d1e1 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/btn_pressed.9.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_selected.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_selected.png new file mode 100644 index 0000000000..7103cb8b32 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_selected.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_add_red.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_add_red.png new file mode 100644 index 0000000000..062e55f3dc Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_add_red.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_white.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_white.png new file mode 100644 index 0000000000..0c5dd35c57 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_shop_cart_white.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_selected.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_selected.png new file mode 100644 index 0000000000..dd1fc183c8 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_selected.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_unselected.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_unselected.png new file mode 100644 index 0000000000..1dce657e73 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_star_unselected.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_unselected.png b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_unselected.png new file mode 100644 index 0000000000..b0a3571779 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/drawable-xxhdpi/icon_unselected.png differ diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_black_top_r10.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_black_top_r10.xml new file mode 100644 index 0000000000..b97f24e2b0 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_black_top_r10.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_blue_top_r10.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_blue_top_r10.xml new file mode 100644 index 0000000000..1888fb9994 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_blue_top_r10.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_white_r10.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_white_r10.xml new file mode 100644 index 0000000000..722d7a9f13 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_bg_white_r10.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_multiselect_selector.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_multiselect_selector.xml new file mode 100644 index 0000000000..33787abc49 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_multiselect_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_normal.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_normal.xml new file mode 100644 index 0000000000..06a75e5cdb --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_normal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_pressed.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_pressed.xml new file mode 100644 index 0000000000..dd27b2e5d9 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_pressed.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_selector.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_selector.xml new file mode 100644 index 0000000000..1b6e0933e0 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_left_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_normal.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_normal.xml new file mode 100644 index 0000000000..4cef391b51 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_normal.xml @@ -0,0 +1,14 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_pressed.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_pressed.xml new file mode 100644 index 0000000000..3edfd7c9e4 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_pressed.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_selector.xml b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_selector.xml new file mode 100644 index 0000000000..fe60390b63 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/drawable/shape_tab_right_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet.xml new file mode 100644 index 0000000000..4b8b92c28a --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet_dialog.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet_dialog.xml new file mode 100644 index 0000000000..a6f0bf4a65 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_bottom_sheet_dialog.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture.xml new file mode 100644 index 0000000000..2c09982072 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_grid.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_grid.xml new file mode 100644 index 0000000000..0d9ebcb3e2 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_grid.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_list.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_list.xml new file mode 100644 index 0000000000..891ffb6029 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_list.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_recy.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_recy.xml new file mode 100644 index 0000000000..b4145d9678 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_recy.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_web.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_web.xml new file mode 100644 index 0000000000..0cfe54759e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_capture_picture_web.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_chip.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_chip.xml new file mode 100644 index 0000000000..db776c4727 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_chip.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_gpu_filter.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_gpu_filter.xml new file mode 100644 index 0000000000..a56738b18f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_gpu_filter.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_palette.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_palette.xml new file mode 100644 index 0000000000..ee960b845f --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_palette.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_create.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_create.xml new file mode 100644 index 0000000000..7c229a9ae6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_create.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_image.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_image.xml new file mode 100644 index 0000000000..61cc1f068c --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_qrcode_image.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_shapeable_image_view.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_shapeable_image_view.xml new file mode 100644 index 0000000000..0210e9caeb --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_shapeable_image_view.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_status_bar.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_status_bar.xml new file mode 100644 index 0000000000..97735af48e --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_status_bar.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_text_calc.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_text_calc.xml new file mode 100644 index 0000000000..2b6d7c1209 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_text_calc.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_textview.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_textview.xml new file mode 100644 index 0000000000..d4feb00f26 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_textview.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_ui_effect.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_ui_effect.xml new file mode 100644 index 0000000000..d7327c8aa9 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_ui_effect.xml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/activity_viewpager2.xml b/application/DevUtilsApp/src/main/res-ui/layout/activity_viewpager2.xml new file mode 100644 index 0000000000..34e3da6d05 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/activity_viewpager2.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_capture_picture.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_capture_picture.xml new file mode 100644 index 0000000000..24baefe7a6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_capture_picture.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_article.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_article.xml new file mode 100644 index 0000000000..01af02448b --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_article.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner.xml new file mode 100644 index 0000000000..3675b1ab60 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner_image.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner_image.xml new file mode 100644 index 0000000000..f03626eea6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_banner_image.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_classify.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_classify.xml new file mode 100644 index 0000000000..e49d89f1d3 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_classify.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_header_footer.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_header_footer.xml new file mode 100644 index 0000000000..1b558d0b92 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_header_footer.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_shapeable_image.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_shapeable_image.xml new file mode 100644 index 0000000000..a4244297fc --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_concat_shapeable_image.xml @@ -0,0 +1,15 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_flexbox_text.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_flexbox_text.xml new file mode 100644 index 0000000000..fa84c4c84b --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_flexbox_text.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_edits.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_edits.xml new file mode 100644 index 0000000000..7fd36b07f0 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_edits.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_shop_cart_anim.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_shop_cart_anim.xml new file mode 100644 index 0000000000..ba6a89f5f6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_shop_cart_anim.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_sticky.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_sticky.xml new file mode 100644 index 0000000000..5ff172ae67 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_item_sticky.xml @@ -0,0 +1,38 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_linear_snap.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_linear_snap.xml new file mode 100644 index 0000000000..399d56a331 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_linear_snap.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_multi_select.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_multi_select.xml new file mode 100644 index 0000000000..b267f4ef6a --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_multi_select.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_pager_snap.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_pager_snap.xml new file mode 100644 index 0000000000..3219c2d856 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_pager_snap.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/adapter_sticky_view.xml b/application/DevUtilsApp/src/main/res-ui/layout/adapter_sticky_view.xml new file mode 100644 index 0000000000..0264bde0fd --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/adapter_sticky_view.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/dialog_bottom_sheet.xml b/application/DevUtilsApp/src/main/res-ui/layout/dialog_bottom_sheet.xml new file mode 100644 index 0000000000..6a3bec5195 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/dialog_bottom_sheet.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/fragment_pager.xml b/application/DevUtilsApp/src/main/res-ui/layout/fragment_pager.xml new file mode 100644 index 0000000000..9d736929da --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/fragment_pager.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/fragment_palette.xml b/application/DevUtilsApp/src/main/res-ui/layout/fragment_palette.xml new file mode 100644 index 0000000000..573c449ce4 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/fragment_palette.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating.xml b/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating.xml new file mode 100644 index 0000000000..f0a9904d71 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating.xml @@ -0,0 +1,48 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating_anim_view.xml b/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating_anim_view.xml new file mode 100644 index 0000000000..13a7b4741c --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/include_bottom_shop_cart_floating_anim_view.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/include_chip.xml b/application/DevUtilsApp/src/main/res-ui/layout/include_chip.xml new file mode 100644 index 0000000000..8f60caacbb --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/include_chip.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/layout/tab_item_view.xml b/application/DevUtilsApp/src/main/res-ui/layout/tab_item_view.xml new file mode 100644 index 0000000000..89c8fdc9a5 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/layout/tab_item_view.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/menu/menu_pager.xml b/application/DevUtilsApp/src/main/res-ui/menu/menu_pager.xml new file mode 100644 index 0000000000..0bfab65e9d --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/menu/menu_pager.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_1.jpg b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_1.jpg new file mode 100644 index 0000000000..85386e3752 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_1.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_2.jpg b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_2.jpg new file mode 100644 index 0000000000..73e42bd961 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_2.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_3.jpg b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_3.jpg new file mode 100644 index 0000000000..e287ebd314 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_3.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_4.jpg b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_4.jpg new file mode 100644 index 0000000000..23066ea6f4 Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_4.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_5.jpg b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_5.jpg new file mode 100644 index 0000000000..d7a611833b Binary files /dev/null and b/application/DevUtilsApp/src/main/res-ui/raw/wallpaper_5.jpg differ diff --git a/application/DevUtilsApp/src/main/res-ui/values/colors.xml b/application/DevUtilsApp/src/main/res-ui/values/colors.xml new file mode 100644 index 0000000000..8e84acf8c9 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/values/colors.xml @@ -0,0 +1,4 @@ + + + #ff2e42 + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res-ui/values/styles.xml b/application/DevUtilsApp/src/main/res-ui/values/styles.xml new file mode 100644 index 0000000000..65542b85c3 --- /dev/null +++ b/application/DevUtilsApp/src/main/res-ui/values/styles.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable-xxhdpi/icon_tips.png b/application/DevUtilsApp/src/main/res/drawable-xxhdpi/icon_tips.png new file mode 100644 index 0000000000..8c8ff63055 Binary files /dev/null and b/application/DevUtilsApp/src/main/res/drawable-xxhdpi/icon_tips.png differ diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_border_image.xml b/application/DevUtilsApp/src/main/res/drawable/shape_border_image.xml new file mode 100644 index 0000000000..fcd3e8de43 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_border_image.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_bottom.xml b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_bottom.xml new file mode 100644 index 0000000000..be5f2f84fb --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_bottom.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_left.xml b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_left.xml new file mode 100644 index 0000000000..cbe51710b0 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_left.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_right.xml b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_right.xml new file mode 100644 index 0000000000..bc7333969d --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_right.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_top.xml b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_top.xml new file mode 100644 index 0000000000..b128cc1094 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_layer_triangle_top.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular.xml b/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular.xml new file mode 100644 index 0000000000..a2d02a9261 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular2.xml b/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular2.xml new file mode 100644 index 0000000000..fb96af2c20 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/shape_oval_annular2.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/drawable/touch.xml b/application/DevUtilsApp/src/main/res/drawable/touch.xml new file mode 100644 index 0000000000..27fdba95a6 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/drawable/touch.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/layout/activity_main.xml b/application/DevUtilsApp/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000000..4857efde9d --- /dev/null +++ b/application/DevUtilsApp/src/main/res/layout/activity_main.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/layout/state_layout_fail.xml b/application/DevUtilsApp/src/main/res/layout/state_layout_fail.xml new file mode 100644 index 0000000000..77c30db555 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/layout/state_layout_fail.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/layout/state_layout_ing.xml b/application/DevUtilsApp/src/main/res/layout/state_layout_ing.xml new file mode 100644 index 0000000000..e9849cf191 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/layout/state_layout_ing.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/layout/state_layout_no_data.xml b/application/DevUtilsApp/src/main/res/layout/state_layout_no_data.xml new file mode 100644 index 0000000000..77c30db555 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/layout/state_layout_no_data.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon.png b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon.png new file mode 100644 index 0000000000..93e166e52e Binary files /dev/null and b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon.png differ diff --git a/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher.png b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher.png new file mode 100644 index 0000000000..ddf6635d30 Binary files /dev/null and b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher.png differ diff --git a/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher_round.png b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher_round.png new file mode 100644 index 0000000000..3e63f580e2 Binary files /dev/null and b/application/DevUtilsApp/src/main/res/mipmap-xxxhdpi/icon_launcher_round.png differ diff --git a/application/DevUtilsApp/src/main/res/values-zh/strings.xml b/application/DevUtilsApp/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000000..00436b3e0e --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values-zh/strings.xml @@ -0,0 +1,6 @@ + + + DevUtils + 开发项目 + 复制成功 + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/attrs.xml b/application/DevUtilsApp/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..cb3d3ebe68 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/attrs.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/colors.xml b/application/DevUtilsApp/src/main/res/values/colors.xml new file mode 100644 index 0000000000..1b5173691d --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3f51b5 + #303f9f + #ff4081 + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/strings.xml b/application/DevUtilsApp/src/main/res/values/strings.xml new file mode 100644 index 0000000000..c81a01339c --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + + DevUtils + DevUtils + copy suc + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/styles.xml b/application/DevUtilsApp/src/main/res/values/styles.xml new file mode 100644 index 0000000000..e8003ca24c --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/styles.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/unified.xml b/application/DevUtilsApp/src/main/res/values/unified.xml new file mode 100644 index 0000000000..2f7a9f4f51 --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/unified.xml @@ -0,0 +1,1687 @@ + + + + + 0.5dp + + + 1.0dp + + + 3.0dp + + + 176.0dp + + + + + 0.0dp + 0.5dp + 1.0dp + 1.5dp + 2.0dp + 2.5dp + 3.0dp + 3.5dp + 4.0dp + 4.5dp + 5.0dp + 5.5dp + 6.0dp + 6.5dp + 7.0dp + 7.5dp + 8.0dp + 8.5dp + 9.0dp + 9.5dp + 10.0dp + 10.5dp + 11.0dp + 11.5dp + 12.0dp + 12.5dp + 13.0dp + 13.5dp + 14.0dp + 14.5dp + 15.0dp + 15.5dp + 16.0dp + 16.5dp + 17.0dp + 17.5dp + 18.0dp + 18.5dp + 19.0dp + 19.5dp + 20.0dp + 20.5dp + 21.0dp + 21.5dp + 22.0dp + 22.5dp + 23.0dp + 23.5dp + 24.0dp + 24.5dp + 25.0dp + 25.5dp + 26.0dp + 26.5dp + 27.0dp + 27.5dp + 28.0dp + 28.5dp + 29.0dp + 29.5dp + 30.0dp + 30.5dp + 31.0dp + 31.5dp + 32.0dp + 32.5dp + 33.0dp + 33.5dp + 34.0dp + 34.5dp + 35.0dp + 35.5dp + 36.0dp + 36.5dp + 37.0dp + 37.5dp + 38.0dp + 38.5dp + 39.0dp + 39.5dp + 40.0dp + 40.5dp + 41.0dp + 41.5dp + 42.0dp + 42.5dp + 43.0dp + 43.5dp + 44.0dp + 44.5dp + 45.0dp + 45.5dp + 46.0dp + 46.5dp + 47.0dp + 47.5dp + 48.0dp + 48.5dp + 49.0dp + 49.5dp + 50.0dp + 50.5dp + 51.0dp + 51.5dp + 52.0dp + 52.5dp + 53.0dp + 53.5dp + 54.0dp + 54.5dp + 55.0dp + 55.5dp + 56.0dp + 56.5dp + 57.0dp + 57.5dp + 58.0dp + 58.5dp + 59.0dp + 59.5dp + 60.0dp + 60.5dp + 61.0dp + 61.5dp + 62.0dp + 62.5dp + 63.0dp + 63.5dp + 64.0dp + 64.5dp + 65.0dp + 65.5dp + 66.0dp + 66.5dp + 67.0dp + 67.5dp + 68.0dp + 68.5dp + 69.0dp + 69.5dp + 70.0dp + 70.5dp + 71.0dp + 71.5dp + 72.0dp + 72.5dp + 73.0dp + 73.5dp + 74.0dp + 74.5dp + 75.0dp + 75.5dp + 76.0dp + 76.5dp + 77.0dp + 77.5dp + 78.0dp + 78.5dp + 79.0dp + 79.5dp + 80.0dp + 80.5dp + 81.0dp + 81.5dp + 82.0dp + 82.5dp + 83.0dp + 83.5dp + 84.0dp + 84.5dp + 85.0dp + 85.5dp + 86.0dp + 86.5dp + 87.0dp + 87.5dp + 88.0dp + 88.5dp + 89.0dp + 89.5dp + 90.0dp + 90.5dp + 91.0dp + 91.5dp + 92.0dp + 92.5dp + 93.0dp + 93.5dp + 94.0dp + 94.5dp + 95.0dp + 95.5dp + 96.0dp + 96.5dp + 97.0dp + 97.5dp + 98.0dp + 98.5dp + 99.0dp + 99.5dp + 100.0dp + 100.5dp + 101.0dp + 101.5dp + 102.0dp + 102.5dp + 103.0dp + 103.5dp + 104.0dp + 104.5dp + 105.0dp + 105.5dp + 106.0dp + 106.5dp + 107.0dp + 107.5dp + 108.0dp + 108.5dp + 109.0dp + 109.5dp + 110.0dp + 110.5dp + 111.0dp + 111.5dp + 112.0dp + 112.5dp + 113.0dp + 113.5dp + 114.0dp + 114.5dp + 115.0dp + 115.5dp + 116.0dp + 116.5dp + 117.0dp + 117.5dp + 118.0dp + 118.5dp + 119.0dp + 119.5dp + 120.0dp + 120.5dp + 121.0dp + 121.5dp + 122.0dp + 122.5dp + 123.0dp + 123.5dp + 124.0dp + 124.5dp + 125.0dp + 125.5dp + 126.0dp + 126.5dp + 127.0dp + 127.5dp + 128.0dp + 128.5dp + 129.0dp + 129.5dp + 130.0dp + 130.5dp + 131.0dp + 131.5dp + 132.0dp + 132.5dp + 133.0dp + 133.5dp + 134.0dp + 134.5dp + 135.0dp + 135.5dp + 136.0dp + 136.5dp + 137.0dp + 137.5dp + 138.0dp + 138.5dp + 139.0dp + 139.5dp + 140.0dp + 140.5dp + 141.0dp + 141.5dp + 142.0dp + 142.5dp + 143.0dp + 143.5dp + 144.0dp + 144.5dp + 145.0dp + 145.5dp + 146.0dp + 146.5dp + 147.0dp + 147.5dp + 148.0dp + 148.5dp + 149.0dp + 149.5dp + 150.0dp + 150.5dp + 151.0dp + 151.5dp + 152.0dp + 152.5dp + 153.0dp + 153.5dp + 154.0dp + 154.5dp + 155.0dp + 155.5dp + 156.0dp + 156.5dp + 157.0dp + 157.5dp + 158.0dp + 158.5dp + 159.0dp + 159.5dp + 160.0dp + 160.5dp + 161.0dp + 161.5dp + 162.0dp + 162.5dp + 163.0dp + 163.5dp + 164.0dp + 164.5dp + 165.0dp + 165.5dp + 166.0dp + 166.5dp + 167.0dp + 167.5dp + 168.0dp + 168.5dp + 169.0dp + 169.5dp + 170.0dp + 170.5dp + 171.0dp + 171.5dp + 172.0dp + 172.5dp + 173.0dp + 173.5dp + 174.0dp + 174.5dp + 175.0dp + 175.5dp + 176.0dp + 176.5dp + 177.0dp + 177.5dp + 178.0dp + 178.5dp + 179.0dp + 179.5dp + 180.0dp + 180.5dp + 181.0dp + 181.5dp + 182.0dp + 182.5dp + 183.0dp + 183.5dp + 184.0dp + 184.5dp + 185.0dp + 185.5dp + 186.0dp + 186.5dp + 187.0dp + 187.5dp + 188.0dp + 188.5dp + 189.0dp + 189.5dp + 190.0dp + 190.5dp + 191.0dp + 191.5dp + 192.0dp + 192.5dp + 193.0dp + 193.5dp + 194.0dp + 194.5dp + 195.0dp + 195.5dp + 196.0dp + 196.5dp + 197.0dp + 197.5dp + 198.0dp + 198.5dp + 199.0dp + 199.5dp + 200.0dp + 200.5dp + 201.0dp + 201.5dp + 202.0dp + 202.5dp + 203.0dp + 203.5dp + 204.0dp + 204.5dp + 205.0dp + 205.5dp + 206.0dp + 206.5dp + 207.0dp + 207.5dp + 208.0dp + 208.5dp + 209.0dp + 209.5dp + 210.0dp + 210.5dp + 211.0dp + 211.5dp + 212.0dp + 212.5dp + 213.0dp + 213.5dp + 214.0dp + 214.5dp + 215.0dp + 215.5dp + 216.0dp + 216.5dp + 217.0dp + 217.5dp + 218.0dp + 218.5dp + 219.0dp + 219.5dp + 220.0dp + 220.5dp + 221.0dp + 221.5dp + 222.0dp + 222.5dp + 223.0dp + 223.5dp + 224.0dp + 224.5dp + 225.0dp + 225.5dp + 226.0dp + 226.5dp + 227.0dp + 227.5dp + 228.0dp + 228.5dp + 229.0dp + 229.5dp + 230.0dp + 230.5dp + 231.0dp + 231.5dp + 232.0dp + 232.5dp + 233.0dp + 233.5dp + 234.0dp + 234.5dp + 235.0dp + 235.5dp + 236.0dp + 236.5dp + 237.0dp + 237.5dp + 238.0dp + 238.5dp + 239.0dp + 239.5dp + 240.0dp + 240.5dp + 241.0dp + 241.5dp + 242.0dp + 242.5dp + 243.0dp + 243.5dp + 244.0dp + 244.5dp + 245.0dp + 245.5dp + 246.0dp + 246.5dp + 247.0dp + 247.5dp + 248.0dp + 248.5dp + 249.0dp + 249.5dp + 250.0dp + 250.5dp + 251.0dp + 251.5dp + 252.0dp + 252.5dp + 253.0dp + 253.5dp + 254.0dp + 254.5dp + 255.0dp + 255.5dp + 256.0dp + 256.5dp + 257.0dp + 257.5dp + 258.0dp + 258.5dp + 259.0dp + 259.5dp + 260.0dp + 260.5dp + 261.0dp + 261.5dp + 262.0dp + 262.5dp + 263.0dp + 263.5dp + 264.0dp + 264.5dp + 265.0dp + 265.5dp + 266.0dp + 266.5dp + 267.0dp + 267.5dp + 268.0dp + 268.5dp + 269.0dp + 269.5dp + 270.0dp + 270.5dp + 271.0dp + 271.5dp + 272.0dp + 272.5dp + 273.0dp + 273.5dp + 274.0dp + 274.5dp + 275.0dp + 275.5dp + 276.0dp + 276.5dp + 277.0dp + 277.5dp + 278.0dp + 278.5dp + 279.0dp + 279.5dp + 280.0dp + 280.5dp + 281.0dp + 281.5dp + 282.0dp + 282.5dp + 283.0dp + 283.5dp + 284.0dp + 284.5dp + 285.0dp + 285.5dp + 286.0dp + 286.5dp + 287.0dp + 287.5dp + 288.0dp + 288.5dp + 289.0dp + 289.5dp + 290.0dp + 290.5dp + 291.0dp + 291.5dp + 292.0dp + 292.5dp + 293.0dp + 293.5dp + 294.0dp + 294.5dp + 295.0dp + 295.5dp + 296.0dp + 296.5dp + 297.0dp + 297.5dp + 298.0dp + 298.5dp + 299.0dp + 299.5dp + 300.0dp + 300.5dp + 301.0dp + 301.5dp + 302.0dp + 302.5dp + 303.0dp + 303.5dp + 304.0dp + 304.5dp + 305.0dp + 305.5dp + 306.0dp + 306.5dp + 307.0dp + 307.5dp + 308.0dp + 308.5dp + 309.0dp + 309.5dp + 310.0dp + 310.5dp + 311.0dp + 311.5dp + 312.0dp + 312.5dp + 313.0dp + 313.5dp + 314.0dp + 314.5dp + 315.0dp + 315.5dp + 316.0dp + 316.5dp + 317.0dp + 317.5dp + 318.0dp + 318.5dp + 319.0dp + 319.5dp + 320.0dp + 320.5dp + 321.0dp + 321.5dp + 322.0dp + 322.5dp + 323.0dp + 323.5dp + 324.0dp + 324.5dp + 325.0dp + 325.5dp + 326.0dp + 326.5dp + 327.0dp + 327.5dp + 328.0dp + 328.5dp + 329.0dp + 329.5dp + 330.0dp + 330.5dp + 331.0dp + 331.5dp + 332.0dp + 332.5dp + 333.0dp + 333.5dp + 334.0dp + 334.5dp + 335.0dp + 335.5dp + 336.0dp + 336.5dp + 337.0dp + 337.5dp + 338.0dp + 338.5dp + 339.0dp + 339.5dp + 340.0dp + 340.5dp + 341.0dp + 341.5dp + 342.0dp + 342.5dp + 343.0dp + 343.5dp + 344.0dp + 344.5dp + 345.0dp + 345.5dp + 346.0dp + 346.5dp + 347.0dp + 347.5dp + 348.0dp + 348.5dp + 349.0dp + 349.5dp + 350.0dp + 350.5dp + 351.0dp + 351.5dp + 352.0dp + 352.5dp + 353.0dp + 353.5dp + 354.0dp + 354.5dp + 355.0dp + 355.5dp + 356.0dp + 356.5dp + 357.0dp + 357.5dp + 358.0dp + 358.5dp + 359.0dp + 359.5dp + 360.0dp + 360.5dp + 361.0dp + 361.5dp + 362.0dp + 362.5dp + 363.0dp + 363.5dp + 364.0dp + 364.5dp + 365.0dp + 365.5dp + 366.0dp + 366.5dp + 367.0dp + 367.5dp + 368.0dp + 368.5dp + 369.0dp + 369.5dp + 370.0dp + 370.5dp + 371.0dp + 371.5dp + 372.0dp + 372.5dp + 373.0dp + 373.5dp + 374.0dp + 374.5dp + 375.0dp + 375.5dp + 376.0dp + 376.5dp + 377.0dp + 377.5dp + 378.0dp + 378.5dp + 379.0dp + 379.5dp + 380.0dp + 380.5dp + 381.0dp + 381.5dp + 382.0dp + 382.5dp + 383.0dp + 383.5dp + 384.0dp + 384.5dp + 385.0dp + 385.5dp + 386.0dp + 386.5dp + 387.0dp + 387.5dp + 388.0dp + 388.5dp + 389.0dp + 389.5dp + 390.0dp + 390.5dp + 391.0dp + 391.5dp + 392.0dp + 392.5dp + 393.0dp + 393.5dp + 394.0dp + 394.5dp + 395.0dp + 395.5dp + 396.0dp + 396.5dp + 397.0dp + 397.5dp + 398.0dp + 398.5dp + 399.0dp + 399.5dp + 400.0dp + 400.5dp + 401.0dp + 401.5dp + 402.0dp + 402.5dp + 403.0dp + 403.5dp + 404.0dp + 404.5dp + 405.0dp + 405.5dp + 406.0dp + 406.5dp + 407.0dp + 407.5dp + 408.0dp + 408.5dp + 409.0dp + 409.5dp + 410.0dp + 410.5dp + 411.0dp + 411.5dp + 412.0dp + 412.5dp + 413.0dp + 413.5dp + 414.0dp + 414.5dp + 415.0dp + 415.5dp + 416.0dp + 416.5dp + 417.0dp + 417.5dp + 418.0dp + 418.5dp + 419.0dp + 419.5dp + 420.0dp + 420.5dp + 421.0dp + 421.5dp + 422.0dp + 422.5dp + 423.0dp + 423.5dp + 424.0dp + 424.5dp + 425.0dp + 425.5dp + 426.0dp + 426.5dp + 427.0dp + 427.5dp + 428.0dp + 428.5dp + 429.0dp + 429.5dp + 430.0dp + 430.5dp + 431.0dp + 431.5dp + 432.0dp + 432.5dp + 433.0dp + 433.5dp + 434.0dp + 434.5dp + 435.0dp + 435.5dp + 436.0dp + 436.5dp + 437.0dp + 437.5dp + 438.0dp + 438.5dp + 439.0dp + 439.5dp + 440.0dp + 440.5dp + 441.0dp + 441.5dp + 442.0dp + 442.5dp + 443.0dp + 443.5dp + 444.0dp + 444.5dp + 445.0dp + 445.5dp + 446.0dp + 446.5dp + 447.0dp + 447.5dp + 448.0dp + 448.5dp + 449.0dp + 449.5dp + 450.0dp + 450.5dp + 451.0dp + 451.5dp + 452.0dp + 452.5dp + 453.0dp + 453.5dp + 454.0dp + 454.5dp + 455.0dp + 455.5dp + 456.0dp + 456.5dp + 457.0dp + 457.5dp + 458.0dp + 458.5dp + 459.0dp + 459.5dp + 460.0dp + 460.5dp + 461.0dp + 461.5dp + 462.0dp + 462.5dp + 463.0dp + 463.5dp + 464.0dp + 464.5dp + 465.0dp + 465.5dp + 466.0dp + 466.5dp + 467.0dp + 467.5dp + 468.0dp + 468.5dp + 469.0dp + 469.5dp + 470.0dp + 470.5dp + 471.0dp + 471.5dp + 472.0dp + 472.5dp + 473.0dp + 473.5dp + 474.0dp + 474.5dp + 475.0dp + 475.5dp + 476.0dp + 476.5dp + 477.0dp + 477.5dp + 478.0dp + 478.5dp + 479.0dp + 479.5dp + 480.0dp + 480.5dp + 481.0dp + 481.5dp + 482.0dp + 482.5dp + 483.0dp + 483.5dp + 484.0dp + 484.5dp + 485.0dp + 485.5dp + 486.0dp + 486.5dp + 487.0dp + 487.5dp + 488.0dp + 488.5dp + 489.0dp + 489.5dp + 490.0dp + 490.5dp + 491.0dp + 491.5dp + 492.0dp + 492.5dp + 493.0dp + 493.5dp + 494.0dp + 494.5dp + 495.0dp + 495.5dp + 496.0dp + 496.5dp + 497.0dp + 497.5dp + 498.0dp + 498.5dp + 499.0dp + 499.5dp + 500.0dp + 500.5dp + 501.0dp + 501.5dp + 502.0dp + 502.5dp + 503.0dp + 503.5dp + 504.0dp + 504.5dp + 505.0dp + 505.5dp + 506.0dp + 506.5dp + 507.0dp + 507.5dp + 508.0dp + 508.5dp + 509.0dp + 509.5dp + 510.0dp + 510.5dp + 511.0dp + 511.5dp + 512.0dp + 512.5dp + 513.0dp + 513.5dp + 514.0dp + 514.5dp + 515.0dp + 515.5dp + 516.0dp + 516.5dp + 517.0dp + 517.5dp + 518.0dp + 518.5dp + 519.0dp + 519.5dp + 520.0dp + 520.5dp + 521.0dp + 521.5dp + 522.0dp + 522.5dp + 523.0dp + 523.5dp + 524.0dp + 524.5dp + 525.0dp + 525.5dp + 526.0dp + 526.5dp + 527.0dp + 527.5dp + 528.0dp + 528.5dp + 529.0dp + 529.5dp + 530.0dp + 530.5dp + 531.0dp + 531.5dp + 532.0dp + 532.5dp + 533.0dp + 533.5dp + 534.0dp + 534.5dp + 535.0dp + 535.5dp + 536.0dp + 536.5dp + 537.0dp + 537.5dp + 538.0dp + 538.5dp + 539.0dp + 539.5dp + 540.0dp + 540.5dp + 541.0dp + 541.5dp + 542.0dp + 542.5dp + 543.0dp + 543.5dp + 544.0dp + 544.5dp + 545.0dp + 545.5dp + 546.0dp + 546.5dp + 547.0dp + 547.5dp + 548.0dp + 548.5dp + 549.0dp + 549.5dp + 550.0dp + 550.5dp + 551.0dp + 551.5dp + 552.0dp + 552.5dp + 553.0dp + 553.5dp + 554.0dp + 554.5dp + 555.0dp + 555.5dp + 556.0dp + 556.5dp + 557.0dp + 557.5dp + 558.0dp + 558.5dp + 559.0dp + 559.5dp + 560.0dp + 560.5dp + 561.0dp + 561.5dp + 562.0dp + 562.5dp + 563.0dp + 563.5dp + 564.0dp + 564.5dp + 565.0dp + 565.5dp + 566.0dp + 566.5dp + 567.0dp + 567.5dp + 568.0dp + 568.5dp + 569.0dp + 569.5dp + 570.0dp + 570.5dp + 571.0dp + 571.5dp + 572.0dp + 572.5dp + 573.0dp + 573.5dp + 574.0dp + 574.5dp + 575.0dp + 575.5dp + 576.0dp + 576.5dp + 577.0dp + 577.5dp + 578.0dp + 578.5dp + 579.0dp + 579.5dp + 580.0dp + 580.5dp + 581.0dp + 581.5dp + 582.0dp + 582.5dp + 583.0dp + 583.5dp + 584.0dp + 584.5dp + 585.0dp + 585.5dp + 586.0dp + 586.5dp + 587.0dp + 587.5dp + 588.0dp + 588.5dp + 589.0dp + 589.5dp + 590.0dp + 590.5dp + 591.0dp + 591.5dp + 592.0dp + 592.5dp + 593.0dp + 593.5dp + 594.0dp + 594.5dp + 595.0dp + 595.5dp + 596.0dp + 596.5dp + 597.0dp + 597.5dp + 598.0dp + 598.5dp + 599.0dp + 599.5dp + 600.0dp + 600.5dp + 601.0dp + 601.5dp + 602.0dp + 602.5dp + 603.0dp + 603.5dp + 604.0dp + 604.5dp + 605.0dp + 605.5dp + 606.0dp + 606.5dp + 607.0dp + 607.5dp + 608.0dp + 608.5dp + 609.0dp + 609.5dp + 610.0dp + 610.5dp + 611.0dp + 611.5dp + 612.0dp + 612.5dp + 613.0dp + 613.5dp + 614.0dp + 614.5dp + 615.0dp + 615.5dp + 616.0dp + 616.5dp + 617.0dp + 617.5dp + 618.0dp + 618.5dp + 619.0dp + 619.5dp + 620.0dp + 620.5dp + 621.0dp + 621.5dp + 622.0dp + 622.5dp + 623.0dp + 623.5dp + 624.0dp + 624.5dp + 625.0dp + 625.5dp + 626.0dp + 626.5dp + 627.0dp + 627.5dp + 628.0dp + 628.5dp + 629.0dp + 629.5dp + 630.0dp + 630.5dp + 631.0dp + 631.5dp + 632.0dp + 632.5dp + 633.0dp + 633.5dp + 634.0dp + 634.5dp + 635.0dp + 635.5dp + 636.0dp + 636.5dp + 637.0dp + 637.5dp + 638.0dp + 638.5dp + 639.0dp + 639.5dp + 640.0dp + 640.5dp + 641.0dp + 641.5dp + 642.0dp + 642.5dp + 643.0dp + 643.5dp + 644.0dp + 644.5dp + 645.0dp + 645.5dp + 646.0dp + 646.5dp + 647.0dp + 647.5dp + 648.0dp + 648.5dp + 649.0dp + 649.5dp + 650.0dp + 650.5dp + 651.0dp + 651.5dp + 652.0dp + 652.5dp + 653.0dp + 653.5dp + 654.0dp + 654.5dp + 655.0dp + 655.5dp + 656.0dp + 656.5dp + 657.0dp + 657.5dp + 658.0dp + 658.5dp + 659.0dp + 659.5dp + 660.0dp + 660.5dp + 661.0dp + 661.5dp + 662.0dp + 662.5dp + 663.0dp + 663.5dp + 664.0dp + 664.5dp + 665.0dp + 665.5dp + 666.0dp + 666.5dp + 667.0dp + 667.5dp + 668.0dp + 668.5dp + 669.0dp + 669.5dp + 670.0dp + 670.5dp + 671.0dp + 671.5dp + 672.0dp + 672.5dp + 673.0dp + 673.5dp + 674.0dp + 674.5dp + 675.0dp + 675.5dp + 676.0dp + 676.5dp + 677.0dp + 677.5dp + 678.0dp + 678.5dp + 679.0dp + 679.5dp + 680.0dp + 680.5dp + 681.0dp + 681.5dp + 682.0dp + 682.5dp + 683.0dp + 683.5dp + 684.0dp + 684.5dp + 685.0dp + 685.5dp + 686.0dp + 686.5dp + 687.0dp + 687.5dp + 688.0dp + 688.5dp + 689.0dp + 689.5dp + 690.0dp + 690.5dp + 691.0dp + 691.5dp + 692.0dp + 692.5dp + 693.0dp + 693.5dp + 694.0dp + 694.5dp + 695.0dp + 695.5dp + 696.0dp + 696.5dp + 697.0dp + 697.5dp + 698.0dp + 698.5dp + 699.0dp + 699.5dp + 700.0dp + 700.5dp + 701.0dp + 701.5dp + 702.0dp + 702.5dp + 703.0dp + 703.5dp + 704.0dp + 704.5dp + 705.0dp + 705.5dp + 706.0dp + 706.5dp + 707.0dp + 707.5dp + 708.0dp + 708.5dp + 709.0dp + 709.5dp + 710.0dp + 710.5dp + 711.0dp + 711.5dp + 712.0dp + 712.5dp + 713.0dp + 713.5dp + 714.0dp + 714.5dp + 715.0dp + 715.5dp + 716.0dp + 716.5dp + 717.0dp + 717.5dp + 718.0dp + 718.5dp + 719.0dp + 719.5dp + 720.0dp + 720.5dp + 721.0dp + 721.5dp + 722.0dp + 722.5dp + 723.0dp + 723.5dp + 724.0dp + 724.5dp + 725.0dp + 725.5dp + 726.0dp + 726.5dp + 727.0dp + 727.5dp + 728.0dp + 728.5dp + 729.0dp + 729.5dp + 730.0dp + 730.5dp + 731.0dp + 731.5dp + 732.0dp + 732.5dp + 733.0dp + 733.5dp + 734.0dp + 734.5dp + 735.0dp + 735.5dp + 736.0dp + 736.5dp + 737.0dp + 737.5dp + 738.0dp + 738.5dp + 739.0dp + 739.5dp + 740.0dp + 740.5dp + 741.0dp + 741.5dp + 742.0dp + 742.5dp + 743.0dp + 743.5dp + 744.0dp + 744.5dp + 745.0dp + 745.5dp + 746.0dp + 746.5dp + 747.0dp + 747.5dp + 748.0dp + 748.5dp + 749.0dp + 749.5dp + 750.0dp + + + 0.0sp + 0.5sp + 1.0sp + 1.5sp + 2.0sp + 2.5sp + 3.0sp + 3.5sp + 4.0sp + 4.5sp + 5.0sp + 5.5sp + 6.0sp + 6.5sp + 7.0sp + 7.5sp + 8.0sp + 8.5sp + 9.0sp + 9.5sp + 10.0sp + 10.5sp + 11.0sp + 11.5sp + 12.0sp + 12.5sp + 13.0sp + 13.5sp + 14.0sp + 14.5sp + 15.0sp + 15.5sp + 16.0sp + 16.5sp + 17.0sp + 17.5sp + 18.0sp + 18.5sp + 19.0sp + 19.5sp + 20.0sp + 20.5sp + 21.0sp + 21.5sp + 22.0sp + 22.5sp + 23.0sp + 23.5sp + 24.0sp + 24.5sp + 25.0sp + 25.5sp + 26.0sp + 26.5sp + 27.0sp + 27.5sp + 28.0sp + 28.5sp + 29.0sp + 29.5sp + 30.0sp + 30.5sp + 31.0sp + 31.5sp + 32.0sp + 32.5sp + 33.0sp + 33.5sp + 34.0sp + 34.5sp + 35.0sp + 35.5sp + 36.0sp + 36.5sp + 37.0sp + 37.5sp + 38.0sp + 38.5sp + 39.0sp + 39.5sp + 40.0sp + 40.5sp + 41.0sp + 41.5sp + 42.0sp + 42.5sp + 43.0sp + 43.5sp + 44.0sp + 44.5sp + 45.0sp + 45.5sp + 46.0sp + 46.5sp + 47.0sp + 47.5sp + 48.0sp + 48.5sp + 49.0sp + 49.5sp + 50.0sp + 50.5sp + 51.0sp + 51.5sp + 52.0sp + 52.5sp + 53.0sp + 53.5sp + 54.0sp + 54.5sp + 55.0sp + 55.5sp + 56.0sp + 56.5sp + 57.0sp + 57.5sp + 58.0sp + 58.5sp + 59.0sp + 59.5sp + 60.0sp + 60.5sp + 61.0sp + 61.5sp + 62.0sp + 62.5sp + 63.0sp + 63.5sp + 64.0sp + 64.5sp + 65.0sp + 65.5sp + 66.0sp + 66.5sp + 67.0sp + 67.5sp + 68.0sp + 68.5sp + 69.0sp + 69.5sp + 70.0sp + 70.5sp + 71.0sp + 71.5sp + 72.0sp + 72.5sp + 73.0sp + 73.5sp + 74.0sp + 74.5sp + 75.0sp + 75.5sp + 76.0sp + 76.5sp + 77.0sp + 77.5sp + 78.0sp + 78.5sp + 79.0sp + 79.5sp + 80.0sp + \ No newline at end of file diff --git a/application/DevUtilsApp/src/main/res/values/values.xml b/application/DevUtilsApp/src/main/res/values/values.xml new file mode 100644 index 0000000000..6f0860c5cf --- /dev/null +++ b/application/DevUtilsApp/src/main/res/values/values.xml @@ -0,0 +1,4 @@ + + + 3 + \ No newline at end of file diff --git a/application/README.md b/application/README.md new file mode 100644 index 0000000000..64e976d2e3 --- /dev/null +++ b/application/README.md @@ -0,0 +1,14 @@ +# About + +该目录主要存储可运行 Android 应用项目 + +## 目录结构 + +``` +- application | 根目录 + - DevBaseDemo | 临时测试代码、库调用调试 Demo + - DevUtilsApp | DevUtils 代码演示应用 +``` + + +## DevUtilsApp 为 Dev 系列开发库代码演示应用 \ No newline at end of file diff --git a/art/module.png b/art/module.png new file mode 100644 index 0000000000..81ec0353d6 Binary files /dev/null and b/art/module.png differ diff --git a/bintray.properties b/bintray.properties new file mode 100644 index 0000000000..0b96a19b37 --- /dev/null +++ b/bintray.properties @@ -0,0 +1,8 @@ +#bintray +bintray.user=afkt +bintray.apikey= + +#developer +developer.id=afkt +developer.name=afkt +developer.email=jtongttt@gmail.com \ No newline at end of file diff --git a/build.gradle b/build.gradle index bbad8e8123..084cdf551c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,28 +1,62 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +apply from: rootProject.file("file/gradle/config.gradle") +apply from: rootProject.file("file/gradle/config_split.gradle") buildscript { - - repositories { + + // Kotlin 版本 + ext.kotlin_version = "1.7.10" + ext.kotlin_stdlib = "1.7.10" + ext.matrix_version = "2.0.8" + + repositories { // 该 repositories 用于 buildscript dependencies 插件、脚本依赖加载 + // AliRepo 阿里仓库服务 https://maven.aliyun.com/mvn/view + maven { url "https://maven.aliyun.com/repository/google" } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } + google() - jcenter() + + // jitpack maven + maven { url "https://jitpack.io" } + + maven { url "https://plugins.gradle.org/m2" } + + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + + maven { url "https://repo1.maven.org/maven2/" } } - dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + dependencies { + // https://mvnrepository.com/artifact/com.android.tools.build/gradle + classpath 'com.android.tools.build:gradle:7.3.0' + // kotlin https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-gradle-plugin + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // https://mvnrepository.com/artifact/org.jetbrains.dokka/dokka-gradle-plugin + classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" + // ARouter AutoRegister https://mvnrepository.com/artifact/com.alibaba/arouter-register + classpath "com.alibaba:arouter-register:1.0.2" + // android maven https://plugins.gradle.org/plugin/com.github.dcendents.android-maven + classpath "com.github.dcendents:android-maven-gradle-plugin:2.1" + // bintray https://plugins.gradle.org/plugin/com.jfrog.bintray + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.5" } } allprojects { - repositories { + repositories { // 该 repositories 用于 app、module dependencies 第三方库 implementation 等依赖加载 google() - jcenter() + + // jitpack maven + maven { url "https://jitpack.io" } + + maven { url "https://plugins.gradle.org/m2" } + + maven { url "https://oss.sonatype.org/content/repositories/snapshots" } + + maven { url "https://repo1.maven.org/maven2/" } } -// // https://blog.csdn.net/u012416928/article/details/47356887 -// // 打印错误信息 // gradle.projectsEvaluated { // tasks.withType(JavaCompile) { // options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" @@ -33,3 +67,11 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir } + +// 全局编码设置 +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} + +// 第三方库版本强制统一处理 +apply from: rootProject.file(files.unified_library_config_gradle) \ No newline at end of file diff --git a/file/gradle/build/arouter/build_arouter_app.gradle b/file/gradle/build/arouter/build_arouter_app.gradle new file mode 100644 index 0000000000..9144758bbd --- /dev/null +++ b/file/gradle/build/arouter/build_arouter_app.gradle @@ -0,0 +1,17 @@ +apply plugin: 'com.alibaba.arouter' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' + +kapt { + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } +} + +dependencies { + + // ARouter 路由 https://github.com/alibaba/ARouter + api deps.lib.arouter_api // https://github.com/alibaba/ARouter/blob/master/README_CN.md + kapt deps.lib.arouter_compiler +} \ No newline at end of file diff --git a/file/gradle/build/arouter/build_arouter_module.gradle b/file/gradle/build/arouter/build_arouter_module.gradle new file mode 100644 index 0000000000..3507720c89 --- /dev/null +++ b/file/gradle/build/arouter/build_arouter_module.gradle @@ -0,0 +1,16 @@ +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' + +kapt { + arguments { + arg("AROUTER_MODULE_NAME", project.getName()) + } +} + +dependencies { + + // ARouter 路由 https://github.com/alibaba/ARouter + api deps.lib.arouter_api // https://github.com/alibaba/ARouter/blob/master/README_CN.md + kapt deps.lib.arouter_compiler +} \ No newline at end of file diff --git a/file/gradle/build/build_app.gradle b/file/gradle/build/build_app.gradle new file mode 100644 index 0000000000..abf8eefafd --- /dev/null +++ b/file/gradle/build/build_app.gradle @@ -0,0 +1,81 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply from: rootProject.file(files.deps_android_lib) +apply from: rootProject.file(files.unified_use_view_data_binding_gradle) + +android { + + compileSdkVersion versions.compileSdkVersion + + defaultConfig { + minSdkVersion versions.app_minSdkVersion + targetSdkVersion versions.targetSdkVersion + + versionCode versions.versionCode + versionName versions.versionName + + // 开启 multidex + multiDexEnabled true + +// ndk { +// // 'armeabi' +// abiFilters 'armeabi-v7a', 'arm64-v8a' +// } + } + + lintOptions { + // https://blog.csdn.net/berber78/article/details/60766091 + abortOnError false + checkReleaseBuilds false + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + kotlinOptions { + jvmTarget = versions.javaVersion_str + } + + // 签名配置 + signingConfigs { + release { + storeFile rootProject.file("file/sign/demo.jks") + storePassword "123456" + keyAlias "demo" + keyPassword "123456" + } + + debug { + storeFile rootProject.file("file/sign/demo.jks") + storePassword "123456" + keyAlias "demo" + keyPassword "123456" + } + } + + buildTypes { + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + debug { + debuggable true + minifyEnabled false + signingConfig signingConfigs.debug + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +// 编码设置 +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/file/gradle/build/build_app_kotlin.gradle b/file/gradle/build/build_app_kotlin.gradle new file mode 100644 index 0000000000..3d8b4f2760 --- /dev/null +++ b/file/gradle/build/build_app_kotlin.gradle @@ -0,0 +1,37 @@ +apply from: rootProject.file(files.lib_app_gradle) +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply from: rootProject.file(files.deps_android_lib) +apply from: rootProject.file(files.unified_use_view_data_binding_gradle) + +android { + lintOptions { + abortOnError false + checkReleaseBuilds false + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + kotlinOptions { + jvmTarget = versions.javaVersion_str + } +} + +dependencies { + + // ============== + // = Dev Module = + // ============== + + implementation project(':DevApp') + implementation project(':DevAssist') + implementation project(':DevBaseMVVM') + implementation project(':DevMVVM') + implementation project(':DevEngine') + implementation project(':DevHttpCapture') + implementation project(':DevWidget') +} \ No newline at end of file diff --git a/file/gradle/build/lib_app.gradle b/file/gradle/build/lib_app.gradle new file mode 100644 index 0000000000..b2083e499f --- /dev/null +++ b/file/gradle/build/lib_app.gradle @@ -0,0 +1,26 @@ +apply plugin: 'com.android.library' + +android { + + compileSdkVersion versions.lib_compileSdkVersion + + defaultConfig { + minSdkVersion versions.lib_minSdkVersion + // 混淆配置 + consumerProguardFiles 'proguard-rules.pro' + } + + lintOptions { + abortOnError false + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } +} + +// 编码设置 +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/file/gradle/build/lib_app_kotlin.gradle b/file/gradle/build/lib_app_kotlin.gradle new file mode 100644 index 0000000000..1d7494d0b7 --- /dev/null +++ b/file/gradle/build/lib_app_kotlin.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + + compileSdkVersion versions.lib_compileSdkVersion + + defaultConfig { + minSdkVersion versions.lib_minSdkVersion + // 混淆配置 + consumerProguardFiles 'proguard-rules.pro' + } + + lintOptions { + abortOnError false + } + + compileOptions { + sourceCompatibility versions.javaVersion + targetCompatibility versions.javaVersion + } + + kotlinOptions { + jvmTarget = versions.javaVersion_str + } +} + +// 编码设置 +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/file/gradle/build/lib_java.gradle b/file/gradle/build/lib_java.gradle new file mode 100644 index 0000000000..2ae4ff6bae --- /dev/null +++ b/file/gradle/build/lib_java.gradle @@ -0,0 +1,6 @@ +apply plugin: 'java-library' + +// 编码设置 +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" +} \ No newline at end of file diff --git a/file/gradle/build/task/task_generate_deps_file.gradle b/file/gradle/build/task/task_generate_deps_file.gradle new file mode 100644 index 0000000000..6d6ca52921 --- /dev/null +++ b/file/gradle/build/task/task_generate_deps_file.gradle @@ -0,0 +1,50 @@ +// ================= +// = config.gradle = +// ================= + +import groovy.json.JsonOutput + +// 存储 config.gradle json 数据 +task saveConfigGradleJSON() { +// println deps.toString() + // 获取 ext.deps json 数据 + def depsJSON = JsonOutput.toJson(deps) + // 格式化 JSON + def formatJSON = JsonOutput.prettyPrint(depsJSON) + // 写入 ext.deps 配置信息 + def file = new File(rootDir, "file/json/deps.json") + file.withPrintWriter { it.print(formatJSON) } +} + +// 生成 deps dependencies gradle +task generateDepsDependencies() { + def newline = System.getProperty("line.separator") + def newline2 = newline + newline + def importSymbol = "implementation" + // 拼接 string + def builder = new StringBuilder() + builder.append("dependencies {") + for (String module : deps.keySet()) { + builder.append("${newline2}\t// = ${module} =${newline}") + // 循环拼接处理 + def valueMaps = deps.get(module) as LinkedHashMap + for (String name : valueMaps.keySet()) { + def value = valueMaps.get(name) + builder.append("${newline}\t// ${name}") + builder.append("${newline}\t${importSymbol} '${value}'") + } + } + builder.append("${newline}}") + // 写入 ext.deps 配置信息 + def file = new File(rootDir, "file/json/deps_gradle") // deps.gradle 防止每次生成显示 gradle sync + file.withPrintWriter { it.print(builder.toString()) } + + // 写入 deps force config copy 记事本 + def forceFile = new File(rootDir, "file/json/deps_force.txt") + forceFile.withPrintWriter { + def txt = builder.toString() + txt = txt.replaceAll("implementation ", "") + txt = txt.replaceAll("'", "\",").replaceAll("\t\",", "\t\"") + it.print(txt) + } +} \ No newline at end of file diff --git a/file/gradle/build/unified/unified_library_config.gradle b/file/gradle/build/unified/unified_library_config.gradle new file mode 100644 index 0000000000..f46aa07e8b --- /dev/null +++ b/file/gradle/build/unified/unified_library_config.gradle @@ -0,0 +1,132 @@ +// ======================= +// = 第三方库版本强制统一处理 = +// ======================= + +allprojects { + repositories { + configurations.all { + // 强制统一版本 => 可通过 file/json/deps.gradle 获取, 视情况选择 + resolutionStrategy.force( + + // = kotlin = + + // stdlib + "org.jetbrains.kotlin:kotlin-stdlib:1.7.10", + // core + "androidx.core:core-ktx:1.8.0", + // coroutines + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4", + // lifecycle_runtime + "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1", + // lifecycle_viewmodel + "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1", + // lifecycle_livedata + "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1", + // lifecycle_viewmodel_savedstate + "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1", + // lifecycle_common_java8 + "androidx.lifecycle:lifecycle-common-java8:2.5.1", + // room_runtime + "androidx.room:room-runtime:2.4.3", + // room_compiler + "androidx.room:room-compiler:2.4.3", + // room + "androidx.room:room-ktx:2.4.3", + // work_runtime + "androidx.work:work-runtime-ktx:2.7.1", + // datastore_preferences + "androidx.datastore:datastore-preferences:1.0.0", + // fragment + "androidx.fragment:fragment-ktx:1.5.2", + // activity + "androidx.activity:activity-ktx:1.5.1", + // navigation_fragment + "androidx.navigation:navigation-fragment-ktx:2.5.2", + // navigation_ui + "androidx.navigation:navigation-ui-ktx:2.5.2", + // palette + "androidx.palette:palette-ktx:1.0.0", + + // = androidx = + + // appcompat + "androidx.appcompat:appcompat:1.5.1", + // appcompat_resources + "androidx.appcompat:appcompat-resources:1.5.1", + // cardview + "androidx.cardview:cardview:1.0.0", + // recyclerview + "androidx.recyclerview:recyclerview:1.2.1", + // multidex + "androidx.multidex:multidex:2.0.1", + // constraint_layout + "androidx.constraintlayout:constraintlayout:2.1.4", + // viewpager2 + "androidx.viewpager2:viewpager2:1.0.0", + // sqlite + "androidx.sqlite:sqlite:2.2.0", + // design + "com.google.android.material:material:1.6.1", + // work_runtime + "androidx.work:work-runtime:2.7.1", + // fragment + "androidx.fragment:fragment:1.5.2", + // activity + "androidx.activity:activity:1.5.1", + // navigation_fragment + "androidx.navigation:navigation-fragment:2.5.2", + // navigation_ui + "androidx.navigation:navigation-ui:2.5.2", + // palette + "androidx.palette:palette:1.0.0", + // flexbox + "com.google.android.flexbox:flexbox:3.0.0", + // startup + "androidx.startup:startup-runtime:1.1.1", + // hilt_android + "com.google.dagger:hilt-android:2.43.2", + // hilt_android_compiler + "com.google.dagger:hilt-android-compiler:2.43.2", + // swiperefreshlayout + "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0", + // exifinterface + "androidx.exifinterface:exifinterface:1.3.3", + + // = compose = + + // ui + "androidx.compose.ui:ui:1.2.1", + // runtime + "androidx.compose.runtime:runtime:1.2.1", + // runtime_livedata + "androidx.compose.runtime:runtime-livedata:1.2.1", + // animation + "androidx.compose.animation:animation:1.2.1", + // foundation + "androidx.compose.foundation:foundation:1.2.1", + // material + "androidx.compose.material:material:1.2.1", + // material3 + "androidx.compose.material3:material3:1.0.0-beta02", + // material3_window_size_class + "androidx.compose.material3:material3-window-size-class:1.0.0-beta02", + // lifecycle_viewmodel + "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1", + // navigation + "androidx.navigation:navigation-compose:2.5.2", + // constraintlayout + "androidx.constraintlayout:constraintlayout-compose:1.0.1", + + // = 手动添加 = + + "androidx.lifecycle:lifecycle-livedata:2.5.1", + "androidx.lifecycle:lifecycle-process:2.4.1", + "androidx.transition:transition:1.4.1", + "androidx.annotation:annotation:1.4.0", + "androidx.collection:collection:1.1.0", + "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10", + "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10", + ) + } + } +} \ No newline at end of file diff --git a/file/gradle/build/unified/unified_use_data_binding.gradle b/file/gradle/build/unified/unified_use_data_binding.gradle new file mode 100644 index 0000000000..9fdc2345d5 --- /dev/null +++ b/file/gradle/build/unified/unified_use_data_binding.gradle @@ -0,0 +1,6 @@ +android { + buildFeatures { + // Data Binding + dataBinding = true + } +} \ No newline at end of file diff --git a/file/gradle/build/unified/unified_use_view_binding.gradle b/file/gradle/build/unified/unified_use_view_binding.gradle new file mode 100644 index 0000000000..445ec160b9 --- /dev/null +++ b/file/gradle/build/unified/unified_use_view_binding.gradle @@ -0,0 +1,6 @@ +android { + buildFeatures { + // View Binding + viewBinding = true + } +} \ No newline at end of file diff --git a/file/gradle/build/unified/unified_use_view_data_binding.gradle b/file/gradle/build/unified/unified_use_view_data_binding.gradle new file mode 100644 index 0000000000..d923f8132b --- /dev/null +++ b/file/gradle/build/unified/unified_use_view_data_binding.gradle @@ -0,0 +1,8 @@ +android { + buildFeatures { + // View Binding + viewBinding = true + // Data Binding + dataBinding = true + } +} \ No newline at end of file diff --git a/file/gradle/config.gradle b/file/gradle/config.gradle new file mode 100644 index 0000000000..4d2cbb61cc --- /dev/null +++ b/file/gradle/config.gradle @@ -0,0 +1,381 @@ +apply from: rootProject.file("file/gradle/versions.gradle") + +ext { + + files = [ + // Android 项目构建配置 + build_app_gradle : "file/gradle/build/build_app.gradle", + // Android Kotlin 项目构建配置 + build_app_kotlin_gradle : "file/gradle/build/build_app_kotlin.gradle", + // Android Kotlin 项目构建配置 + build_arouter_app_gradle : "file/gradle/build/arouter/build_arouter_app.gradle", + // Android Kotlin 项目构建配置 + build_arouter_module_gradle : "file/gradle/build/arouter/build_arouter_module.gradle", + + // Android Lib 通用配置 + lib_app_gradle : "file/gradle/build/lib_app.gradle", + // Android Kotlin Lib 通用配置 + lib_app_kotlin_gradle : "file/gradle/build/lib_app_kotlin.gradle", + // Android Bintray Upload + bintray_upload_android : "file/gradle/upload/bintray/bintrayUploadAndroid.gradle", + // Android Sonatype Upload + sonatype_upload_android : "file/gradle/upload/sonatype/sonatypeUploadAndroid.gradle", + + // Java Lib 通用配置 + lib_java_gradle : "file/gradle/build/lib_java.gradle", + // Java Bintray Upload + bintray_upload_java : "file/gradle/upload/bintray/bintrayUploadJava.gradle", + // Java Sonatype Upload + sonatype_upload_java : "file/gradle/upload/sonatype/sonatypeUploadJava.gradle", + + // Android lib 依赖配置 + deps_android_lib : "file/gradle/deps/deps_android_lib.gradle", + // 第三方 lib 依赖配置 + deps_other_lib : "file/gradle/deps/deps_other_lib.gradle", + // 性能检测相关 lib 依赖配置 + deps_qa_lib : "file/gradle/deps/deps_qa_lib.gradle", + + // =============== + // = Gradle Task = + // =============== + + // 生成依赖库 deps 文件信息任务 + task_generate_deps_file_gradle : "file/gradle/build/task/task_generate_deps_file.gradle", + + // =========== + // = Unified = + // =========== + + // 第三方库版本强制统一处理 + unified_library_config_gradle : "file/gradle/build/unified/unified_library_config.gradle", + // 统一使用 View Binding 处理 + unified_use_view_binding_gradle : "file/gradle/build/unified/unified_use_view_binding.gradle", + // 统一使用 Data Binding 处理 + unified_use_data_binding_gradle : "file/gradle/build/unified/unified_use_data_binding.gradle", + // 统一使用 View、Data Binding 处理 + unified_use_view_data_binding_gradle: "file/gradle/build/unified/unified_use_view_data_binding.gradle", + ] + + deps = [ + + // ================ + // = Dev 系列开发库 = + // ================ + + "dev" : [ + // https://github.com/afkT/DevUtils + // https://search.maven.org/search?q=io.github.afkt + // https://repo1.maven.org/maven2/io/github/afkt/ + // https://mvnrepository.com/search?q=io.github.afkt + dev_app : "io.github.afkt:DevAppX:${versions.dev_app_versionName}", + dev_assist : "io.github.afkt:DevAssist:${versions.dev_assist_versionName}", + dev_base : "io.github.afkt:DevBase:${versions.dev_base_versionName}", + dev_base_mvvm : "io.github.afkt:DevBaseMVVM:${versions.dev_base_mvvm_versionName}", + dev_mvvm : "io.github.afkt:DevMVVM:${versions.dev_mvvm_versionName}", + dev_engine : "io.github.afkt:DevEngine:${versions.dev_engine_versionName}", + dev_java : "io.github.afkt:DevJava:${versions.dev_java_version}", + dev_widget : "io.github.afkt:DevWidgetX:${versions.dev_widget_versionName}", + // 环境配置切换库 + dev_environment : "io.github.afkt:DevEnvironment:${versions.dev_environment_version}", + dev_environment_base : "io.github.afkt:DevEnvironmentBase:${versions.dev_environment_base_version}", + dev_environment_compiler : "io.github.afkt:DevEnvironmentCompiler:${versions.dev_environment_compiler_version}", + dev_environment_compiler_release : "io.github.afkt:DevEnvironmentCompilerRelease:${versions.dev_environment_compiler_release_version}", + // OkHttp 抓包工具库 + dev_http_capture : "io.github.afkt:DevHttpCapture:${versions.dev_http_capture_versionName}", + dev_http_capture_compiler : "io.github.afkt:DevHttpCaptureCompiler:${versions.dev_http_capture_compiler_version}", + dev_http_capture_compiler_release: "io.github.afkt:DevHttpCaptureCompilerRelease:${versions.dev_http_capture_compiler_release_version}", + // OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + dev_http_manager : "io.github.afkt:DevHttpManager:${versions.dev_http_manager_versionName}", + // DevRetrofit - Retrofit + Kotlin Coroutines 封装 + dev_retrofit : "io.github.afkt:DevRetrofit:${versions.dev_retrofit_versionName}", + ], + + // ================= + // = Android 官方库 = + // ================= + + // https://developer.android.com/jetpack/androidx/explorer + // https://developer.android.com/jetpack/androidx/releases + // https://developer.android.com/jetpack/androidx/versions + // https://developer.android.com/jetpack/androidx/versions/stable-channel + // https://developer.android.com/jetpack/androidx/migrate/artifact-mappings + + "kotlin" : [ + // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib + stdlib : "org.jetbrains.kotlin:kotlin-stdlib:${ext.kotlin_stdlib}", + // https://mvnrepository.com/artifact/androidx.core/core-ktx + core : "androidx.core:core-ktx:1.8.0", + // https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core + coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4", + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-runtime-ktx + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-ktx + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-livedata-ktx + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-savedstate + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-common-java8 + // Lifecycles only ( without ViewModel or LiveData ) + // https://developer.android.com/jetpack/androidx/releases/lifecycle + lifecycle_runtime : "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1", + // ViewModel + lifecycle_viewmodel : "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1", + // LiveData + lifecycle_livedata : "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1", + // Saved state module for ViewModel + lifecycle_viewmodel_savedstate: "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1", + // DefaultLifecycleObserver Java8 + lifecycle_common_java8 : "androidx.lifecycle:lifecycle-common-java8:2.5.1", + // https://mvnrepository.com/artifact/androidx.room/room-runtime + // https://mvnrepository.com/artifact/androidx.room/room-compiler + // https://mvnrepository.com/artifact/androidx.room/room-ktx + // Room 持久性库 https://developer.android.com/training/data-storage/room + room_runtime : "androidx.room:room-runtime:2.4.3", + room_compiler : "androidx.room:room-compiler:2.4.3", + room : "androidx.room:room-ktx:2.4.3", + // https://mvnrepository.com/artifact/androidx.work/work-runtime-ktx + // WorkManager Kotlin + coroutines + work_runtime : "androidx.work:work-runtime-ktx:2.7.1", + // https://mvnrepository.com/artifact/androidx.datastore/datastore-preferences + // https://developer.android.com/jetpack/androidx/releases/datastore + // DataStore https://developer.android.com/topic/libraries/architecture/datastore + datastore_preferences : "androidx.datastore:datastore-preferences:1.0.0", + // https://mvnrepository.com/artifact/androidx.fragment/fragment-ktx + fragment : "androidx.fragment:fragment-ktx:1.5.2", + // https://mvnrepository.com/artifact/androidx.activity/activity-ktx + // https://developer.android.com/jetpack/androidx/releases/activity + activity : "androidx.activity:activity-ktx:1.5.1", + // https://mvnrepository.com/artifact/androidx.navigation/navigation-fragment-ktx + // https://mvnrepository.com/artifact/androidx.navigation/navigation-ui-ktx + // https://developer.android.com/guide/navigation + // https://developer.android.com/jetpack/androidx/releases/navigation + navigation_fragment : "androidx.navigation:navigation-fragment-ktx:2.5.2", + navigation_ui : "androidx.navigation:navigation-ui-ktx:2.5.2", + // https://mvnrepository.com/artifact/androidx.palette/palette-ktx + // Palette 调色板 ( 从图片中获取颜色 ) + palette : "androidx.palette:palette-ktx:1.0.0", + ], + "androidx": [ + // https://mvnrepository.com/artifact/androidx.appcompat/appcompat + appcompat : "androidx.appcompat:appcompat:1.5.1", + // https://mvnrepository.com/artifact/androidx.appcompat/appcompat-resources + appcompat_resources : "androidx.appcompat:appcompat-resources:1.5.1", + // https://mvnrepository.com/artifact/androidx.legacy/legacy-support-v4 + //support_v4 : "androidx.legacy:legacy-support-v4:1.0.0", + // https://mvnrepository.com/artifact/androidx.cardview/cardview + cardview : "androidx.cardview:cardview:1.0.0", + // https://mvnrepository.com/artifact/androidx.recyclerview/recyclerview + recyclerview : "androidx.recyclerview:recyclerview:1.2.1", + // https://mvnrepository.com/artifact/androidx.multidex/multidex + multidex : "androidx.multidex:multidex:2.0.1", + // https://mvnrepository.com/artifact/androidx.constraintlayout/constraintlayout + constraint_layout : "androidx.constraintlayout:constraintlayout:2.1.4", + // https://mvnrepository.com/artifact/androidx.viewpager2/viewpager2 + viewpager2 : "androidx.viewpager2:viewpager2:1.0.0", + // https://mvnrepository.com/artifact/androidx.sqlite/sqlite + sqlite : "androidx.sqlite:sqlite:2.2.0", // 推荐使用 Room Jetpack 组件 + // https://mvnrepository.com/artifact/com.google.android.material/material + // Android Material 组件使用详解 https://blog.csdn.net/magic0908/article/details/101029876 + design : "com.google.android.material:material:1.6.1", + // https://mvnrepository.com/artifact/androidx.work/work-runtime + // WorkManager Java only + work_runtime : "androidx.work:work-runtime:2.7.1", + // https://mvnrepository.com/artifact/androidx.fragment/fragment + fragment : "androidx.fragment:fragment:1.5.2", + // https://mvnrepository.com/artifact/androidx.activity/activity + activity : "androidx.activity:activity:1.5.1", + // https://mvnrepository.com/artifact/androidx.navigation/navigation-fragment + // https://mvnrepository.com/artifact/androidx.navigation/navigation-ui + // https://developer.android.com/guide/navigation + // https://developer.android.com/jetpack/androidx/releases/navigation + navigation_fragment : "androidx.navigation:navigation-fragment:2.5.2", + navigation_ui : "androidx.navigation:navigation-ui:2.5.2", + // https://mvnrepository.com/artifact/androidx.palette/palette + // Palette 调色板 ( 从图片中获取颜色 ) + palette : "androidx.palette:palette:1.0.0", + // https://mvnrepository.com/artifact/com.google.android.flexbox/flexbox + // https://github.com/google/flexbox-layout + // https://juejin.im/post/58d1035161ff4b00603ca9c4 + // Android 可伸缩布局 FlexboxLayout ( 支持 RecyclerView 集成 ) + flexbox : "com.google.android.flexbox:flexbox:3.0.0", + // https://mvnrepository.com/artifact/androidx.startup/startup-runtime + // https://developer.android.com/topic/libraries/app-startup + startup : "androidx.startup:startup-runtime:1.1.1", + // https://mvnrepository.com/artifact/com.google.dagger/hilt-android + // https://mvnrepository.com/artifact/com.google.dagger/hilt-android-compiler + // https://blog.csdn.net/petterp/article/details/106771203 + // Hilt 依赖注入 https://developer.android.com/training/dependency-injection/hilt-android + hilt_android : "com.google.dagger:hilt-android:2.43.2", + hilt_android_compiler: "com.google.dagger:hilt-android-compiler:2.43.2", + // https://mvnrepository.com/artifact/androidx.swiperefreshlayout/swiperefreshlayout + // Google 原生刷新库 https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout + swiperefreshlayout : "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0", + // https://mvnrepository.com/artifact/androidx.exifinterface/exifinterface + // 读取和写入图片文件 EXIF 标记 https://developer.android.com/jetpack/androidx/releases/exifinterface + exifinterface : "androidx.exifinterface:exifinterface:1.3.3", + ], + "compose" : [ + // https://developer.android.com/jetpack/compose + // https://developer.android.com/jetpack/androidx/releases/compose + + // =================== + // = Android Compose = + // =================== + + // https://mvnrepository.com/artifact/androidx.compose.ui/ui + // https://developer.android.com/jetpack/androidx/releases/compose-ui + // 与设备互动所需的 Compose UI 的基本组件, 包括布局、绘图和输入 + ui : "androidx.compose.ui:ui:1.2.1", + // https://mvnrepository.com/artifact/androidx.compose.runtime/runtime + // https://mvnrepository.com/artifact/androidx.compose.runtime/runtime-livedata + // https://developer.android.com/jetpack/androidx/releases/compose-runtime + // Compose 编程模型和状态管理的基本构建块, 以及 Compose Compiler 插件针对的核心运行时 + runtime : "androidx.compose.runtime:runtime:1.2.1", + runtime_livedata : "androidx.compose.runtime:runtime-livedata:1.2.1", + // https://mvnrepository.com/artifact/androidx.compose.animation/animation + // https://developer.android.com/jetpack/androidx/releases/compose-animation + // 在 Jetpack Compose 应用中构建动画,丰富用户体验 + animation : "androidx.compose.animation:animation:1.2.1", + // https://mvnrepository.com/artifact/androidx.compose.foundation/foundation + // https://developer.android.com/jetpack/androidx/releases/compose-foundation + // 使用现成可用的构建块编写 Jetpack Compose 应用, 并扩展 Foundation 以构建您自己的设计系统元素 + foundation : "androidx.compose.foundation:foundation:1.2.1", + // https://mvnrepository.com/artifact/androidx.compose.material/material + // https://developer.android.com/jetpack/androidx/releases/compose-material + // 使用现成可用的 Material Design 组件构建 Jetpack Compose UI + material : "androidx.compose.material:material:1.2.1", + // https://mvnrepository.com/artifact/androidx.compose.material3/material3 + // https://mvnrepository.com/artifact/androidx.compose.material3/material3-window-size-class + // https://developer.android.com/jetpack/androidx/releases/compose-material3 + // 使用 Material Design 3 ( 下一代 Material Design ) 组件构建 Jetpack Compose 界面 + material3 : "androidx.compose.material3:material3:1.0.0-beta02", + // material3-window-size-class 是一个为窗口大小类别提供支持的新库 + material3_window_size_class: "androidx.compose.material3:material3-window-size-class:1.0.0-beta02", + // https://mvnrepository.com/artifact/androidx.lifecycle/lifecycle-viewmodel-compose + // https://developer.android.com/jetpack/androidx/releases/lifecycle + lifecycle_viewmodel : "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1", + // https://mvnrepository.com/artifact/androidx.navigation/navigation-compose + // https://developer.android.com/guide/navigation + // https://developer.android.com/jetpack/androidx/releases/navigation + navigation : "androidx.navigation:navigation-compose:2.5.2", + // https://mvnrepository.com/artifact/androidx.constraintlayout/constraintlayout-compose + // https://developer.android.com/jetpack/androidx/releases/constraintlayout + constraintlayout : "androidx.constraintlayout:constraintlayout-compose:1.0.1", + ], + + // =================== + // = 非 Android 官方库 = + // =================== + + "lib" : [ + + // ================= + // = 第三方快捷开发库 = + // ================= + + // https://mvnrepository.com/artifact/com.google.code.gson/gson + // Gson https://github.com/google/gson + gson : "com.google.code.gson:gson:2.9.1", + // https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 + // Fastjson2 https://github.com/alibaba/fastjson2 + fastjson2 : "com.alibaba.fastjson2:fastjson2:2.0.14", + fastjson2_android : "com.alibaba.fastjson2:fastjson2:2.0.14.android", + // https://mvnrepository.com/artifact/com.github.bumptech.glide/glide + // https://mvnrepository.com/artifact/com.github.bumptech.glide/compiler + // Glide 图片加载框架 https://github.com/bumptech/glide + glide : "com.github.bumptech.glide:glide:4.13.2", + glide_compiler : "com.github.bumptech.glide:compiler:4.13.2", + // https://mvnrepository.com/artifact/jp.wasabeef/glide-transformations + // Glide 图形库 https://github.com/wasabeef/glide-transformations + glide_transformations: "jp.wasabeef:glide-transformations:4.3.0", + // https://mvnrepository.com/artifact/com.tencent/mmkv + // 基于 mmap 的高性能通用 key-value 组件 https://github.com/Tencent/MMKV/blob/master/README_CN.md + mmkv : "com.tencent:mmkv:1.2.14", + // https://mvnrepository.com/artifact/org.greenrobot/eventbus + // EventBus 事件订阅分发 https://github.com/greenrobot/EventBus + eventbus : "org.greenrobot:eventbus:3.3.1", + // https://mvnrepository.com/artifact/io.github.jeremyliao/live-event-bus-x + // LiveEventBus 消息总线 https://github.com/JeremyLiao/LiveEventBus + live_eventbus : "io.github.jeremyliao:live-event-bus-x:1.8.0", + // https://mvnrepository.com/artifact/com.squareup.okio/okio + // okio https://github.com/square/okio + okio : "com.squareup.okio:okio:3.2.0", + // https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp + // https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor + // okhttp3 网络请求框架 https://github.com/square/okhttp + okhttp3 : "com.squareup.okhttp3:okhttp:4.10.0", + // okhttp 日志拦截器 https://github.com/square/okhttp/blob/master/okhttp-logging-interceptor + okhttp3_logging : "com.squareup.okhttp3:logging-interceptor:4.10.0", + // https://mvnrepository.com/artifact/com.squareup.retrofit2/retrofit + // https://mvnrepository.com/artifact/com.squareup.retrofit2/converter-gson + // https://mvnrepository.com/artifact/com.squareup.retrofit2/adapter-rxjava3 + // Retrofit 网络请求库 https://github.com/square/retrofit + retrofit : "com.squareup.retrofit2:retrofit:2.9.0", + // Retrofit Gson Converter https://github.com/square/retrofit/blob/master/retrofit-converters/gson + retrofit_gson : "com.squareup.retrofit2:converter-gson:2.9.0", + // Retrofit RxJava3 Adapter https://github.com/square/retrofit/blob/master/retrofit-adapters/rxjava3 + retrofit_rxjava3 : "com.squareup.retrofit2:adapter-rxjava3:2.9.0", + // https://mvnrepository.com/artifact/com.airbnb.android/lottie + // https://www.jianshu.com/p/9a2136ecbc7b + // 动画库 https://github.com/airbnb/lottie-android + lottie : "com.airbnb.android:lottie:5.2.0", + // https://mvnrepository.com/artifact/com.github.JessYanCoding/AndroidAutoSize + // 今日头条屏幕适配方案终极版 https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md + autosize : "com.github.JessYanCoding:AndroidAutoSize:v1.2.1", + // https://mvnrepository.com/artifact/com.alibaba/arouter-api + // https://mvnrepository.com/artifact/com.alibaba/arouter-compiler + // https://github.com/alibaba/ARouter/blob/master/README_CN.md + // ARouter 路由 https://github.com/alibaba/ARouter + arouter_api : "com.alibaba:arouter-api:1.5.2", + arouter_compiler : "com.alibaba:arouter-compiler:1.5.2", + ], + "aop" : [ + // 安卓 AOP 三剑客: APT、AspectJ、Javassist https://www.jianshu.com/p/dca3e2c8608a + + // https://mvnrepository.com/artifact/com.squareup/javapoet + // JavaPoet 编译时代码生成 https://github.com/square/javapoet + javapoet : "com.squareup:javapoet:1.13.0", + // https://mvnrepository.com/artifact/com.google.auto.service/auto-service + // APT 编译时注解简化服务 https://github.com/google/auto/blob/master/service + auto_service: "com.google.auto.service:auto-service:1.0.1", + ], + "widget" : [ + + // ============================ + // = 第三方简约小功能、UI 小组件等 = + // ============================ + + // https://mvnrepository.com/artifact/me.tatarka.bindingcollectionadapter2/bindingcollectionadapter + // https://mvnrepository.com/artifact/me.tatarka.bindingcollectionadapter2/bindingcollectionadapter-recyclerview + // https://mvnrepository.com/artifact/me.tatarka.bindingcollectionadapter2/bindingcollectionadapter-viewpager2 + // MVVM Adapter Binding https://github.com/evant/binding-collection-adapter + binding_collection_adapter : "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter:4.0.0", + binding_collection_adapter_recyclerview: "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-recyclerview:4.0.0", + binding_collection_adapter_viewpager2 : "me.tatarka.bindingcollectionadapter2:bindingcollectionadapter-viewpager2:4.0.0", + // https://mvnrepository.com/artifact/com.drakeet.multitype/multitype + // 多类型 ViewType Adapter https://github.com/drakeet/MultiType + multitype : "com.drakeet.multitype:multitype:4.3.0", + // https://mvnrepository.com/artifact/com.github.donkingliang/ConsecutiveScroller + // 多子 view 嵌套滚动通用解决方案 https://github.com/MFC-TEC/ELinkageScroll + // WebView、RecyclerView 多布局连贯滑动 https://github.com/donkingliang/ConsecutiveScroller + consecutiveScroller : "com.github.donkingliang:ConsecutiveScroller:4.6.3", + // https://mvnrepository.com/artifact/io.github.youth5201314/banner + // Banner 库 https://github.com/youth5201314/banner + banner : "io.github.youth5201314:banner:2.2.2", + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-layout-kernel + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-header-classics + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-header-radar + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-header-falsify + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-header-material + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-header-two-level + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-footer-ball + // https://mvnrepository.com/artifact/io.github.scwang90/refresh-footer-classics + // 下拉刷新框架 https://github.com/scwang90/SmartRefreshLayout + smartrefreshlayout : "io.github.scwang90:refresh-layout-kernel:2.0.5", + smartrefresh_header_classics : "io.github.scwang90:refresh-header-classics:2.0.5", // 经典刷新头 + smartrefresh_header_radar : "io.github.scwang90:refresh-header-radar:2.0.5", // 雷达刷新头 + smartrefresh_header_falsify : "io.github.scwang90:refresh-header-falsify:2.0.5", // 虚拟刷新头 + smartrefresh_header_material : "io.github.scwang90:refresh-header-material:2.0.5", // 谷歌刷新头 + smartrefresh_header_two_level : "io.github.scwang90:refresh-header-two-level:2.0.5", // 二级刷新头 + smartrefresh_footer_ball : "io.github.scwang90:refresh-footer-ball:2.0.5", // 球脉冲加载 + smartrefresh_footer_classics : "io.github.scwang90:refresh-footer-classics:2.0.5", // 经典加载 + ] + ] +} \ No newline at end of file diff --git a/file/gradle/config_split.gradle b/file/gradle/config_split.gradle new file mode 100644 index 0000000000..d736a6dabe --- /dev/null +++ b/file/gradle/config_split.gradle @@ -0,0 +1,228 @@ +apply from: rootProject.file("file/gradle/versions.gradle") + +// ===================== +// = config.gradle 拆分 = +// ===================== + +/** + * 拆分非【常用开发库、新技术 ( 趋向 ) 取代库】、抛弃已不更新库等 + * 可自行 copy 根据项目依赖需要进行合并 + */ +ext { + deps_split = [ + "lib" : [ + + // ================= + // = 第三方快捷开发库 = + // ================= + + // https://mvnrepository.com/artifact/io.reactivex.rxjava3/rxjava + // https://mvnrepository.com/artifact/io.reactivex.rxjava3/rxandroid + // https://mvnrepository.com/artifact/io.reactivex.rxjava3/rxkotlin + // RxJava3 https://github.com/ReactiveX/RxJava/tree/3.x + rxjava3 : "io.reactivex.rxjava3:rxjava:3.1.5", + // RxAndroid3 https://github.com/ReactiveX/RxAndroid/tree/3.x + rxandroid3 : "io.reactivex.rxjava3:rxandroid:3.0.0", + rxkotlin : "io.reactivex.rxjava3:rxkotlin:3.0.1", + // https://mvnrepository.com/artifact/com.trello.rxlifecycle4/rxlifecycle + // https://mvnrepository.com/artifact/com.trello.rxlifecycle4/rxlifecycle-kotlin + // https://mvnrepository.com/artifact/com.trello.rxlifecycle4/rxlifecycle-android-lifecycle-kotlin + // RxLifecycle https://github.com/trello/RxLifecycle + rxlifecycle : "com.trello.rxlifecycle4:rxlifecycle:4.0.2", + rxlifecycle_kotlin : "com.trello.rxlifecycle4:rxlifecycle-kotlin:4.0.2", + rxlifecycle_android_lifecycle_kotlin: "com.trello.rxlifecycle4:rxlifecycle-android-lifecycle-kotlin:4.0.2", + // https://mvnrepository.com/artifact/com.uber.autodispose2/autodispose + // https://mvnrepository.com/artifact/com.uber.autodispose2/autodispose-android + // https://mvnrepository.com/artifact/com.uber.autodispose2/autodispose-androidx-lifecycle + // AutoDispose 自动绑定解绑 https://github.com/uber/AutoDispose + auto_dispose : "com.uber.autodispose2:autodispose:2.1.1", + auto_dispose_android : "com.uber.autodispose2:autodispose-android:2.1.1", + auto_dispose_lifecycle : "com.uber.autodispose2:autodispose-androidx-lifecycle:2.1.1", + // https://mvnrepository.com/artifact/org.greenrobot/greendao + // GreenDAO ORM 框架 https://github.com/greenrobot/greenDAO + greenDAO : "org.greenrobot:greendao:3.3.0", + // https://mvnrepository.com/artifact/net.zetetic/android-database-sqlcipher + // SQLCipher for Android https://github.com/sqlcipher/android-database-sqlcipher + sqlcipher : "net.zetetic:android-database-sqlcipher:4.5.2", + // https://mvnrepository.com/artifact/io.github.lucksiege/pictureselector + // https://mvnrepository.com/artifact/io.github.lucksiege/compress + // https://mvnrepository.com/artifact/io.github.lucksiege/ucrop + // https://mvnrepository.com/artifact/io.github.lucksiege/camerax + // Android 平台下的图片选择器 https://github.com/LuckSiege/PictureSelector + // PictureSelector 基础 ( 必须 ) + pictureSelector : "io.github.lucksiege:pictureselector:v3.10.6", + // 图片压缩 ( 按需引入 ) + pictureSelector_compress : "io.github.lucksiege:compress:v3.10.6", + // 图片裁剪 ( 按需引入 ) + pictureSelector_ucrop : "io.github.lucksiege:ucrop:v3.10.6", + // 自定义相机 ( 按需引入 ) + pictureSelector_camerax : "io.github.lucksiege:camerax:v3.10.6", + // https://mvnrepository.com/artifact/com.google.zxing/core + // https://mvnrepository.com/artifact/com.google.zxing/android-core + // 二维码 ZXing https://github.com/zxing/zxing + zxing_code : "com.google.zxing:core:3.5.0", + zxing_android_code : "com.google.zxing:android-core:3.3.0", + // https://mvnrepository.com/artifact/top.zibin/Luban + // https://mvnrepository.com/artifact/top.zibin/Luban?repo=jcenter + // Luban 鲁班图片压缩 https://github.com/Curzibn/Luban + luban : "top.zibin:Luban:1.1.8", + // Kotlin Luban 图片压缩 https://github.com/forJrking/KLuban + kluban : "com.github.forJrking:KLuban:1.1.0", + // https://mvnrepository.com/artifact/me.laoyuyu.aria/core + // https://mvnrepository.com/artifact/me.laoyuyu.aria/compiler + // https://mvnrepository.com/artifact/me.laoyuyu.aria/ftp + // https://mvnrepository.com/artifact/me.laoyuyu.aria/sftp + // https://mvnrepository.com/artifact/me.laoyuyu.aria/m3u8 + // Aria 下载可以很简单 https://github.com/AriaLyy/Aria + aria_core : "me.laoyuyu.aria:core:3.8.16", + aria_compiler : "me.laoyuyu.aria:compiler:3.8.16", + aria_ftp : "me.laoyuyu.aria:ftp:3.8.16", + aria_sftp : "me.laoyuyu.aria:sftp:3.8.16", + aria_m3u8 : "me.laoyuyu.aria:m3u8:3.8.16", + // https://mvnrepository.com/artifact/com.github.getActivity/XXPermissions + // XXPermissions 权限请求框架 https://github.com/getActivity/XXPermissions + xxPermissions : "com.github.getActivity:XXPermissions:16.2", + // Anchors 启动框架 https://github.com/DSAppTeam/Anchors/blob/master/README-zh.md + anchors : "com.github.DSAppTeam:Anchors:v1.1.7", + // 功能面板切换辅助 https://github.com/DSAppTeam/PanelSwitchHelper/blob/master/README-zh.md + panelSwitchHelper : "com.github.DSAppTeam:PanelSwitchHelper:v1.5.0", + ], + "property" : [ + + // ==================== + // = 性能检测、排查相关库 = + // ==================== + + // https://mvnrepository.com/artifact/com.tencent.bugly/crashreport + // https://mvnrepository.com/artifact/com.tencent.bugly/nativecrashreport + // Bugly https://bugly.qq.com/docs/ + bugly : "com.tencent.bugly:crashreport:4.1.9", + bugly_ndk : "com.tencent.bugly:nativecrashreport:3.9.2", + // 饿了么 UETool https://github.com/eleme/UETool/blob/master/README_zh.md + uetool : "com.github.eleme.UETool:uetool:1.3.4", + uetool_base : "com.github.eleme.UETool:uetool-base:1.3.4", + uetool_no_op : "com.github.eleme.UETool:uetool-no-op:1.3.4", + // https://mvnrepository.com/artifact/com.squareup.leakcanary/leakcanary-android + // 内存检测工具 https://github.com/square/leakcanary + leakcanary : "com.squareup.leakcanary:leakcanary-android:2.9.1", + // https://mvnrepository.com/artifact/com.github.markzhai/blockcanary-android + // https://mvnrepository.com/artifact/com.github.markzhai/blockcanary-no-op + // BlockCanary 性能监控组件 https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README_CN.md + blockcanary_android : "com.github.markzhai:blockcanary-android:1.5.0", + blockcanary_no_op : "com.github.markzhai:blockcanary-no-op:1.5.0", + // https://mvnrepository.com/search?q=com.tencent.matrix + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-android-lib + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-android-commons + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-trace-canary + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-resource-canary-android + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-resource-canary-common + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-io-canary + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-sqlite-lint-android-sdk + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-battery-canary + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-hooks + // https://mvnrepository.com/artifact/com.tencent.matrix/matrix-backtrace + // Matrix https://github.com/Tencent/matrix/#matrix_android_cn + matrix_android_lib : "com.tencent.matrix:matrix-android-lib:${ext.matrix_version}", + matrix_android_commons : "com.tencent.matrix:matrix-android-commons:${ext.matrix_version}", + matrix_trace_canary : "com.tencent.matrix:matrix-trace-canary:${ext.matrix_version}", + matrix_resource_canary_android: "com.tencent.matrix:matrix-resource-canary-android:${ext.matrix_version}", + matrix_resource_canary_common : "com.tencent.matrix:matrix-resource-canary-common:${ext.matrix_version}", + matrix_io_canary : "com.tencent.matrix:matrix-io-canary:${ext.matrix_version}", + matrix_sqlite_lint : "com.tencent.matrix:matrix-sqlite-lint-android-sdk:${ext.matrix_version}", + matrix_battery_canary : "com.tencent.matrix:matrix-battery-canary:${ext.matrix_version}", + matrix_hooks : "com.tencent.matrix:matrix-hooks:${ext.matrix_version}", + matrix_backtrace : "com.tencent.matrix:matrix-backtrace:${ext.matrix_version}", + + // xCrash Android 应用崩溃捕获工具 https://github.com/iqiyi/xCrash/blob/master/README.zh-CN.md + + // 滴滴出行 DoraemonKit https://www.dokit.cn + + // 滴滴 Booster 质量优化框架 https://github.com/didi/booster + + // 微信开源的资源混淆库 AndResGuard https://github.com/shwenzhang/AndResGuard/blob/master/README.zh-cn.md + ], + "build_apk" : [ + + // =================================== + // = APK 构建、打包相关 ( 多渠道、压缩等 ) = + // =================================== + + // 瓦力多渠道打包 https://github.com/Meituan-Dianping/walle + walle : "com.meituan.android.walle:library:1.1.7", + // https://mvnrepository.com/artifact/com.tencent.vasdolly/helper + // VasDolly 多渠道打包 https://github.com/Tencent/VasDolly + vas_dolly: "com.tencent.vasdolly:helper:3.0.4", + ], + "widget" : [ + + // ============================ + // = 第三方简约小功能、UI 小组件等 = + // ============================ + + // https://mvnrepository.com/artifact/jp.co.cyberagent.android/gpuimage + // GPU Filters https://github.com/cats-oss/android-gpuimage + gpuimage : "jp.co.cyberagent.android:gpuimage:2.1.0", + // https://mvnrepository.com/artifact/com.github.chrisbanes/PhotoView + // 图片缩放 https://github.com/chrisbanes/PhotoView + photoview : "com.github.chrisbanes:PhotoView:2.3.0", + // https://mvnrepository.com/artifact/com.haibin/calendarview + // CalendarView 日历控件 https://github.com/huanghaibin-dev/CalendarView + calendarview : "com.haibin:calendarview:3.7.1", + // https://mvnrepository.com/artifact/com.github.PhilJay/MPAndroidChart + // https://blog.csdn.net/ww897532167/article/details/74171294 + // 图表 https://github.com/PhilJay/MPAndroidChart + mpAndroidChart : "com.github.PhilJay:MPAndroidChart:v3.1.0", + // https://mvnrepository.com/artifact/com.tt/whorlviewlibrary + // 加载动画效果 https://github.com/Kyson/WhorlView + whorlviewlibrary : "com.tt:whorlviewlibrary:1.0.3", + // 加载动画效果 https://github.com/dinuscxj/LoadingDrawable + loading_drawable : "com.github.elmliu:loadingdrawable:1.0", + // https://mvnrepository.com/artifact/com.gavin.com.library/stickyDecoration + // RecyclerView 实现顶部吸附效果 https://github.com/Gavin-ZYX/StickyDecoration + stickyDecoration : "com.github.Gavin-ZYX:StickyDecoration:1.6.1", + // https://mvnrepository.com/artifact/com.contrarywind/Android-PickerView + // https://blog.csdn.net/qq_22393017/article/details/58099486 + // 滚轮选择库 https://github.com/Bigkoo/Android-PickerView + pickerview : "com.contrarywind:Android-PickerView:4.1.9", + // https://mvnrepository.com/artifact/com.github.yalantis/ucrop + // 图片裁剪库 https://github.com/Yalantis/uCrop + ucrop : "com.github.yalantis:ucrop:2.2.8", // 轻量级通用解决方案 + ucrop_native : "com.github.yalantis:ucrop:2.2.8-native", // 手机原生功能支持 + // https://mvnrepository.com/artifact/com.qmuiteam/qmui + // QMUI Android https://qmuiteam.com/android + qmui : "com.qmuiteam:qmui:2.1.0", + // https://mvnrepository.com/artifact/com.github.CymChad/BaseRecyclerViewAdapterHelper + // RecyclerView Adapter https://github.com/CymChad/BaseRecyclerViewAdapterHelper + base_recyclerview_adapter_helper: "com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.8", + // 评价等级控件 https://github.com/williamyyu/SimpleRatingBar + simple_ratingbar : "com.github.williamyyu:SimpleRatingBar:1.5.1", + + // 骨架屏 https://github.com/ethanhua/Skeleton + // https://juejin.cn/post/6844903785215574023 + + // 骨架屏 https://github.com/sharish/ShimmerRecyclerView + // https://www.jianshu.com/p/9d27a1563a48 + + // PaintedSkin 一款解决 Android App 换肤功能的框架 https://juejin.cn/post/6941657381281464333 + // https://github.com/CoderAlee/PaintedSkin + ], + "deprecated": [ + + // =================================== + // = 已经抛弃不再更新或者被其他库、技术取代 = + // =================================== + + // ImageLoader 图片加载框架 https://github.com/nostra13/Android-Universal-Image-Loader + imageloader : "com.nostra13.universalimageloader:universal-image-loader:1.9.5", + // View 注入框架 https://github.com/JakeWharton/butterknife + butterknife : "com.jakewharton:butterknife:10.2.3", + butterknife_compiler: "com.jakewharton:butterknife-compiler:10.2.3", + // Kotlin ButterKnife https://github.com/JakeWharton/kotterknife + kotterknife : "com.jakewharton:kotterknife:0.1.0-SNAPSHOT", + // OkGo https://github.com/jeasonlzy/okhttp-OkGo + okgo : "com.lzy.net:okgo:3.0.4", + // 下载管理和上传管理扩展 https://github.com/jeasonlzy/okhttp-OkGo/wiki + okserver : "com.lzy.net:okserver:2.0.5", + ] + ] +} \ No newline at end of file diff --git a/file/gradle/deps/deps_android_lib.gradle b/file/gradle/deps/deps_android_lib.gradle new file mode 100644 index 0000000000..4224329adf --- /dev/null +++ b/file/gradle/deps/deps_android_lib.gradle @@ -0,0 +1,37 @@ +// Android lib 依赖配置 +dependencies { + + // ========== + // = kotlin = + // ========== + + implementation deps.kotlin.stdlib + implementation deps.kotlin.core + implementation deps.kotlin.coroutines + implementation deps.kotlin.lifecycle_runtime + implementation deps.kotlin.lifecycle_viewmodel + implementation deps.kotlin.lifecycle_livedata + implementation deps.kotlin.lifecycle_viewmodel_savedstate + implementation deps.kotlin.lifecycle_common_java8 + implementation deps.kotlin.datastore_preferences + implementation deps.kotlin.fragment + implementation deps.kotlin.activity + + // ============ + // = androidx = + // ============ + + implementation deps.androidx.design + implementation deps.androidx.appcompat + implementation deps.androidx.appcompat_resources + implementation deps.androidx.cardview + implementation deps.androidx.recyclerview + implementation deps.androidx.multidex + implementation deps.androidx.constraint_layout + implementation deps.androidx.viewpager2 + implementation deps.androidx.work_runtime + implementation deps.androidx.fragment + implementation deps.androidx.activity + implementation deps.androidx.palette + implementation deps.androidx.flexbox +} \ No newline at end of file diff --git a/file/gradle/deps/deps_other_lib.gradle b/file/gradle/deps/deps_other_lib.gradle new file mode 100644 index 0000000000..aaead5bad4 --- /dev/null +++ b/file/gradle/deps/deps_other_lib.gradle @@ -0,0 +1,67 @@ +// 第三方 lib 依赖配置 +dependencies { + + // ============ + // = 快捷开发库 = + // ============ + + // OkHttp3 网络请求框架 https://github.com/square/okhttp + implementation deps.lib.okhttp3 + // Retrofit 网络请求库 https://github.com/square/retrofit + implementation deps.lib.retrofit + // Retrofit Gson Converter https://github.com/square/retrofit/blob/master/retrofit-converters/gson + implementation deps.lib.retrofit_gson + // Gson https://github.com/google/gson + implementation deps.lib.gson + // Fastjson2 https://github.com/alibaba/fastjson2 + implementation deps.lib.fastjson2_android + // Glide 图片加载框架 https://github.com/bumptech/glide + implementation deps.lib.glide + kapt deps.lib.glide_compiler + // 基于 mmap 的高性能通用 key-value 组件 https://github.com/Tencent/MMKV/blob/master/README_CN.md + implementation deps.lib.mmkv + // 今日头条屏幕适配方案终极版 https://github.com/JessYanCoding/AndroidAutoSize/blob/master/README-zh.md + implementation deps.lib.autosize + + // ========== + // = Widget = + // ========== + + // Banner 库 https://github.com/youth5201314/banner + implementation deps.widget.banner + // 下拉刷新框架 https://github.com/scwang90/SmartRefreshLayout + implementation deps.widget.smartrefreshlayout + implementation deps.widget.smartrefresh_header_classics + implementation deps.widget.smartrefresh_footer_classics + // 多类型 ViewType Adapter https://github.com/drakeet/MultiType + implementation deps.widget.multitype + + // ========================== + // = 其他第三方库 - 小功能、简约 = + // ========================== + + // Luban 鲁班图片压缩 https://github.com/Curzibn/Luban + implementation deps_split.lib.luban + // 二维码 ZXing https://github.com/zxing/zxing + implementation deps_split.lib.zxing_code + // Android 平台下的图片选择器 https://github.com/LuckSiege/PictureSelector + implementation deps_split.lib.pictureSelector +// implementation deps_split.lib.pictureSelector_compress +// implementation deps_split.lib.pictureSelector_ucrop + implementation deps_split.lib.pictureSelector_camerax + + // ============== + // = deps_split = + // ============== + + // GPU Filters https://github.com/cats-oss/android-gpuimage + implementation deps_split.widget.gpuimage + // 评价等级控件 https://github.com/williamyyu/SimpleRatingBar + implementation deps_split.widget.simple_ratingbar + // 加载动画效果 https://github.com/Kyson/WhorlView + implementation deps_split.widget.whorlviewlibrary + // 加载动画效果 https://github.com/dinuscxj/LoadingDrawable + implementation deps_split.widget.loading_drawable + // RecyclerView 实现顶部吸附效果 https://github.com/Gavin-ZYX/StickyDecoration + implementation deps_split.widget.stickyDecoration +} \ No newline at end of file diff --git a/file/gradle/deps/deps_qa_lib.gradle b/file/gradle/deps/deps_qa_lib.gradle new file mode 100644 index 0000000000..8104fae23b --- /dev/null +++ b/file/gradle/deps/deps_qa_lib.gradle @@ -0,0 +1,32 @@ +// 性能检测相关 lib 依赖配置 +dependencies { + + // ==================== + // = 性能检测、排查相关库 = + // ==================== + +// // Bugly https://bugly.qq.com/docs/ +// implementation deps_split.property.bugly +// implementation deps_split.property.bugly_ndk + +// // 饿了么 UETool https://github.com/eleme/UETool/blob/master/README_zh.md +// debugImplementation deps_split.property.uetool +// releaseImplementation deps_split.property.uetool_no_op + +// // 内存检测工具 https://github.com/square/leakcanary +// debugImplementation deps_split.property.leakcanary + +// // BlockCanary 性能监控组件 https://github.com/markzhai/AndroidPerformanceMonitor/blob/master/README_CN.md +// debugImplementation deps_split.property.blockcanary_android +// releaseImplementation deps_split.property.blockcanary_no_op + +// // Matrix https://github.com/Tencent/matrix/#matrix_android_cn +// implementation(deps_split.property.matrix_android_lib) { changing = true } +// implementation(deps_split.property.matrix_android_commons) { changing = true } +// implementation(deps_split.property.matrix_trace_canary) { changing = true } +// implementation(deps_split.property.matrix_resource_canary_android) { changing = true } +// implementation(deps_split.property.matrix_resource_canary_common) { changing = true } +// implementation(deps_split.property.matrix_io_canary) { changing = true } +// implementation(deps_split.property.matrix_sqlite_lint) { changing = true } +// implementation(deps_split.property.matrix_battery_canary) { changing = true } +} \ No newline at end of file diff --git a/file/gradle/upload/bintray/bintrayUploadAndroid.gradle b/file/gradle/upload/bintray/bintrayUploadAndroid.gradle new file mode 100644 index 0000000000..5a4b3bc11e --- /dev/null +++ b/file/gradle/upload/bintray/bintrayUploadAndroid.gradle @@ -0,0 +1,120 @@ +apply plugin: "com.github.dcendents.android-maven" +apply plugin: "com.jfrog.bintray" + +// load properties +Properties properties = new Properties() +//File localPropertiesFile = project.file("bintray.properties") +File localPropertiesFile = new File(rootDir, "bintray.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} +File projectPropertiesFile = project.file("project.properties") +if (projectPropertiesFile.exists()) { + properties.load(projectPropertiesFile.newDataInputStream()) +} + +// read properties +def projectName = properties.getProperty("project.name") +def projectGroupId = properties.getProperty("project.groupId") +def projectArtifactId = properties.getProperty("project.artifactId") +def projectVersionName = android.defaultConfig.versionName +def projectPackaging = properties.getProperty("project.packaging") +def projectSiteUrl = properties.getProperty("project.siteUrl") +def projectGitUrl = properties.getProperty("project.gitUrl") + +def developerId = properties.getProperty("developer.id") +def developerName = properties.getProperty("developer.name") +def developerEmail = properties.getProperty("developer.email") + +def bintrayUser = properties.getProperty("bintray.user") +def bintrayApikey = properties.getProperty("bintray.apikey") + +def javadocName = properties.getProperty("javadoc.name") + +group = projectGroupId + +// This generates POM.xml with proper parameters +install { + repositories.mavenInstaller { + pom { + project { + name projectName + groupId projectGroupId + artifactId projectArtifactId + version projectVersionName + packaging projectPackaging + url projectSiteUrl + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id developerId + name developerName + email developerEmail + } + } + scm { + connection projectGitUrl + developerConnection projectGitUrl + url projectSiteUrl + } + } + } + } +} + +// This generates sources.jar +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = "sources" +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs +// classpath += configurations.compile + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + failOnError false +} + +// This generates javadoc.jar +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = "javadoc" + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +// javadoc configuration +javadoc { + options { + encoding "UTF-8" + charSet "UTF-8" + author true + version projectVersionName + links "http://docs.oracle.com/javase/7/docs/api" + title javadocName + } +// options.addStringOption("Xdoclint:none", "-quiet") +} + +// bintray configuration +bintray { + user = bintrayUser + key = bintrayApikey + configurations = ["archives"] + pkg { + repo = "maven" + name = projectName + websiteUrl = projectSiteUrl + vcsUrl = projectGitUrl + licenses = ["Apache-2.0"] + publish = true + } +} \ No newline at end of file diff --git a/file/gradle/upload/bintray/bintrayUploadJava.gradle b/file/gradle/upload/bintray/bintrayUploadJava.gradle new file mode 100644 index 0000000000..c75ffd30eb --- /dev/null +++ b/file/gradle/upload/bintray/bintrayUploadJava.gradle @@ -0,0 +1,113 @@ +apply plugin: "com.github.dcendents.android-maven" +apply plugin: "com.jfrog.bintray" + +// load properties +Properties properties = new Properties() +//File localPropertiesFile = project.file("bintray.properties") +File localPropertiesFile = new File(rootDir, "bintray.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} +File projectPropertiesFile = project.file("project.properties") +if (projectPropertiesFile.exists()) { + properties.load(projectPropertiesFile.newDataInputStream()) +} + +// read properties +def projectName = properties.getProperty("project.name") +def projectGroupId = properties.getProperty("project.groupId") +def projectArtifactId = properties.getProperty("project.artifactId") +def projectVersionName = version +def projectPackaging = properties.getProperty("project.packaging") +def projectSiteUrl = properties.getProperty("project.siteUrl") +def projectGitUrl = properties.getProperty("project.gitUrl") + +def developerId = properties.getProperty("developer.id") +def developerName = properties.getProperty("developer.name") +def developerEmail = properties.getProperty("developer.email") + +def bintrayUser = properties.getProperty("bintray.user") +def bintrayApikey = properties.getProperty("bintray.apikey") + +def javadocName = properties.getProperty("javadoc.name") + +group = projectGroupId + +// This generates POM.xml with proper parameters +install { + repositories.mavenInstaller { + pom { + project { + name projectName + groupId projectGroupId + artifactId projectArtifactId + version projectVersionName + packaging projectPackaging + url projectSiteUrl + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id developerId + name developerName + email developerEmail + } + } + scm { + connection projectGitUrl + developerConnection projectGitUrl + url projectSiteUrl + } + } + } + } +} + +// This generates sources.jar +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource +} + +// This generates javadoc.jar +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = "javadoc" + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +// javadoc configuration +javadoc { + options { + encoding "UTF-8" + charSet "UTF-8" + author true + version projectVersionName + links "http://docs.oracle.com/javase/7/docs/api" + title javadocName + } + options.addStringOption("Xdoclint:none", "-quiet") +} + +// bintray configuration +bintray { + user = bintrayUser + key = bintrayApikey + configurations = ["archives"] + pkg { + repo = "maven" + name = projectName + websiteUrl = projectSiteUrl + vcsUrl = projectGitUrl + licenses = ["Apache-2.0"] + publish = true + } +} \ No newline at end of file diff --git a/file/gradle/upload/sonatype/sonatypeUploadAndroid.gradle b/file/gradle/upload/sonatype/sonatypeUploadAndroid.gradle new file mode 100644 index 0000000000..f6de385f09 --- /dev/null +++ b/file/gradle/upload/sonatype/sonatypeUploadAndroid.gradle @@ -0,0 +1,176 @@ +// https://github.com/gradle/gradle/issues +apply plugin: "maven-publish" +apply plugin: "signing" + +// load properties +Properties properties = new Properties() +File localPropertiesFile = new File(rootDir, "sonatype.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} +File projectPropertiesFile = project.file("project.properties") +if (projectPropertiesFile.exists()) { + properties.load(projectPropertiesFile.newDataInputStream()) +} + +// ========================================= +// = 本地 sonatype properties 文件防止意外上传 = +// ========================================= + +localPropertiesFile = new File(rootDir, "../../File/sonatype.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} + +// ======= +// = 结束 = +// ======= + +// read properties +def projectName = properties.getProperty("project.name") +def projectGroupId = properties.getProperty("project.groupId") +def projectArtifactId = properties.getProperty("project.artifactId") +def projectVersionName = android.defaultConfig.versionName +def projectPackaging = properties.getProperty("project.packaging") +def projectSiteUrl = properties.getProperty("project.siteUrl") +def projectGitUrl = properties.getProperty("project.gitUrl") + +def developerId = properties.getProperty("developer.id") +def developerName = properties.getProperty("developer.name") +def developerEmail = properties.getProperty("developer.email") + +def signingKeyId = properties.getProperty("signing.keyId") +def signingPassword = properties.getProperty("signing.password") +def signingSecretKeyRingFile = properties.getProperty("signing.secretKeyRingFile") +def ossrhUsername = properties.getProperty("ossrhUsername") +def ossrhPassword = properties.getProperty("ossrhPassword") + +def javadocName = properties.getProperty("javadoc.name") + +// 适配 macOS 非使用绝对路径 +if (!new File(signingSecretKeyRingFile).exists()) { + signingSecretKeyRingFile = new File( + rootDir, signingSecretKeyRingFile + ).getAbsolutePath() +} + +ext["signing.keyId"] = signingKeyId +ext["signing.password"] = signingPassword +ext["signing.secretKeyRingFile"] = signingSecretKeyRingFile +ext["ossrhUsername"] = ossrhUsername +ext["ossrhPassword"] = ossrhPassword + +// This generates sources.jar +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = "sources" +} + +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs +// classpath += configurations.compile + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) + failOnError false +} + +// This generates javadoc.jar +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = "javadoc" + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +// javadoc configuration +javadoc { + options { + encoding "UTF-8" + charSet "UTF-8" + author true + version projectVersionName + links "http://docs.oracle.com/javase/7/docs/api" + title javadocName + } +} + +// https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html +publishing { + publications { + release(MavenPublication) { + groupId projectGroupId + artifactId projectArtifactId + version projectVersionName + + artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") + artifact javadocJar + artifact sourcesJar + + // https://docs.gradle.org/6.7.1/dsl/org.gradle.api.publish.maven.MavenPom.html + pom { + name = projectName + url = projectSiteUrl + packaging = projectPackaging + groupId = projectGroupId + artifactId = projectArtifactId + version = projectVersionName + description = "${projectName}:${projectVersionName} publish" + licenses { + license { + name = "The Apache Software License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = developerId + name = developerName + email = developerEmail + } + } + scm { + connection = projectGitUrl + developerConnection = projectGitUrl + url = projectSiteUrl + } + withXml { + def dependenciesNode = asNode().appendNode("dependencies") + project.configurations.implementation.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode("dependency") + dependencyNode.appendNode("groupId", it.group) + dependencyNode.appendNode("artifactId", it.name) + dependencyNode.appendNode("version", it.version) + } + } + } + } + } + // https://docs.gradle.org/current/userguide/publishing_maven.html + repositories { + maven { + name = projectName + // https://s01.oss.sonatype.org/#view-repositories + // https://central.sonatype.org/publish/publish-guide/#deployment + def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = version.endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl + + credentials { + username ossrhUsername + password ossrhPassword + } + } + } +} + +signing { + sign publishing.publications +} + +//module gradle +// +//tasks-build-assemble +// +//publishing-publishReleasePublicationToXXXXXRepository \ No newline at end of file diff --git a/file/gradle/upload/sonatype/sonatypeUploadJava.gradle b/file/gradle/upload/sonatype/sonatypeUploadJava.gradle new file mode 100644 index 0000000000..9c7258686e --- /dev/null +++ b/file/gradle/upload/sonatype/sonatypeUploadJava.gradle @@ -0,0 +1,170 @@ +// https://github.com/gradle/gradle/issues +apply plugin: "maven-publish" +apply plugin: "signing" + +// load properties +Properties properties = new Properties() +File localPropertiesFile = new File(rootDir, "sonatype.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} +File projectPropertiesFile = project.file("project.properties") +if (projectPropertiesFile.exists()) { + properties.load(projectPropertiesFile.newDataInputStream()) +} + +// ========================================= +// = 本地 sonatype properties 文件防止意外上传 = +// ========================================= + +localPropertiesFile = new File(rootDir, "../../File/sonatype.properties") +if (localPropertiesFile.exists()) { + properties.load(localPropertiesFile.newDataInputStream()) +} + +// ======= +// = 结束 = +// ======= + +// read properties +def projectName = properties.getProperty("project.name") +def projectGroupId = properties.getProperty("project.groupId") +def projectArtifactId = properties.getProperty("project.artifactId") +def projectVersionName = version +def projectPackaging = properties.getProperty("project.packaging") +def projectSiteUrl = properties.getProperty("project.siteUrl") +def projectGitUrl = properties.getProperty("project.gitUrl") + +def developerId = properties.getProperty("developer.id") +def developerName = properties.getProperty("developer.name") +def developerEmail = properties.getProperty("developer.email") + +def signingKeyId = properties.getProperty("signing.keyId") +def signingPassword = properties.getProperty("signing.password") +def signingSecretKeyRingFile = properties.getProperty("signing.secretKeyRingFile") +def ossrhUsername = properties.getProperty("ossrhUsername") +def ossrhPassword = properties.getProperty("ossrhPassword") + +def javadocName = properties.getProperty("javadoc.name") + +// 适配 macOS 非使用绝对路径 +if (!new File(signingSecretKeyRingFile).exists()) { + signingSecretKeyRingFile = new File( + rootDir, signingSecretKeyRingFile + ).getAbsolutePath() +} + +ext["signing.keyId"] = signingKeyId +ext["signing.password"] = signingPassword +ext["signing.secretKeyRingFile"] = signingSecretKeyRingFile +ext["ossrhUsername"] = ossrhUsername +ext["ossrhPassword"] = ossrhPassword + +// This generates sources.jar +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource +} + +// This generates javadoc.jar +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = "javadoc" + from javadoc.destinationDir +} + +artifacts { + archives javadocJar + archives sourcesJar +} + +// javadoc configuration +javadoc { + options { + encoding "UTF-8" + charSet "UTF-8" + author true + version projectVersionName + links "http://docs.oracle.com/javase/7/docs/api" + title javadocName + } + options.addStringOption("Xdoclint:none", "-quiet") +} + +// https://docs.gradle.org/current/dsl/org.gradle.api.publish.maven.MavenPublication.html +publishing { + publications { + release(MavenPublication) { + groupId projectGroupId + artifactId projectArtifactId + version projectVersionName + + artifact("$buildDir/libs/${project.getName()}-${projectVersionName}.jar") + artifact javadocJar + artifact sourcesJar + + // https://docs.gradle.org/6.7.1/dsl/org.gradle.api.publish.maven.MavenPom.html + pom { + name = projectName + url = projectSiteUrl + packaging = projectPackaging + groupId = projectGroupId + artifactId = projectArtifactId + version = projectVersionName + description = "${projectName}:${projectVersionName} publish" + licenses { + license { + name = "The Apache Software License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = developerId + name = developerName + email = developerEmail + } + } + scm { + connection = projectGitUrl + developerConnection = projectGitUrl + url = projectSiteUrl + } + withXml { + def dependenciesNode = asNode().appendNode("dependencies") + project.configurations.implementation.allDependencies.each { + def dependencyNode = dependenciesNode.appendNode("dependency") + dependencyNode.appendNode("groupId", it.group) + dependencyNode.appendNode("artifactId", it.name) + dependencyNode.appendNode("version", it.version) + } + } + } + } + } + // https://docs.gradle.org/current/userguide/publishing_maven.html + repositories { + maven { + name = projectName + // https://s01.oss.sonatype.org/#view-repositories + // https://central.sonatype.org/publish/publish-guide/#deployment + def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = version.endsWith("SNAPSHOT") ? snapshotsRepoUrl : releasesRepoUrl + + credentials { + username ossrhUsername + password ossrhPassword + } + } + } +} + +signing { + sign publishing.publications +} + +//module gradle +// +//tasks-build-assemble +// +//publishing-publishReleasePublicationToXXXXXRepository \ No newline at end of file diff --git a/file/gradle/versions.gradle b/file/gradle/versions.gradle new file mode 100644 index 0000000000..faa1a1190a --- /dev/null +++ b/file/gradle/versions.gradle @@ -0,0 +1,81 @@ +ext { + versions = [ + + // 版本号 + versionCode : 999, + // 版本名 + versionName : "9.9.9", + // 支持最小的 SDK + lib_minSdkVersion : 14, + // 支持最小的 SDK + app_minSdkVersion : 21, + // Lib 编译 SDK 版本 + lib_compileSdkVersion : 32, + // 编译 SDK 版本 + compileSdkVersion : 32, + // 向前兼容 SDK 版本 + targetSdkVersion : 32, + // 构建工具版本 + buildToolsVersion : "32.0.0", + // Java 版本 + javaVersion : JavaVersion.VERSION_1_8, + javaVersion_str : "1.8", + + // =========== + // = Dev Lib = + // =========== + + // DevApp - Android 工具类库 + dev_app_versionCode : 242, + dev_app_versionName : "2.4.2", + + // DevAssist - 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 + dev_assist_versionCode : 138, + dev_assist_versionName : "1.3.8", + + // DevBase - Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 + dev_base_versionCode : 114, + dev_base_versionName : "1.1.4", + + // DevBaseMVVM - MVVM ( ViewDataBinding + ViewModel ) 基类库 + dev_base_mvvm_versionCode : 112, + dev_base_mvvm_versionName : "1.1.2", + + // DevMVVM - DataBinding 工具类库 + dev_mvvm_versionCode : 100, + dev_mvvm_versionName : "1.0.0", + + // DevEngine - 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 + dev_engine_versionCode : 110, + dev_engine_versionName : "1.1.0", + + // DevHttpCapture - OkHttp 抓包工具库 + dev_http_capture_versionCode : 113, + dev_http_capture_versionName : "1.1.3", + dev_http_capture_compiler_version : "1.1.3", + dev_http_capture_compiler_release_version: "1.1.3", + + // DevHttpManager - OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + dev_http_manager_versionCode : 103, + dev_http_manager_versionName : "1.0.3", + + // DevRetrofit - Retrofit + Kotlin Coroutines 封装 + dev_retrofit_versionCode : 102, + dev_retrofit_versionName : "1.0.2", + + // DevWidget - 自定义 View UI 库 + dev_widget_versionCode : 120, + dev_widget_versionName : "1.2.0", + + // DevEnvironment - Android 环境配置切换库 + dev_environment_versionCode : 112, + dev_environment_version : "1.1.2", + dev_environment_base_version : "1.1.2", + dev_environment_compiler_version : "1.1.2", + dev_environment_compiler_release_version : "1.1.2", + + // DevJava - Java 工具类库 ( 不依赖 android api ) + dev_java_versionCode : 148, + dev_java_version : "1.4.8", + ] +} \ No newline at end of file diff --git a/file/sign/demo.jks b/file/sign/demo.jks new file mode 100644 index 0000000000..75addb0910 Binary files /dev/null and b/file/sign/demo.jks differ diff --git a/gradle.properties b/gradle.properties index aac7c9b461..e4d556f5cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,41 @@ # Project-wide Gradle settings. - # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. - # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html - # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - +org.gradle.jvmargs=-Xmx4096m -XX:+UseParallelGC # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# https://www.jianshu.com/p/d852705613ea + +org.gradle.daemon=true +org.gradle.configureondemand=true +org.gradle.parallel=true + +org.gradle.caching=true + +kotlin.incremental=true +kotlin.incremental.java=true +kotlin.incremental.js=true +kotlin.caching.enabled=true + +#kapt.use.worker.api=true +kapt.incremental.apt=true +kapt.include.compile.classpath=false + +#https://docs.gradle.org/7.3.3/userguide/build_environment.html#gradle_system_properties +https.protocols=TLSv1.2,TLSv1.3 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fab5eef69c..9a0710ef53 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 07 12:58:58 CST 2018 +#Tue Oct 13 02:22:52 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip \ No newline at end of file diff --git a/gradlew b/gradlew index 9d82f78915..d1c75a26cc 100644 --- a/gradlew +++ b/gradlew @@ -157,4 +157,4 @@ function splitJvmOpts() { eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" \ No newline at end of file diff --git a/lib/DevApp/.gitignore b/lib/DevApp/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevApp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevApp/CHANGELOG.md b/lib/DevApp/CHANGELOG.md new file mode 100644 index 0000000000..932ef16ac6 --- /dev/null +++ b/lib/DevApp/CHANGELOG.md @@ -0,0 +1,776 @@ +Change Log +========== + +Version 2.4.2 *(2022-09-18)* +---------------------------- + +* `[Add]` 新增 UriUtils#ofUri + +* `[Add]` 新增 NumberUtils#calculateUnitD、calculateUnitF + +* `[Add]` 新增 FormatUtils 工具类及 UnitSpanFormatter、ArgsFormatter 封装类 + +* `[Add]` 新增部分通用 able 接口 + +Version 2.4.1 *(2022-07-18)* +---------------------------- + +* `[Add]` 新增 TextViewUitls AppCompat AutoSizeableTextView 接口实现方法 + +* `[Add]` 新增 TextViewUtils 方法到 Helper 类 + +* `[Add]` 新增部分通用 able 接口 + +Version 2.4.0 *(2022-07-04)* +---------------------------- + +* `[Add]` 新增 UriUtils#isAndroidResourceScheme、isFileScheme、isContentScheme、isUriScheme + +* `[Add]` 新增 StringUtils#equalsIgnoreCase、equalsIgnoreCaseNotNull + +* `[Add]` 新增 ValiToPhoneUtils 中国广电手机号码段、更新最新运营商号码段 + +* `[Add]` 新增 DevFinal 部分常量值 + +Version 2.3.9 *(2022-06-26)* +---------------------------- + +* `[Review]` 检查并调整使用 equals、equalsIgnoreCase 代码 + +* `[Add]` 新增 StringUtils#equalsNotNull 方法 + +* `[Add]` 新增 图片 EXIF 读写辅助类 + +Version 2.3.8 *(2022-06-02)* +---------------------------- + +* `[Refactor]` 新建 DefaultActivityResult 类并迁移 ActivityUtils 原始 startActivityForResult 封装实现代码 + +* `[Add]` 新增 New Activity Result API 封装辅助类 ActivityResultAssist + +* `[Add]` 新增 ActivityResultUtils 工具类,用于使用新旧 ActivityResult 兼容 + +Version 2.3.7 *(2022-05-13)* +---------------------------- + +* `[Add]` 新增 DevUtils#getHandler(handler) 判断 null,视情况返回全局 MainHandler + +* `[Refactor]` 重构 AccessibilityUtils 工具类,并对节点信息、节点、日志打印操作进行独立封装 Node、Operation、Print 类 + +* `[Refactor]` Helper 链式相关类,移除 IHelper 并且新增 BaseHelper 内置通用方法 + +* `[Add]` 新增 FlowHelper 流式 ( 链式 ) 连接 Helper 类,便捷链接其他 Helper 但不局限于上述类使用 + +* `[Add]` 新增 DevFinal#INT 部分默认值 + +* `[Add]` 新增 UrlExtras Url 携带信息解析类,并默认提供 DevJavaUrlParser、AndroidUrlParser 实现 + +* `[Add]` 新增 ConvertUtils#newString 方法处理 CharSequence + +* `[Add]` 新增 ConvertUtils#newStringNotArrayDecode 并修改所有工具类 instanceof String 判断转换使用该方法 + +* `[Add]` 新增 BigDecimalUtils 抛出异常相对应捕获异常快捷方法,并修改抛出方法名为 xxxThrow(param) + +Version 2.3.6 *(2022-03-20)* +---------------------------- + +* `[Add]` 新增 DevFinal#DEFAULT 默认值,并全局统一该库默认值 + +* `[Add]` 新增 BigDecimalUtils 快捷方法抛出异常便于自定义异常值 + +* `[Add]` 新增 RecyclerViewUtils#getOrientation、canScrollVertically、canScrollHorizontally LayoutManager 参数 + +* `[Add]` 新增 NotificationUtils#createNotificationBuilder 方法 + +Version 2.3.5 *(2022-02-12)* +---------------------------- + +* `[Add]` StringUtils#join、joinArgs + +* `[Add]` CollectionUtils#clearAndAddAll、clearAndAddAllNotNull + +* `[Add]` RecyclerViewUtils#setSpanCount、getSpanCount + +* `[Add]` ViewHelper、QuickHelper#setSpanCount + +* `[Add]` IFloatingListener、DevFloatingListener、DevFloatingCommon 解决悬浮窗 onTouchEvent 与 click、longClick 冲突处理 + +Version 2.3.4 *(2022-01-23)* +---------------------------- + +* `[Update]` SizeUtils#dipConvertPx、dipConvertPxf、pxConvertDip、pxConvertDipf、spConvertPx、spConvertPxf、pxConvertSp、pxConvertSpf 方法重命名为 dp2px、dp2pxf、px2dp、px2dpf、sp2px、sp2pxf、px2sp、px2spf + +* `[Update]` ClickUtils、ListenerUtils 移除 listener 判 null 处理 + +Version 2.3.3 *(2022-01-10)* +---------------------------- + +* `[Add]` ComparatorUtils 排序比较器工具类 + +* `[Add]` FileUtils#getFile、listOrEmpty、listFilesOrEmpty + +Version 2.3.2 *(2021-12-30)* +---------------------------- + +* `[Refactor]` 修改整个 DevFinal 常量类,并统一使用该常量类 + +* `[Add]` IFloatingEdge、DevFloatingEdgeIMPL、DevFloatingTouchIMPL2 等悬浮窗边缘检测、触摸实现类 + +* `[Add]` QuickHelper#setX、setY + +* `[Add]` ViewHelper#setX、setY + +* `[Add]` ActivityUtils#isDestroyed、isNotDestroyed、assertValidActivity + +* `[Add]` ViewUtils#setX、setY、getX、getY + +Version 2.3.1 *(2021-12-20)* +---------------------------- + +* `[Refactor]` 修改整个 DevFinal 常量类,并统一使用该常量类 + +* `[Add]` FloatingWindowManagerAssist、FloatingWindowManagerAssist2 悬浮窗两种实现方案辅助类 + +* `[Add]` ActivityLifecycleAssist Activity 生命周期监听辅助类 + +* `[Add]` WindowAssist Window 辅助类 + +* `[Add]` WindowUtils Window 工具类 + +* `[Add]` DevHelper Dev 工具类链式调用类 + +* `[Add]` FlagsValue 标记值计算存储 ( 位运算符 ) + +Version 2.3.0 *(2021-11-26)* +---------------------------- + +* `[Refactor]` 检查整个项目新增部分方法支持传入 Context 参数 ( 以便屏幕适配等 ) + +* `[Add]` ActivityManagerAssist Activity 栈管理辅助类 + +* `[Add]` WeakReferenceAssist 弱引用辅助类 + +* `[Add]` MapUtils#mapToString + +Version 2.2.9 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +* `[Add]` ForUtils + +* `[Add]` XorUtils#xorChecksum + +* `[Add]` FileUtils#createTimestampFileName + +* `[Add]` RecyclerViewUtils + +* `[Add]` SharedPreferences 操作监听器 OnSPOperateListener + +* `[Add]` ViewUtils#setVisibilityIN、setVisibilityINs、getGlobalVisibleRect、getLocalVisibleRect、getLocationInWindow、getLocationOnScreen、setBarProgress、setBarMax、setBarValue + +* `[Add]` KeyBoardUtils#openKeyboardByFocus + +* `[Add]` ImageViewUtils#setImageLevel + +* `[Add]` VersionUtils#isEclair、isKitkat_Watch、isLollipop_MR1、convertSDKVersionName + +* `[Refactor]` 重构 QuickHelper、ViewHelper、VersionHelper + +* `[Delete]` DevHelper + +Version 2.2.8 *(2021-06-28)* +---------------------------- + +* `[Delete]` AnalysisRecordUtils + +* `[Refactor]` 重构 FileRecordUtils 并进行内部类拆分便于 DevApp 模块使用 + +* `[Update]` MediaStoreUtils createImageUri、createVideoUri、createAudioUri、createDownloadUri 方法参数位置 + +Version 2.2.7 *(2021-06-21)* +---------------------------- + +* `[Delete]` AppCommonUtils 使用 VersionUtils + +* `[Add]` MediaStoreUtils#notifyMediaStore、createDownloadUri、createUriByPath、createUriByFile + +* `[Add]` VersionHelper#createDownloadUri、createUriByPath、createUriByFile + +* `[Add]` BarUtils#getStatusBarHeight2、ScreenUtils#getStatusBarHeight2 + +* `[Add]` AppUtils#getCurrentWindowMetrics、getMaximumWindowMetrics + +* `[Add]` UriUtils#fromFile + +* `[Add]` StringUtils#urlDecodeWhile + +* `[Add]` HttpParamsUtils#getUrlParams、getUrlParamsArray、existsParams、existsParamsByURL、joinUrlParams、getUrlParamsJoinSymbol、splitParamsByUrl + +Version 2.2.6 *(2021-06-04)* +---------------------------- + +* `[Add]` UriUtils#getUriForString、isUri、getUriScheme + +* `[Add]` DevFinal 新增部分常量 + +Version 2.2.5 *(2021-05-19)* +---------------------------- + +* `[Add]` ColorUtils#sortHUE、sortSaturation、sortBrightness + +Version 2.2.4 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 2.2.3 *(2021-03-27)* +---------------------------- + +* `[Refactor]` DevCache + +* `[Add]` ViewUtils#getWidthHeightExact、getWidthHeightExact2 + +Version 2.2.2 *(2021-03-21)* +---------------------------- + +* `[Add]` ColorUtils#blendColor、transitionColor + +Version 2.2.1 *(2021-03-17)* +---------------------------- + +* `[Refactor]` TimerManager、DevTimer 简化使用复杂性并进行拆分类 + +Version 2.2.0 *(2021-03-16)* +---------------------------- + +* `[Add]` IPreference、PreferenceImpl 新增 Double 类型存储、默认值传参 ( 配合 DevAssis#StorageEngine ) + +Version 2.1.9 *(2021-03-02)* +---------------------------- + +* `[Refactor]` BigDecimalUtils + +Version 2.1.8 *(2021-03-01)* +---------------------------- + +* `[Refactor]` NotificationUtils 新增 Params、Callback + +* `[Add]` DeviceUtils#isDevelopmentSettingsEnabled + +Version 2.1.7 *(2021-02-27)* +---------------------------- + +* `[Add]` NumberUtils#subZeroAndDot + +* `[Refactor]` BigDecimalUtils + +* `[Add]` DevFinal 新增部分常量 + +* `[Add]` ClickUtils#OnMultiClickListener + +Version 2.1.6 *(2021-02-08)* +---------------------------- + +* `[Add]` ColorUtils#getRandomColorString 方法 + +* `[Add]` DevFinal 新增部分常量 + +* `[Add]` DevicePolicyUtils 设备管理工具类 + +Version 2.1.5 *(2021-01-24)* +---------------------------- + +* `[Perf]` 进行代码检测优化 + +* `[Add]` DevFinal 新增部分常量 + +* `[Add]` VersionUtils + +* `[Add]` DeviceUtils#getAppDeviceInfo、refreshAppDeviceInfo、getUUID、getUUIDDevice 方法 + +* `[Delete]` AppCommonUtils#getAppDeviceInfo、refreshAppDeviceInfo、getUUID、getUUIDDevice、getFormatRes 方法 + +Version 2.1.4 *(2021-01-01)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Add]` 移除 MissingPermission 重新补回权限要求 RequiresPermission 注解 + +Version 2.1.3 *(2020-12-28)* +---------------------------- + +* `[Add]` DelayAssist 延迟触发辅助类 + +* `[Add]` WallpaperUtils 壁纸工具类 + +* `[Update]` KeyBoardUtils 移除 close/openKeyboard Handler 参数方法修改为 close/openKeyboardDelay + +Version 2.1.2 *(2020-12-21)* +---------------------------- + +* `[Add]` MediaStoreUtils#insertMedia Bitmap、Drawable、File、Stream 参数方法 + +* `[Add]` VersionHelper#insertMedia Bitmap、Drawable、File、Stream 参数方法 + +Version 2.1.1 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Update]` 修改 CallBack 相关代码为 Callback + +* `[Add]` DevFinal 新增部分常量 + +* `[Delete]` PermissionConstants 类 + +* `[Add]` ViewUtils#getChildAtLast、getId、setId + +Version 2.1.0 *(2020-11-15)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 2.0.9 *(2020-11-05)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 2.0.8 *(2020-10-29)* +---------------------------- + +* `[Feature]` 适配 Android 11 ( R ) + +* `[Add]` VersionHelper ( Android 版本适配 Helper 类 ),方便快捷使用并简化需多工具类组合使用的功能 + +* `[Add]` MediaStoreUtils#createWriteRequest、createFavoriteRequest、createTrashRequest、createDeleteRequest + +* `[Add]` PathUtils#isExternalStorageManager、checkExternalStorageAndIntentSetting + +* `[Add]` AppUtils#startIntentSenderForResult + +* `[Add]` IntentUtils#getManageAppAllFilesAccessPermissionIntent、getManageAllFilesAccessPermissionIntent、getImageCaptureIntent、getVideoCaptureIntent + +* `[Add]` ArrayUtils#asListArgs + +* `[Update]` MapUtils#putToList、removeToList、removeToMap 参数类型 + +* `[Update]` 进行 Spelling typo Analyze 修改部分拼写错误字段 + +Version 2.0.7 *(2020-10-20)* +---------------------------- + +* `[Update]` StringUtils#convertHideMobile、convertSymbolHide Method Name + +* `[Fix]` StringUtils#replaceSEWith、clearSEWiths、clearEndsWith 索引判断问题 + +Version 2.0.6 *(2020-10-12)* +---------------------------- + +* `[Add]` ScreenshotUtils 截图监听工具类 + +* `[Add]` FilePartUtils 文件分片工具类 + +* `[Add]` BitmapUtils#getVideoThumbnail + +* `[Add]` DevHelper#flush、flushQuietly、flushCloseIO、flushCloseIOQuietly + +* `[Add]` CloseUtils#flush、flushQuietly、flushCloseIO、flushCloseIOQuietly + +* `[Add]` FileUtils#convertFiles、convertPaths + +* `[Refactor]` 修改整个库 Closeable Close 代码内部调用 CloseUtils + +* `[Update]` SpannableStringUtils 修改为 SpanUtils + +Version 2.0.5 *(2020-09-30)* +---------------------------- + +* `[Add]` ResourceAssist ( Resources 辅助类 ) + +* `[Add]` ResourcePluginUtils ( 从 APK 中读取 Resources 可实现换肤等功能 ) + +* `[Update]` 修改部分方法 obtain 为 get、newCache ( DevCache ) + +* `[Refactor]` 整合 DevApp Utils 代码,统一通过 ResourceAssist 辅助类进行 Resources 获取、适配控制等 + +Version 2.0.4 *(2020-09-27)* +---------------------------- + +* `[Add]` DateUtils#getZodiac、getConstellation、getConstellationDate 获取生肖、星座方法 + +* `[Add]` CalendarUtils 日历 ( 公历、农历 ) 工具类 + +Version 2.0.3 *(2020-09-20)* +---------------------------- + +* `[Fix]` NotificationUtils#createNotification 方法新增适配处理 + +* `[Fix]` PermissionUtils 内存泄露问题 + +* `[Delete]` DevCommonUtils 中其他工具类快捷方法 + +* `[Update]` 更新部分代码注释 + +Version 2.0.2 *(2020-09-15)* +---------------------------- + +* `[Add]` AnimationUtils#cancelAnimation + +* `[Add]` KeyBoardUtils#setSoftInputMode + +* `[Add]` HandlerUtils#isMainThread + +* `[Add]` HandlerUtils 新增 Key Runnable Map 方便通过 Key 快捷控制 Runnable,进行 postDelayed、removeCallbacks + +* `[Add]` StringUtils#clearTab、clearTabTrim、clearLine、clearLineTrim、clearSpaceTabLine、clearSpaceTabLineTrim + +* `[Add]` ScaleUtils#XY type 扩展字段 + +* `[Add]` NumberUtils#addZero + +* `[Update]` DateUtils#convertTime 为 timeAddZero + +* `[Update]` 去除部分方法名 to 前缀 + +Version 2.0.1 *(2020-09-07)* +---------------------------- + +* `[Add]` ScaleUtils#calcScale、calcScaleToMath、calcXY + +* `[Add]` StringUtils#forJoint + +* `[Add]` AppCommonUtils#isR + +* `[Add]` SnackbarUtils#getSnackbarLayout、getSnackbarContentLayout + +* `[Add]` ClickUtils#setCheckViewId 方法,OnDebouncingClickListener、OnCountClickListener 事件 + +* `[Fix]` 修复部分 LayoutParams 操作,未 setLayoutParams 处理 + +Version 2.0.0 *(2020-08-29)* +---------------------------- + +* `[Add]` FileUtils#canRead、canWrite、canReadWrite + +Version 1.9.9 *(2020-08-27)* +---------------------------- + +* `[Fix]` UriUtils#getFilePathByUri 方法新增 Android 10 ( Q ) 适配操作 + +* `[Add]` ContentResolverUtils#getDisplayNameColumn + +* `[Add]` UriUtils#copyByUri + +Version 1.9.8 *(2020-08-23)* +---------------------------- + +* `[Update]` ShapeUtils + +* `[Add]` ViewHelper#quickHelper、QuickHelper#quickHelper、DevHelper#quickHelper + +* `[Add]` IntentUtils#getCategoryLauncherIntent + +Version 1.9.7 *(2020-08-04)* +---------------------------- + +* `[Add]` ChineseUtils 中文工具类 + +* `[Add]` QuickHelper 简化链式设置 View Helper 类 + +* `[Add]` StringUtils#forString + +* `[Delete]` PhoneUtils 双卡模块代码 + +* `[Delete]` AsyncExecutor 异步执行辅助类 + +Version 1.9.6 *(2020-07-19)* +---------------------------- + +* `[Add]` ViewHelper#setPaintFlags、setAntiAliasFlag + +* `[Add]` ViewUtils#setPaintFlags、setAntiAliasFlag + +* `[Add]` DevLogger#setPrint、LogPrintUtils#setPrint 自定义输出接口 + +* `[Update]` ClickUtils 变量声明顺序 + +Version 1.9.5 *(2020-06-08)* +---------------------------- + +* `[Update]` PermissionUtils#isGranted + +* `[Add]` ResourceUtils#getDimensionInt + +Version 1.9.4 *(2020-05-18)* +---------------------------- + +* `[Update]` MediaStoreUtils#notifyMediaStore 通知刷新本地资源方法版本处理 + +* `[Update]` ClassUtils#getGenericSuperclass、getGenericInterfaces 返回类型 + +Version 1.9.3 *(2020-04-25)* +---------------------------- + +* `[Add]` LanguageUtils#isEn、isZh、isZhCN、isZhTW、isLanguage、isRegion、getSystemCountry + +* `[Add]` DeviceUtils#isTablet + +* `[Add]` ScreenUtils#isFullScreen、setFullScreenNoTitle + +* `[Update]` BarUtils + +* `[Delete]` OSUtils + +* `[Add]` ROMUtils + +Version 1.9.2 *(2020-03-19)* +---------------------------- + +* `[Add]` WidgetUtils 控件工具类 + +* `[Add]` ViewUtils#getClipChildren、setClipChildren、getContentView、getRootParent + +* `[Add]` ViewHelper#setClipChildren + +* `[Add]` TextViewUtils#reckonTextSizeByWidth + +* `[Update]` TextViewUtils#reckonTextSize 方法名为 TextViewUtils#reckonTextSizeByHeight + +Version 1.9.1 *(2020-03-11)* +---------------------------- + +* `[Add]` ViewUtils#isShown、isShowns + +* `[Add]` StringUtils#split + +* `[Add]` NumberUtils#calculateUnit + +* `[Update]` StringUtils#replaceStr、replaceStrToNull 方法名为 StringUtils#replaceAll、replaceAllToNull + +* `[Update]` TimerManager#startTimer、closeTimer 返回值为 AbsTimer + +Version 1.9.0 *(2020-02-21)* +---------------------------- + +* `[Add]` StringUtils#getBytes + +* `[Add]` FileIOUtils#getFileInputStream、getFileOutputStream + +* `[Update]` FileUtils#saveFile、appendFile + +* `[Update]` FileRecordUtils、AnalysisRecordUtils 关联引用 saveFile、appendFile 方法处理 + +Version 1.8.9 *(2020-01-26)* +---------------------------- + +* `[Add]` TypeUtils 类型工具类 + +* `[Add]` ClassUtils#getClass、isGenericParamType、getGenericParamType + +* `[Add]` ConvertUtils#toBigDecimal、toBigInteger、newString、charAt + +* `[Update]` ConvertUtils#toString、toInt、toBoolean、toFloat、toDouble、toLong、toShort、toChar、toByte、toChars、toBytes + +Version 1.8.8 *(2020-01-16)* +---------------------------- + +* `[Add]` BitmapUtils#calculateQuality 计算最佳压缩质量值方法 + +* `[Add]` FileUtils#listFilesInDirBean、listFilesInDirWithFilterBean 方法,获取文件目录列表集合 FileList + +* `[Fix]` 修复 AppUtils#isInstalledApp 判断是否安装错误情况 + +* `[Update]` 兼容 Android P 获取 versionCode 处理 ( getLongVersionCode ) + +Version 1.8.7 *(2020-01-07)* +---------------------------- + +* `[Update]` PermissionUtils#shouldShowRequestPermissionRationale 方法,增加可变数组权限传入 + +* `[Add]` PermissionUtils#getDeniedPermissionStatus 获取拒绝权限询问状态集合方法、PermissionUtils#againRequest 处理拒绝权限操作方法 + +* `[Update]` ActivityUtils#appExit 为 exitApplication + +* `[Add]` ActivityUtils#startActivityForResult 跳转方法,支持通过接口回调方式通知 + +Version 1.8.6 *(2019-12-25)* +---------------------------- + +* `[Feature]` 适配 Android 10 ( Q ) 并重构 PathUtils 工具类,提供适配思路以及增加 MediaStoreUtils 多媒体工具类用于外部存储适配操作 + +* `[Add]` ViewUtils#toggleFocusable、toggleSelected、toggleEnabled、toggleClickable、toggleLongClickable、getChilds + +* `[Add]` AppCommonUtils#getUUIDDevice、NotificationUtils#checkAndIntentSetting、isNotificationListenerEnabled、startNotificationListenSettings + +* `[Add]` UriUtils#isUriExists、IntentUtils#getLaunchAppNotificationListenSettingsIntent、getOpenBrowserIntent、getCreateDocumentIntent、getOpenDocumentIntent + +* `[Add]` CrashUtils UncaughtException 处理工具类、MediaStoreUtils 多媒体工具类 + +* `[Change]` 移动 ImageViewUtils 部分方法到 ViewUtils、更新 ContentResolverUtils 工具类代码,拆分到 UriUtils、MediaStoreUtils + +* `[Add]` ColorUtils#getARGB、grayLevel、sortGray、sortHSB 并增加内部类 ColorInfo,支持颜色排序 + +* `[Add]` FileIOUtils#copyLarge、DateUtils#yyyyMMdd_HHmmss、CoordinateUtils#getDistance、getAngle、getDirection + +* `[Add]` DevCommonUtils、StringUtils#appendsIgnoreLast + +* `[Update]` DevCommonUtils、StringUtils 几个重载方法 appends + +* `[Update]` 更新部分工具类、方法注释代码、代码间距等 + +Version 1.8.5 *(2019-11-25)* +---------------------------- + +* `[Refactor]` 重构整个项目,优化代码逻辑判断、代码风格、合并工具类减少包大小等,并修改 95% 返回值 void 的方法为 boolean 明确获取调用结果 + +* `[Add]` JSONObjectUtils#isJSONObject、isJSONArray、jsonToMap、jsonToList、getJSONObject、getJSONArray、get、opt + +* `[Add]` AppCommonUtils#getAppDeviceInfo、refreshAppDeviceInfo + +* `[Add]` AnalysisRecordUtils、FileRecordUtils 文件记录结果回调 + +* `[Add]` BigDecimalUtils#setScale、setRoundingMode、getBigDecimal、toString、toPlainString、toEngineeringString + +* `[Add]` ClassUtils#getClass、isPrimitive、isMap + +* `[Add]` MapUtils、CollectionUtils 获取泛型数组 toArrayT + +* `[Update]` 移动 FileRecordUtils、HtmlUtils 到 Java 模块 + +Version 1.8.4 *(2019-11-05)* +---------------------------- + +* `[Add]` FileUtils#isImageFormats、isAudioFormats、isVideoFormats、isFileFormats + +* `[Add]` ViewUtils#getWidthHeight、getNextFocusUpId、getNextFocusRightId、getNextFocusLeftId、getNextFocusDownId、getNextFocusForwardId、isScrollContainer、getChildCount、getRotation、getRotationX、getRotationY、getScaleX、getScaleY、getTextAlignment、getTextDirection、getPivotX、getPivotY、getTranslationX、getTranslationY、getLayerType、isFocusable、isSelected、isEnabled、isClickable、isLongClickable、findFocus、isFocused、hasFocus、hasFocusable、isFocusableInTouchMode、setFocusableInTouchMode、scrollTo、scrollBy、setScrollX、setScrollY、getScrollX、getScrollY、isHorizontalScrollBarEnabled、setHorizontalScrollBarEnabled、isVerticalScrollBarEnabled、setVerticalScrollBarEnabled、setDescendantFocusability、setOverScrollMode + +* `[Add]` TextViewUtils#getTypeface、getLetterSpacing、getLineSpacingExtra、getLineSpacingMultiplier、getTextScaleX、getIncludeFontPadding、getInputType、getImeOptions、getMaxLines、getMinLines、getMaxEms、getMinEms、getEllipsize、getAutoLinkMask、getGravity、clearFocus、requestFocus、requestLayout、getTransformationMethod、setTransformationMethod + +* `[Add]` EditTextUtils#isCursorVisible、getInputType、getImeOptions、getTransformationMethod、setTransformationMethod + +* `[Add]` AnimationUtils#setAnimationListener + +* `[Add]` ListViewUtils - 列表 View 相关工具类 ( 支持快捷滑动到指定索引、指定 x、y 轴坐标、回到顶部、底部等 ) + +* `[Add]` DevHelper、ViewHelper 快捷链式调用 Helper 类 + +Version 1.8.3 *(2019-10-31)* +---------------------------- + +* `[Add]` ArrayUtils#getMinimum、getMaximum、getMinimumIndex、getMaximumIndex、sumarray + +* `[Add]` CollectionUtils#getMinimum、getMaximum、、getMinimumIndex、getMaximumIndex、sumlist + +* `[Add]` AnimationUtils#setAnimation、getAnimation、clearAnimation、startAnimation、cancel + +* `[Add]` ViewUtils#setAnimation、getAnimation、clearAnimation、startAnimation、cancel、measureView、setWidthHeight、setWidth、setHeight、addRule、removeRule、getRule、addRules、removeRules + +* `[Add]` AppUtils#startActivity、startActivityForResult + +* `[Add]` IntentUtils#getLaunchAppInstallPermissionSettingsIntent、getLaunchAppNotificationSettingsIntent + +* `[Add]` PermissionUtils#canRequestPackageInstalls + +* `[Add]` NotificationUtils#isNotificationEnabled + +* `[Add]` CapturePictureUtils 截图工具类 ( 支持 View、Activity、FrameLayout、RelativeLayout、LinearLayout、ListView、GridView、ScrollView、HorizontalScrollView、NestedScrollView、WebView、RecyclerView(GridLayoutManager、LinearLayoutManager、StaggeredGridLayoutManager) ) + +Version 1.8.2 *(2019-10-18)* +---------------------------- + +* `[Add]` TextViewUtils#setMinLines、setMaxEms、setMinEms、setEms、setMaxLength、setMaxLengthAndText、setInputType、setImeOptions + +* `[Add]` EditTextUtils#setInputType、setImeOptions + +* `[Add]` JSONObjectUtils#isJSON + +* `[Add]` ViewUtils#setLayerType、setAllCaps、setAlpha、getAlpha、setScrollContainer、setNextFocusForwardId、setNextFocusDownId、setNextFocusLeftId、setNextFocusRightId、setNextFocusUpId、setRotation、setRotationX、setRotationY、setScaleX、setScaleY、setTextAlignment、setTextDirection、setPivotX、setPivotY、setTranslationX、setTranslationY + +Version 1.8.1 *(2019-10-13)* +---------------------------- + +* `[Add]` EditTextUtils#addTextChangedListener、removeTextChangedListener、setTexts + +* `[Add]` TextViewUtils#getHint、getHints、getHintTextColors、setHintTextColor、setHintTextColors、getTextColors、setTextColor、setTextColors、setGravity、setHint、setAutoLinkMask、setEllipsize、setMaxLines、setLines + +* `[Add]` ViewUtils#getMinimumHeight、setMinimumHeight、getMinimumWidth、setMinimumWidth + +* `[Add]` ImageViewUtils#getAdjustViewBounds、setAdjustViewBounds、getMaxHeight、setMaxHeight、getMaxWidth、setMaxWidth + +Version 1.8.0 *(2019-10-09)* +---------------------------- + +* `[Update]` TextViewUtils#calcTextWidth 使用二分法优化处理 + +* `[Add]` TextViewUtils#calcTextLine、TextViewUtils#getPaint、TextViewUtils#getTextWidth + +* `[Add]` DialogUtils#dismiss(DialogFragment) + +* `[Add]` ViewUtils#inflate + +* `[Add]` NumberUtils#getMultiple、getMultipleI、getMultipleD、getMultipleL、getMultipleF + +Version 1.7.9 *(2019-09-19)* +---------------------------- + +* `[Update]` compileSdkVersion 29 Android 10 ( Q ) + +* `[Update]` AppCommonUtils#convertSDKVersion + +* `[Update]` ImageUtils#getImageType、ImageUtils#isImage modify to isImageFormats + +* `[Update]` 修改部分方法 void 返回值 ( 返回当前对象,方便链式调用 ) + +* `[Add]` AppCommonUtils#isQ + +* `[Add]` BitmapUtils#isImage + +* `[Add]` ListenerUtils#setOnLongClicks + +* `[Add]` ImageUtils#isICO、ImageUtils#isTIFF + +* `[Add]` ViewUtils#getTag、setTag + +Version 1.7.8 *(2019-09-12)* +---------------------------- + +* `[Add]` ImageViewUtils#setBackgroundResources + +* `[Add]` ViewUtils#getParent + +* `[Add]` ConvertUtils#convert + +* `[Fix]` DialogUtils#showDialog、closeDialog try catch + +Version 1.7.7 *(2019-08-25)* +---------------------------- + +* `[New]` Support for AndroidX + +* `[Add]` DevCommonUtils#subSetSymbol + +* `[Add]` ScreenUtils#setWindowSecure + +* `[Add]` ViewUtils#set/getMargin、set/getPadding,ViewUtils#set/getLayoutParams + +* `[Add]` AndroidManifest.xml FileProvider config + +* `[Update]` Update AppUtils、IntentUtils、UriUtils 使用 FileProvider authority 处理 + +* `[Fix]` Reflect2Utils#getDeclaredFieldParent fieldNumber param 判断处理 + +Version 1.7.6 *(2019-08-02)* +---------------------------- + +* `[New]` SpannableStringUtils + +* `[Add]` ViewUtils#set/getCompoundDrawables、set/getCompoundDrawablePadding + +* `[Add]` ImageUtils#setBounds + +Version 1.0.0 ~ 1.7.5 *(2019-07-26)* +---------------------------- + +* 整个工具类 review code,并规范代码风格、检测注释、代码间距等 diff --git a/lib/DevApp/README.md b/lib/DevApp/README.md new file mode 100644 index 0000000000..1edd4f79b7 --- /dev/null +++ b/lib/DevApp/README.md @@ -0,0 +1,6504 @@ + +## Gradle + +```gradle +// Android ( 1.9.4 以后只更新 AndroidX ) JCenter +//implementation 'com.afkt:DevApp:1.9.4' + +// AndroidX ( Maven Central ) +implementation 'io.github.afkt:DevAppX:2.4.2' +``` + +## 目录结构 + +``` +- dev.utils | 根目录 + - app | APP 相关工具类 + - activity_result | Activity Result API + - anim | 动画工具类 + - assist | 辅助类 + - exif | 图片 EXIF 读写辅助类 + - floating | 悬浮窗实现方案辅助类 + - lifecycle | Activity 生命周期监听辅助类 + - record | 文件记录分析类 + - url | Url 携带信息解析 + - cache | 缓存工具类 + - camera | 摄像头相关 + - camera1 | android.hardware.Camera ( Camera1 相关 ) + - helper | 功能 Helper 辅助类 + - dev | Dev 工具类链式调用 Helper 类 + - flow | 流式 ( 链式 ) 连接 Helper 类 + - quick | 简化链式设置 View Quick Helper 类 + - version | Android 版本适配 Helper 类 + - view | View 链式调用快捷设置 Helper 类 + - image | 图片相关处理 + - info | APP 信息、PackageInfo 等 + - logger | 日志库 DevLogger + - permission | 权限工具类 + - player | 多媒体 ( 视频、音频 ) 播放封装 + - share | SharedPreferences 封装 + - timer | 定时器 + - toast | Toast + - toaster | Toaster 处理无通知权限 + - wifi | Wifi、热点工具类 + - common | Java 工具类, 不依赖 android api + - able | 通用接口定义 + - assist | 各种快捷辅助类 + - record | 文件记录分析类 + - search | 搜索相关 ( 文件搜索等 ) + - url | Url 携带信息解析 + - cipher | 编 / 解码工具类 + - comparator | 排序比较器 + - sort | 各种类型比较器排序实现 + - encrypt | 加密工具类 + - file | 文件分片相关 + - format | 格式化相关 + - random | 随机概率算法工具类 + - thread | 线程相关 + - validator | 数据校验工具类 +``` + + +## 初始化 + +> ##### ~~只需要在 Application 中调用 `DevUtils.init()` 进行初始化~~,在 DevUtils FileProviderDevApp 中已初始化,无需主动调用 + +> 视情况决定是否主动调用 `DevUtils.init()` 方法 ( 可自行查阅 FileProvider onCreate() 方法什么时候被调用及 Application 初始化顺序 ) + +## 事项 + +- 部分 API 更新不及时或有遗漏等,`具体以对应的工具类为准` + +- [检测代码规范、注释内容排版,API 文档生成](https://github.com/afkT/JavaDoc) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/CHANGELOG.md) + +- 内部存在两个日志工具类 ( 工具类内部调用 ),对外使用 [DevLogger](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/DevLogger.md) + +```java +// 整个工具类内部日志信息,都通过以下两个工具类输出打印,并且通过 DevUtils.openLog() 控制开关 + +// dev.utils.app - APP 日志打印工具类 +LogPrintUtils +// dev.utils.common - Java Common 日志打印工具类 +JCLogUtils +``` + +- 开启日志 +```java +// 打开 lib 内部日志 - 线上 (release) 环境,不调用方法 +DevUtils.openLog(); +// 标记 Debug 模式 +DevUtils.openDebug(); +``` + +- 工具类部分模块配置与使用 - [Use and Config](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/USE_CONFIG.md) + +- [View 链式调用快捷设置 Helper 类](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/view/ViewHelper.java) + +- [Dev 工具类链式调用 Helper 类](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/dev/DevHelper.java) + +- [Android 版本适配 Helper 类](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/version/VersionHelper.java) + +## API + + +- dev.utils | 根目录 + - [app](#devutilsapp) | APP 相关工具类 + - [activity_result](#devutilsappactivity_result) | Activity Result API + - [anim](#devutilsappanim) | 动画工具类 + - [assist](#devutilsappassist) | 辅助类 + - [exif](#devutilsappassistexif) | 图片 EXIF 读写辅助类 + - [floating](#devutilsappassistfloating) | 悬浮窗实现方案辅助类 + - [lifecycle](#devutilsappassistlifecycle) | Activity 生命周期监听辅助类 + - [record](#devutilsappassistrecord) | 文件记录分析类 + - [url](#devutilsappassisturl) | Url 携带信息解析 + - [cache](#devutilsappcache) | 缓存工具类 + - [camera](#devutilsappcamera) | 摄像头相关 + - [camera1](#devutilsappcameracamera1) | android.hardware.Camera ( Camera1 相关 ) + - [helper](#devutilsapphelper) | 功能 Helper 辅助类 + - [dev](#devutilsapphelperdev) | Dev 工具类链式调用 Helper 类 + - [flow](#devutilsapphelperflow) | 流式 ( 链式 ) 连接 Helper 类 + - [quick](#devutilsapphelperquick) | 简化链式设置 View Quick Helper 类 + - [version](#devutilsapphelperversion) | Android 版本适配 Helper 类 + - [view](#devutilsapphelperview) | View 链式调用快捷设置 Helper 类 + - [image](#devutilsappimage) | 图片相关处理 + - [info](#devutilsappinfo) | APP 信息、PackageInfo 等 + - [logger](#devutilsapplogger) | 日志库 DevLogger + - [permission](#devutilsapppermission) | 权限工具类 + - [player](#devutilsappplayer) | 多媒体 ( 视频、音频 ) 播放封装 + - [share](#devutilsappshare) | SharedPreferences 封装 + - [timer](#devutilsapptimer) | 定时器 + - [toast](#devutilsapptoast) | Toast + - [toaster](#devutilsapptoasttoaster) | Toaster 处理无通知权限 + - [wifi](#devutilsappwifi) | Wifi、热点工具类 + - [common](#devutilscommon) | Java 工具类, 不依赖 android api + - [able](#devutilscommonable) | 通用接口定义 + - [assist](#devutilscommonassist) | 各种快捷辅助类 + - [record](#devutilscommonassistrecord) | 文件记录分析类 + - [search](#devutilscommonassistsearch) | 搜索相关 ( 文件搜索等 ) + - [url](#devutilscommonassisturl) | Url 携带信息解析 + - [cipher](#devutilscommoncipher) | 编 / 解码工具类 + - [comparator](#devutilscommoncomparator) | 排序比较器 + - [sort](#devutilscommoncomparatorsort) | 各种类型比较器排序实现 + - [encrypt](#devutilscommonencrypt) | 加密工具类 + - [file](#devutilscommonfile) | 文件分片相关 + - [format](#devutilscommonformat) | 格式化相关 + - [random](#devutilscommonrandom) | 随机概率算法工具类 + - [thread](#devutilscommonthread) | 线程相关 + - [validator](#devutilscommonvalidator) | 数据校验工具类 + + +## **`dev.utils.app`** + + +* **无障碍功能工具类 ->** [AccessibilityUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/AccessibilityUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getService | 获取 AccessibilityService 对象 | +| setService | 设置 AccessibilityService 对象 | +| checkAccessibility | 检查是否开启无障碍功能 | +| isAccessibilitySettingsOn | 判断是否开启无障碍功能 | +| disableSelf | 禁用无障碍服务 | +| getServiceInfo | 获取无障碍服务信息 | +| setServiceInfo | 设置无障碍服务信息 ( 动态配置方式 ) | +| getRootInActiveWindow | 获取根节点 | +| operation | 获取 Operation | +| node | 获取 Node | +| performGlobalAction | 模拟全局对应 Action 操作 | +| dispatchGesture | 模拟手势操作 | +| performActionBack | 触发返回键 | +| performActionHome | 触发 Home 键 | +| performActionPowerDialog | 启动长按电源按钮 Dialog | +| performActionLockScreen | 锁定屏幕 ( 非锁屏 ) | +| performActionTakeScreenshot | 截屏 | +| performActionNotifications | 打开通知栏 | +| performActionRecents | 最近打开应用列表 | +| performActionQuickSettings | 打开设置 | +| performActionSplitScreen | 分屏 | +| accept | 是否允许添加 | +| getNodeInfo | 获取无障碍节点 | +| performAction | 模拟对应 Action 操作 | +| performClick | 点击指定节点 | +| performLongClick | 长按指定节点 | +| inputText | 指定节点输入文本 | +| findFocus | 查找符合条件的节点 | +| findAccessibilityNodeInfosByText | 查找符合条件的节点 | +| findAccessibilityNodeInfosByViewId | 查找符合条件的节点 | +| findByFilter | 查找全部子节点并进行筛选 | +| logEvent | 拼接 AccessibilityEvent 信息日志 | +| logNodeInfo | 拼接 AccessibilityNodeInfo 信息日志 | +| logComplete | 拼接 AccessibilityEvent、AccessibilityService 完整信息日志 | +| logNodeInfoChild | 拼接 AccessibilityNodeInfo 以及 Child 信息日志 | +| contentChangeTypesToString | copy AccessibilityEvent singleContentChangeTypeToString | +| windowChangeTypesToString | copy AccessibilityEvent singleWindowChangeTypeToString | +| movementGranularitiesToString | copy AccessibilityNodeInfo getMovementGranularitySymbolicName | +| getMovementGranularitySymbolicName | 封装 AccessibilityNodeInfo#toString() granularity 拼接代码 | + + +* **Activity Result 工具类 ->** [ActivityResultUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ActivityResultUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDefault | 获取默认实现 ( 原始 onActivityResult ) 封装辅助类 | +| launch | 执行 ActivityResultContract createIntent 并进行跳转 | +| unregister | 取消启动器注册, 并释放回调监听 | +| getContract | 获取创建启动器对应 ActivityResultContract | +| registerForActivityResult | 注册创建跳转回传值启动器并返回 | +| register | 注册创建跳转回传值启动器并返回 | + + +* **Activity 工具类 ( 包含 Activity 控制管理 ) ->** [ActivityUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ActivityUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getActivity | 通过 Context 获取 Activity | +| isFinishing | 判断 Activity 是否关闭 | +| isNotFinishing | 判断 Activity 是否未关闭 | +| isDestroyed | 判断 Activity 是否销毁 | +| isNotDestroyed | 判断 Activity 是否未销毁 | +| assertValidActivity | 判断 Activity 是否有效 | +| isActivityExists | 判断是否存在指定的 Activity | +| startHomeActivity | 回到桌面 ( 同点击 Home 键效果 ) | +| getLauncherActivity | 获取 Launcher activity | +| getActivityIcon | 获取 Activity 对应的 icon | +| getActivityLogo | 获取 Activity 对应的 logo | +| getActivityToLauncher | 获取对应包名应用启动的 Activity | +| getLauncherCategoryHomeToResolveInfo | 获取系统桌面信息 | +| getLauncherCategoryHomeToPackageName | 获取系统桌面信息 ( packageName ) | +| getLauncherCategoryHomeToActivityName | 获取系统桌面信息 ( activityName ) | +| getLauncherCategoryHomeToPackageAndName | 获取系统桌面信息 ( package/activityName ) | +| getOptionsBundle | 设置跳转动画 | +| getManager | 获取 ActivityManagerAssist 管理实例 | + + +* **ADB shell 工具类 ->** [ADBUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ADBUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isDeviceRooted | 判断设备是否 root | +| requestRoot | 请求 Root 权限 | +| isGrantedRoot | 判断 APP 是否授权 Root 权限 | +| getAppList | 获取 APP 列表 ( 包名 ) | +| getInstallAppList | 获取 APP 安装列表 ( 包名 ) | +| getUserAppList | 获取用户安装的应用列表 ( 包名 ) | +| getSystemAppList | 获取系统应用列表 ( 包名 ) | +| getEnableAppList | 获取启用的应用列表 ( 包名 ) | +| getDisableAppList | 获取禁用的应用列表 ( 包名 ) | +| getAppListToFilter | 获取包名包含字符串 xxx 的应用列表 | +| isInstalledApp | 判断是否安装应用 | +| getAppInstallPath | 查看应用安装路径 | +| clearAppDataCache | 清除应用数据与缓存 ( 相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」 ) | +| getAppMessage | 查看应用详细信息 | +| getVersionCode | 获取 APP versionCode | +| getVersionName | 获取 APP versionName | +| installApp | 安装应用 | +| installAppSilent | 静默安装应用 | +| uninstallApp | 卸载应用 | +| uninstallAppSilent | 静默卸载应用 | +| getActivityToLauncher | 获取对应包名应用启动的 Activity | +| getWindowCurrent | 获取当前显示的 Window | +| getWindowCurrent2 | 获取当前显示的 Window | +| getWindowCurrentToPackage | 获取对应包名显示的 Window | +| getActivityCurrent | 获取当前显示的 Activity | +| getActivitys | 获取 Activity 栈 | +| getActivitysToPackage | 获取对应包名的 Activity 栈 | +| getActivitysToPackageLists | 获取对应包名的 Activity 栈 ( 最新的 Activity 越靠后 ) | +| isActivityTopRepeat | 判断 Activity 栈顶是否重复 | +| getActivityTopRepeatCount | 获取 Activity 栈顶重复总数 | +| getServices | 查看正在运行的 Services | +| startSelfApp | 启动自身应用 | +| startActivity | 跳转页面 Activity | +| startService | 启动服务 | +| stopService | 停止服务 | +| sendBroadcastToAll | 发送广播 ( 向所有组件发送 ) | +| sendBroadcast | 发送广播 | +| kill | 销毁进程 | +| sendTrimMemory | 收紧内存 | +| tap | 点击某个区域 | +| swipeClick | 按压某个区域 ( 点击 ) | +| swipe | 滑动到某个区域 | +| text | 输入文本 ( 不支持中文 ) | +| keyevent | 触发某些按键 | +| screencap | 屏幕截图 | +| screenrecord | 录制屏幕 ( 以 mp4 格式保存 ) | +| wifiConf | 查看连接过的 Wifi 密码 | +| wifiSwitch | 开启 / 关闭 Wifi | +| setSystemTime | 设置系统时间 | +| setSystemTime2 | 设置系统时间 | +| shutdown | 关机 ( 需要 root 权限 ) | +| reboot | 重启设备 ( 需要 root 权限 ) | +| rebootToRecovery | 重启引导到 recovery ( 需要 root 权限 ) | +| rebootToBootloader | 重启引导到 bootloader ( 需要 root 权限 ) | +| sendEventSlide | 发送事件滑动 | +| getSDKVersion | 获取 SDK 版本 | +| getAndroidVersion | 获取 Android 系统版本 | +| getModel | 获取设备型号 ( 如 RedmiNote4X ) | +| getBrand | 获取设备品牌 | +| getDeviceName | 获取设备名 | +| getCpuAbiList | 获取 CPU 支持的 abi 列表 | +| getAppHeapsize | 获取每个应用程序的内存上限 | +| getBattery | 获取电池状况 | +| getDensity | 获取屏幕密度 | +| getScreenSize | 获取屏幕分辨率 | +| getDisplays | 获取显示屏参数 | +| getAndroidId | 获取 Android id | +| getIMEI | 获取 IMEI 码 | +| getIPAddress | 获取 IP 地址 | +| getMac | 获取 Mac 地址 | +| getCPU | 获取 CPU 信息 | +| getMemInfo | 获取内存信息 | +| setScreenSize | 设置屏幕大小 | +| resetScreen | 恢复原分辨率命令 | +| setDensity | 设置屏幕密度 | +| resetDensity | 恢复原屏幕密度 | +| setOverscan | 显示区域 ( 设置留白边距 ) | +| resetOverscan | 恢复原显示区域 | +| getScreenBrightnessMode | 获取亮度是否为自动获取 ( 自动调节亮度 ) | +| setScreenBrightnessMode | 设置亮度是否为自动获取 ( 自动调节亮度 ) | +| getScreenBrightness | 获取屏幕亮度值 | +| setScreenBrightness | 更改屏幕亮度值 ( 亮度值在 0-255 之间 ) | +| getScreenOffTimeout | 获取自动锁屏休眠时间 ( 单位毫秒 ) | +| setScreenOffTimeout | 设置自动锁屏休眠时间 ( 单位毫秒 ) | +| getGlobalAutoTime | 获取日期时间选项中通过网络获取时间的状态 | +| setGlobalAutoTime | 修改日期时间选项中通过网络获取时间的状态, 设置是否开启 | +| disableADB | 关闭 USB 调试模式 | +| putHiddenApi | 允许访问非 SDK API | +| deleteHiddenApi | 禁止访问非 SDK API | +| openAccessibility | 开启无障碍辅助功能 | +| closeAccessibility | 关闭无障碍辅助功能 | + + +* **AlarmManager ( 全局定时器、闹钟 ) 工具类 ->** [AlarmUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/AlarmUtils.java) + +| 方法 | 注释 | +| :- | :- | +| startAlarmIntent | 开启一次性闹钟 | +| stopAlarmIntent | 关闭闹钟 | +| startAlarmService | 开启 Service 闹钟 | +| stopAlarmService | 关闭 Service 闹钟 | +| startAlarmForegroundService | 开启 ForegroundService 闹钟 | +| stopAlarmForegroundService | 关闭 ForegroundService 闹钟 | +| startAlarmBroadcast | 开启 Receiver 闹钟 | +| stopAlarmBroadcast | 关闭 Receiver 闹钟 | +| startAlarmActivity | 开启 Activity 闹钟 | +| stopAlarmActivity | 关闭 Activity 闹钟 | + + +* **APP ( Android ) 工具类 ->** [AppUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/AppUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getSystemService | 获取 SystemService | +| getWindowManager | 获取 WindowManager | +| getAudioManager | 获取 AudioManager | +| getStatusBarManager | 获取 StatusBarManager | +| getSensorManager | 获取 SensorManager | +| getStorageManager | 获取 StorageManager | +| getWifiManager | 获取 WifiManager | +| getConnectivityManager | 获取 ConnectivityManager | +| getTelephonyManager | 获取 TelephonyManager | +| getAppOpsManager | 获取 AppOpsManager | +| getNotificationManager | 获取 NotificationManager | +| getShortcutManager | 获取 ShortcutManager | +| getActivityManager | 获取 ActivityManager | +| getPowerManager | 获取 PowerManager | +| getKeyguardManager | 获取 KeyguardManager | +| getInputMethodManager | 获取 InputMethodManager | +| getClipboardManager | 获取 ClipboardManager | +| getUsageStatsManager | 获取 UsageStatsManager | +| getAlarmManager | 获取 AlarmManager | +| getLocationManager | 获取 LocationManager | +| getVibrator | 获取 Vibrator | +| getDevicePolicyManager | 获取 DevicePolicyManager | +| getSensorPrivacyManager | 获取 SensorPrivacyManager | +| getWallpaperManager | 获取 WallpaperManager | +| getPackageManager | 获取 PackageManager | +| getCurrentWindowMetrics | 获取 Current WindowMetrics | +| getMaximumWindowMetrics | 获取 Maximum WindowMetrics | +| getApplicationInfo | 获取 ApplicationInfo | +| getPackageInfo | 获取 PackageInfo | +| getSharedPreferences | 获取 SharedPreferences | +| deleteDatabase | 根据名称清除数据库 | +| getPackageName | 获取 APP 包名 | +| getAppIcon | 获取 APP 图标 | +| getAppName | 获取 APP 应用名 | +| getAppVersionName | 获取 APP versionName | +| getAppVersionCode | 获取 APP versionCode | +| getAppPath | 获取 APP 安装包路径 /data/data/packageName/.apk | +| getAppSignature | 获取 APP Signature | +| getAppSignatureMD5 | 获取 APP 签名 MD5 值 | +| getAppSignatureSHA1 | 获取 APP 签名 SHA1 值 | +| getAppSignatureSHA256 | 获取 APP 签名 SHA256 值 | +| getAppSignatureHash | 获取应用签名 Hash 值 | +| isAppDebug | 判断 APP 是否 debug 模式 | +| isAppRelease | 判断 APP 是否 release 模式 | +| isAppSystem | 判断 APP 是否系统 app | +| isAppForeground | 判断 APP 是否在前台 | +| isInstalledApp | 判断是否安装了 APP | +| isInstalledApp2 | 判断是否安装了 APP | +| startActivity | Activity 跳转 | +| startActivityForResult | Activity 跳转回传 | +| startIntentSenderForResult | Activity 请求权限跳转回传 | +| registerReceiver | 注册广播监听 | +| unregisterReceiver | 注销广播监听 | +| sendBroadcast | 发送广播 ( 无序 ) | +| sendOrderedBroadcast | 发送广播 ( 有序 ) | +| startService | 启动服务 | +| stopService | 停止服务 | +| installApp | 安装 APP ( 支持 8.0 ) 的意图 | +| installAppSilent | 静默安装应用 | +| uninstallApp | 卸载应用 | +| uninstallAppSilent | 静默卸载应用 | +| launchApp | 打开 APP | +| launchAppDetailsSettings | 跳转到 APP 设置详情页面 | +| launchAppDetails | 跳转到 APP 应用商城详情页面 | +| openFile | 打开文件 | +| openFileByApp | 打开文件 ( 指定应用 ) | +| openPDFFile | 打开 PDF 文件 | +| openWordFile | 打开 Word 文件 | +| openOfficeByWPS | 调用 WPS 打开 office 文档 | +| startSysSetting | 跳转到系统设置页面 | +| openWirelessSettings | 打开网络设置界面 | +| openGpsSettings | 打开 GPS 设置界面 | + + +* **音频管理工具类 ->** [AudioManagerUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/AudioManagerUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getStreamMaxVolume | 获取指定声音流最大音量大小 | +| getStreamVolume | 获取指定声音流音量大小 | +| setStreamVolume | 设置指定声音流音量大小 | +| adjustVolumeLower | 控制手机音量, 调小一个单位 | +| adjustVolumeRaise | 控制手机音量, 调大一个单位 | +| adjustVolume | 控制手机音量, 调大或者调小一个单位 | +| adjustStreamVolumeLower | 控制指定声音流音量, 调小一个单位 | +| adjustStreamVolumeRaise | 控制指定声音流音量, 调大一个单位 | +| adjustStreamVolume | 控制指定声音流音量, 调大或者调小一个单位 | +| setStreamMuteByMusic | 设置媒体声音静音状态 | +| setStreamMuteByVoiceCall | 设置通话声音静音状态 | +| setStreamMuteBySystem | 设置系统声音静音状态 | +| setStreamMuteByRing | 设置来电响铃静音状态 | +| setStreamMuteByAlarm | 设置闹钟声音静音状态 | +| setStreamMuteByNotification | 设置通知声音静音状态 | +| setStreamMute | 设置指定声音流静音状态 | +| getMode | 获取当前的音频模式 | +| setMode | 设置当前的音频模式 | +| getRingerMode | 获取当前的铃声模式 | +| setRingerMode | 获取当前的铃声模式 | +| ringerSilent | 设置静音模式 ( 静音, 且无振动 ) | +| ringerVibrate | 设置震动模式 ( 静音, 但有振动 ) | +| ringerNormal | 设置正常模式 ( 正常声音, 振动开关由 setVibrateSetting 决定 ) | +| isDoNotDisturb | 判断是否授权 Do not disturb 权限 | +| setSpeakerphoneOn | 设置是否打开扩音器 ( 扬声器 ) | +| setMicrophoneMute | 设置是否让麦克风静音 | +| isSpeakerphoneOn | 判断是否打开扩音器 ( 扬声器 ) | +| isMicrophoneMute | 判断麦克风是否静音 | +| isMusicActive | 判断是否有音乐处于活跃状态 | +| isWiredHeadsetOn | 判断是否插入了耳机 | +| isBluetoothA2dpOn | 检查蓝牙 A2DP 音频外设是否已连接 | +| isBluetoothScoAvailableOffCall | 检查当前平台是否支持使用 SCO 的关闭调用用例 | +| isBluetoothScoOn | 检查通信是否使用蓝牙 SCO | +| setBluetoothScoOn | 设置是否使用蓝牙 SCO 耳机进行通讯 | +| startBluetoothSco | 启动蓝牙 SCO 音频连接 | +| stopBluetoothSco | 停止蓝牙 SCO 音频连接 | +| loadSoundEffects | 加载音效 | +| unloadSoundEffects | 卸载音效 | +| playSoundEffect | 播放音效 | +| abandonAudioFocus | 放弃音频焦点, 使上一个焦点所有者 ( 如果有 ) 接收焦点 | +| adjustSuggestedStreamVolume | 调整最相关的流的音量, 或者给定的回退流 | +| getParameters | 获取音频硬件指定 key 的参数值 | +| getVibrateSetting | 获取用户对振动类型的振动设置 | + + +* **Bar 相关工具类 ->** [BarUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/BarUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getStatusBarHeight | 获取 StatusBar 高度 | +| getStatusBarHeight2 | 获取 StatusBar 高度 | +| isStatusBarVisible | 判断 StatusBar 是否显示 | +| setStatusBarVisibility | 设置 StatusBar 是否显示 | +| setStatusBarLightMode | 设置 StatusBar 是否高亮模式 | +| isStatusBarLightMode | 获取 StatusBar 是否高亮模式 | +| addMarginTopEqualStatusBarHeight | 添加 View 向上 StatusBar 同等高度边距 | +| subtractMarginTopEqualStatusBarHeight | 移除 View 向上 StatusBar 同等高度边距 | +| setStatusBarColor | 设置 StatusBar 颜色 | +| setStatusBarCustom | 设置自定义 StatusBar View | +| setStatusBarColorDrawer | 设置 DrawerLayout StatusBar 颜色 | +| transparentStatusBar | 设置透明 StatusBar | +| getActionBarHeight | 获取 ActionBar 高度 | +| setNotificationBarVisibility | 设置 Notification Bar 是否显示 | +| getNavBarHeight | 获取 Navigation Bar 高度 | +| setNavBarVisibility | 设置 Navigation Bar 是否可见 | +| isNavBarVisible | 判断 Navigation Bar 是否可见 | +| isSupportNavBar | 判断是否支持 Navigation Bar | +| setNavBarColor | 设置 Navigation Bar 颜色 | +| getNavBarColor | 获取 Navigation Bar 颜色 | +| setNavBarLightMode | 设置 Navigation Bar 是否高亮模式 | +| isNavBarLightMode | 获取 Navigation Bar 是否高亮模式 | + + +* **亮度相关工具类 ->** [BrightnessUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/BrightnessUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isAutoBrightnessEnabled | 判断是否开启自动调节亮度 | +| setAutoBrightnessEnabled | 设置是否开启自动调节亮度 | +| getBrightness | 获取屏幕亮度 0-255 | +| setBrightness | 设置屏幕亮度 | +| setWindowBrightness | 设置窗口亮度 | +| getWindowBrightness | 获取窗口亮度 | + + +* **截图工具类 ->** [CapturePictureUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/CapturePictureUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setBitmapConfig | 设置 Bitmap Config | +| setBackgroundColor | 设置 Canvas 背景色 | +| setPaint | 设置画笔 | +| snapshotWithStatusBar | 获取当前屏幕截图, 包含状态栏 ( 顶部灰色 TitleBar 高度, 没有设置 android:theme 的 NoTitleBar 时会显示 ) | +| snapshotWithoutStatusBar | 获取当前屏幕截图, 不包含状态栏 ( 如果 android:theme 全屏, 则截图无状态栏 ) | +| enableSlowWholeDocumentDraw | 关闭 WebView 优化 | +| snapshotByWebView | 截图 WebView | +| snapshotByView | 通过 View 绘制为 Bitmap | +| snapshotByViewCache | 通过 View Cache 绘制为 Bitmap | +| snapshotByLinearLayout | 通过 LinearLayout 绘制为 Bitmap | +| snapshotByFrameLayout | 通过 FrameLayout 绘制为 Bitmap | +| snapshotByRelativeLayout | 通过 RelativeLayout 绘制为 Bitmap | +| snapshotByScrollView | 通过 ScrollView 绘制为 Bitmap | +| snapshotByHorizontalScrollView | 通过 HorizontalScrollView 绘制为 Bitmap | +| snapshotByNestedScrollView | 通过 NestedScrollView 绘制为 Bitmap | +| snapshotByListView | 通过 ListView 绘制为 Bitmap | +| snapshotByGridView | 通过 GridView 绘制为 Bitmap | +| snapshotByRecyclerView | 通过 RecyclerView 绘制为 Bitmap | + + +* **本应用数据清除管理工具类 ->** [CleanUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/CleanUtils.java) + +| 方法 | 注释 | +| :- | :- | +| cleanCache | 清除外部缓存 ( path /storage/emulated/0/android/data/package/cache ) | +| cleanAppCache | 清除内部缓存 ( path /data/data/package/cache ) | +| cleanAppFiles | 清除内部文件 ( path /data/data/package/files ) | +| cleanAppSp | 清除内部 SP ( path /data/data/package/shared_prefs ) | +| cleanAppDbs | 清除内部数据库 ( path /data/data/package/databases ) | +| cleanAppDbByName | 根据名称清除数据库 ( path /data/data/package/databases/dbName ) | +| cleanCustomDir | 清除自定义路径下的文件 | +| cleanApplicationData | 清除本应用所有的数据 | + + +* **点击 ( 双击 ) 工具类 ->** [ClickUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ClickUtils.java) + +| 方法 | 注释 | +| :- | :- | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setCheckViewId | 设置全局是否校验 viewId | +| setGlobalIntervalTime | 设置全局双击间隔时间 | +| get | 获取对应功能模块点击辅助类 | +| remove | 移除对应功能模块点击辅助类 | +| isFastDoubleClick | 判断是否双击 ( 无效点击, 短时间内多次点击 ) | +| initConfig | 初始化配置信息 | +| putConfig | 添加配置信息 | +| removeConfig | 移除配置信息 | +| getConfigTime | 获取配置时间 | +| removeRecord | 移除点击记录 | +| clearRecord | 清空全部点击记录 | +| setIntervalTime | 设置默认点击时间间隔 | +| reset | 重置处理 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | + + +* **剪贴板相关工具类 ->** [ClipboardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ClipboardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| copyText | 复制文本到剪贴板 | +| getText | 获取剪贴板文本 | +| copyUri | 复制 URI 到剪贴板 | +| getUri | 获取剪贴板 URI | +| copyIntent | 复制意图到剪贴板 | +| getIntent | 获取剪贴板意图 | + + +* **ContentResolver 工具类 ->** [ContentResolverUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ContentResolverUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDataColumn | 获取 Uri Cursor 对应条件的数据行 data 字段 | +| getDisplayNameColumn | 获取 Uri Cursor 对应条件的数据行 display_name 字段 | +| delete | 删除多媒体资源 | +| update | 更新多媒体资源 | +| deleteDocument | 删除文件 | +| query | 获取 Uri Cursor | +| getMediaUri | 通过 File 获取 Media Uri | +| mediaQuery | 通过 File 获取 Media 信息 | +| getResult | 获取查询结果 | +| getProjection | 获取查询的字段 | +| getSelection | 获取查询条件 | +| getSelectionArgs | 获取查询条件的参数 | +| getSortOrder | 获取排序方式 | + + +* **获取 CPU 信息工具类 ->** [CPUUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/CPUUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getProcessorsCount | 获取处理器的 Java 虚拟机的数量 | +| getSysCPUSerialNum | 获取手机 CPU 序列号 | +| getCpuInfo | 获取 CPU 信息 | +| getCpuModel | 获取 CPU 型号 | +| getMaxCpuFreq | 获取 CPU 最大频率 ( 单位 KHZ ) | +| getMinCpuFreq | 获取 CPU 最小频率 ( 单位 KHZ ) | +| getCurCpuFreq | 获取 CPU 当前频率 ( 单位 KHZ ) | +| getCoresNumbers | 获取 CPU 核心数 | +| getCpuName | 获取 CPU 名字 | +| getCMDOutputString | 获取 CMD 指令回调数据 | + + +* **UncaughtException 处理工具类 ->** [CrashUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/CrashUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 CrashUtils 实例 | +| initialize | 初始化方法 | +| uncaughtException | 当 UncaughtException 发生时会转入该函数来处理 | +| handleException | 处理异常 | + + +* **数据库工具类 ( 导入导出等 ) ->** [DBUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/DBUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getAppDbsPath | 获取应用内部存储数据库路径 ( path /data/data/package/databases ) | +| getAppDbPath | 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) | +| deleteDatabase | 根据名称清除数据库 | +| startExportDatabase | 导出数据库 | +| startImportDatabase | 导入数据库 | + + +* **设备管理工具类 ->** [DevicePolicyUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/DevicePolicyUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevicePolicyUtils 实例 | +| getDevicePolicyManager | 获取 DevicePolicyManager | +| isAdminActive | 判断给定的组件是否启动 ( 活跃 ) 中 | +| getActiveIntent | 获取激活跳转 Intent | +| activeAdmin | 激活给定的组件 | +| removeActiveAdmin | 移除激活组件 | +| startLockPassword | 设置锁屏密码 ( 不需要激活就可以运行 ) | +| setLockPassword | 设置锁屏密码 | +| lockNow | 立刻锁屏 | +| lockByTime | 设置多长时间后锁屏 | +| wipeData | 清除所有数据 ( 恢复出厂设置 ) | +| resetPassword | 设置新解锁密码 | +| setStorageEncryption | 设置存储设备加密 | +| setCameraDisabled | 设置停用相机 | +| getComponentName | 获取 ComponentName | +| setComponentName | 设置 ComponentName | + + +* **设备相关工具类 ->** [DeviceUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/DeviceUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getAppDeviceInfo | 获取应用、设备信息 | +| refreshAppDeviceInfo | 刷新应用、设备信息 | +| getUUID | 获取设备唯一 UUID | +| getUUIDDevice | 获取设备唯一 UUID ( 使用硬件信息拼凑出来的 ) | +| getDeviceInfo | 获取设备信息 | +| handlerDeviceInfo | 处理设备信息 | +| getBoard | 获取设备基板名称 | +| getBootloader | 获取设备引导程序版本号 | +| getBrand | 获取设备品牌 | +| getCPU_ABI | 获取支持的第一个指令集 | +| getCPU_ABI2 | 获取支持的第二个指令集 | +| getABIs | 获取支持的指令集 如: [arm64-v8a, armeabi-v7a, armeabi] | +| getSUPPORTED_32_BIT_ABIS | 获取支持的 32 位指令集 | +| getSUPPORTED_64_BIT_ABIS | 获取支持的 64 位指令集 | +| getDevice | 获取设备驱动名称 | +| getDisplay | 获取设备显示的版本包 ( 在系统设置中显示为版本号 ) 和 ID 一样 | +| getFingerprint | 获取设备的唯一标识, 由设备的多个信息拼接合成 | +| getHardware | 获取设备硬件名称, 一般和基板名称一样 ( BOARD ) | +| getHost | 获取设备主机地址 | +| getID | 获取设备版本号 | +| getModel | 获取设备型号 如 RedmiNote4X | +| getManufacturer | 获取设备厂商 如 Xiaomi | +| getProduct | 获取整个产品的名称 | +| getRadio | 获取无线电固件版本号, 通常是不可用的 显示 unknown | +| getTags | 获取设备标签, 如 release-keys 或测试的 test-keys | +| getTime | 获取设备时间 | +| getType | 获取设备版本类型 主要为 "user" 或 "eng". | +| getUser | 获取设备用户名 基本上都为 android-build | +| getSDKVersion | 获取 SDK 版本号 | +| getRelease | 获取系统版本号, 如 4.1.2 或 2.2 或 2.3 等 | +| getCodename | 获取设备当前的系统开发代号, 一般使用 REL 代替 | +| getIncremental | 获取系统源代码控制值, 一个数字或者 git hash 值 | +| getAndroidId | 获取 Android id | +| getBaseband_Ver | 获取基带版本 BASEBAND-VER | +| getLinuxCore_Ver | 获取内核版本 CORE-VER | +| isDeviceRooted | 判断设备是否 root | +| isAdbEnabled | 获取是否启用 ADB | +| isDevelopmentSettingsEnabled | 是否打开开发者选项 | +| getMacAddress | 获取设备 MAC 地址 | +| shutdown | 关机 ( 需要 root 权限 ) | +| reboot | 重启设备 ( 需要 root 权限 ) | +| rebootToRecovery | 重启引导到 recovery ( 需要 root 权限 ) | +| rebootToBootloader | 重启引导到 bootloader ( 需要 root 权限 ) | +| isTablet | 判断是否是平板 | + + +* **Dialog 操作相关工具类 ->** [DialogUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/DialogUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setStatusBarColor | 设置 Dialog 状态栏颜色 | +| setSemiTransparentStatusBarColor | 设置 Dialog 高版本状态栏蒙层 | +| setStatusBarColorAndFlag | 设置 Dialog 状态栏颜色、高版本状态栏蒙层 | +| getAttributes | 获取 Dialog Window LayoutParams | +| setAttributes | 设置 Dialog Window LayoutParams | +| setWidth | 设置 Dialog 宽度 | +| setHeight | 设置 Dialog 高度 | +| setWidthHeight | 设置 Dialog 宽度、高度 | +| setX | 设置 Dialog X 轴坐标 | +| setY | 设置 Dialog Y 轴坐标 | +| setXY | 设置 Dialog X、Y 轴坐标 | +| setGravity | 设置 Dialog Gravity | +| setDimAmount | 设置 Dialog 透明度 | +| setCancelable | 设置是否允许返回键关闭 | +| setCanceledOnTouchOutside | 设置是否允许点击其他地方自动关闭 | +| setCancelableAndTouchOutside | 设置是否允许 返回键关闭、点击其他地方自动关闭 | +| isShowing | 获取 Dialog 是否显示 | +| showDialog | 显示 Dialog | +| closeDialog | 关闭 Dialog | +| closeDialogs | 关闭多个 Dialog | +| closePopupWindow | 关闭 PopupWindow | +| closePopupWindows | 关闭多个 PopupWindow | +| showDialogAndCloses | 显示 Dialog 并关闭其他 Dialog | +| createAlertDialog | 创建提示 Dialog ( 原生样式 ) | +| createProgressDialog | 创建加载中 Dialog ( 原生样式 ) | +| autoCloseDialog | 自动关闭 dialog | +| autoClosePopupWindow | 自动关闭 PopupWindow | +| createSingleChoiceListDialog | 创建单选列表样式 Dialog | +| createSingleChoiceDialog | 创建单选样式 Dialog | +| createMultiChoiceDialog | 创建多选样式 Dialog | +| createViewDialog | 创建自定义 View 样式 Dialog | + + +* **EditText 工具类 ->** [EditTextUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/EditTextUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getEditText | 获取 EditText | +| getText | 获取输入的内容 | +| getTextLength | 获取输入的内容长度 | +| setText | 设置内容 | +| setTexts | 设置多个 EditText 文本 | +| insert | 追加内容 ( 当前光标位置追加 ) | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| isCursorVisible | 是否显示光标 | +| setCursorVisible | 设置是否显示光标 | +| setTextCursorDrawable | 设置光标 | +| getSelectionStart | 获取光标位置 | +| setSelectionToTop | 设置光标在第一位 | +| setSelectionToBottom | 设置光标在最后一位 | +| setSelection | 设置光标位置 | +| getInputType | 设置输入类型 | +| setInputType | 设置输入类型 | +| getImeOptions | 设置软键盘右下角按钮类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| getTransformationMethod | 获取文本视图显示转换 | +| setTransformationMethod | 设置文本视图显示转换 | +| addTextChangedListener | 添加输入监听事件 | +| removeTextChangedListener | 移除输入监听事件 | +| setKeyListener | 设置 KeyListener | +| getLettersKeyListener | 获取 DigitsKeyListener ( 限制只能输入字母, 默认弹出英文软键盘 ) | +| getNumberAndLettersKeyListener | 获取 DigitsKeyListener ( 限制只能输入字母和数字, 默认弹出英文软键盘 ) | +| getNumberKeyListener | 获取 DigitsKeyListener ( 限制只能输入数字, 默认弹出数字列表 ) | +| createDigitsKeyListener | 创建 DigitsKeyListener | + + +* **Handler 工具类 ->** [HandlerUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/HandlerUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getMainHandler | 获取主线程 Handler | +| isMainThread | 当前线程是否主线程 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | +| getRunnableMaps | 获取 Key Runnable Map | +| clearRunnableMaps | 清空 Key Runnable Map | +| containsKey | 判断 Map 是否存储 key Runnable | +| put | 通过 Key 存储 Runnable | +| remove | 通过 Key 移除 Runnable | + + +* **ImageView 工具类 ->** [ImageViewUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ImageViewUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getImageView | 获取 ImageView | +| getAdjustViewBounds | 获取 ImageView 是否保持宽高比 | +| setAdjustViewBounds | 设置 ImageView 是否保持宽高比 | +| getMaxHeight | 获取 ImageView 最大高度 | +| setMaxHeight | 设置 ImageView 最大高度 | +| getMaxWidth | 获取 ImageView 最大宽度 | +| setMaxWidth | 设置 ImageView 最大宽度 | +| setImageLevel | 设置 ImageView Level | +| setImageBitmap | 设置 ImageView Bitmap | +| setImageDrawable | 设置 ImageView Drawable | +| setImageResource | 设置 ImageView 资源 | +| setImageMatrix | 设置 ImageView Matrix | +| setImageTintList | 设置 ImageView 着色颜色 | +| setImageTintMode | 设置 ImageView 着色模式 | +| setScaleType | 设置 ImageView 缩放类型 | +| getImageMatrix | 获取 ImageView Matrix | +| getImageTintList | 获取 ImageView 着色颜色 | +| getImageTintMode | 获取 ImageView 着色模式 | +| getScaleType | 获取 ImageView 缩放模式 | +| getDrawable | 获取 ImageView Drawable | +| setBackgroundResources | 设置 View 图片资源 | +| setImageResources | 设置 View 图片资源 | +| setImageBitmaps | 设置 View Bitmap | +| setImageDrawables | 设置 View Drawable | +| setScaleTypes | 设置 View 缩放模式 | +| getImageViewSize | 根据 ImageView 获适当的宽高 | + + +* **Intent 相关工具类 ->** [IntentUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/IntentUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getIntent | 获取 Intent | +| isIntentAvailable | 判断 Intent 是否可用 | +| getCategoryLauncherIntent | 获取 CATEGORY_LAUNCHER Intent | +| getInstallAppIntent | 获取安装 APP ( 支持 8.0 ) 的意图 | +| getUninstallAppIntent | 获取卸载 APP 的意图 | +| getLaunchAppIntent | 获取打开 APP 的意图 | +| getSystemSettingIntent | 获取跳转到系统设置的意图 | +| getLaunchAppInstallPermissionSettingsIntent | 获取 APP 安装权限设置的意图 | +| getLaunchAppNotificationSettingsIntent | 获取 APP 通知权限设置的意图 | +| getLaunchAppNotificationListenSettingsIntent | 获取 APP 通知使用权页面 | +| getManageOverlayPermissionIntent | 获取悬浮窗口权限列表的意图 | +| getManageAppAllFilesAccessPermissionIntent | 获取 APP 授予所有文件管理权限的意图 | +| getManageAllFilesAccessPermissionIntent | 获取授予所有文件管理权限列表的意图 | +| getLaunchAppDetailsSettingsIntent | 获取 APP 具体设置的意图 | +| getLaunchAppDetailIntent | 获取到应用商店 APP 详情界面的意图 | +| getShareTextIntent | 获取分享文本的意图 | +| getShareImageIntent | 获取分享图片的意图 | +| getComponentIntent | 获取其他应用组件的意图 | +| getShutdownIntent | 获取关机的意图 | +| getDialIntent | 获取跳至拨号界面意图 | +| getCallIntent | 获取拨打电话意图 | +| getSendSmsIntent | 获取发送短信界面的意图 | +| getImageCaptureIntent | 获取图片拍摄的意图 | +| getVideoCaptureIntent | 获取视频拍摄的意图 | +| getOpenDocumentIntent | 获取存储访问框架的意图 | +| getCreateDocumentIntent | 获取创建文件的意图 | +| getOpenBrowserIntent | 获取打开浏览器的意图 | +| getOpenAndroidBrowserIntent | 获取打开 Android 浏览器的意图 | + + +* **Android 原生 JSONObject 工具类 ->** [JSONObjectUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/JSONObjectUtils.java) + +| 方法 | 注释 | +| :- | :- | +| toJson | 转换为 JSON 格式字符串 | +| fromJson | Object 转换 JSON 对象 | +| wrap | 包装转换 Object | +| stringJSONEscape | 字符串 JSON 转义处理 | +| isJSON | 判断字符串是否 JSON 格式 | +| isJSONObject | 判断字符串是否 JSON Object 格式 | +| isJSONArray | 判断字符串是否 JSON Array 格式 | +| jsonToMap | 将 JSON 格式字符串转化为 Map | +| jsonToList | 将 JSON 格式字符串转化为 List | +| getJSONObject | 获取 JSONObject | +| getJSONArray | 获取 JSONArray | +| get | 获取指定 key 数据 | +| opt | 获取指定 key 数据 | + + +* **软键盘相关工具类 ->** [KeyBoardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/KeyBoardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setDelayMillis | 设置延迟时间 | +| setSoftInputMode | 设置 Window 软键盘是否显示 | +| judgeView | 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 | +| isSoftInputVisible | 判断软键盘是否可见 | +| registerSoftInputChangedListener | 注册软键盘改变监听 | +| registerSoftInputChangedListener2 | 注册软键盘改变监听 | +| fixSoftInputLeaks | 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 | +| toggleKeyboard | 自动切换键盘状态, 如果键盘显示则隐藏反之显示 | +| openKeyboard | 打开软键盘 | +| openKeyboardDelay | 延时打开软键盘 | +| openKeyboardByFocus | 打开软键盘 | +| closeKeyboard | 关闭软键盘 | +| closeKeyBoardSpecial | 关闭软键盘 | +| closeKeyBoardSpecialDelay | 延时关闭软键盘 | +| closeKeyboardDelay | 延时关闭软键盘 | + + +* **锁屏管理工具类 ( 锁屏、禁用锁屏, 判断是否锁屏 ) ->** [KeyguardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/KeyguardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 KeyguardUtils 实例 | +| isKeyguardLocked | 是否锁屏 ( android 4.1 以上支持 ) | +| isKeyguardSecure | 是否有锁屏密码 ( android 4.1 以上支持 ) | +| inKeyguardRestrictedInputMode | 是否锁屏 | +| getKeyguardManager | 获取 KeyguardManager | +| setKeyguardManager | 设置 KeyguardManager | +| disableKeyguard | 屏蔽系统的屏保 | +| reenableKeyguard | 使能显示锁屏界面, 如果你之前调用了 disableKeyguard() 方法取消锁屏界面, 那么会马上显示锁屏界面 | +| release | 释放资源 | +| getKeyguardLock | 获取 KeyguardManager.KeyguardLock | +| setKeyguardLock | 设置 KeyguardManager.KeyguardLock | + + +* **语言工具类 ->** [LanguageUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/LanguageUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getSystemLanguage | 获取系统语言 | +| getSystemCountry | 获取系统语言区域 | +| getSystemPreferredLanguage | 获取系统首选语言 | +| applyLanguage | 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) | +| getSupportLanguages | 获取支持的语言 | +| putSupportLanguage | 添加支持的语言 | +| removeSupportLanguage | 移除支持的语言 | +| isSupportLanguage | 是否支持此语言 | +| getSupportLanguage | 获取支持语言 | +| isEn | 判断是否为英文语言环境 | +| isZh | 判断是否为中文语言环境 | +| isZhCN | 判断是否为中文简体语言环境 | +| isZhTW | 判断是否为中文繁体语言环境 | +| isLanguage | 判断是否为指定语言环境 | +| isRegion | 判断是否为指定区域语言环境 | + + +* **事件工具类 ->** [ListenerUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ListenerUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getTouchListener | 获取 View 设置的 OnTouchListener 事件对象 | +| getClickListener | 获取 View 设置的 OnClickListener 事件对象 | +| getListenerInfo | 获取 View ListenerInfo 对象 ( 内部类 ) | +| getListenerInfoListener | 获取 View ListenerInfo 对象内部事件对象 | +| setOnClicks | 设置点击事件 | +| setOnLongClicks | 设置长按事件 | +| setOnTouchs | 设置触摸事件 | + + +* **List View ( 列表 View ) 相关工具类 ->** [ListViewUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ListViewUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getItemCount | 获取 Adapter Item 总数 | +| getItemView | 获取指定索引 Item View | +| smoothScrollToPosition | 滑动到指定索引 ( 有滚动过程 ) | +| scrollToPosition | 滑动到指定索引 ( 无滚动过程 ) | +| smoothScrollToTop | 滑动到顶部 ( 有滚动过程 ) | +| scrollToTop | 滑动到顶部 ( 无滚动过程 ) | +| smoothScrollToBottom | 滑动到底部 ( 有滚动过程 ) | +| scrollToBottom | 滑动到底部 ( 无滚动过程 ) | +| smoothScrollTo | 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) | +| smoothScrollBy | 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) | +| fullScroll | 滚动方向 ( 有滚动过程 ) | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| getScrollX | 获取 View 滑动的 X 轴坐标 | +| getScrollY | 获取 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| calcListViewHeight | 计算 ListView 高度 | +| calcGridViewHeight | 计算 GridView 高度 | + + +* **定位相关工具类 ->** [LocationUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/LocationUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isGpsEnabled | 判断 GPS 是否可用 | +| isLocationEnabled | 判断定位是否可用 | +| openGpsSettings | 打开 GPS 设置界面 | +| register | 注册 | +| unregister | 注销监听 | +| getLocation | 获取位置 ( 需要先判断是否开启了定位 ) | +| getAddress | 根据经纬度获取地理位置 | +| getCountryName | 根据经纬度获取所在国家 | +| getLocality | 根据经纬度获取所在地 | +| getStreet | 根据经纬度获取所在街道 | +| isBetterLocation | 判断是否更好的位置 | +| isSameProvider | 是否相同的提供者 | +| getLastKnownLocation | 获取最后一次保留的坐标 | +| onLocationChanged | 当坐标改变时触发此函数, 如果 Provider 传进相同的坐标, 它就不会被触发 | +| onStatusChanged | provider 的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 | + + +* **Android Manifest 工具类 ->** [ManifestUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ManifestUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getMetaData | 获取 Application meta Data | +| getMetaDataInActivity | 获取 Activity meta Data | +| getMetaDataInService | 获取 Service meta Data | +| getMetaDataInReceiver | 获取 Receiver meta Data | +| getMetaDataInProvider | 获取 ContentProvider meta Data | +| getAppVersion | 获取 APP 版本信息 | +| getAppVersionCode | 获取 APP versionCode | +| getAppVersionName | 获取 APP versionName | + + +* **MediaStore 工具类 ->** [MediaStoreUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/MediaStoreUtils.java) + +| 方法 | 注释 | +| :- | :- | +| notifyMediaStore | 通知刷新本地资源 | +| getDisplayName | 获取待显示名 | +| getImageDisplayName | 获取 Image 显示名 | +| getVideoDisplayName | 获取 Video 显示名 | +| getAudioDisplayName | 获取 Audio 显示名 | +| createImageUri | 创建图片 Uri | +| createVideoUri | 创建视频 Uri | +| createAudioUri | 创建音频 Uri | +| createDownloadUri | 创建 Download Uri | +| createMediaUri | 创建预存储 Media Uri | +| createUriByPath | 通过 File Path 创建 Uri | +| createUriByFile | 通过 File Path 创建 Uri | +| insertImage | 插入一张图片 | +| insertVideo | 插入一条视频 | +| insertAudio | 插入一条音频 | +| insertDownload | 插入一条文件资源 | +| insertMedia | 插入一条多媒体资源 | +| getVideoDuration | 获取本地视频时长 | +| getVideoSize | 获取本地视频宽高 | +| getImageWidthHeight | 获取本地图片宽高 | +| getMediaInfo | 获取多媒体资源信息 | +| createWriteRequest | 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 | +| createFavoriteRequest | 获取用户将设备上指定的媒体文件标记为收藏的请求 | +| createTrashRequest | 获取用户将指定的媒体文件放入设备垃圾箱的请求 | +| createDeleteRequest | 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 | +| getMimeTypeFromExtension | 通过后缀获取 MimeType | +| getExtensionFromMimeType | 通过 MimeType 获取后缀 ( 不含 . ) | +| getFileExtensionFromUrl | 通过 Url 获取文件后缀 | +| hasMimeType | 判断 MimeMap 是否存在指定的 MimeType | +| hasExtension | 判断是否支持的 MimeType 后缀 | + + +* **内存信息工具类 ->** [MemoryUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/MemoryUtils.java) + +| 方法 | 注释 | +| :- | :- | +| printMemoryInfo | 获取内存信息 | +| printMemoryInfo2 | 获取内存信息 | +| getMemoryInfo | 获取内存信息 | +| getAvailMemory | 获取可用内存信息 | +| getAvailMemoryFormat | 获取可用内存信息 ( 格式化 ) | +| getTotalMemory | 获取总内存大小 | +| getTotalMemoryFormat | 获取总内存大小 ( 格式化 ) | +| getMemoryAvailable | 获取可用内存大小 | +| getMemoryAvailableFormat | 获取可用内存大小 ( 格式化 ) | +| getMemInfoType | 通过不同 type 获取对应的内存信息 | + + +* **网络管理工具类 ->** [NetWorkUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/NetWorkUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getMobileDataEnabled | 获取移动网络打开状态 ( 默认属于未打开 ) | +| setMobileDataEnabled | 设置移动网络开关 ( 无判断是否已开启移动网络 ) | +| isConnect | 判断是否连接了网络 | +| getConnectType | 获取连接的网络类型 | +| isConnWifi | 判断是否连接 Wifi ( 连接上、连接中 ) | +| isConnMobileData | 判断是否连接移动网络 ( 连接上、连接中 ) | +| isAvailable | 判断网络是否可用 | +| isAvailableByPing | 使用 ping ip 方式判断网络是否可用 | +| getActiveNetworkInfo | 获取活动网络信息 | +| getActiveNetwork | 获取活动网络 | +| is4G | 判断是否 4G 网络 | +| getWifiEnabled | 判断 Wifi 是否打开 | +| isWifiAvailable | 判断 Wifi 数据是否可用 | +| getNetworkOperatorName | 获取网络运营商名称 ( 中国移动、如中国联通、中国电信 ) | +| getNetworkType | 获取当前网络类型 | +| getNetworkClass | 获取移动网络连接类型 | +| getBroadcastIpAddress | 获取广播 IP 地址 | +| getDomainAddress | 获取域名 IP 地址 | +| getIPAddress | 获取 IP 地址 | +| getIpAddressByWifi | 根据 Wifi 获取网络 IP 地址 | +| getGatewayByWifi | 根据 Wifi 获取网关 IP 地址 | +| getNetMaskByWifi | 根据 Wifi 获取子网掩码 IP 地址 | +| getServerAddressByWifi | 根据 Wifi 获取服务端 IP 地址 | + + +* **通知栏管理工具类 ->** [NotificationUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/NotificationUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getNotificationManager | 获取通知栏管理对象 | +| isNotificationEnabled | 检查通知栏权限是否开启 | +| checkAndIntentSetting | 检查是否有获取通知栏信息权限并跳转设置页面 | +| isNotificationListenerEnabled | 判断是否有获取通知栏信息权限 | +| startNotificationListenSettings | 跳转到设置页面, 开启获取通知栏信息权限 | +| cancelAll | 移除通知 ( 移除所有通知 ) | +| cancel | 移除通知 ( 移除标记为 id 的通知 ) | +| notify | 进行通知 | +| getNotificationChannel | 获取 NotificationChannel | +| createNotificationChannel | 创建 NotificationChannel | +| createPendingIntent | 获取 PendingIntent | +| createNotification | 创建通知栏对象 | +| createNotificationBuilder | 创建通知栏 Builder 对象 | +| get | 获取 Led 配置参数 | +| isEmpty | 判断是否为 null | + + +* **路径相关工具类 ->** [PathUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/PathUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInternal | 获取内部存储路径类 | +| getAppExternal | 获取应用外部存储路径类 | +| getSDCard | 获取 SDCard 外部存储路径类 | +| isExternalStorageManager | 是否获得 MANAGE_EXTERNAL_STORAGE 权限 | +| checkExternalStorageAndIntentSetting | 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 | +| isSDCardEnable | 判断 SDCard 是否正常挂载 | +| getSDCardFile | 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) | +| getSDCardPath | 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) | +| getExternalStoragePublicPath | 获取 SDCard 外部存储文件路径 ( path /storage/emulated/0/ ) | +| getExternalStoragePublicDir | 获取 SDCard 外部存储文件路径 ( path /storage/emulated/0/ ) | +| getMusicPath | 获取 SDCard 外部存储音乐路径 ( path /storage/emulated/0/Music ) | +| getMusicDir | 获取 SDCard 外部存储音乐路径 ( path /storage/emulated/0/Music ) | +| getPodcastsPath | 获取 SDCard 外部存储播客路径 ( path /storage/emulated/0/Podcasts ) | +| getPodcastsDir | 获取 SDCard 外部存储播客路径 ( path /storage/emulated/0/Podcasts ) | +| getRingtonesPath | 获取 SDCard 外部存储铃声路径 ( path /storage/emulated/0/Ringtones ) | +| getRingtonesDir | 获取 SDCard 外部存储铃声路径 ( path /storage/emulated/0/Ringtones ) | +| getAlarmsPath | 获取 SDCard 外部存储闹铃路径 ( path /storage/emulated/0/Alarms ) | +| getAlarmsDir | 获取 SDCard 外部存储闹铃路径 ( path /storage/emulated/0/Alarms ) | +| getNotificationsPath | 获取 SDCard 外部存储通知路径 ( path /storage/emulated/0/Notifications ) | +| getNotificationsDir | 获取 SDCard 外部存储通知路径 ( path /storage/emulated/0/Notifications ) | +| getPicturesPath | 获取 SDCard 外部存储图片路径 ( path /storage/emulated/0/Pictures ) | +| getPicturesDir | 获取 SDCard 外部存储图片路径 ( path /storage/emulated/0/Pictures ) | +| getMoviesPath | 获取 SDCard 外部存储影片路径 ( path /storage/emulated/0/Movies ) | +| getMoviesDir | 获取 SDCard 外部存储影片路径 ( path /storage/emulated/0/Movies ) | +| getDownloadPath | 获取 SDCard 外部存储下载路径 ( path /storage/emulated/0/Download ) | +| getDownloadDir | 获取 SDCard 外部存储下载路径 ( path /storage/emulated/0/Download ) | +| getDCIMPath | 获取 SDCard 外部存储数码相机图片路径 ( path /storage/emulated/0/DCIM ) | +| getDCIMDir | 获取 SDCard 外部存储数码相机图片路径 ( path /storage/emulated/0/DCIM ) | +| getDocumentsPath | 获取 SDCard 外部存储文档路径 ( path /storage/emulated/0/Documents ) | +| getDocumentsDir | 获取 SDCard 外部存储文档路径 ( path /storage/emulated/0/Documents ) | +| getAudiobooksPath | 获取 SDCard 外部存储有声读物路径 ( path /storage/emulated/0/Audiobooks ) | +| getAudiobooksDir | 获取 SDCard 外部存储有声读物路径 ( path /storage/emulated/0/Audiobooks ) | +| getAppDataPath | 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) | +| getAppDataDir | 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) | +| getAppCachePath | 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) | +| getAppCacheDir | 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) | +| getExternalFilesPath | 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) | +| getExternalFilesDir | 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) | +| getAppFilesPath | 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) | +| getAppFilesDir | 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) | +| getAppMusicPath | 获取应用外部存储音乐路径 ( path /storage/emulated/0/Android/data/package/files/Music ) | +| getAppMusicDir | 获取应用外部存储音乐路径 ( path /storage/emulated/0/Android/data/package/files/Music ) | +| getAppPodcastsPath | 获取应用外部存储播客路径 ( path /storage/emulated/0/Android/data/package/files/Podcasts ) | +| getAppPodcastsDir | 获取应用外部存储播客路径 ( path /storage/emulated/0/Android/data/package/files/Podcasts ) | +| getAppRingtonesPath | 获取应用外部存储铃声路径 ( path /storage/emulated/0/Android/data/package/files/Ringtones ) | +| getAppRingtonesDir | 获取应用外部存储铃声路径 ( path /storage/emulated/0/Android/data/package/files/Ringtones ) | +| getAppAlarmsPath | 获取应用外部存储闹铃路径 ( path /storage/emulated/0/Android/data/package/files/Alarms ) | +| getAppAlarmsDir | 获取应用外部存储闹铃路径 ( path /storage/emulated/0/Android/data/package/files/Alarms ) | +| getAppNotificationsPath | 获取应用外部存储通知路径 ( path /storage/emulated/0/Android/data/package/files/Notifications ) | +| getAppNotificationsDir | 获取应用外部存储通知路径 ( path /storage/emulated/0/Android/data/package/files/Notifications ) | +| getAppPicturesPath | 获取应用外部存储图片路径 ( path /storage/emulated/0/Android/data/package/files/Pictures ) | +| getAppPicturesDir | 获取应用外部存储图片路径 ( path /storage/emulated/0/Android/data/package/files/Pictures ) | +| getAppMoviesPath | 获取应用外部存储影片路径 ( path /storage/emulated/0/Android/data/package/files/Movies ) | +| getAppMoviesDir | 获取应用外部存储影片路径 ( path /storage/emulated/0/Android/data/package/files/Movies ) | +| getAppDownloadPath | 获取应用外部存储下载路径 ( path /storage/emulated/0/Android/data/package/files/Download ) | +| getAppDownloadDir | 获取应用外部存储下载路径 ( path /storage/emulated/0/Android/data/package/files/Download ) | +| getAppDCIMPath | 获取应用外部存储数码相机图片路径 ( path /storage/emulated/0/Android/data/package/files/DCIM ) | +| getAppDCIMDir | 获取应用外部存储数码相机图片路径 ( path /storage/emulated/0/Android/data/package/files/DCIM ) | +| getAppDocumentsPath | 获取应用外部存储文档路径 ( path /storage/emulated/0/Android/data/package/files/Documents ) | +| getAppDocumentsDir | 获取应用外部存储文档路径 ( path /storage/emulated/0/Android/data/package/files/Documents ) | +| getAppAudiobooksPath | 获取应用外部存储有声读物路径 ( path /storage/emulated/0/Android/data/package/files/Audiobooks ) | +| getAppAudiobooksDir | 获取应用外部存储有声读物路径 ( path /storage/emulated/0/Android/data/package/files/Audiobooks ) | +| getAppObbPath | 获取应用外部存储 OBB 路径 ( path /storage/emulated/0/Android/obb/package ) | +| getAppObbDir | 获取应用外部存储 OBB 路径 ( path /storage/emulated/0/Android/obb/package ) | +| getRootPath | 获取 Android 系统根目录 ( path /system ) | +| getRootDirectory | 获取 Android 系统根目录 ( path /system ) | +| getDataPath | 获取 data 目录 ( path /data ) | +| getDataDirectory | 获取 data 目录 ( path /data ) | +| getDownloadCachePath | 获取下载缓存目录 ( path data/cache ) | +| getDownloadCacheDirectory | 获取下载缓存目录 ( path data/cache ) | +| getAppCodeCachePath | 获取应用内部存储代码缓存路径 ( path /data/data/package/code_cache ) | +| getAppCodeCacheDir | 获取应用内部存储代码缓存路径 ( path /data/data/package/code_cache ) | +| getAppDbsPath | 获取应用内部存储数据库路径 ( path /data/data/package/databases ) | +| getAppDbsDir | 获取应用内部存储数据库路径 ( path /data/data/package/databases ) | +| getAppDbPath | 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) | +| getAppDbFile | 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) | +| getAppSpPath | 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) | +| getAppSpDir | 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) | +| getAppSpFile | 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) | +| getAppNoBackupFilesPath | 获取应用内部存储未备份文件路径 ( path /data/data/package/no_backup ) | +| getAppNoBackupFilesDir | 获取应用内部存储未备份文件路径 ( path /data/data/package/no_backup ) | + + +* **手机相关工具类 ->** [PhoneUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/PhoneUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isPhone | 判断设备是否是手机 | +| getSimState | 获取 SIM 卡状态 | +| isSimReady | 判断是否装载 SIM 卡 | +| getSimCountryIso | 获取 SIM 卡运营商的国家代码 | +| getNetworkCountryIso | 获取 SIM 卡注册的网络运营商的国家代码 | +| getSimCountry | 获取 SIM 卡运营商的国家代码 | +| checkSimCountry | 判断 SIM 卡运营商是否国内 | +| getMEID | 获取 MEID 码 | +| getIMEI | 获取 IMEI 码 | +| getIMSI | 获取 IMSI 码 | +| getSimOperatorName | 获取 SIM 卡运营商名称 ( 如: 中国移动、如中国联通、中国电信 ) | +| getSimOperator | 获取 SIM 卡运营商 MCC + MNC | +| getChinaOperatorByIMSI | 通过 IMSI 获取中国运营商简称 | +| getChinaOperatorBySimOperator | 获取 SIM 卡中国运营商简称 | +| getPhoneType | 获取手机类型 | +| getDeviceId | 获取设备 id | +| getAndroidId | 获取 Android id | +| getSerialNumber | 获取设备序列号 | +| getSimSerialNumber | 获取 SIM 卡序列号 | +| getUUID | 获取设备唯一 UUID | +| getPhoneStatus | 获取手机状态信息 | +| dial | 跳至拨号界面 | +| call | 拨打电话 | +| sendSms | 跳至发送短信界面 | +| sendSmsSilent | 发送短信 | +| getContactNum | 打开手机联系人界面点击联系人后便获取该号码 | +| getAllContactInfo | 获取手机联系人信息 | +| getAllContactInfo2 | 获取手机联系人信息 | +| getAllSMS | 获取手机短信并保存到 xml 中 | + + +* **电源管理工具类 ->** [PowerManagerUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/PowerManagerUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 PowerManagerUtils 实例 | +| isScreenOn | 屏幕是否打开 ( 亮屏 ) | +| turnScreenOn | 唤醒 / 点亮 屏幕 | +| turnScreenOff | 释放屏幕锁 ( 允许休眠时间自动黑屏 ) | +| getWakeLock | 获取 PowerManager.WakeLock | +| setWakeLock | 设置 PowerManager.WakeLock | +| getPowerManager | 获取 PowerManager | +| setPowerManager | 设置 PowerManager | +| setBright | 设置屏幕常亮 | +| setWakeLockToBright | 设置 WakeLock 常亮 | + + +* **进程相关工具类 ->** [ProcessUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ProcessUtils.java) + +| 方法 | 注释 | +| :- | :- | +| kill | 销毁自身进程 | +| myPid | 获取自身进程 id | +| isCurProcess | 判断是否当前进程 | +| getCurProcessName | 获取当前进程名 | +| getProcessName | 获取进程 id 对应的进程名 | +| getPid | 根据包名获取进程 id | +| getRunningAppProcessInfo | 根据进程 id 获取进程信息 | +| getForegroundProcessName | 获取前台线程包名 | +| getAllBackgroundProcesses | 获取后台服务进程 | +| killAllBackgroundProcesses | 杀死所有的后台服务进程 | +| killBackgroundProcesses | 杀死后台服务进程 | + + +* **RecyclerView 工具类 ->** [RecyclerViewUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/RecyclerViewUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getRecyclerView | 获取 RecyclerView | +| getLayoutParams | 获取 RecyclerView Item View LayoutParams | +| setLayoutManager | 设置 RecyclerView LayoutManager | +| getLayoutManager | 获取 RecyclerView LayoutManager | +| getLinearLayoutManager | 获取 LinearLayoutManager | +| getGridLayoutManager | 获取 GridLayoutManager | +| getStaggeredGridLayoutManager | 获取 StaggeredGridLayoutManager | +| setSpanCount | 设置 GridLayoutManager SpanCount | +| getSpanCount | 获取 GridLayoutManager SpanCount | +| getPosition | 获取 RecyclerView 对应 Item View 索引 | +| findViewByPosition | 获取 RecyclerView 对应索引 Item View | +| findFirstCompletelyVisibleItemPosition | 获取 RecyclerView 第一条完全显示 Item 索引 | +| findFirstCompletelyVisibleItemPositions | 获取 RecyclerView 第一条完全显示 Item 索引数组 | +| findLastCompletelyVisibleItemPosition | 获取 RecyclerView 最后一条完全显示 Item 索引 | +| findLastCompletelyVisibleItemPositions | 获取 RecyclerView 最后一条完全显示 Item 索引数组 | +| findFirstVisibleItemPosition | 获取 RecyclerView 第一条显示 Item 索引 | +| findFirstVisibleItemPositions | 获取 RecyclerView 第一条显示 Item 索引数组 | +| findLastVisibleItemPosition | 获取 RecyclerView 最后一条显示 Item 索引 | +| findLastVisibleItemPositions | 获取 RecyclerView 最后一条显示 Item 索引数组 | +| setOrientation | 设置 RecyclerView Orientation | +| getOrientation | 获取 RecyclerView Orientation | +| canScrollVertically | 校验 RecyclerView Orientation 是否为 VERTICAL | +| canScrollHorizontally | 校验 RecyclerView Orientation 是否为 HORIZONTAL | +| setAdapter | 设置 RecyclerView Adapter | +| getAdapter | 获取 RecyclerView Adapter | +| getItemCount | 获取 Adapter ItemCount | +| getItemId | 获取 Adapter 指定索引 Item Id | +| getItemViewType | 获取 Adapter 指定索引 Item Type | +| notifyItemRemoved | RecyclerView notifyItemRemoved | +| notifyItemInserted | RecyclerView notifyItemInserted | +| notifyItemMoved | RecyclerView notifyItemMoved | +| notifyDataSetChanged | RecyclerView notifyDataSetChanged | +| attachLinearSnapHelper | 设置 RecyclerView LinearSnapHelper | +| attachPagerSnapHelper | 设置 RecyclerView PagerSnapHelper | +| getItemDecorationCount | 获取 RecyclerView ItemDecoration 总数 | +| getItemDecorationAt | 获取 RecyclerView ItemDecoration | +| addItemDecoration | 添加 RecyclerView ItemDecoration | +| removeItemDecoration | 移除 RecyclerView ItemDecoration | +| removeItemDecorationAt | 移除 RecyclerView ItemDecoration | +| removeAllItemDecoration | 移除 RecyclerView 全部 ItemDecoration | +| setOnScrollListener | 设置 RecyclerView ScrollListener | +| addOnScrollListener | 添加 RecyclerView ScrollListener | +| removeOnScrollListener | 移除 RecyclerView ScrollListener | +| clearOnScrollListeners | 清空 RecyclerView ScrollListener | +| getScrollState | 获取 RecyclerView 滑动状态 | +| isNestedScrollingEnabled | 获取 RecyclerView 嵌套滚动开关 | +| setNestedScrollingEnabled | 设置 RecyclerView 嵌套滚动开关 | +| requestChildRectangleOnScreen | requestChildRectangleOnScreen | + + +* **APK Resource 工具类 ->** [ResourcePluginUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ResourcePluginUtils.java) + +| 方法 | 注释 | +| :- | :- | +| invokeByPackageName | 通过 packageName 获取 APK Resources | +| invokeByAPKPath | 通过 APK 文件获取 APK Resources | +| getResourceAssist | 获取 Resources 辅助类 | +| getResources | 获取 Resources | +| getPackageName | 获取 APK 包名 | +| getAPKPath | 获取 APK 文件路径 | +| getApkInfoItem | 获取 APK 信息 Item | +| getDisplayMetrics | 获取 DisplayMetrics | +| getConfiguration | 获取 Configuration | +| getAssets | 获取 AssetManager | +| getIdentifier | 获取资源 id | +| getResourceName | 获取给定资源标识符的全名 | +| getStringId | 获取 String id | +| getString | 获取 String | +| getDimenId | 获取 Dimension id | +| getDimension | 获取 Dimension | +| getDimensionInt | 获取 Dimension | +| getColorId | 获取 Color id | +| getColor | 获取 Color | +| getDrawableId | 获取 Drawable id | +| getDrawable | 获取 Drawable | +| getNinePatchDrawable | 获取 .9 Drawable | +| getBitmap | 获取 Bitmap | +| getMipmapId | 获取 Mipmap id | +| getDrawableMipmap | 获取 Mipmap Drawable | +| getNinePatchDrawableMipmap | 获取 Mipmap .9 Drawable | +| getBitmapMipmap | 获取 Mipmap Bitmap | +| getAnimId | 获取 Anim id | +| getAnimationXml | 获取 Animation Xml | +| getAnimation | 获取 Animation | +| getBooleanId | 获取 Boolean id | +| getBoolean | 获取 Boolean | +| getIntegerId | 获取 Integer id | +| getInteger | 获取 Integer | +| getArrayId | 获取 Array id | +| getIntArray | 获取 int[] | +| getStringArray | 获取 String[] | +| getTextArray | 获取 CharSequence[] | +| getId | 获取 id ( view ) | +| getLayoutId | 获取 Layout id | +| getMenuId | 获取 Menu id | +| getRawId | 获取 Raw id | +| getAttrId | 获取 Attr id | +| getStyleId | 获取 Style id | +| getStyleableId | 获取 Styleable id | +| getAnimatorId | 获取 Animator id | +| getXmlId | 获取 Xml id | +| getInterpolatorId | 获取 Interpolator id | +| getPluralsId | 获取 Plurals id | +| getColorStateList | 获取 ColorStateList | +| getColorDrawable | 获取十六进制颜色值 Drawable | +| open | 获取 AssetManager 指定资源 InputStream | +| openFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openNonAssetFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openRawResource | 获取对应资源 InputStream | +| openRawResourceFd | 获取对应资源 AssetFileDescriptor | +| readBytesFromAssets | 获取 Assets 资源文件数据 | +| readStringFromAssets | 获取 Assets 资源文件数据 | +| readBytesFromRaw | 获取 Raw 资源文件数据 | +| readStringFromRaw | 获取 Raw 资源文件数据 | +| geFileToListFromAssets | 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| geFileToListFromRaw | 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| saveAssetsFormFile | 获取 Assets 资源文件数据并保存到本地 | +| saveRawFormFile | 获取 Raw 资源文件数据并保存到本地 | + + +* **资源文件工具类 ->** [ResourceUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ResourceUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getResources | 获取 Resources | +| getTheme | 获取 Resources.Theme | +| getContentResolver | 获取 ContentResolver | +| getDisplayMetrics | 获取 DisplayMetrics | +| getConfiguration | 获取 Configuration | +| getAssets | 获取 AssetManager | +| getIdentifier | 获取资源 id | +| getResourceName | 获取给定资源标识符的全名 | +| getStringId | 获取 String id | +| getString | 获取 String | +| getDimenId | 获取 Dimension id | +| getDimension | 获取 Dimension | +| getDimensionInt | 获取 Dimension | +| getColorId | 获取 Color id | +| getColor | 获取 Color | +| getDrawableId | 获取 Drawable id | +| getDrawable | 获取 Drawable | +| getNinePatchDrawable | 获取 .9 Drawable | +| getBitmap | 获取 Bitmap | +| getMipmapId | 获取 Mipmap id | +| getDrawableMipmap | 获取 Mipmap Drawable | +| getNinePatchDrawableMipmap | 获取 Mipmap .9 Drawable | +| getBitmapMipmap | 获取 Mipmap Bitmap | +| getAnimId | 获取 Anim id | +| getAnimationXml | 获取 Animation Xml | +| getAnimation | 获取 Animation | +| getBooleanId | 获取 Boolean id | +| getBoolean | 获取 Boolean | +| getIntegerId | 获取 Integer id | +| getInteger | 获取 Integer | +| getArrayId | 获取 Array id | +| getIntArray | 获取 int[] | +| getStringArray | 获取 String[] | +| getTextArray | 获取 CharSequence[] | +| getId | 获取 id ( view ) | +| getLayoutId | 获取 Layout id | +| getMenuId | 获取 Menu id | +| getRawId | 获取 Raw id | +| getAttrId | 获取 Attr id | +| getStyleId | 获取 Style id | +| getStyleableId | 获取 Styleable id | +| getAnimatorId | 获取 Animator id | +| getXmlId | 获取 Xml id | +| getInterpolatorId | 获取 Interpolator id | +| getPluralsId | 获取 Plurals id | +| getColorStateList | 获取 ColorStateList | +| getColorDrawable | 获取十六进制颜色值 Drawable | +| openInputStream | 获取 Uri InputStream | +| openOutputStream | 获取 Uri OutputStream | +| openFileDescriptor | 获取 Uri ParcelFileDescriptor | +| openAssetFileDescriptor | 获取 Uri AssetFileDescriptor | +| open | 获取 AssetManager 指定资源 InputStream | +| openFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openNonAssetFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openRawResource | 获取对应资源 InputStream | +| openRawResourceFd | 获取对应资源 AssetFileDescriptor | +| readBytesFromAssets | 获取 Assets 资源文件数据 | +| readStringFromAssets | 获取 Assets 资源文件数据 | +| readBytesFromRaw | 获取 Raw 资源文件数据 | +| readStringFromRaw | 获取 Raw 资源文件数据 | +| geFileToListFromAssets | 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| geFileToListFromRaw | 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| saveAssetsFormFile | 获取 Assets 资源文件数据并保存到本地 | +| saveRawFormFile | 获取 Raw 资源文件数据并保存到本地 | + + +* **ROM 相关工具类 ->** [ROMUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ROMUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isHuawei | 判断 ROM 是否 Huawei ( 华为 ) | +| isVivo | 判断 ROM 是否 Vivo ( VIVO ) | +| isXiaomi | 判断 ROM 是否 Xiaomi ( 小米 ) | +| isOppo | 判断 ROM 是否 Oppo ( OPPO ) | +| isLeeco | 判断 ROM 是否 Leeco ( 乐视 ) | +| is360 | 判断 ROM 是否 360 ( 360 ) | +| isZte | 判断 ROM 是否 Zte ( 中兴 ) | +| isOneplus | 判断 ROM 是否 Oneplus ( 一加 ) | +| isNubia | 判断 ROM 是否 Nubia ( 努比亚 ) | +| isCoolpad | 判断 ROM 是否 Coolpad ( 酷派 ) | +| isLg | 判断 ROM 是否 Lg ( LG ) | +| isGoogle | 判断 ROM 是否 Google ( 谷歌 ) | +| isSamsung | 判断 ROM 是否 Samsung ( 三星 ) | +| isMeizu | 判断 ROM 是否 Meizu ( 魅族 ) | +| isLenovo | 判断 ROM 是否 Lenovo ( 联想 ) | +| isSmartisan | 判断 ROM 是否 Smartisan ( 锤子 ) | +| isHtc | 判断 ROM 是否 Htc ( HTC ) | +| isSony | 判断 ROM 是否 Sony ( 索尼 ) | +| isGionee | 判断 ROM 是否 Gionee ( 金立 ) | +| isMotorola | 判断 ROM 是否 Motorola ( 摩托罗拉 ) | +| getRomInfo | 获取 ROM 信息 | +| isRightRom | 是否匹配正确 ROM | + + +* **截图监听工具类 ->** [ScreenshotUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ScreenshotUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 ScreenshotUtils 实例 | +| getStartListenTime | 获取开始监听时间 | +| isCheckPrefix | 是否判断文件名前缀 | +| setCheckPrefix | 设置是否判断文件名前缀 | +| getScreenshotChecker | 获取截图校验接口 | +| setScreenshotChecker | 设置截图校验接口 | +| getListener | 获取截图校验成功回调接口 | +| setListener | 设置截图校验成功回调接口 | +| startListener | 启动截图监听 | +| stopListener | 停止截图监听 | +| handleMediaContentChange | 内容变更处理 | +| handleMediaChecker | 内容校验处理 | + + +* **屏幕相关工具类 ->** [ScreenUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ScreenUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDisplayMetrics | 获取 DisplayMetrics | +| getScreenWidth | 获取屏幕宽度 | +| getScreenHeight | 获取屏幕高度 | +| getScreenWidthHeight | 获取屏幕宽高 | +| getScreenWidthHeightToPoint | 获取屏幕宽高 | +| getScreenSize | 获取屏幕分辨率 | +| getScreenSizeOfDevice | 获取屏幕英寸 ( 例 5.5 英寸 ) | +| getDensity | 获取屏幕密度 | +| getDensityDpi | 获取屏幕密度 dpi | +| getScaledDensity | 获取屏幕缩放密度 | +| getXDpi | 获取 X 轴 dpi | +| getYDpi | 获取 Y 轴 dpi | +| getWidthDpi | 获取宽度比例 dpi 基准 | +| getHeightDpi | 获取高度比例 dpi 基准 | +| getScreenInfo | 获取屏幕信息 | +| setWindowSecure | 设置禁止截屏 | +| isFullScreen | 是否屏幕为全屏 | +| setFullScreen | 设置屏幕为全屏 | +| setFullScreenNoTitle | 设置屏幕为全屏无标题 | +| setLandscape | 设置屏幕为横屏 | +| setPortrait | 设置屏幕为竖屏 | +| isLandscape | 判断是否横屏 | +| isPortrait | 判断是否竖屏 | +| toggleScreenOrientation | 切换屏幕方向 | +| getScreenRotation | 获取屏幕旋转角度 | +| isScreenLock | 判断是否锁屏 | +| isTablet | 判断是否是平板 | +| getStatusBarHeight | 获取 StatusBar 高度 | +| getStatusBarHeight2 | 获取 StatusBar 高度 | +| setSleepDuration | 设置进入休眠时长 | +| getSleepDuration | 获取进入休眠时长 | +| getNavigationBarHeight | 获取底部导航栏高度 | +| checkDeviceHasNavigationBar | 检测是否具有底部导航栏 | + + +* **SDCard 工具类 ->** [SDCardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SDCardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSDCardEnable | 判断 SDCard 是否正常挂载 | +| getSDCardFile | 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) | +| getSDCardPath | 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) | +| isSDCardEnablePath | 判断 SDCard 是否可用 | +| getSDCardPaths | 获取 SDCard 路径 | +| getAllBlockSize | 获取内置 SDCard 空间总大小 | +| getAllBlockSizeFormat | 获取内置 SDCard 空间总大小 | +| getAvailableBlocks | 获取内置 SDCard 空闲空间大小 | +| getAvailableBlocksFormat | 获取内置 SDCard 空闲空间大小 | +| getUsedBlocks | 获取内置 SDCard 已使用空间大小 | +| getUsedBlocksFormat | 获取内置 SDCard 已使用空间大小 | +| getBlockSizeInfos | 返回内置 SDCard 空间大小信息 | + + +* **服务相关工具类 ->** [ServiceUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ServiceUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isServiceRunning | 判断服务是否运行 | +| getAllRunningService | 获取所有运行的服务 | +| startService | 启动服务 | +| stopService | 停止服务 | +| bindService | 绑定服务 | +| unbindService | 解绑服务 | + + +* **Shape 工具类 ->** [ShapeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ShapeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDrawable | 获取 GradientDrawable | +| setDrawable | 设置 Drawable 背景 | +| getOrientation | 获取渐变角度 | +| newShape | 创建 Shape | +| setAlpha | 设置透明度 | +| setShape | 设置形状类型 | +| setInnerRadius | 设置内环半径 | +| setInnerRadiusRatio | 设置内环半径相对于环的宽度比例 | +| setThickness | 设置环厚度 | +| setThicknessRatio | 设置环厚度相对于环的宽度比例 | +| setColor | 设置背景填充颜色 | +| setStroke | 设置描边 | +| setCornerRadius | 设置圆角 | +| setCornerRadiusLeft | 设置 leftTop、leftBottom 圆角 | +| setCornerRadiusRight | 设置 rightTop、rightBottom 圆角 | +| setCornerRadiusTop | 设置 leftTop、rightTop 圆角 | +| setCornerRadiusBottom | 设置 leftBottom、rightBottom 圆角 | +| setColors | 设置渐变颜色 | +| setGradientType | 设置渐变类型 | +| setOrientation | 设置渐变角度 | +| setGradientCenter | 设置渐变中心坐标值 | +| setGradientRadius | 设置渐变色半径 | +| setUseLevel | 是否使用 LevelListDrawable | +| setPadding | 设置内边距 | +| setSize | 设置 shape drawable 宽高 | + + +* **Shell 命令工具类 ->** [ShellUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ShellUtils.java) + +| 方法 | 注释 | +| :- | :- | +| execCmd | 执行 shell 命令 | +| isSuccess | 判断是否执行成功 | +| isSuccess2 | 判断是否执行成功 ( 判断 errorMsg ) | +| isSuccess3 | 判断是否执行成功 ( 判断 successMsg ) | +| isSuccess4 | 判断是否执行成功 ( 判断 successMsg ) , 并且 successMsg 是否包含某个字符串 | + + +* **快捷方式工具类 ->** [ShortCutUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ShortCutUtils.java) + +| 方法 | 注释 | +| :- | :- | +| hasShortcut | 检测是否存在桌面快捷方式 | +| getShortCutIntent | 获取桌面快捷方式点击 Intent | +| addShortcut | 创建桌面快捷方式 | +| deleteShortcut | 删除桌面快捷方式 | + + +* **签名工具类 ( 获取 APP 签名信息 ) ->** [SignaturesUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SignaturesUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getAppSignature | 获取 APP Signature | +| signatureMD5 | 获取 MD5 签名 | +| signatureSHA1 | 获取签名 SHA1 加密字符串 | +| signatureSHA256 | 获取签名 SHA256 加密字符串 | +| isDebuggable | 判断 debug 签名还是 release 签名 | +| getX509Certificate | 获取证书对象 | +| printSignatureInfo | 打印签名信息 | +| getSignaturesFromApk | 从 APK 中读取签名 | +| getCertificateFromApk | 从 APK 中读取签名 | + + +* **大小工具类 ( dp, px, sp 转换、View 获取宽高等 ) ->** [SizeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SizeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| dp2px | dp 转 px | +| dp2pxf | dp 转 px ( float ) | +| px2dp | px 转 dp | +| px2dpf | px 转 dp ( float ) | +| sp2px | sp 转 px | +| sp2pxf | sp 转 px ( float ) | +| px2sp | px 转 sp | +| px2spf | px 转 sp ( float ) | +| applyDimension | 各种单位转换 ( 该方法存在于 TypedValue.applyDimension ) | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | +| measureView | 测量 View | +| getMeasuredWidth | 获取 View 的宽度 | +| getMeasuredHeight | 获取 View 的高度 | + + +* **Snackbar 工具类 ->** [SnackbarUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SnackbarUtils.java) + +| 方法 | 注释 | +| :- | :- | +| with | 获取 SnackbarUtils 对象 | +| getStyle | 获取样式 | +| setStyle | 设置样式 | +| getSnackbar | 获取 Snackbar | +| getSnackbarView | 获取 Snackbar View | +| getTextView | 获取 Snackbar TextView ( snackbar_text ) | +| getActionButton | 获取 Snackbar Action Button ( snackbar_action ) | +| getSnackbarLayout | 获取 Snackbar.SnackbarLayout ( FrameLayout ) | +| getSnackbarContentLayout | 获取 SnackbarContentLayout ( LinearLayout ( messageView、actionView ) ) | +| addView | 向 Snackbar 布局中添加 View ( Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示 ) | +| setCallback | 设置 Snackbar 展示完成、隐藏完成 的监听 | +| setAction | 设置 Action 按钮文字内容及点击监听 | +| dismiss | 关闭 Snackbar | +| showShort | 显示 Short Snackbar | +| showLong | 显示 Long Snackbar | +| showIndefinite | 显示 Indefinite Snackbar ( 无限时, 一直显示 ) | +| setSnackbarStyle | 设置 Snackbar 样式配置 | +| getShadowMargin | 获取阴影边距 | +| setShadowMargin | 设置阴影边距 | +| isAutoCalc | 判断是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) | +| setAutoCalc | 设置是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) | +| above | 设置 Snackbar 显示在指定 View 的上方 | +| bellow | 设置 Snackbar 显示在指定 View 的下方 | + + +* **SpannableString 工具类 ->** [SpanUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SpanUtils.java) + +| 方法 | 注释 | +| :- | :- | +| with | 获取持有 TextView SpannableString Utils | +| setFlag | 设置标识 | +| setForegroundColor | 设置前景色 | +| setBackgroundColor | 设置背景色 | +| setLineHeight | 设置行高 | +| setQuoteColor | 设置引用线的颜色 | +| setLeadingMargin | 设置缩进 | +| setBullet | 设置列表标记 | +| setFontSize | 设置字体尺寸 | +| setFontProportion | 设置字体比例 | +| setFontXProportion | 设置字体横向比例 | +| setStrikethrough | 设置删除线 | +| setUnderline | 设置下划线 | +| setSuperscript | 设置上标 | +| setSubscript | 设置下标 | +| setBold | 设置粗体 | +| setItalic | 设置斜体 | +| setBoldItalic | 设置粗斜体 | +| setFontFamily | 设置字体系列 | +| setTypeface | 设置字体 | +| setHorizontalAlign | 设置水平对齐 | +| setVerticalAlign | 设置垂直对齐 | +| setClickSpan | 设置点击事件 | +| setUrl | 设置超链接 | +| setBlur | 设置模糊 | +| setShader | 设置着色器 | +| setShadow | 设置阴影 | +| setSpans | 自定义 setSpan 参数 | +| append | 追加文本 | +| appendLine | 追加换行 | +| appendImage | 追加 Image | +| appendSpace | 追加空格 | +| get | 获取 SpannableStringBuilder | +| create | 创建 SpannableStringBuilder | + + +* **颜色状态列表工具类 ->** [StateListUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/StateListUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getColorStateList | 获取 ColorStateList | +| createColorStateList | 创建 ColorStateList | +| newSelector | 创建 StateListDrawable | + + +* **TextView 工具类 ->** [TextViewUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/TextViewUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getTextView | 获取 TextView | +| getHint | 获取 Hint 文本 | +| getHints | 获取多个 TextView Hint 文本 | +| setHint | 设置 Hint 文本 | +| getHintTextColors | 获取 Hint 字体颜色 | +| setHintTextColor | 设置 Hint 字体颜色 | +| setHintTextColors | 设置多个 TextView Hint 字体颜色 | +| getText | 获取文本 | +| getTexts | 获取多个 TextView 文本 | +| setText | 设置文本 | +| setTexts | 设置多个 TextView 文本 | +| getTextColors | 获取字体颜色 | +| setTextColor | 设置字体颜色 | +| setTextColors | 设置多个 TextView 字体颜色 | +| setHtmlText | 设置 Html 内容 | +| setHtmlTexts | 设置多个 TextView Html 内容 | +| getTypeface | 获取字体 | +| setTypeface | 设置字体 | +| setTextSizeByPx | 设置字体大小 ( px 像素 ) | +| setTextSizeBySp | 设置字体大小 ( sp 缩放像素 ) | +| setTextSizeByDp | 设置字体大小 ( dp 与设备无关的像素 ) | +| setTextSizeByIn | 设置字体大小 ( inches 英寸 ) | +| setTextSize | 设置字体大小 | +| setTextSizes | 设置多个 TextView 字体大小 | +| getTextSize | 获取 TextView 字体大小 ( px ) | +| clearFlags | 清空 flags | +| setPaintFlags | 设置 TextView flags | +| setAntiAliasFlag | 设置 TextView 抗锯齿 flags | +| setBold | 设置 TextView 是否加粗 | +| setUnderlineText | 设置下划线 | +| setStrikeThruText | 设置中划线 | +| getLetterSpacing | 获取文字水平间距 | +| setLetterSpacing | 设置文字水平间距 | +| getLineSpacingExtra | 获取文字行间距 ( 行高 ) | +| getLineSpacingMultiplier | 获取文字行间距倍数 | +| setLineSpacing | 设置文字行间距 ( 行高 ) | +| setLineSpacingAndMultiplier | 设置文字行间距 ( 行高 ) 、行间距倍数 | +| getTextScaleX | 获取字体水平方向的缩放 | +| setTextScaleX | 设置字体水平方向的缩放 | +| getIncludeFontPadding | 是否保留字体留白间隙区域 | +| setIncludeFontPadding | 设置是否保留字体留白间隙区域 | +| getInputType | 获取输入类型 | +| setInputType | 设置输入类型 | +| getImeOptions | 获取软键盘右下角按钮类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| setLines | 设置行数 | +| getMaxLines | 获取最大行数 | +| setMaxLines | 设置最大行数 | +| getMinLines | 获取最小行数 | +| setMinLines | 设置最小行数 | +| getMaxEms | 获取最大字符宽度限制 | +| setMaxEms | 设置最大字符宽度限制 | +| getMinEms | 获取最小字符宽度限制 | +| setMinEms | 设置最小字符宽度限制 | +| setEms | 设置指定字符宽度 | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| getEllipsize | 获取 Ellipsize 效果 | +| setEllipsize | 设置 Ellipsize 效果 | +| getAutoLinkMask | 获取自动识别文本类型 | +| setAutoLinkMask | 设置自动识别文本链接 | +| setAllCaps | 设置文本全为大写 | +| getGravity | 获取 Gravity | +| setGravity | 设置 Gravity | +| getTransformationMethod | 获取文本视图显示转换 | +| setTransformationMethod | 设置文本视图显示转换 | +| getPaint | 获取 TextView Paint | +| getTextHeight | 获取字体高度 | +| getTextTopOffsetHeight | 获取字体顶部偏移高度 | +| getTextWidth | 计算字体宽度 | +| getCenterRectY | 获取画布中间居中位置 | +| reckonTextSizeByHeight | 通过需要的高度, 计算字体大小 | +| reckonTextSizeByWidth | 通过需要的宽度, 计算字体大小 ( 最接近该宽度的字体大小 ) | +| calcTextWidth | 计算第几位超过宽度 | +| calcTextLine | 计算文本换行行数 | +| getCompoundDrawables | 获取 CompoundDrawables | +| getCompoundDrawablePadding | 获取 CompoundDrawables Padding | +| setCompoundDrawablePadding | 设置 CompoundDrawables Padding | +| setCompoundDrawablesByLeft | 设置 Left CompoundDrawables | +| setCompoundDrawablesByTop | 设置 Top CompoundDrawables | +| setCompoundDrawablesByRight | 设置 Right CompoundDrawables | +| setCompoundDrawablesByBottom | 设置 Bottom CompoundDrawables | +| setCompoundDrawables | 设置 CompoundDrawables | +| setCompoundDrawablesWithIntrinsicBoundsByLeft | 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByTop | 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByRight | 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByBottom | 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBounds | 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setAutoSizeTextTypeWithDefaults | 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 | +| setAutoSizeTextTypeUniformWithConfiguration | 设置 TextView 自动调整字体大小配置 | +| setAutoSizeTextTypeUniformWithPresetSizes | 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM | +| getAutoSizeTextType | 获取 TextView 自动调整大小类型 | +| getAutoSizeStepGranularity | 获取 TextView 自动调整大小变动粒度 ( 跨度区间值 ) | +| getAutoSizeMinTextSize | 获取 TextView 自动调整最小字体大小 | +| getAutoSizeMaxTextSize | 获取 TextView 自动调整最大字体大小 | +| getAutoSizeTextAvailableSizes | 获取 TextView 自动调整大小预设范围数组 | + + +* **Uri 工具类 ->** [UriUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/UriUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getUriForFile | 获取 FileProvider File Uri | +| getUriForPath | 获取 FileProvider File Path Uri | +| getUriForFileToName | 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) | +| getUriForString | 通过 String 获取 Uri | +| fromFile | 通过 File Path 创建 Uri | +| ofUri | 通过 String 获取 Uri | +| isUri | 判断是否 Uri | +| getUriScheme | 获取 Uri Scheme | +| isUriExists | 判断 Uri 路径资源是否存在 | +| getMediaUri | 通过 File 获取 Media Uri | +| copyByUri | 通过 Uri 复制文件 | +| getFilePathByUri | 通过 Uri 获取文件路径 | +| isExternalStorageDocument | 判读 Uri authority 是否为 ExternalStorage Provider | +| isDownloadsDocument | 判读 Uri authority 是否为 Downloads Provider | +| isMediaDocument | 判断 Uri authority 是否为 Media Provider | +| isGooglePhotosUri | 判断 Uri authority 是否为 Google Photos Provider | +| isAndroidResourceScheme | 判断 Uri Scheme 是否 ContentResolver.SCHEME_ANDROID_RESOURCE | +| isFileScheme | 判断 Uri Scheme 是否 ContentResolver.SCHEME_FILE | +| isContentScheme | 判断 Uri Scheme 是否 ContentResolver.SCHEME_CONTENT | +| isUriScheme | 判断是否指定的 Uri Scheme | + + +* **版本工具类 ->** [VersionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/VersionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getSDKVersion | 获取 SDK 版本 | +| isEclair | 是否在 2.1 版本及以上 | +| isFroyo | 是否在 2.2 版本及以上 | +| isGingerbread | 是否在 2.3 版本及以上 | +| isGingerbreadMR1 | 是否在 2.3.3 版本及以上 | +| isHoneycomb | 是否在 3.0 版本及以上 | +| isHoneycombMR1 | 是否在 3.1 版本及以上 | +| isIceCreamSandwich | 是否在 4.0 版本及以上 | +| isIceCreamSandwichMR1 | 是否在 4.0.3 版本及以上 | +| isJellyBean | 是否在 4.1 版本及以上 | +| isJellyBeanMR1 | 是否在 4.2 版本及以上 | +| isJellyBeanMR2 | 是否在 4.3 版本及以上 | +| isKitkat | 是否在 4.4.2 版本及以上 | +| isKitkat_Watch | 是否在 4.4W 版本及以上 | +| isLollipop | 是否在 5.0 版本及以上 | +| isLollipop_MR1 | 是否在 5.1 版本及以上 | +| isM | 是否在 6.0 版本及以上 | +| isN | 是否在 7.0 版本及以上 | +| isN_MR1 | 是否在 7.1.1 版本及以上 | +| isO | 是否在 8.0 版本及以上 | +| isO_MR1 | 是否在 8.1 版本及以上 | +| isP | 是否在 9.0 版本及以上 | +| isQ | 是否在 10.0 版本及以上 | +| isR | 是否在 11.0 版本及以上 | +| isS | 是否在 12.0 版本及以上 | +| convertSDKVersion | 转换 SDK 版本 convertSDKVersion(31) = Android 12.0 | +| convertSDKVersionName | 转换 SDK 版本名字 convertSDKVersionName(31) = Android S | + + +* **震动相关工具类 ->** [VibrationUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/VibrationUtils.java) + +| 方法 | 注释 | +| :- | :- | +| vibrate | 震动 | +| cancel | 取消震动 | + + +* **View 操作相关工具类 ->** [ViewUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/ViewUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getContext | 获取 View Context | +| getActivity | 获取 View Context 所属的 Activity | +| inflate | 获取 View | +| getId | 获取 View Id | +| setId | 设置 View Id | +| getParent | 获取指定 View 父布局 | +| getContentView | 获取 android.R.id.content View | +| getRootParent | 获取指定 View 根布局 ( 最底层布局 ) | +| getClipChildren | 获取是否限制子 View 在其边界内绘制 | +| setClipChildren | 设置是否限制子 View 在其边界内绘制 | +| getChildCount | 获取子 View 总数 | +| getChildAt | 获取指定索引 View | +| getChildAtLast | 获取最后一个索引 View | +| removeAllViews | 移除全部子 View | +| getChilds | 获取全部子 View | +| addView | 添加 View | +| getLayoutParams | 获取 LayoutParams | +| setLayoutParams | 设置 View LayoutParams | +| findViewById | 初始化 View | +| convertView | 转换 View | +| convertViewGroup | 转换 ViewGroup | +| isEmpty | 判断 View 是否为 null | +| isNotEmpty | 判断 View 是否不为 null | +| getWidthHeight | 获取 View 宽高 | +| setWidthHeight | 设置 View 宽度、高度 | +| setWeight | 设置 View weight 权重 | +| getWidthHeightExact | 获取 View 宽高 ( 准确 ) | +| getWidthHeightExact2 | 获取 View 宽高 ( 准确 ) | +| getLocationOnScreen | 获取 View 在屏幕中坐标区域 | +| getLocationInWindow | 获取 View 在父窗口中坐标区域 | +| getGlobalVisibleRect | 获取 View 在屏幕中可见的坐标区域 | +| getLocalVisibleRect | 获取 View 本身可见的坐标区域 | +| isCompletelyVisible | 判断 View 是否完全显示 | +| getWidth | 获取 View 宽度 | +| setWidth | 设置 View 宽度 | +| getHeight | 获取 View 高度 | +| setHeight | 设置 View 高度 | +| getMinimumHeight | 获取 View 最小高度 | +| setMinimumHeight | 设置 View 最小高度 | +| getMinimumWidth | 获取 View 最小宽度 | +| setMinimumWidth | 设置 View 最小宽度 | +| getAlpha | 获取 View 透明度 | +| setAlpha | 设置 View 透明度 | +| getTag | 获取 View TAG | +| setTag | 设置 View TAG | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| getScrollX | 获取 View 滑动的 X 轴坐标 | +| getScrollY | 获取 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| isHorizontalScrollBarEnabled | 是否绘制横向滚动条 | +| setHorizontalScrollBarEnabled | 设置是否绘制横向滚动条 | +| isVerticalScrollBarEnabled | 是否绘制垂直滚动条 | +| setVerticalScrollBarEnabled | 设置是否绘制垂直滚动条 | +| isScrollContainer | 获取 View 是否需要滚动效应 | +| setScrollContainer | 设置 View 滚动效应 | +| getNextFocusForwardId | 下一个获取焦点的 View id | +| setNextFocusForwardId | 设置下一个获取焦点的 View id | +| getNextFocusDownId | 向下移动焦点时, 下一个获取焦点的 View id | +| setNextFocusDownId | 设置向下移动焦点时, 下一个获取焦点的 View id | +| getNextFocusLeftId | 向左移动焦点时, 下一个获取焦点的 View id | +| setNextFocusLeftId | 设置向左移动焦点时, 下一个获取焦点的 View id | +| getNextFocusRightId | 向右移动焦点时, 下一个获取焦点的 View id | +| setNextFocusRightId | 设置向右移动焦点时, 下一个获取焦点的 View id | +| getNextFocusUpId | 向上移动焦点时, 下一个获取焦点的 View id | +| setNextFocusUpId | 设置向上移动焦点时, 下一个获取焦点的 View id | +| getRotation | 获取 View 旋转度数 | +| setRotation | 设置 View 旋转度数 | +| getRotationX | 获取 View 水平旋转度数 | +| setRotationX | 设置 View 水平旋转度数 | +| getRotationY | 获取 View 竖直旋转度数 | +| setRotationY | 设置 View 竖直旋转度数 | +| getScaleX | 获取 View 水平方向缩放比例 | +| setScaleX | 设置 View 水平方向缩放比例 | +| getScaleY | 获取 View 竖直方向缩放比例 | +| setScaleY | 设置 View 竖直方向缩放比例 | +| getTextAlignment | 获取文本的显示方式 | +| setTextAlignment | 设置文本的显示方式 | +| getTextDirection | 获取文本的显示方向 | +| setTextDirection | 设置文本的显示方向 | +| getPivotX | 获取水平方向偏转量 | +| setPivotX | 设置水平方向偏转量 | +| getPivotY | 获取竖直方向偏转量 | +| setPivotY | 设置竖直方向偏转量 | +| getTranslationX | 获取水平方向的移动距离 | +| setTranslationX | 设置水平方向的移动距离 | +| getTranslationY | 获取竖直方向的移动距离 | +| setTranslationY | 设置竖直方向的移动距离 | +| getX | 获取 X 轴位置 | +| setX | 设置 X 轴位置 | +| getY | 获取 Y 轴位置 | +| setY | 设置 Y 轴位置 | +| getLayerType | 获取 View 硬件加速类型 | +| setLayerType | 设置 View 硬件加速类型 | +| requestLayout | 请求重新对 View 布局 | +| requestFocus | View 请求获取焦点 | +| clearFocus | View 清除焦点 | +| findFocus | 获取 View 里获取焦点的 View | +| isFocused | 获取是否当前 View 就是焦点 View | +| hasFocus | 获取当前 View 是否是焦点 View 或者子 View 里面有焦点 View | +| hasFocusable | 获取当前 View 或者子 View 是否可以获取焦点 | +| isFocusableInTouchMode | 获取 View 是否在触摸模式下获得焦点 | +| setFocusableInTouchMode | 设置 View 是否在触摸模式下获得焦点 | +| isFocusable | 获取 View 是否可以获取焦点 | +| setFocusable | 设置 View 是否可以获取焦点 | +| toggleFocusable | 切换获取焦点状态 | +| isSelected | 获取 View 是否选中 | +| setSelected | 设置 View 是否选中 | +| toggleSelected | 切换选中状态 | +| isEnabled | 获取 View 是否启用 | +| setEnabled | 设置 View 是否启用 | +| toggleEnabled | 切换 View 是否启用状态 | +| isClickable | 获取 View 是否可以点击 | +| setClickable | 设置 View 是否可以点击 | +| toggleClickable | 切换 View 是否可以点击状态 | +| isLongClickable | 获取 View 是否可以长按 | +| setLongClickable | 设置 View 是否可以长按 | +| toggleLongClickable | 切换 View 是否可以长按状态 | +| isShown | 判断 View 是否显示 ( 如果存在父级则判断父级 ) | +| isShowns | 判断 View 是否都显示 ( 如果存在父级则判断父级 ) | +| isVisibility | 判断 View 是否显示 | +| isVisibilitys | 判断 View 是否都显示 | +| isVisibilityIN | 判断 View 是否隐藏占位 | +| isVisibilityINs | 判断 View 是否都隐藏占位 | +| isVisibilityGone | 判断 View 是否隐藏 | +| isVisibilityGones | 判断 View 是否都隐藏 | +| getVisibility | 获取显示的状态 ( View.VISIBLE : View.GONE ) | +| getVisibilityIN | 获取显示的状态 ( View.VISIBLE : View.INVISIBLE ) | +| setVisibility | 设置 View 显示的状态 | +| setVisibilityIN | 设置 View 显示的状态 | +| setVisibilitys | 设置 View 显示的状态 | +| setVisibilityINs | 设置 View 显示的状态 | +| toggleVisibilitys | 切换 View 显示的状态 | +| reverseVisibilitys | 反转 View 显示的状态 | +| toggleView | 切换 View 状态 | +| toggleViews | 切换 View 状态 | +| removeSelfFromParent | 把自身从父 View 中移除 | +| isTouchInView | 判断触点是否落在该 View 上 | +| requestLayoutParent | View 请求更新 | +| measureView | 测量 View | +| getMeasuredWidth | 获取 View 的宽度 | +| getMeasuredHeight | 获取 View 的高度 | +| getLayoutGravity | 获取 View Layout Gravity | +| setLayoutGravity | 设置 View Layout Gravity | +| getMarginLeft | 获取 View Left Margin | +| getMarginTop | 获取 View Top Margin | +| getMarginRight | 获取 View Right Margin | +| getMarginBottom | 获取 View Bottom Margin | +| getMargin | 获取 View Margin | +| setMarginLeft | 设置 View Left Margin | +| setMarginTop | 设置 View Top Margin | +| setMarginRight | 设置 View Right Margin | +| setMarginBottom | 设置 View Bottom Margin | +| setMargin | 设置 Margin 边距 | +| getPaddingLeft | 获取 View Left Padding | +| getPaddingTop | 获取 View Top Padding | +| getPaddingRight | 获取 View Right Padding | +| getPaddingBottom | 获取 View Bottom Padding | +| getPadding | 获取 View Padding | +| setPaddingLeft | 设置 View Left Padding | +| setPaddingTop | 设置 View Top Padding | +| setPaddingRight | 设置 View Right Padding | +| setPaddingBottom | 设置 View Bottom Padding | +| setPadding | 设置 Padding 边距 | +| addRule | 设置 RelativeLayout View 布局规则 | +| removeRule | 移除 RelativeLayout View 布局规则 | +| getRule | 获取 RelativeLayout View 指定布局位置 View id | +| addRules | 设置多个 RelativeLayout View 布局规则 | +| removeRules | 移除多个 RelativeLayout View 布局规则 | +| setAnimation | 设置动画 | +| getAnimation | 获取动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| setBackground | 设置背景图片 | +| setBackgroundColor | 设置背景颜色 | +| setBackgroundResource | 设置背景资源 | +| setBackgroundTintList | 设置背景着色颜色 | +| setBackgroundTintMode | 设置背景着色模式 | +| setForeground | 设置前景图片 | +| setForegroundGravity | 设置前景重心 | +| setForegroundTintList | 设置前景着色颜色 | +| setForegroundTintMode | 设置前景着色模式 | +| getBackground | 获取 View 背景 Drawable | +| getBackgroundTintList | 获取 View 背景着色颜色 | +| getBackgroundTintMode | 获取 View 背景着色模式 | +| getForeground | 获取 View 前景 Drawable | +| getForegroundGravity | 获取 View 前景重心 | +| getForegroundTintList | 获取 View 前景着色颜色 | +| getForegroundTintMode | 获取 View 前景着色模式 | +| setColorFilter | View 着色处理 | +| setProgressDrawable | 设置 ProgressBar 进度条样式 | +| setBarProgress | 设置 ProgressBar 进度值 | +| setBarMax | 设置 ProgressBar 最大值 | +| setBarValue | 设置 ProgressBar 最大值 | +| onWidthHeight | 获取宽高回调 | + + +* **壁纸工具类 ->** [WallpaperUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/WallpaperUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isWallpaperSupported | 是否支持壁纸 | +| isSetWallpaperAllowed | 是否支持设置壁纸 | +| hasResourceWallpaper | 判断当前壁纸是否使用给定的资源 Id | +| forgetLoadedWallpaper | 删除所有内部引用到最后加载的壁纸 | +| clear | 删除壁纸 ( 恢复为系统内置桌面壁纸 ) | +| clearWallpaper | 删除壁纸 ( 恢复为系统内置壁纸 ) | +| getWallpaperId | 获取当前壁纸 Id | +| getWallpaperInfo | 获取动态壁纸信息 | +| getWallpaperColors | 获取壁纸颜色信息 | +| getDesiredMinimumHeight | 获取壁纸所需最小高度 | +| getDesiredMinimumWidth | 获取壁纸所需最小宽度 | +| getBuiltInDrawable | 获取系统内置静态壁纸 ( 桌面壁纸 ) | +| getDrawable | 获取当前壁纸 ( 桌面壁纸 ) | +| getFastDrawable | 获取当前壁纸 ( 桌面壁纸 ) | +| peekDrawable | 获取当前壁纸 ( 桌面壁纸 ) | +| peekFastDrawable | 获取当前壁纸 ( 桌面壁纸 ) | +| setBitmap | 通过 Bitmap 设置壁纸 ( 桌面壁纸 ) | +| setResource | 通过 res 设置壁纸 | +| setStream | 通过 InputStream 设置壁纸 | +| setUri | 通过 Uri 设置壁纸 ( 跳转到设置页 ) | +| callback | 非适配 ROM 则触发回调 | + + +* **控件工具类 ->** [WidgetUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/WidgetUtils.java) + +| 方法 | 注释 | +| :- | :- | +| viewMeasure | View Measure | +| calculateSize | 计算大小 | +| getSize | 从提供的测量规范中提取大小 | +| getMode | 从提供的测量规范中提取模式 | +| measureView | 测量 View | +| getMeasuredWidth | 获取 View 的宽度 | +| getMeasuredHeight | 获取 View 的高度 | + + +* **Window 工具类 ->** [WindowUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/WindowUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getWindow | 获取 Window | +| get | 获取 WindowAssist | + + +## **`dev.utils.app.activity_result`** + + +* **Activity Result 封装辅助类 ->** [ActivityResultAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/activity_result/ActivityResultAssist.java) + +| 方法 | 注释 | +| :- | :- | +| isLauncherEmpty | 判断启动器是否为 null | +| isLauncherNotEmpty | 判断启动器是否不为 null | +| setOperateCallback | 设置操作回调 | +| getInputValue | 获取启动输入参数值 | +| getOptionsValue | 获取 Activity 启动选项值 | +| getMethodType | 获取对应 Type 所属方法 | +| launch | launch | +| unregister | unregister | +| getContract | getContract | +| onStart | 操作前回调 | +| onState | 操作状态回调 | + + +* **Activity Result 封装辅助类 ->** [DefaultActivityResult.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/activity_result/DefaultActivityResult.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DefaultActivityResult 实例 | +| startActivityForResult | Activity 跳转回传 | +| onStartActivityForResult | 跳转 Activity 操作 | +| onActivityResult | 回传处理 | +| start | 跳转回传结果处理 Activity 内部方法 | +| onCreate | onCreate | +| onDestroy | onDestroy | + + +## **`dev.utils.app.anim`** + + +* **动画工具类 ->** [AnimationUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/anim/AnimationUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setAnimationRepeat | 设置动画重复处理 | +| setAnimationListener | 设置动画事件 | +| setAnimation | 设置动画 | +| getAnimation | 获取动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| getRotateAnimation | 获取一个旋转动画 | +| getRotateAnimationByCenter | 获取一个根据视图自身中心点旋转的动画 | +| getAlphaAnimation | 获取一个透明度渐变动画 | +| getHiddenAlphaAnimation | 获取一个由完全显示变为不可见的透明度渐变动画 | +| getShowAlphaAnimation | 获取一个由不可见变为完全显示的透明度渐变动画 | +| getScaleAnimation | 获取一个缩放动画 | +| getScaleAnimationCenter | 获取一个中心点缩放动画 | +| getLessenScaleAnimation | 获取一个缩小动画 | +| getAmplificationAnimation | 获取一个放大动画 | +| getTranslateAnimation | 获取一个视图移动动画 | +| getShakeAnimation | 获取一个视图摇晃动画 | + + +* **View 动画工具类 ->** [ViewAnimationUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java) + +| 方法 | 注释 | +| :- | :- | +| invisibleViewByAlpha | 将给定视图渐渐隐去 | +| goneViewByAlpha | 将给定视图渐渐隐去最后从界面中移除 | +| visibleViewByAlpha | 将给定视图渐渐显示出来 | +| translate | 视图移动 | +| shake | 视图摇晃 | + + +## **`dev.utils.app.assist`** + + +* **Activity 栈管理辅助类 ->** [ActivityManagerAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/ActivityManagerAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getActivityStacks | 获取 Activity 栈 | +| addActivity | 添加 Activity | +| removeActivity | 移除 Activity | +| currentActivity | 获取最后一个 ( 当前 ) Activity | +| finishActivity | 关闭最后一个 ( 当前 ) Activity | +| existActivitys | 检测是否包含指定的 Activity | +| finishAllActivityToIgnore | 结束全部 Activity 除忽略的 Activity 外 | +| finishAllActivity | 结束所有 Activity | +| exitApplication | 退出应用程序 | +| restartApplication | 重启 APP | + + +* **播放「bee」的声音, 并且震动辅助类 ->** [BeepVibrateAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java) + +| 方法 | 注释 | +| :- | :- | +| isPlayBeep | 判断是否允许播放声音 | +| isVibrate | 判断是否允许震动 | +| setVibrate | 设置是否允许震动 | +| setMediaPlayer | 设置播放资源对象 | +| playBeepSoundAndVibrate | 进行播放声音, 并且震动 | +| close | 关闭震动、提示声, 并释放资源 | +| buildMediaPlayer | 创建 MediaPlayer 对象 | + + +* **延迟触发辅助类 ->** [DelayAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/DelayAssist.java) + +| 方法 | 注释 | +| :- | :- | +| remove | 移除消息 | +| post | 发送消息 ( 功能由该方法实现 ) | +| setDelayMillis | 设置搜索延迟时间 | +| setCallback | 设置搜索回调接口 | +| callback | 回调方法 | + + +* **Activity 无操作定时辅助类 ->** [InactivityTimerAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java) + +| 方法 | 注释 | +| :- | :- | +| onPause | 暂停检测 | +| onResume | 回到 Activity 处理 | +| onDestroy | Activity 销毁处理 | + + +* **Resources 辅助类 ->** [ResourceAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/ResourceAssist.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 ResourceAssist | +| staticResources | 获取 Resources | +| staticTheme | 获取 Resources.Theme | +| staticContentResolver | 获取 ContentResolver | +| staticDisplayMetrics | 获取 DisplayMetrics | +| staticConfiguration | 获取 Configuration | +| staticAssets | 获取 AssetManager | +| reset | 重置操作 | +| getPackageName | 获取应用包名 | +| getResources | 获取 Resources | +| getTheme | 获取 Resources.Theme | +| getContentResolver | 获取 ContentResolver | +| getDisplayMetrics | 获取 DisplayMetrics | +| getConfiguration | 获取 Configuration | +| getAssets | 获取 AssetManager | +| getIdentifier | 获取资源 id | +| getResourceName | 获取给定资源标识符的全名 | +| getStringId | 获取 String id | +| getString | 获取 String | +| getDimenId | 获取 Dimension id | +| getDimension | 获取 Dimension | +| getDimensionInt | 获取 Dimension | +| getColorId | 获取 Color id | +| getColor | 获取 Color | +| getDrawableId | 获取 Drawable id | +| getDrawable | 获取 Drawable | +| getNinePatchDrawable | 获取 .9 Drawable | +| getBitmap | 获取 Bitmap | +| getMipmapId | 获取 Mipmap id | +| getDrawableMipmap | 获取 Mipmap Drawable | +| getNinePatchDrawableMipmap | 获取 Mipmap .9 Drawable | +| getBitmapMipmap | 获取 Mipmap Bitmap | +| getAnimId | 获取 Anim id | +| getAnimationXml | 获取 Animation Xml | +| getAnimation | 获取 Animation | +| getBooleanId | 获取 Boolean id | +| getBoolean | 获取 Boolean | +| getIntegerId | 获取 Integer id | +| getInteger | 获取 Integer | +| getArrayId | 获取 Array id | +| getIntArray | 获取 int[] | +| getStringArray | 获取 String[] | +| getTextArray | 获取 CharSequence[] | +| getId | 获取 id ( view ) | +| getLayoutId | 获取 Layout id | +| getMenuId | 获取 Menu id | +| getRawId | 获取 Raw id | +| getAttrId | 获取 Attr id | +| getStyleId | 获取 Style id | +| getStyleableId | 获取 Styleable id | +| getAnimatorId | 获取 Animator id | +| getXmlId | 获取 Xml id | +| getInterpolatorId | 获取 Interpolator id | +| getPluralsId | 获取 Plurals id | +| getColorStateList | 获取 ColorStateList | +| getColorDrawable | 获取十六进制颜色值 Drawable | +| openInputStream | 获取 Uri InputStream | +| openOutputStream | 获取 Uri OutputStream | +| openFileDescriptor | 获取 Uri ParcelFileDescriptor | +| openAssetFileDescriptor | 获取 Uri AssetFileDescriptor | +| open | 获取 AssetManager 指定资源 InputStream | +| openFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openNonAssetFd | 获取 AssetManager 指定资源 AssetFileDescriptor | +| openRawResource | 获取对应资源 InputStream | +| openRawResourceFd | 获取对应资源 AssetFileDescriptor | +| readBytesFromAssets | 获取 Assets 资源文件数据 | +| readStringFromAssets | 获取 Assets 资源文件数据 | +| readBytesFromRaw | 获取 Raw 资源文件数据 | +| readStringFromRaw | 获取 Raw 资源文件数据 | +| geFileToListFromAssets | 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| geFileToListFromRaw | 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) | +| saveAssetsFormFile | 获取 Assets 资源文件数据并保存到本地 | +| saveRawFormFile | 获取 Raw 资源文件数据并保存到本地 | + + +* **屏幕传感器辅助类 ( 监听是否横竖屏 ) ->** [ScreenSensorAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java) + +| 方法 | 注释 | +| :- | :- | +| start | 开始监听 | +| stop | 停止监听 | +| isPortrait | 是否竖屏 | +| isAllowChange | 是否允许切屏 | + + +* **Window 辅助类 ->** [WindowAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/WindowAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getWindow | 获取 Window | +| get | 获取 WindowAssist | +| getDecorView | 获取 Window DecorView | +| peekDecorView | 获取 Window DecorView | +| getCurrentFocus | 获取 Window 当前获取焦点 View | +| setSystemUiVisibility | 设置 Window System UI 可见性 | +| getSystemUiVisibility | 获取 Window System UI 可见性 | +| setSystemUiVisibilityByAdd | 设置 Window System UI 可见性 ( 原来基础上进行追加 ) | +| setSystemUiVisibilityByClear | 设置 Window System UI 可见性 ( 原来基础上进行清除 ) | +| getAttributes | 获取 Window LayoutParams | +| setAttributes | 设置 Window LayoutParams | +| refreshSelfAttributes | 刷新自身 Window LayoutParams | +| clearFlags | 清除 Window flags | +| addFlags | 添加 Window flags | +| setFlags | 设置 Window flags | +| hasFlags | Window 是否设置指定 flags 值 | +| notHasFlags | Window 是否没有设置指定 flags 值 | +| requestFeature | 启用 Window Extended Feature | +| hasFeature | Window 是否开启指定 Extended Feature | +| notHasFeature | Window 是否没有开启指定 Extended Feature | +| setSoftInputMode | 设置 Window 输入模式 | +| setStatusBarColor | 设置 StatusBar Color | +| getStatusBarColor | 获取 StatusBar Color | +| setNavigationBarColor | 设置 NavigationBar Color | +| getNavigationBarColor | 获取 NavigationBar Color | +| setNavigationBarDividerColor | 设置 NavigationBar Divider Color | +| getNavigationBarDividerColor | 获取 NavigationBar Divider Color | +| setWidthByParams | 设置 Dialog 宽度 | +| setHeightByParams | 设置 Dialog 高度 | +| setWidthHeightByParams | 设置 Dialog 宽度、高度 | +| setXByParams | 设置 Dialog X 轴坐标 | +| setYByParams | 设置 Dialog Y 轴坐标 | +| setXYByParams | 设置 Dialog X、Y 轴坐标 | +| setGravityByParams | 设置 Dialog Gravity | +| setDimAmountByParams | 设置 Dialog 透明度 | +| setWindowBrightness | 设置窗口亮度 | +| getWindowBrightness | 获取窗口亮度 | +| setKeyBoardSoftInputMode | 设置 Window 软键盘是否显示 | +| isKeepScreenOnFlag | 是否屏幕常亮 | +| setFlagKeepScreenOn | 设置屏幕常亮 | +| clearFlagKeepScreenOn | 移除屏幕常亮 | +| isSecureFlag | 是否禁止截屏 | +| setFlagSecure | 设置禁止截屏 | +| clearFlagSecure | 移除禁止截屏 | +| isFullScreenFlag | 是否屏幕为全屏 | +| setFlagFullScreen | 设置屏幕为全屏 | +| clearFlagFullScreen | 移除屏幕全屏 | +| isTranslucentStatusFlag | 是否透明状态栏 | +| setFlagTranslucentStatus | 设置透明状态栏 | +| clearFlagTranslucentStatus | 移除透明状态栏 | +| isDrawsSystemBarBackgroundsFlag | 是否系统状态栏背景绘制 | +| setFlagDrawsSystemBarBackgrounds | 设置系统状态栏背景绘制 | +| clearFlagDrawsSystemBarBackgrounds | 移除系统状态栏背景绘制 | +| isNoTitleFeature | 是否屏幕页面为无标题 | +| setFeatureNoTitle | 设置屏幕页面无标题 | +| setFlagFullScreenAndNoTitle | 设置屏幕为全屏无标题 | +| setSemiTransparentStatusBarColor | 设置高版本状态栏蒙层 | +| setStatusBarColorAndFlag | 设置状态栏颜色、高版本状态栏蒙层 | + + +## **`dev.utils.app.assist.exif`** + + +* **图片 EXIF 读写辅助类 ->** [ExifAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifAssist.java) + +| 方法 | 注释 | +| :- | :- | +| get | get | +| getByRequire | 创建可获取 EXIF 敏感信息辅助类 | +| requireOriginal | 获取 EXIF 敏感信息, 请求获取原始 Uri | +| requestPermission | 请求 ACCESS_MEDIA_LOCATION 权限并进行通知 | +| isSupportedMimeType | 判断是否支持读取的资源类型 | +| clone | 克隆图片 EXIF 读写信息 | +| getExif | 获取图片 EXIF 操作接口 | +| getExifError | 获取 EXIF 初始化异常信息 | +| isExifNull | 是否图片 EXIF 为 null | +| isExifNotNull | 是否图片 EXIF 不为 null | +| isExifError | 是否 EXIF 初始化异常 | +| getAttributeInt | 根据 TAG 获取对应值 | +| getAttributeDouble | 根据 TAG 获取对应值 | +| getAttribute | 根据 TAG 获取对应值 | +| getAttributeBytes | 根据 TAG 获取对应值 | +| getAttributeRange | 根据 TAG 获取对应值 | +| hasAttribute | 是否存在指定 TAG 值 | +| setAttribute | 设置对应 TAG 值 | +| saveAttributes | 将标签数据存储到图片中 ( 最终必须调用 ) | +| eraseAllExif | 擦除图像 Exif 信息 ( 全部 ) | +| eraseExifByList | 擦除图像 Exif 信息 ( 指定集合 ) | +| eraseExifByArray | 擦除图像 Exif 信息 ( 指定数组 ) | +| eraseExifLocation | 擦除图像所有 GPS 位置信息 | +| existLocation | 是否存在 GPS 位置信息 | +| getLatLong | 获取经纬度信息 | +| setLatLong | 设置经纬度信息 | +| getGpsInfo | 获取 GPS 信息 | +| setGpsInfo | 设置 GPS 信息 | +| getGpsDateTime | 获取 GPS 定位时间信息 | +| getAltitude | 获取海拔高度信息 ( 单位米 ) | +| setAltitude | 设置海拔高度信息 | +| hasThumbnail | 是否存在缩略图 | +| isThumbnailCompressed | 是否存在 JPEG 压缩缩略图 | +| getThumbnail | 获取 JPEG 压缩缩略图 | +| getThumbnailBytes | 获取 Exif 缩略图 | +| getThumbnailBitmap | 获取 Exif 缩略图 | +| getThumbnailRange | 获取缩略图数据偏移量位置和长度信息 | +| isFlipped | 当前图片是否翻转 | +| flipHorizontally | 进行水平翻转图片 | +| flipVertically | 进行垂直翻转图片 | +| getRotationDegrees | 获取图片旋转角度 | +| rotate | 将图片顺时针旋转给定度数 | +| resetOrientation | 重置图片方向为默认方向 | +| getAttributeByGroup | 获取 Exif 信息 ( ExifTag Group ) | +| getAttributeByList | 获取 Exif 信息 ( 指定集合 ) | +| getAttributeByArray | 获取 Exif 信息 ( 指定数组 ) | + + +* **图片 EXIF Tag Group 常量类 ->** [ExifTag.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifTag.java) + +| 方法 | 注释 | +| :- | :- | +| asList | 快捷创建 List 简化 add 操作 | + + +## **`dev.utils.app.assist.floating`** + + +* **悬浮窗通用代码 ->** [DevFloatingCommon.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingCommon.java) + +| 方法 | 注释 | +| :- | :- | +| update | 实时更新方法 | +| getView | getView | +| getEvent | getEvent | +| getListener | getListener | +| getDownTime | getDownTime | +| getPoint | getPoint | +| getFirstPoint | getFirstPoint | +| getDelayAssist | getDelayAssist | +| actionDown | 手势按下 | +| actionMove | 手势移动 | +| actionUp | 手势抬起 | +| onClick | 悬浮窗 View 点击事件 | +| onLongClick | 悬浮窗 View 长按事件 | +| getDiffTime | 获取时间差 ( 当前时间 - 触摸时间 ) | +| isValidTime | 是否有效间隔时间 | +| isValidClickByTime | 通过时间判断点击是否有效 | +| isValidLongClickByTime | 通过时间判断长按是否有效 | +| isValidEvent | 是否有效事件 ( 是否在小范围内移动 ) | +| isTouchInView | 判断触点是否落在该 View 上 | +| postLongClick | 开始校验长按 | +| callback | callback | + + +* **DevApp 悬浮窗边缘检测辅助类实现 ->** [DevFloatingEdgeIMPL.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingEdgeIMPL.java) + +| 方法 | 注释 | +| :- | :- | +| calculateEdge | calculateEdge | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| getMarginTop | 获取向上边距 | +| setMarginTop | 设置向上边距 | +| getMarginBottom | 获取向下边距 | +| setMarginBottom | 设置向下边距 | +| setStatusBarHeightMargin | 设置向上边距为状态栏高度 | +| setNavigationBarHeightMargin | 设置向下边距为底部导航栏高度 | + + +* **悬浮窗触摸事件接口实现 ->** [DevFloatingListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingListener.java) + +| 方法 | 注释 | +| :- | :- | +| getClickIntervalTime | 获取点击事件间隔时间 | +| setClickIntervalTime | 获取点击事件间隔时间 | +| getLongClickIntervalTime | 获取长按事件间隔时间 | +| setLongClickIntervalTime | 获取长按事件间隔时间 | + + +* **DevApp 悬浮窗触摸辅助类实现 ->** [DevFloatingTouchIMPL.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL.java) + +| 方法 | 注释 | +| :- | :- | +| onTouchEvent | onTouchEvent | +| getFloatingListener | 获取悬浮窗触摸事件接口 | +| setFloatingListener | 获取悬浮窗触摸事件接口 | +| getCommon | 获取悬浮窗通用代码 | + + +* **DevApp 悬浮窗触摸辅助类实现 ->** [DevFloatingTouchIMPL2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL2.java) + +| 方法 | 注释 | +| :- | :- | +| onTouchEvent | onTouchEvent | +| updateViewLayout | 更新 View Layout | +| getX | 获取 X 轴坐标 | +| setX | 设置 X 轴坐标 | +| getY | 获取 Y 轴坐标 | +| setY | 设置 Y 轴坐标 | +| getFloatingEdge | 获取悬浮窗边缘检测接口实现 | +| setFloatingEdge | 设置悬浮窗边缘检测接口实现 | +| getFloatingListener | 获取悬浮窗触摸事件接口 | +| setFloatingListener | 获取悬浮窗触摸事件接口 | +| getCommon | 获取悬浮窗通用代码 | + + +* **悬浮窗管理辅助类 ( 需权限 ) ->** [FloatingWindowManagerAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getIMPL | 获取悬浮窗管理辅助类实现 | +| getWindowManager | 获取 WindowManager | +| getLayoutParams | 获取 Window LayoutParams | +| addView | 添加悬浮 View | +| removeView | 移除悬浮 View | +| updateViewLayout | 更新 View Layout | +| canDrawOverlays | 是否存在悬浮窗权限 | +| checkOverlayPermission | 检测是否存在悬浮窗权限并跳转 | +| isOverlayRequestCode | 是否悬浮窗请求回调 code | + + +* **悬浮窗管理辅助类 ( 无需权限依赖 Activity ) ->** [FloatingWindowManagerAssist2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist2.java) + +| 方法 | 注释 | +| :- | :- | +| removeFloatingView | 移除悬浮窗 View | +| addFloatingView | 添加悬浮窗 View | +| removeAllFloatingView | 移除所有悬浮窗 View | +| updateViewLayout | 更新悬浮窗 View Layout | +| isNeedsAdd | 是否处理悬浮 View 添加操作 | +| setNeedsAdd | 设置是否处理悬浮 View 添加操作 | + + +* **悬浮窗辅助类接口 ->** [IFloatingActivity.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingActivity.java) + +| 方法 | 注释 | +| :- | :- | +| getAttachActivity | 获取悬浮窗依附的 Activity | +| getMapFloatingKey | 获取悬浮窗 Map Key | +| getMapFloatingView | 获取悬浮窗 Map Value View | +| getMapFloatingViewLayoutParams | 获取悬浮窗 View LayoutParams | + + +* **悬浮窗边缘检测接口 ->** [IFloatingEdge.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingEdge.java) + +| 方法 | 注释 | +| :- | :- | +| calculateEdge | 计算悬浮窗边缘检测坐标 | + + +* **悬浮窗触摸事件接口 ->** [IFloatingListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingListener.java) + +| 方法 | 注释 | +| :- | :- | +| onClick | 悬浮窗 View 点击事件 | +| onLongClick | 悬浮窗 View 长按事件 | +| getClickIntervalTime | 获取点击事件间隔时间 | +| setClickIntervalTime | 获取点击事件间隔时间 | +| getLongClickIntervalTime | 获取长按事件间隔时间 | +| setLongClickIntervalTime | 获取长按事件间隔时间 | + + +* **悬浮窗操作辅助类接口 ->** [IFloatingOperate.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingOperate.java) + +| 方法 | 注释 | +| :- | :- | +| removeFloatingView | 移除悬浮窗 View | +| addFloatingView | 添加悬浮窗 View | +| removeAllFloatingView | 移除所有悬浮窗 View | +| updateViewLayout | 更新悬浮窗 View Layout | +| isNeedsAdd | 是否处理悬浮 View 添加操作 | +| setNeedsAdd | 设置是否处理悬浮 View 添加操作 | + + +* **悬浮窗触摸辅助类接口 ->** [IFloatingTouch.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingTouch.java) + +| 方法 | 注释 | +| :- | :- | +| onTouchEvent | 悬浮窗 View 触摸事件 | +| updateViewLayout | 更新 View Layout | +| getFloatingListener | 获取悬浮窗触摸事件接口 | +| setFloatingListener | 获取悬浮窗触摸事件接口 | + + +## **`dev.utils.app.assist.lifecycle`** + + +* **ActivityLifecycleCallbacks 抽象类 ->** [AbstractActivityLifecycle.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/AbstractActivityLifecycle.java) + +| 方法 | 注释 | +| :- | :- | +| onActivityCreated | onActivityCreated | +| onActivityStarted | onActivityStarted | +| onActivityResumed | onActivityResumed | +| onActivityPaused | onActivityPaused | +| onActivityStopped | onActivityStopped | +| onActivitySaveInstanceState | onActivitySaveInstanceState | +| onActivityDestroyed | onActivityDestroyed | + + +* **Activity 生命周期辅助类 ->** [ActivityLifecycleAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getActivityLifecycleGet | 获取 Activity 生命周期 相关信息获取接口类 | +| getActivityLifecycleNotify | 获取 Activity 生命周期 事件监听接口类 | +| getTopActivity | 获取 Top Activity | +| setActivityLifecycleFilter | 设置 Activity 生命周期 过滤判断接口 | +| setAbstractActivityLifecycle | 设置 ActivityLifecycle 监听回调 | +| registerActivityLifecycleCallbacks | 注册绑定 Activity 生命周期事件处理 | +| unregisterActivityLifecycleCallbacks | 解除注册 Activity 生命周期事件处理 | + + +* **Activity 生命周期 过滤判断接口 ->** [ActivityLifecycleFilter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleFilter.java) + +| 方法 | 注释 | +| :- | :- | +| filter | 判断是否过滤该类 ( 不进行添加等操作 ) | + + +* **Activity 生命周期 相关信息获取接口 ->** [ActivityLifecycleGet.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleGet.java) + +| 方法 | 注释 | +| :- | :- | +| getTopActivity | 获取最顶部 ( 当前或最后一个显示 ) Activity | +| isTopActivity | 判断某个 Activity 是否 Top Activity | +| isBackground | 判断应用是否在后台 ( 不可见 ) | +| getActivityCount | 获取 Activity 总数 | + + +* **Activity 生命周期 通知接口 ->** [ActivityLifecycleNotify.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleNotify.java) + +| 方法 | 注释 | +| :- | :- | +| addOnAppStatusChangedListener | 添加 APP 状态改变事件监听 | +| removeOnAppStatusChangedListener | 移除 APP 状态改变事件监听 | +| removeAllOnAppStatusChangedListener | 移除全部 APP 状态改变事件监听 | +| addOnActivityDestroyedListener | 添加 Activity 销毁通知事件 | +| removeOnActivityDestroyedListener | 移除 Activity 销毁通知事件 | +| removeAllOnActivityDestroyedListener | 移除全部 Activity 销毁通知事件 | + + +* **Activity 销毁事件 ->** [OnActivityDestroyedListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnActivityDestroyedListener.java) + +| 方法 | 注释 | +| :- | :- | +| onActivityDestroyed | Activity 销毁通知 | + + +* **APP 状态改变事件 ->** [OnAppStatusChangedListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnAppStatusChangedListener.java) + +| 方法 | 注释 | +| :- | :- | +| onForeground | 切换到前台 | +| onBackground | 切换到后台 | + + +## **`dev.utils.app.assist.record`** + + +* **App 日志记录插入信息 ->** [AppRecordInsert.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/record/AppRecordInsert.java) + +| 方法 | 注释 | +| :- | :- | +| setFileInfo | setFileInfo | +| getFileInfo | getFileInfo | + + +## **`dev.utils.app.assist.url`** + + +* **Android Api 实现 Url 解析器 ->** [AndroidUrlParser.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/assist/url/AndroidUrlParser.java) + +| 方法 | 注释 | +| :- | :- | +| reset | reset | +| setUrl | setUrl | +| getUrl | getUrl | +| getUrlByPrefix | getUrlByPrefix | +| getUrlByParams | getUrlByParams | +| getUrlParams | getUrlParams | +| getUrlParamsDecode | getUrlParamsDecode | +| isConvertMap | isConvertMap | +| setConvertMap | setConvertMap | + + +## **`dev.utils.app.cache`** + + +* **缓存类 ->** [DevCache.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java) + +| 方法 | 注释 | +| :- | :- | +| newCache | 获取 DevCache | +| getCachePath | 获取缓存地址 | +| remove | 移除数据 | +| removeForKeys | 删除 Key[] 配置、数据文件 | +| contains | 是否存在 key | +| isDue | 判断某个 key 是否过期 | +| clear | 清除全部数据 | +| clearDue | 清除过期数据 | +| clearType | 清除某个类型的全部数据 | +| getItemByKey | 通过 Key 获取 Item | +| getKeys | 获取有效 Key 集合 | +| getPermanentKeys | 获取永久有效 Key 集合 | +| getCount | 获取有效 Key 数量 | +| getSize | 获取有效 Key 占用总大小 | +| put | 保存 int 类型的数据 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getBytes | 获取 byte[] 类型的数据 | +| getBitmap | 获取 Bitmap 类型的数据 | +| getDrawable | 获取 Drawable 类型的数据 | +| getSerializable | 获取 Serializable 类型的数据 | +| getParcelable | 获取 Parcelable 类型的数据 | +| getJSONObject | 获取 JSONObject 类型的数据 | +| getJSONArray | 获取 JSONArray 类型的数据 | +| getKey | 获取存储 Key | +| isPermanent | 是否永久有效 | +| getType | 获取数据存储类型 | +| getSaveTime | 获取保存时间 ( 毫秒 ) | +| getValidTime | 获取有效期 ( 毫秒 ) | +| setType | setType | +| setSaveTime | setSaveTime | +| setValidTime | setValidTime | +| isInt | isInt | +| isLong | isLong | +| isFloat | isFloat | +| isDouble | isDouble | +| isBoolean | isBoolean | +| isString | isString | +| isBytes | isBytes | +| isBitmap | isBitmap | +| isDrawable | isDrawable | +| isSerializable | isSerializable | +| isParcelable | isParcelable | +| isJSONObject | isJSONObject | +| isJSONArray | isJSONArray | + + +## **`dev.utils.app.camera`** + + +## **`dev.utils.app.camera.camera1`** + + +* **摄像头自动获取焦点辅助类 ->** [AutoFocusAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/AutoFocusAssist.java) + +| 方法 | 注释 | +| :- | :- | +| setFocusModes | 设置对焦模式 | +| isAutoFocus | 是否允许自动对焦 | +| setAutoFocus | 设置是否开启自动对焦 | +| start | 开始对焦 | +| stop | 停止对焦 | + + +* **摄像头辅助类 ->** [CameraAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraAssist.java) + +| 方法 | 注释 | +| :- | :- | +| openDriver | 打开摄像头程序 | +| closeDriver | 关闭摄像头程序 | +| startPreview | 开始将 Camera 画面预览到手机上 | +| stopPreview | 停止 Camera 画面预览 | +| getCameraResolution | 获取相机分辨率 | +| getPreviewSize | 获取预览分辨率 | +| getCameraSizeAssist | 获取 Camera.Size 计算辅助类 | +| getCamera | 获取摄像头 | +| setCamera | 设置摄像头 | +| setPreviewNotify | 设置预览回调 | +| setAutoFocus | 设置是否开启自动对焦 | +| isPreviewing | 是否预览中 | +| setAutoInterval | 设置自动对焦时间间隔 | +| isFlashlightEnable | 是否支持手机闪光灯 | +| setFlashlightOn | 打开闪光灯 | +| setFlashlightOff | 关闭闪光灯 | +| isFlashlightOn | 是否打开闪光灯 | + + +* **摄像头 ( 预览、输出大小 ) 辅助类 ->** [CameraSizeAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraSizeAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getCamera | 获取摄像头 | +| setPreviewSize | 设置预览大小 | +| getPreviewSize | 根据手机支持的预览分辨率计算, 设置预览尺寸 | +| setPictureSize | 设置拍照图片大小 | +| getPictureSize | 根据手机支持的拍照分辨率计算 | +| getVideoSize | 根据手机支持的视频录制分辨率计算 | + + +* **摄像头相关工具类 ->** [CameraUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSupportReverse | 判断是否支持反转摄像头 ( 是否存在前置摄像头 ) | +| checkCameraFacing | 检查是否有指定的摄像头 | +| isFrontCamera | 判断是否使用前置摄像头 | +| isBackCamera | 判断是否使用后置摄像头 | +| isUseCameraFacing | 判断使用的摄像头 | +| freeCameraResource | 释放摄像头资源 | +| initCamera | 初始化摄像头 | +| open | 打开摄像头 | + + +* **手电筒工具类 ->** [FlashlightUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/FlashlightUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 FlashlightUtils 实例 | +| register | 注册摄像头 | +| unregister | 注销摄像头 | +| isFlashlightEnable | 是否支持手机闪光灯 | +| setFlashlightOn | 打开闪光灯 | +| setFlashlightOff | 关闭闪光灯 | +| isFlashlightOn | 是否打开闪光灯 | + + +## **`dev.utils.app.helper`** + + +* **基础 Helper 通用实现类 ->** [BaseHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/BaseHelper.java) + +| 方法 | 注释 | +| :- | :- | +| devHelper | 获取 DevHelper | +| quickHelper | 获取 QuickHelper | +| viewHelper | 获取 ViewHelper | +| flowHelper | 获取 FlowHelper | +| flow | 执行 Action 流方法 | +| flowValue | 流式返回传入值 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | + + +## **`dev.utils.app.helper.dev`** + + +* **Dev 工具类链式调用 Helper 类 ->** [DevHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/dev/DevHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取单例 DevHelper | +| flow | 执行 Action 流方法 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | +| setAnimationRepeat | 设置动画重复处理 | +| setAnimationListener | 设置动画事件 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| recycle | Bitmap 通知回收 | +| startTimer | 运行定时器 | +| stopTimer | 关闭定时器 | +| recycleTimer | 回收定时器资源 | +| closeAllTimer | 关闭全部定时器 | +| closeAllNotRunningTimer | 关闭所有未运行的定时器 | +| closeAllInfiniteTimer | 关闭所有无限循环的定时器 | +| closeAllTagTimer | 关闭所有对应 TAG 定时器 | +| closeAllUUIDTimer | 关闭所有对应 UUID 定时器 | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| copyText | 复制文本到剪贴板 | +| copyUri | 复制 URI 到剪贴板 | +| copyIntent | 复制意图到剪贴板 | +| setDialogStatusBarColor | 设置 Dialog 状态栏颜色 | +| setDialogSemiTransparentStatusBarColor | 设置 Dialog 高版本状态栏蒙层 | +| setDialogStatusBarColorAndFlag | 设置 Dialog 状态栏颜色、高版本状态栏蒙层 | +| setDialogAttributes | 设置 Dialog Window LayoutParams | +| setDialogWidth | 设置 Dialog 宽度 | +| setDialogHeight | 设置 Dialog 高度 | +| setDialogWidthHeight | 设置 Dialog 宽度、高度 | +| setDialogX | 设置 Dialog X 轴坐标 | +| setDialogY | 设置 Dialog Y 轴坐标 | +| setDialogXY | 设置 Dialog X、Y 轴坐标 | +| setDialogGravity | 设置 Dialog Gravity | +| setDialogDimAmount | 设置 Dialog 透明度 | +| setDialogCancelable | 设置是否允许返回键关闭 | +| setDialogCanceledOnTouchOutside | 设置是否允许点击其他地方自动关闭 | +| setDialogCancelableAndTouchOutside | 设置是否允许 返回键关闭、点击其他地方自动关闭 | +| showDialog | 显示 Dialog | +| closeDialogs | 关闭多个 Dialog | +| closePopupWindows | 关闭多个 PopupWindow | +| autoCloseDialog | 自动关闭 dialog | +| autoClosePopupWindow | 自动关闭 PopupWindow | +| setSoftInputMode | 设置 Window 软键盘是否显示 | +| judgeView | 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 | +| registerSoftInputChangedListener | 注册软键盘改变监听 | +| registerSoftInputChangedListener2 | 注册软键盘改变监听 | +| fixSoftInputLeaks | 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 | +| toggleKeyboard | 自动切换键盘状态, 如果键盘显示则隐藏反之显示 | +| openKeyboard | 打开软键盘 | +| openKeyboardDelay | 延时打开软键盘 | +| openKeyboardByFocus | 打开软键盘 | +| closeKeyboard | 关闭软键盘 | +| closeKeyBoardSpecial | 关闭软键盘 | +| closeKeyBoardSpecialDelay | 延时关闭软键盘 | +| closeKeyboardDelay | 延时关闭软键盘 | +| applyLanguage | 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) | +| cancelAllNotification | 移除通知 ( 移除所有通知 ) | +| cancelNotification | 移除通知 ( 移除标记为 id 的通知 ) | +| notifyNotification | 进行通知 | +| createNotificationChannel | 创建 NotificationChannel | +| dial | 跳至拨号界面 | +| call | 拨打电话 | +| sendSms | 跳至发送短信界面 | +| sendSmsSilent | 发送短信 | +| setBright | 设置屏幕常亮 | +| setWindowSecure | 设置禁止截屏 | +| setFullScreen | 设置屏幕为全屏 | +| setFullScreenNoTitle | 设置屏幕为全屏无标题 | +| setLandscape | 设置屏幕为横屏 | +| setPortrait | 设置屏幕为竖屏 | +| toggleScreenOrientation | 切换屏幕方向 | +| setSleepDuration | 设置进入休眠时长 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | +| vibrate | 震动 | +| cancelVibrate | 取消震动 | +| getWidthHeightExact | 获取 View 宽高 ( 准确 ) | +| getWidthHeightExact2 | 获取 View 宽高 ( 准确 ) | +| measureView | 测量 View | +| closeIO | 关闭 IO | +| closeIOQuietly | 安静关闭 IO | +| flush | 将缓冲区数据输出 | +| flushQuietly | 安静将缓冲区数据输出 | +| flushCloseIO | 将缓冲区数据输出并关闭流 | +| flushCloseIOQuietly | 安静将缓冲区数据输出并关闭流 | +| setSystemUiVisibility | 设置 Window System UI 可见性 | +| setSystemUiVisibilityByAdd | 设置 Window System UI 可见性 ( 原来基础上进行追加 ) | +| setSystemUiVisibilityByClear | 设置 Window System UI 可见性 ( 原来基础上进行清除 ) | +| setAttributes | 设置 Window LayoutParams | +| refreshSelfAttributes | 刷新自身 Window LayoutParams | +| clearFlags | 清除 Window flags | +| addFlags | 添加 Window flags | +| setFlags | 设置 Window flags | +| requestFeature | 启用 Window Extended Feature | +| setStatusBarColor | 设置 StatusBar Color | +| setNavigationBarColor | 设置 NavigationBar Color | +| setNavigationBarDividerColor | 设置 NavigationBar Divider Color | +| setWidthByParams | 设置 Dialog 宽度 | +| setHeightByParams | 设置 Dialog 高度 | +| setWidthHeightByParams | 设置 Dialog 宽度、高度 | +| setXByParams | 设置 Dialog X 轴坐标 | +| setYByParams | 设置 Dialog Y 轴坐标 | +| setXYByParams | 设置 Dialog X、Y 轴坐标 | +| setGravityByParams | 设置 Dialog Gravity | +| setDimAmountByParams | 设置 Dialog 透明度 | +| setWindowBrightness | 设置窗口亮度 | +| setKeyBoardSoftInputMode | 设置 Window 软键盘是否显示 | +| setFlagKeepScreenOn | 设置屏幕常亮 | +| clearFlagKeepScreenOn | 移除屏幕常亮 | +| setFlagSecure | 设置禁止截屏 | +| clearFlagSecure | 移除禁止截屏 | +| setFlagFullScreen | 设置屏幕为全屏 | +| clearFlagFullScreen | 移除屏幕全屏 | +| setFlagTranslucentStatus | 设置透明状态栏 | +| clearFlagTranslucentStatus | 移除透明状态栏 | +| setFlagDrawsSystemBarBackgrounds | 设置系统状态栏背景绘制 | +| clearFlagDrawsSystemBarBackgrounds | 移除系统状态栏背景绘制 | +| setFeatureNoTitle | 设置屏幕页面无标题 | +| setFlagFullScreenAndNoTitle | 设置屏幕为全屏无标题 | +| setSemiTransparentStatusBarColor | 设置高版本状态栏蒙层 | +| setStatusBarColorAndFlag | 设置状态栏颜色、高版本状态栏蒙层 | + + +* **DevHelper 接口 ->** [IHelperByDev.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/dev/IHelperByDev.java) + +| 方法 | 注释 | +| :- | :- | +| setAnimationRepeat | 设置动画重复处理 | +| setAnimationListener | 设置动画事件 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| recycle | Bitmap 通知回收 | +| startTimer | 运行定时器 | +| stopTimer | 关闭定时器 | +| recycleTimer | 回收定时器资源 | +| closeAllTimer | 关闭全部定时器 | +| closeAllNotRunningTimer | 关闭所有未运行的定时器 | +| closeAllInfiniteTimer | 关闭所有无限循环的定时器 | +| closeAllTagTimer | 关闭所有对应 TAG 定时器 | +| closeAllUUIDTimer | 关闭所有对应 UUID 定时器 | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| copyText | 复制文本到剪贴板 | +| copyUri | 复制 URI 到剪贴板 | +| copyIntent | 复制意图到剪贴板 | +| setDialogStatusBarColor | 设置 Dialog 状态栏颜色 | +| setDialogSemiTransparentStatusBarColor | 设置 Dialog 高版本状态栏蒙层 | +| setDialogStatusBarColorAndFlag | 设置 Dialog 状态栏颜色、高版本状态栏蒙层 | +| setDialogAttributes | 设置 Dialog Window LayoutParams | +| setDialogWidth | 设置 Dialog 宽度 | +| setDialogHeight | 设置 Dialog 高度 | +| setDialogWidthHeight | 设置 Dialog 宽度、高度 | +| setDialogX | 设置 Dialog X 轴坐标 | +| setDialogY | 设置 Dialog Y 轴坐标 | +| setDialogXY | 设置 Dialog X、Y 轴坐标 | +| setDialogGravity | 设置 Dialog Gravity | +| setDialogDimAmount | 设置 Dialog 透明度 | +| setDialogCancelable | 设置是否允许返回键关闭 | +| setDialogCanceledOnTouchOutside | 设置是否允许点击其他地方自动关闭 | +| setDialogCancelableAndTouchOutside | 设置是否允许 返回键关闭、点击其他地方自动关闭 | +| showDialog | 显示 Dialog | +| closeDialogs | 关闭多个 Dialog | +| closePopupWindows | 关闭多个 PopupWindow | +| autoCloseDialog | 自动关闭 dialog | +| autoClosePopupWindow | 自动关闭 PopupWindow | +| setSoftInputMode | 设置 Window 软键盘是否显示 | +| judgeView | 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 | +| registerSoftInputChangedListener | 注册软键盘改变监听 | +| registerSoftInputChangedListener2 | 注册软键盘改变监听 | +| fixSoftInputLeaks | 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 | +| toggleKeyboard | 自动切换键盘状态, 如果键盘显示则隐藏反之显示 | +| openKeyboard | 打开软键盘 | +| openKeyboardDelay | 延时打开软键盘 | +| openKeyboardByFocus | 打开软键盘 | +| closeKeyboard | 关闭软键盘 | +| closeKeyBoardSpecial | 关闭软键盘 | +| closeKeyBoardSpecialDelay | 延时关闭软键盘 | +| closeKeyboardDelay | 延时关闭软键盘 | +| applyLanguage | 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) | +| cancelAllNotification | 移除通知 ( 移除所有通知 ) | +| cancelNotification | 移除通知 ( 移除标记为 id 的通知 ) | +| notifyNotification | 进行通知 | +| createNotificationChannel | 创建 NotificationChannel | +| dial | 跳至拨号界面 | +| call | 拨打电话 | +| sendSms | 跳至发送短信界面 | +| sendSmsSilent | 发送短信 | +| setBright | 设置屏幕常亮 | +| setWindowSecure | 设置禁止截屏 | +| setFullScreen | 设置屏幕为全屏 | +| setFullScreenNoTitle | 设置屏幕为全屏无标题 | +| setLandscape | 设置屏幕为横屏 | +| setPortrait | 设置屏幕为竖屏 | +| toggleScreenOrientation | 切换屏幕方向 | +| setSleepDuration | 设置进入休眠时长 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | +| vibrate | 震动 | +| cancelVibrate | 取消震动 | +| getWidthHeightExact | 获取 View 宽高 ( 准确 ) | +| getWidthHeightExact2 | 获取 View 宽高 ( 准确 ) | +| measureView | 测量 View | +| closeIO | 关闭 IO | +| closeIOQuietly | 安静关闭 IO | +| flush | 将缓冲区数据输出 | +| flushQuietly | 安静将缓冲区数据输出 | +| flushCloseIO | 将缓冲区数据输出并关闭流 | +| flushCloseIOQuietly | 安静将缓冲区数据输出并关闭流 | +| setSystemUiVisibility | 设置 Window System UI 可见性 | +| setSystemUiVisibilityByAdd | 设置 Window System UI 可见性 ( 原来基础上进行追加 ) | +| setSystemUiVisibilityByClear | 设置 Window System UI 可见性 ( 原来基础上进行清除 ) | +| setAttributes | 设置 Window LayoutParams | +| refreshSelfAttributes | 刷新自身 Window LayoutParams | +| clearFlags | 清除 Window flags | +| addFlags | 添加 Window flags | +| setFlags | 设置 Window flags | +| requestFeature | 启用 Window Extended Feature | +| setStatusBarColor | 设置 StatusBar Color | +| setNavigationBarColor | 设置 NavigationBar Color | +| setNavigationBarDividerColor | 设置 NavigationBar Divider Color | +| setWidthByParams | 设置 Dialog 宽度 | +| setHeightByParams | 设置 Dialog 高度 | +| setWidthHeightByParams | 设置 Dialog 宽度、高度 | +| setXByParams | 设置 Dialog X 轴坐标 | +| setYByParams | 设置 Dialog Y 轴坐标 | +| setXYByParams | 设置 Dialog X、Y 轴坐标 | +| setGravityByParams | 设置 Dialog Gravity | +| setDimAmountByParams | 设置 Dialog 透明度 | +| setWindowBrightness | 设置窗口亮度 | +| setKeyBoardSoftInputMode | 设置 Window 软键盘是否显示 | +| setFlagKeepScreenOn | 设置屏幕常亮 | +| clearFlagKeepScreenOn | 移除屏幕常亮 | +| setFlagSecure | 设置禁止截屏 | +| clearFlagSecure | 移除禁止截屏 | +| setFlagFullScreen | 设置屏幕为全屏 | +| clearFlagFullScreen | 移除屏幕全屏 | +| setFlagTranslucentStatus | 设置透明状态栏 | +| clearFlagTranslucentStatus | 移除透明状态栏 | +| setFlagDrawsSystemBarBackgrounds | 设置系统状态栏背景绘制 | +| clearFlagDrawsSystemBarBackgrounds | 移除系统状态栏背景绘制 | +| setFeatureNoTitle | 设置屏幕页面无标题 | +| setFlagFullScreenAndNoTitle | 设置屏幕为全屏无标题 | +| setSemiTransparentStatusBarColor | 设置高版本状态栏蒙层 | +| setStatusBarColorAndFlag | 设置状态栏颜色、高版本状态栏蒙层 | + + +## **`dev.utils.app.helper.flow`** + + +* **流式 ( 链式 ) 连接 Helper 类 ->** [FlowHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/flow/FlowHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取单例 FlowHelper | +| flow | 执行 Action 流方法 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | +| action | 操作方法 | + + +## **`dev.utils.app.helper.quick`** + + +* **QuickHelper 接口 ->** [IHelperByQuick.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/quick/IHelperByQuick.java) + +| 方法 | 注释 | +| :- | :- | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| setId | 设置 View Id | +| setClipChildren | 设置是否限制子 View 在其边界内绘制 | +| removeAllViews | 移除全部子 View | +| addView | 添加 View | +| setLayoutParams | 设置 View LayoutParams | +| setWidthHeight | 设置 View[] 宽度、高度 | +| setWeight | 设置 View weight 权重 | +| setWidth | 设置 View 宽度 | +| setHeight | 设置 View 高度 | +| setMinimumWidth | 设置 View 最小宽度 | +| setMinimumHeight | 设置 View 最小高度 | +| setAlpha | 设置 View 透明度 | +| setTag | 设置 View TAG | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| setHorizontalScrollBarEnabled | 设置是否绘制横向滚动条 | +| setVerticalScrollBarEnabled | 设置是否绘制垂直滚动条 | +| setScrollContainer | 设置 View 滚动效应 | +| setNextFocusForwardId | 设置下一个获取焦点的 View id | +| setNextFocusDownId | 设置向下移动焦点时, 下一个获取焦点的 View id | +| setNextFocusLeftId | 设置向左移动焦点时, 下一个获取焦点的 View id | +| setNextFocusRightId | 设置向右移动焦点时, 下一个获取焦点的 View id | +| setNextFocusUpId | 设置向上移动焦点时, 下一个获取焦点的 View id | +| setRotation | 设置 View 旋转度数 | +| setRotationX | 设置 View 水平旋转度数 | +| setRotationY | 设置 View 竖直旋转度数 | +| setScaleX | 设置 View 水平方向缩放比例 | +| setScaleY | 设置 View 竖直方向缩放比例 | +| setTextAlignment | 设置文本的显示方式 | +| setTextDirection | 设置文本的显示方向 | +| setPivotX | 设置水平方向偏转量 | +| setPivotY | 设置竖直方向偏转量 | +| setTranslationX | 设置水平方向的移动距离 | +| setTranslationY | 设置竖直方向的移动距离 | +| setX | 设置 X 轴位置 | +| setY | 设置 Y 轴位置 | +| setLayerType | 设置 View 硬件加速类型 | +| requestLayout | 请求重新对 View 布局 | +| requestFocus | View 请求获取焦点 | +| clearFocus | View 清除焦点 | +| setFocusableInTouchMode | 设置 View 是否在触摸模式下获得焦点 | +| setFocusable | 设置 View 是否可以获取焦点 | +| toggleFocusable | 切换获取焦点状态 | +| setSelected | 设置 View 是否选中 | +| toggleSelected | 切换选中状态 | +| setEnabled | 设置 View 是否启用 | +| toggleEnabled | 切换 View 是否启用状态 | +| setClickable | 设置 View 是否可以点击 | +| toggleClickable | 切换 View 是否可以点击状态 | +| setLongClickable | 设置 View 是否可以长按 | +| toggleLongClickable | 切换 View 是否可以长按状态 | +| setVisibilitys | 设置 View 显示的状态 | +| setVisibilityINs | 设置 View 显示的状态 | +| toggleVisibilitys | 切换 View 显示的状态 | +| reverseVisibilitys | 反转 View 显示的状态 | +| toggleViews | 切换 View 状态 | +| removeSelfFromParent | 把自身从父 View 中移除 | +| requestLayoutParent | View 请求更新 | +| measureView | 测量 View | +| setLayoutGravity | 设置 View Layout Gravity | +| setMarginLeft | 设置 View Left Margin | +| setMarginTop | 设置 View Top Margin | +| setMarginRight | 设置 View Right Margin | +| setMarginBottom | 设置 View Bottom Margin | +| setMargin | 设置 Margin 边距 | +| setPaddingLeft | 设置 View Left Padding | +| setPaddingTop | 设置 View Top Padding | +| setPaddingRight | 设置 View Right Padding | +| setPaddingBottom | 设置 View Bottom Padding | +| setPadding | 设置 Padding 边距 | +| addRules | 设置多个 RelativeLayout View 布局规则 | +| removeRules | 移除多个 RelativeLayout View 布局规则 | +| setAnimation | 设置动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| setBackground | 设置背景图片 | +| setBackgroundColor | 设置背景颜色 | +| setBackgroundResource | 设置背景资源 | +| setBackgroundTintList | 设置背景着色颜色 | +| setBackgroundTintMode | 设置背景着色模式 | +| setForeground | 设置前景图片 | +| setForegroundGravity | 设置前景重心 | +| setForegroundTintList | 设置前景着色颜色 | +| setForegroundTintMode | 设置前景着色模式 | +| setColorFilter | View 着色处理 | +| setProgressDrawable | 设置 ProgressBar 进度条样式 | +| setBarProgress | 设置 ProgressBar 进度值 | +| setBarMax | 设置 ProgressBar 最大值 | +| setBarValue | 设置 ProgressBar 最大值 | +| smoothScrollToPosition | 滑动到指定索引 ( 有滚动过程 ) | +| scrollToPosition | 滑动到指定索引 ( 无滚动过程 ) | +| smoothScrollToTop | 滑动到顶部 ( 有滚动过程 ) | +| scrollToTop | 滑动到顶部 ( 无滚动过程 ) | +| smoothScrollToBottom | 滑动到底部 ( 有滚动过程 ) | +| scrollToBottom | 滑动到底部 ( 无滚动过程 ) | +| smoothScrollTo | 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) | +| smoothScrollBy | 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) | +| fullScroll | 滚动方向 ( 有滚动过程 ) | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setAdjustViewBounds | 设置 ImageView 是否保持宽高比 | +| setMaxHeight | 设置 ImageView 最大高度 | +| setMaxWidth | 设置 ImageView 最大宽度 | +| setImageLevel | 设置 ImageView Level | +| setImageBitmap | 设置 ImageView Bitmap | +| setImageDrawable | 设置 ImageView Drawable | +| setImageResource | 设置 ImageView 资源 | +| setImageMatrix | 设置 ImageView Matrix | +| setImageTintList | 设置 ImageView 着色颜色 | +| setImageTintMode | 设置 ImageView 着色模式 | +| setScaleType | 设置 ImageView 缩放类型 | +| setBackgroundResources | 设置 View 图片资源 | +| setImageResources | 设置 View 图片资源 | +| setImageBitmaps | 设置 View Bitmap | +| setImageDrawables | 设置 View Drawable | +| setScaleTypes | 设置 View 缩放模式 | +| setText | 设置文本 | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| setInputType | 设置输入类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| setTransformationMethod | 设置文本视图显示转换 | +| insert | 追加内容 ( 当前光标位置追加 ) | +| setCursorVisible | 设置是否显示光标 | +| setTextCursorDrawable | 设置光标 | +| setSelectionToTop | 设置光标在第一位 | +| setSelectionToBottom | 设置光标在最后一位 | +| setSelection | 设置光标位置 | +| addTextChangedListener | 添加输入监听事件 | +| removeTextChangedListener | 移除输入监听事件 | +| setKeyListener | 设置 KeyListener | +| setHint | 设置 Hint 文本 | +| setHintTextColors | 设置多个 TextView Hint 字体颜色 | +| setTextColors | 设置多个 TextView 字体颜色 | +| setHtmlTexts | 设置多个 TextView Html 内容 | +| setTypeface | 设置字体 | +| setTextSizeByPx | 设置字体大小 ( px 像素 ) | +| setTextSizeBySp | 设置字体大小 ( sp 缩放像素 ) | +| setTextSizeByDp | 设置字体大小 ( dp 与设备无关的像素 ) | +| setTextSizeByIn | 设置字体大小 ( inches 英寸 ) | +| setTextSize | 设置字体大小 | +| clearFlags | 清空 flags | +| setPaintFlags | 设置 TextView flags | +| setAntiAliasFlag | 设置 TextView 抗锯齿 flags | +| setBold | 设置 TextView 是否加粗 | +| setUnderlineText | 设置下划线 | +| setStrikeThruText | 设置中划线 | +| setLetterSpacing | 设置文字水平间距 | +| setLineSpacing | 设置文字行间距 ( 行高 ) | +| setLineSpacingAndMultiplier | 设置文字行间距 ( 行高 ) 、行间距倍数 | +| setTextScaleX | 设置字体水平方向的缩放 | +| setIncludeFontPadding | 设置是否保留字体留白间隙区域 | +| setLines | 设置行数 | +| setMaxLines | 设置最大行数 | +| setMinLines | 设置最小行数 | +| setMaxEms | 设置最大字符宽度限制 | +| setMinEms | 设置最小字符宽度限制 | +| setEms | 设置指定字符宽度 | +| setEllipsize | 设置 Ellipsize 效果 | +| setAutoLinkMask | 设置自动识别文本链接 | +| setAllCaps | 设置文本全为大写 | +| setGravity | 设置 Gravity | +| setCompoundDrawablePadding | 设置 CompoundDrawables Padding | +| setCompoundDrawablesByLeft | 设置 Left CompoundDrawables | +| setCompoundDrawablesByTop | 设置 Top CompoundDrawables | +| setCompoundDrawablesByRight | 设置 Right CompoundDrawables | +| setCompoundDrawablesByBottom | 设置 Bottom CompoundDrawables | +| setCompoundDrawables | 设置 CompoundDrawables | +| setCompoundDrawablesWithIntrinsicBoundsByLeft | 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByTop | 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByRight | 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByBottom | 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBounds | 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setAutoSizeTextTypeWithDefaults | 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 | +| setAutoSizeTextTypeUniformWithConfiguration | 设置 TextView 自动调整字体大小配置 | +| setAutoSizeTextTypeUniformWithPresetSizes | 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM | +| setLayoutManager | 设置 RecyclerView LayoutManager | +| setSpanCount | 设置 GridLayoutManager SpanCount | +| setOrientation | 设置 RecyclerView Orientation | +| setAdapter | 设置 RecyclerView Adapter | +| notifyItemRemoved | RecyclerView notifyItemRemoved | +| notifyItemInserted | RecyclerView notifyItemInserted | +| notifyItemMoved | RecyclerView notifyItemMoved | +| notifyDataSetChanged | RecyclerView notifyDataSetChanged | +| attachLinearSnapHelper | 设置 RecyclerView LinearSnapHelper | +| attachPagerSnapHelper | 设置 RecyclerView PagerSnapHelper | +| addItemDecoration | 添加 RecyclerView ItemDecoration | +| removeItemDecoration | 移除 RecyclerView ItemDecoration | +| removeItemDecorationAt | 移除 RecyclerView ItemDecoration | +| removeAllItemDecoration | 移除 RecyclerView 全部 ItemDecoration | +| setOnScrollListener | 设置 RecyclerView ScrollListener | +| addOnScrollListener | 添加 RecyclerView ScrollListener | +| removeOnScrollListener | 移除 RecyclerView ScrollListener | +| clearOnScrollListeners | 清空 RecyclerView ScrollListener | +| setNestedScrollingEnabled | 设置 RecyclerView 嵌套滚动开关 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | + + +* **简化链式设置 View Quick Helper 类 ->** [QuickHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/quick/QuickHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 QuickHelper | +| getView | 获取 View | +| targetView | 获取目标 View | +| targetViewGroup | 获取目标 View ( 转 ViewGroup ) | +| targetImageView | 获取目标 View ( 转 ImageView ) | +| targetTextView | 获取目标 View ( 转 TextView ) | +| targetEditText | 获取目标 View ( 转 EditText ) | +| targetRecyclerView | 获取目标 View ( 转 RecyclerView ) | +| flow | 执行 Action 流方法 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| setId | 设置 View Id | +| setClipChildren | 设置是否限制子 View 在其边界内绘制 | +| removeAllViews | 移除全部子 View | +| addView | 添加 View | +| setLayoutParams | 设置 View LayoutParams | +| setWidthHeight | 设置 View[] 宽度、高度 | +| setWeight | 设置 View weight 权重 | +| setWidth | 设置 View 宽度 | +| setHeight | 设置 View 高度 | +| setMinimumWidth | 设置 View 最小宽度 | +| setMinimumHeight | 设置 View 最小高度 | +| setAlpha | 设置 View 透明度 | +| setTag | 设置 View TAG | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| setHorizontalScrollBarEnabled | 设置是否绘制横向滚动条 | +| setVerticalScrollBarEnabled | 设置是否绘制垂直滚动条 | +| setScrollContainer | 设置 View 滚动效应 | +| setNextFocusForwardId | 设置下一个获取焦点的 View id | +| setNextFocusDownId | 设置向下移动焦点时, 下一个获取焦点的 View id | +| setNextFocusLeftId | 设置向左移动焦点时, 下一个获取焦点的 View id | +| setNextFocusRightId | 设置向右移动焦点时, 下一个获取焦点的 View id | +| setNextFocusUpId | 设置向上移动焦点时, 下一个获取焦点的 View id | +| setRotation | 设置 View 旋转度数 | +| setRotationX | 设置 View 水平旋转度数 | +| setRotationY | 设置 View 竖直旋转度数 | +| setScaleX | 设置 View 水平方向缩放比例 | +| setScaleY | 设置 View 竖直方向缩放比例 | +| setTextAlignment | 设置文本的显示方式 | +| setTextDirection | 设置文本的显示方向 | +| setPivotX | 设置水平方向偏转量 | +| setPivotY | 设置竖直方向偏转量 | +| setTranslationX | 设置水平方向的移动距离 | +| setTranslationY | 设置竖直方向的移动距离 | +| setX | 设置 X 轴位置 | +| setY | 设置 Y 轴位置 | +| setLayerType | 设置 View 硬件加速类型 | +| requestLayout | 请求重新对 View 布局 | +| requestFocus | View 请求获取焦点 | +| clearFocus | View 清除焦点 | +| setFocusableInTouchMode | 设置 View 是否在触摸模式下获得焦点 | +| setFocusable | 设置 View 是否可以获取焦点 | +| toggleFocusable | 切换获取焦点状态 | +| setSelected | 设置 View 是否选中 | +| toggleSelected | 切换选中状态 | +| setEnabled | 设置 View 是否启用 | +| toggleEnabled | 切换 View 是否启用状态 | +| setClickable | 设置 View 是否可以点击 | +| toggleClickable | 切换 View 是否可以点击状态 | +| setLongClickable | 设置 View 是否可以长按 | +| toggleLongClickable | 切换 View 是否可以长按状态 | +| setVisibilitys | 设置 View 显示的状态 | +| setVisibilityINs | 设置 View 显示的状态 | +| toggleVisibilitys | 切换 View 显示的状态 | +| reverseVisibilitys | 反转 View 显示的状态 | +| toggleViews | 切换 View 状态 | +| removeSelfFromParent | 把自身从父 View 中移除 | +| requestLayoutParent | View 请求更新 | +| measureView | 测量 View | +| setLayoutGravity | 设置 View Layout Gravity | +| setMarginLeft | 设置 View Left Margin | +| setMarginTop | 设置 View Top Margin | +| setMarginRight | 设置 View Right Margin | +| setMarginBottom | 设置 View Bottom Margin | +| setMargin | 设置 Margin 边距 | +| setPaddingLeft | 设置 View Left Padding | +| setPaddingTop | 设置 View Top Padding | +| setPaddingRight | 设置 View Right Padding | +| setPaddingBottom | 设置 View Bottom Padding | +| setPadding | 设置 Padding 边距 | +| addRules | 设置多个 RelativeLayout View 布局规则 | +| removeRules | 移除多个 RelativeLayout View 布局规则 | +| setAnimation | 设置动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| setBackground | 设置背景图片 | +| setBackgroundColor | 设置背景颜色 | +| setBackgroundResource | 设置背景资源 | +| setBackgroundTintList | 设置背景着色颜色 | +| setBackgroundTintMode | 设置背景着色模式 | +| setForeground | 设置前景图片 | +| setForegroundGravity | 设置前景重心 | +| setForegroundTintList | 设置前景着色颜色 | +| setForegroundTintMode | 设置前景着色模式 | +| setColorFilter | View 着色处理 | +| setProgressDrawable | 设置 ProgressBar 进度条样式 | +| setBarProgress | 设置 ProgressBar 进度值 | +| setBarMax | 设置 ProgressBar 最大值 | +| setBarValue | 设置 ProgressBar 最大值 | +| smoothScrollToPosition | 滑动到指定索引 ( 有滚动过程 ) | +| scrollToPosition | 滑动到指定索引 ( 无滚动过程 ) | +| smoothScrollToTop | 滑动到顶部 ( 有滚动过程 ) | +| scrollToTop | 滑动到顶部 ( 无滚动过程 ) | +| smoothScrollToBottom | 滑动到底部 ( 有滚动过程 ) | +| scrollToBottom | 滑动到底部 ( 无滚动过程 ) | +| smoothScrollTo | 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) | +| smoothScrollBy | 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) | +| fullScroll | 滚动方向 ( 有滚动过程 ) | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setAdjustViewBounds | 设置 ImageView 是否保持宽高比 | +| setMaxHeight | 设置 ImageView 最大高度 | +| setMaxWidth | 设置 ImageView 最大宽度 | +| setImageLevel | 设置 ImageView Level | +| setImageBitmap | 设置 ImageView Bitmap | +| setImageDrawable | 设置 ImageView Drawable | +| setImageResource | 设置 ImageView 资源 | +| setImageMatrix | 设置 ImageView Matrix | +| setImageTintList | 设置 ImageView 着色颜色 | +| setImageTintMode | 设置 ImageView 着色模式 | +| setScaleType | 设置 ImageView 缩放类型 | +| setBackgroundResources | 设置 View 图片资源 | +| setImageResources | 设置 View 图片资源 | +| setImageBitmaps | 设置 View Bitmap | +| setImageDrawables | 设置 View Drawable | +| setScaleTypes | 设置 View 缩放模式 | +| setText | 设置文本 | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| setInputType | 设置输入类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| setTransformationMethod | 设置文本视图显示转换 | +| insert | 追加内容 ( 当前光标位置追加 ) | +| setCursorVisible | 设置是否显示光标 | +| setTextCursorDrawable | 设置光标 | +| setSelectionToTop | 设置光标在第一位 | +| setSelectionToBottom | 设置光标在最后一位 | +| setSelection | 设置光标位置 | +| addTextChangedListener | 添加输入监听事件 | +| removeTextChangedListener | 移除输入监听事件 | +| setKeyListener | 设置 KeyListener | +| setHint | 设置 Hint 文本 | +| setHintTextColors | 设置多个 TextView Hint 字体颜色 | +| setTextColors | 设置多个 TextView 字体颜色 | +| setHtmlTexts | 设置多个 TextView Html 内容 | +| setTypeface | 设置字体 | +| setTextSizeByPx | 设置字体大小 ( px 像素 ) | +| setTextSizeBySp | 设置字体大小 ( sp 缩放像素 ) | +| setTextSizeByDp | 设置字体大小 ( dp 与设备无关的像素 ) | +| setTextSizeByIn | 设置字体大小 ( inches 英寸 ) | +| setTextSize | 设置字体大小 | +| clearFlags | 清空 flags | +| setPaintFlags | 设置 TextView flags | +| setAntiAliasFlag | 设置 TextView 抗锯齿 flags | +| setBold | 设置 TextView 是否加粗 | +| setUnderlineText | 设置下划线 | +| setStrikeThruText | 设置中划线 | +| setLetterSpacing | 设置文字水平间距 | +| setLineSpacing | 设置文字行间距 ( 行高 ) | +| setLineSpacingAndMultiplier | 设置文字行间距 ( 行高 ) 、行间距倍数 | +| setTextScaleX | 设置字体水平方向的缩放 | +| setIncludeFontPadding | 设置是否保留字体留白间隙区域 | +| setLines | 设置行数 | +| setMaxLines | 设置最大行数 | +| setMinLines | 设置最小行数 | +| setMaxEms | 设置最大字符宽度限制 | +| setMinEms | 设置最小字符宽度限制 | +| setEms | 设置指定字符宽度 | +| setEllipsize | 设置 Ellipsize 效果 | +| setAutoLinkMask | 设置自动识别文本链接 | +| setAllCaps | 设置文本全为大写 | +| setGravity | 设置 Gravity | +| setCompoundDrawablePadding | 设置 CompoundDrawables Padding | +| setCompoundDrawablesByLeft | 设置 Left CompoundDrawables | +| setCompoundDrawablesByTop | 设置 Top CompoundDrawables | +| setCompoundDrawablesByRight | 设置 Right CompoundDrawables | +| setCompoundDrawablesByBottom | 设置 Bottom CompoundDrawables | +| setCompoundDrawables | 设置 CompoundDrawables | +| setCompoundDrawablesWithIntrinsicBoundsByLeft | 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByTop | 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByRight | 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByBottom | 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBounds | 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setAutoSizeTextTypeWithDefaults | 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 | +| setAutoSizeTextTypeUniformWithConfiguration | 设置 TextView 自动调整字体大小配置 | +| setAutoSizeTextTypeUniformWithPresetSizes | 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM | +| setLayoutManager | 设置 RecyclerView LayoutManager | +| setSpanCount | 设置 GridLayoutManager SpanCount | +| setOrientation | 设置 RecyclerView Orientation | +| setAdapter | 设置 RecyclerView Adapter | +| notifyItemRemoved | RecyclerView notifyItemRemoved | +| notifyItemInserted | RecyclerView notifyItemInserted | +| notifyItemMoved | RecyclerView notifyItemMoved | +| notifyDataSetChanged | RecyclerView notifyDataSetChanged | +| attachLinearSnapHelper | 设置 RecyclerView LinearSnapHelper | +| attachPagerSnapHelper | 设置 RecyclerView PagerSnapHelper | +| addItemDecoration | 添加 RecyclerView ItemDecoration | +| removeItemDecoration | 移除 RecyclerView ItemDecoration | +| removeItemDecorationAt | 移除 RecyclerView ItemDecoration | +| removeAllItemDecoration | 移除 RecyclerView 全部 ItemDecoration | +| setOnScrollListener | 设置 RecyclerView ScrollListener | +| addOnScrollListener | 添加 RecyclerView ScrollListener | +| removeOnScrollListener | 移除 RecyclerView ScrollListener | +| clearOnScrollListeners | 清空 RecyclerView ScrollListener | +| setNestedScrollingEnabled | 设置 RecyclerView 嵌套滚动开关 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | + + +## **`dev.utils.app.helper.version`** + + +* **VersionHelper 接口 ->** [IHelperByVersion.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/version/IHelperByVersion.java) + +| 方法 | 注释 | +| :- | :- | +| getUriForFile | 获取 FileProvider File Uri | +| getUriForPath | 获取 FileProvider File Path Uri | +| getUriForFileToName | 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) | +| getUriForString | 通过 String 获取 Uri | +| fromFile | 通过 File Path 创建 Uri | +| isUri | 判断是否 Uri | +| getUriScheme | 获取 Uri Scheme | +| isUriExists | 判断 Uri 路径资源是否存在 | +| copyByUri | 通过 Uri 复制文件 | +| getFilePathByUri | 通过 Uri 获取文件路径 | +| getMediaUri | 通过 File 获取 Media Uri | +| mediaQuery | 通过 File 获取 Media 信息 | +| notifyMediaStore | 通知刷新本地资源 | +| createImageUri | 创建图片 Uri | +| createVideoUri | 创建视频 Uri | +| createAudioUri | 创建音频 Uri | +| createDownloadUri | 创建 Download Uri | +| createMediaUri | 创建预存储 Media Uri | +| createUriByPath | 通过 File Path 创建 Uri | +| createUriByFile | 通过 File Path 创建 Uri | +| insertImage | 插入一张图片 | +| insertVideo | 插入一条视频 | +| insertAudio | 插入一条音频 | +| insertDownload | 插入一条文件资源 | +| insertMedia | 插入一条多媒体资源 | +| createWriteRequest | 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 | +| createFavoriteRequest | 获取用户将设备上指定的媒体文件标记为收藏的请求 | +| createTrashRequest | 获取用户将指定的媒体文件放入设备垃圾箱的请求 | +| createDeleteRequest | 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 | +| isExternalStorageManager | 是否获得 MANAGE_EXTERNAL_STORAGE 权限 | +| checkExternalStorageAndIntentSetting | 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 | + + +* **Android 版本适配 Helper 类 ->** [VersionHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/version/VersionHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取单例 VersionHelper | +| getUriForFile | 获取 FileProvider File Uri | +| getUriForPath | 获取 FileProvider File Path Uri | +| getUriForFileToName | 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) | +| getUriForString | 通过 String 获取 Uri | +| fromFile | 通过 File Path 创建 Uri | +| isUri | 判断是否 Uri | +| getUriScheme | 获取 Uri Scheme | +| isUriExists | 判断 Uri 路径资源是否存在 | +| copyByUri | 通过 Uri 复制文件 | +| getFilePathByUri | 通过 Uri 获取文件路径 | +| getMediaUri | 通过 File 获取 Media Uri | +| mediaQuery | 通过 File 获取 Media 信息 | +| notifyMediaStore | 通知刷新本地资源 | +| createImageUri | 创建图片 Uri | +| createVideoUri | 创建视频 Uri | +| createAudioUri | 创建音频 Uri | +| createDownloadUri | 创建 Download Uri | +| createMediaUri | 创建预存储 Media Uri | +| createUriByPath | 通过 File Path 创建 Uri | +| createUriByFile | 通过 File Path 创建 Uri | +| insertImage | 插入一张图片 | +| insertVideo | 插入一条视频 | +| insertAudio | 插入一条音频 | +| insertDownload | 插入一条文件资源 | +| insertMedia | 插入一条多媒体资源 | +| createWriteRequest | 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 | +| createFavoriteRequest | 获取用户将设备上指定的媒体文件标记为收藏的请求 | +| createTrashRequest | 获取用户将指定的媒体文件放入设备垃圾箱的请求 | +| createDeleteRequest | 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 | +| isExternalStorageManager | 是否获得 MANAGE_EXTERNAL_STORAGE 权限 | +| checkExternalStorageAndIntentSetting | 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 | + + +## **`dev.utils.app.helper.view`** + + +* **ViewHelper 接口 ->** [IHelperByView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/view/IHelperByView.java) + +| 方法 | 注释 | +| :- | :- | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| setId | 设置 View Id | +| setClipChildren | 设置是否限制子 View 在其边界内绘制 | +| removeAllViews | 移除全部子 View | +| addView | 添加 View | +| setLayoutParams | 设置 View LayoutParams | +| setWidthHeight | 设置 View[] 宽度、高度 | +| setWeight | 设置 View weight 权重 | +| setWidth | 设置 View 宽度 | +| setHeight | 设置 View 高度 | +| setMinimumWidth | 设置 View 最小宽度 | +| setMinimumHeight | 设置 View 最小高度 | +| setAlpha | 设置 View 透明度 | +| setTag | 设置 View TAG | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| setHorizontalScrollBarEnabled | 设置是否绘制横向滚动条 | +| setVerticalScrollBarEnabled | 设置是否绘制垂直滚动条 | +| setScrollContainer | 设置 View 滚动效应 | +| setNextFocusForwardId | 设置下一个获取焦点的 View id | +| setNextFocusDownId | 设置向下移动焦点时, 下一个获取焦点的 View id | +| setNextFocusLeftId | 设置向左移动焦点时, 下一个获取焦点的 View id | +| setNextFocusRightId | 设置向右移动焦点时, 下一个获取焦点的 View id | +| setNextFocusUpId | 设置向上移动焦点时, 下一个获取焦点的 View id | +| setRotation | 设置 View 旋转度数 | +| setRotationX | 设置 View 水平旋转度数 | +| setRotationY | 设置 View 竖直旋转度数 | +| setScaleX | 设置 View 水平方向缩放比例 | +| setScaleY | 设置 View 竖直方向缩放比例 | +| setTextAlignment | 设置文本的显示方式 | +| setTextDirection | 设置文本的显示方向 | +| setPivotX | 设置水平方向偏转量 | +| setPivotY | 设置竖直方向偏转量 | +| setTranslationX | 设置水平方向的移动距离 | +| setTranslationY | 设置竖直方向的移动距离 | +| setX | 设置 X 轴位置 | +| setY | 设置 Y 轴位置 | +| setLayerType | 设置 View 硬件加速类型 | +| requestLayout | 请求重新对 View 布局 | +| requestFocus | View 请求获取焦点 | +| clearFocus | View 清除焦点 | +| setFocusableInTouchMode | 设置 View 是否在触摸模式下获得焦点 | +| setFocusable | 设置 View 是否可以获取焦点 | +| toggleFocusable | 切换获取焦点状态 | +| setSelected | 设置 View 是否选中 | +| toggleSelected | 切换选中状态 | +| setEnabled | 设置 View 是否启用 | +| toggleEnabled | 切换 View 是否启用状态 | +| setClickable | 设置 View 是否可以点击 | +| toggleClickable | 切换 View 是否可以点击状态 | +| setLongClickable | 设置 View 是否可以长按 | +| toggleLongClickable | 切换 View 是否可以长按状态 | +| setVisibilitys | 设置 View 显示的状态 | +| setVisibilityINs | 设置 View 显示的状态 | +| toggleVisibilitys | 切换 View 显示的状态 | +| reverseVisibilitys | 反转 View 显示的状态 | +| toggleViews | 切换 View 状态 | +| removeSelfFromParent | 把自身从父 View 中移除 | +| requestLayoutParent | View 请求更新 | +| measureView | 测量 View | +| setLayoutGravity | 设置 View Layout Gravity | +| setMarginLeft | 设置 View Left Margin | +| setMarginTop | 设置 View Top Margin | +| setMarginRight | 设置 View Right Margin | +| setMarginBottom | 设置 View Bottom Margin | +| setMargin | 设置 Margin 边距 | +| setPaddingLeft | 设置 View Left Padding | +| setPaddingTop | 设置 View Top Padding | +| setPaddingRight | 设置 View Right Padding | +| setPaddingBottom | 设置 View Bottom Padding | +| setPadding | 设置 Padding 边距 | +| addRules | 设置多个 RelativeLayout View 布局规则 | +| removeRules | 移除多个 RelativeLayout View 布局规则 | +| setAnimation | 设置动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| setBackground | 设置背景图片 | +| setBackgroundColor | 设置背景颜色 | +| setBackgroundResource | 设置背景资源 | +| setBackgroundTintList | 设置背景着色颜色 | +| setBackgroundTintMode | 设置背景着色模式 | +| setForeground | 设置前景图片 | +| setForegroundGravity | 设置前景重心 | +| setForegroundTintList | 设置前景着色颜色 | +| setForegroundTintMode | 设置前景着色模式 | +| setColorFilter | View 着色处理 | +| setProgressDrawable | 设置 ProgressBar 进度条样式 | +| setBarProgress | 设置 ProgressBar 进度值 | +| setBarMax | 设置 ProgressBar 最大值 | +| setBarValue | 设置 ProgressBar 最大值 | +| smoothScrollToPosition | 滑动到指定索引 ( 有滚动过程 ) | +| scrollToPosition | 滑动到指定索引 ( 无滚动过程 ) | +| smoothScrollToTop | 滑动到顶部 ( 有滚动过程 ) | +| scrollToTop | 滑动到顶部 ( 无滚动过程 ) | +| smoothScrollToBottom | 滑动到底部 ( 有滚动过程 ) | +| scrollToBottom | 滑动到底部 ( 无滚动过程 ) | +| smoothScrollTo | 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) | +| smoothScrollBy | 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) | +| fullScroll | 滚动方向 ( 有滚动过程 ) | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setAdjustViewBounds | 设置 ImageView 是否保持宽高比 | +| setMaxHeight | 设置 ImageView 最大高度 | +| setMaxWidth | 设置 ImageView 最大宽度 | +| setImageLevel | 设置 ImageView Level | +| setImageBitmap | 设置 ImageView Bitmap | +| setImageDrawable | 设置 ImageView Drawable | +| setImageResource | 设置 ImageView 资源 | +| setImageMatrix | 设置 ImageView Matrix | +| setImageTintList | 设置 ImageView 着色颜色 | +| setImageTintMode | 设置 ImageView 着色模式 | +| setScaleType | 设置 ImageView 缩放类型 | +| setBackgroundResources | 设置 View 图片资源 | +| setImageResources | 设置 View 图片资源 | +| setImageBitmaps | 设置 View Bitmap | +| setImageDrawables | 设置 View Drawable | +| setScaleTypes | 设置 View 缩放模式 | +| setText | 设置文本 | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| setInputType | 设置输入类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| setTransformationMethod | 设置文本视图显示转换 | +| insert | 追加内容 ( 当前光标位置追加 ) | +| setCursorVisible | 设置是否显示光标 | +| setTextCursorDrawable | 设置光标 | +| setSelectionToTop | 设置光标在第一位 | +| setSelectionToBottom | 设置光标在最后一位 | +| setSelection | 设置光标位置 | +| addTextChangedListener | 添加输入监听事件 | +| removeTextChangedListener | 移除输入监听事件 | +| setKeyListener | 设置 KeyListener | +| setHint | 设置 Hint 文本 | +| setHintTextColors | 设置多个 TextView Hint 字体颜色 | +| setTextColors | 设置多个 TextView 字体颜色 | +| setHtmlTexts | 设置多个 TextView Html 内容 | +| setTypeface | 设置字体 | +| setTextSizeByPx | 设置字体大小 ( px 像素 ) | +| setTextSizeBySp | 设置字体大小 ( sp 缩放像素 ) | +| setTextSizeByDp | 设置字体大小 ( dp 与设备无关的像素 ) | +| setTextSizeByIn | 设置字体大小 ( inches 英寸 ) | +| setTextSize | 设置字体大小 | +| clearFlags | 清空 flags | +| setPaintFlags | 设置 TextView flags | +| setAntiAliasFlag | 设置 TextView 抗锯齿 flags | +| setBold | 设置 TextView 是否加粗 | +| setUnderlineText | 设置下划线 | +| setStrikeThruText | 设置中划线 | +| setLetterSpacing | 设置文字水平间距 | +| setLineSpacing | 设置文字行间距 ( 行高 ) | +| setLineSpacingAndMultiplier | 设置文字行间距 ( 行高 ) 、行间距倍数 | +| setTextScaleX | 设置字体水平方向的缩放 | +| setIncludeFontPadding | 设置是否保留字体留白间隙区域 | +| setLines | 设置行数 | +| setMaxLines | 设置最大行数 | +| setMinLines | 设置最小行数 | +| setMaxEms | 设置最大字符宽度限制 | +| setMinEms | 设置最小字符宽度限制 | +| setEms | 设置指定字符宽度 | +| setEllipsize | 设置 Ellipsize 效果 | +| setAutoLinkMask | 设置自动识别文本链接 | +| setAllCaps | 设置文本全为大写 | +| setGravity | 设置 Gravity | +| setCompoundDrawablePadding | 设置 CompoundDrawables Padding | +| setCompoundDrawablesByLeft | 设置 Left CompoundDrawables | +| setCompoundDrawablesByTop | 设置 Top CompoundDrawables | +| setCompoundDrawablesByRight | 设置 Right CompoundDrawables | +| setCompoundDrawablesByBottom | 设置 Bottom CompoundDrawables | +| setCompoundDrawables | 设置 CompoundDrawables | +| setCompoundDrawablesWithIntrinsicBoundsByLeft | 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByTop | 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByRight | 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByBottom | 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBounds | 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setAutoSizeTextTypeWithDefaults | 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 | +| setAutoSizeTextTypeUniformWithConfiguration | 设置 TextView 自动调整字体大小配置 | +| setAutoSizeTextTypeUniformWithPresetSizes | 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM | +| setLayoutManager | 设置 RecyclerView LayoutManager | +| setSpanCount | 设置 GridLayoutManager SpanCount | +| setOrientation | 设置 RecyclerView Orientation | +| setAdapter | 设置 RecyclerView Adapter | +| notifyItemRemoved | RecyclerView notifyItemRemoved | +| notifyItemInserted | RecyclerView notifyItemInserted | +| notifyItemMoved | RecyclerView notifyItemMoved | +| notifyDataSetChanged | RecyclerView notifyDataSetChanged | +| attachLinearSnapHelper | 设置 RecyclerView LinearSnapHelper | +| attachPagerSnapHelper | 设置 RecyclerView PagerSnapHelper | +| addItemDecoration | 添加 RecyclerView ItemDecoration | +| removeItemDecoration | 移除 RecyclerView ItemDecoration | +| removeItemDecorationAt | 移除 RecyclerView ItemDecoration | +| removeAllItemDecoration | 移除 RecyclerView 全部 ItemDecoration | +| setOnScrollListener | 设置 RecyclerView ScrollListener | +| addOnScrollListener | 添加 RecyclerView ScrollListener | +| removeOnScrollListener | 移除 RecyclerView ScrollListener | +| clearOnScrollListeners | 清空 RecyclerView ScrollListener | +| setNestedScrollingEnabled | 设置 RecyclerView 嵌套滚动开关 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | + + +* **View 链式调用快捷设置 Helper 类 ->** [ViewHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/helper/view/ViewHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取单例 ViewHelper | +| flow | 执行 Action 流方法 | +| postRunnable | 在主线程 Handler 中执行任务 | +| removeRunnable | 在主线程 Handler 中清除任务 | +| addTouchArea | 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 | +| setOnClick | 设置点击事件 | +| setOnLongClick | 设置长按事件 | +| setOnTouch | 设置触摸事件 | +| setId | 设置 View Id | +| setClipChildren | 设置是否限制子 View 在其边界内绘制 | +| removeAllViews | 移除全部子 View | +| addView | 添加 View | +| setLayoutParams | 设置 View LayoutParams | +| setWidthHeight | 设置 View[] 宽度、高度 | +| setWeight | 设置 View weight 权重 | +| setWidth | 设置 View 宽度 | +| setHeight | 设置 View 高度 | +| setMinimumWidth | 设置 View 最小宽度 | +| setMinimumHeight | 设置 View 最小高度 | +| setAlpha | 设置 View 透明度 | +| setTag | 设置 View TAG | +| setScrollX | 设置 View 滑动的 X 轴坐标 | +| setScrollY | 设置 View 滑动的 Y 轴坐标 | +| setDescendantFocusability | 设置 ViewGroup 和其子控件两者之间的关系 | +| setOverScrollMode | 设置 View 滚动模式 | +| setHorizontalScrollBarEnabled | 设置是否绘制横向滚动条 | +| setVerticalScrollBarEnabled | 设置是否绘制垂直滚动条 | +| setScrollContainer | 设置 View 滚动效应 | +| setNextFocusForwardId | 设置下一个获取焦点的 View id | +| setNextFocusDownId | 设置向下移动焦点时, 下一个获取焦点的 View id | +| setNextFocusLeftId | 设置向左移动焦点时, 下一个获取焦点的 View id | +| setNextFocusRightId | 设置向右移动焦点时, 下一个获取焦点的 View id | +| setNextFocusUpId | 设置向上移动焦点时, 下一个获取焦点的 View id | +| setRotation | 设置 View 旋转度数 | +| setRotationX | 设置 View 水平旋转度数 | +| setRotationY | 设置 View 竖直旋转度数 | +| setScaleX | 设置 View 水平方向缩放比例 | +| setScaleY | 设置 View 竖直方向缩放比例 | +| setTextAlignment | 设置文本的显示方式 | +| setTextDirection | 设置文本的显示方向 | +| setPivotX | 设置水平方向偏转量 | +| setPivotY | 设置竖直方向偏转量 | +| setTranslationX | 设置水平方向的移动距离 | +| setTranslationY | 设置竖直方向的移动距离 | +| setX | 设置 X 轴位置 | +| setY | 设置 Y 轴位置 | +| setLayerType | 设置 View 硬件加速类型 | +| requestLayout | 请求重新对 View 布局 | +| requestFocus | View 请求获取焦点 | +| clearFocus | View 清除焦点 | +| setFocusableInTouchMode | 设置 View 是否在触摸模式下获得焦点 | +| setFocusable | 设置 View 是否可以获取焦点 | +| toggleFocusable | 切换获取焦点状态 | +| setSelected | 设置 View 是否选中 | +| toggleSelected | 切换选中状态 | +| setEnabled | 设置 View 是否启用 | +| toggleEnabled | 切换 View 是否启用状态 | +| setClickable | 设置 View 是否可以点击 | +| toggleClickable | 切换 View 是否可以点击状态 | +| setLongClickable | 设置 View 是否可以长按 | +| toggleLongClickable | 切换 View 是否可以长按状态 | +| setVisibilitys | 设置 View 显示的状态 | +| setVisibilityINs | 设置 View 显示的状态 | +| toggleVisibilitys | 切换 View 显示的状态 | +| reverseVisibilitys | 反转 View 显示的状态 | +| toggleViews | 切换 View 状态 | +| removeSelfFromParent | 把自身从父 View 中移除 | +| requestLayoutParent | View 请求更新 | +| measureView | 测量 View | +| setLayoutGravity | 设置 View Layout Gravity | +| setMarginLeft | 设置 View Left Margin | +| setMarginTop | 设置 View Top Margin | +| setMarginRight | 设置 View Right Margin | +| setMarginBottom | 设置 View Bottom Margin | +| setMargin | 设置 Margin 边距 | +| setPaddingLeft | 设置 View Left Padding | +| setPaddingTop | 设置 View Top Padding | +| setPaddingRight | 设置 View Right Padding | +| setPaddingBottom | 设置 View Bottom Padding | +| setPadding | 设置 Padding 边距 | +| addRules | 设置多个 RelativeLayout View 布局规则 | +| removeRules | 移除多个 RelativeLayout View 布局规则 | +| setAnimation | 设置动画 | +| clearAnimation | 清空动画 | +| startAnimation | 启动动画 | +| cancelAnimation | 取消动画 | +| setBackground | 设置背景图片 | +| setBackgroundColor | 设置背景颜色 | +| setBackgroundResource | 设置背景资源 | +| setBackgroundTintList | 设置背景着色颜色 | +| setBackgroundTintMode | 设置背景着色模式 | +| setForeground | 设置前景图片 | +| setForegroundGravity | 设置前景重心 | +| setForegroundTintList | 设置前景着色颜色 | +| setForegroundTintMode | 设置前景着色模式 | +| setColorFilter | View 着色处理 | +| setProgressDrawable | 设置 ProgressBar 进度条样式 | +| setBarProgress | 设置 ProgressBar 进度值 | +| setBarMax | 设置 ProgressBar 最大值 | +| setBarValue | 设置 ProgressBar 最大值 | +| smoothScrollToPosition | 滑动到指定索引 ( 有滚动过程 ) | +| scrollToPosition | 滑动到指定索引 ( 无滚动过程 ) | +| smoothScrollToTop | 滑动到顶部 ( 有滚动过程 ) | +| scrollToTop | 滑动到顶部 ( 无滚动过程 ) | +| smoothScrollToBottom | 滑动到底部 ( 有滚动过程 ) | +| scrollToBottom | 滑动到底部 ( 无滚动过程 ) | +| smoothScrollTo | 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) | +| smoothScrollBy | 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) | +| fullScroll | 滚动方向 ( 有滚动过程 ) | +| scrollTo | View 内容滚动位置 ( 相对于初始位置移动 ) | +| scrollBy | View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) | +| setAdjustViewBounds | 设置 ImageView 是否保持宽高比 | +| setMaxHeight | 设置 ImageView 最大高度 | +| setMaxWidth | 设置 ImageView 最大宽度 | +| setImageLevel | 设置 ImageView Level | +| setImageBitmap | 设置 ImageView Bitmap | +| setImageDrawable | 设置 ImageView Drawable | +| setImageResource | 设置 ImageView 资源 | +| setImageMatrix | 设置 ImageView Matrix | +| setImageTintList | 设置 ImageView 着色颜色 | +| setImageTintMode | 设置 ImageView 着色模式 | +| setScaleType | 设置 ImageView 缩放类型 | +| setBackgroundResources | 设置 View 图片资源 | +| setImageResources | 设置 View 图片资源 | +| setImageBitmaps | 设置 View Bitmap | +| setImageDrawables | 设置 View Drawable | +| setScaleTypes | 设置 View 缩放模式 | +| setText | 设置文本 | +| setMaxLength | 设置长度限制 | +| setMaxLengthAndText | 设置长度限制, 并且设置内容 | +| setInputType | 设置输入类型 | +| setImeOptions | 设置软键盘右下角按钮类型 | +| setTransformationMethod | 设置文本视图显示转换 | +| insert | 追加内容 ( 当前光标位置追加 ) | +| setCursorVisible | 设置是否显示光标 | +| setTextCursorDrawable | 设置光标 | +| setSelectionToTop | 设置光标在第一位 | +| setSelectionToBottom | 设置光标在最后一位 | +| setSelection | 设置光标位置 | +| addTextChangedListener | 添加输入监听事件 | +| removeTextChangedListener | 移除输入监听事件 | +| setKeyListener | 设置 KeyListener | +| setHint | 设置 Hint 文本 | +| setHintTextColors | 设置多个 TextView Hint 字体颜色 | +| setTextColors | 设置多个 TextView 字体颜色 | +| setHtmlTexts | 设置多个 TextView Html 内容 | +| setTypeface | 设置字体 | +| setTextSizeByPx | 设置字体大小 ( px 像素 ) | +| setTextSizeBySp | 设置字体大小 ( sp 缩放像素 ) | +| setTextSizeByDp | 设置字体大小 ( dp 与设备无关的像素 ) | +| setTextSizeByIn | 设置字体大小 ( inches 英寸 ) | +| setTextSize | 设置字体大小 | +| clearFlags | 清空 flags | +| setPaintFlags | 设置 TextView flags | +| setAntiAliasFlag | 设置 TextView 抗锯齿 flags | +| setBold | 设置 TextView 是否加粗 | +| setUnderlineText | 设置下划线 | +| setStrikeThruText | 设置中划线 | +| setLetterSpacing | 设置文字水平间距 | +| setLineSpacing | 设置文字行间距 ( 行高 ) | +| setLineSpacingAndMultiplier | 设置文字行间距 ( 行高 ) 、行间距倍数 | +| setTextScaleX | 设置字体水平方向的缩放 | +| setIncludeFontPadding | 设置是否保留字体留白间隙区域 | +| setLines | 设置行数 | +| setMaxLines | 设置最大行数 | +| setMinLines | 设置最小行数 | +| setMaxEms | 设置最大字符宽度限制 | +| setMinEms | 设置最小字符宽度限制 | +| setEms | 设置指定字符宽度 | +| setEllipsize | 设置 Ellipsize 效果 | +| setAutoLinkMask | 设置自动识别文本链接 | +| setAllCaps | 设置文本全为大写 | +| setGravity | 设置 Gravity | +| setCompoundDrawablePadding | 设置 CompoundDrawables Padding | +| setCompoundDrawablesByLeft | 设置 Left CompoundDrawables | +| setCompoundDrawablesByTop | 设置 Top CompoundDrawables | +| setCompoundDrawablesByRight | 设置 Right CompoundDrawables | +| setCompoundDrawablesByBottom | 设置 Bottom CompoundDrawables | +| setCompoundDrawables | 设置 CompoundDrawables | +| setCompoundDrawablesWithIntrinsicBoundsByLeft | 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByTop | 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByRight | 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBoundsByBottom | 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setCompoundDrawablesWithIntrinsicBounds | 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) | +| setAutoSizeTextTypeWithDefaults | 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 | +| setAutoSizeTextTypeUniformWithConfiguration | 设置 TextView 自动调整字体大小配置 | +| setAutoSizeTextTypeUniformWithPresetSizes | 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM | +| setLayoutManager | 设置 RecyclerView LayoutManager | +| setSpanCount | 设置 GridLayoutManager SpanCount | +| setOrientation | 设置 RecyclerView Orientation | +| setAdapter | 设置 RecyclerView Adapter | +| notifyItemRemoved | RecyclerView notifyItemRemoved | +| notifyItemInserted | RecyclerView notifyItemInserted | +| notifyItemMoved | RecyclerView notifyItemMoved | +| notifyDataSetChanged | RecyclerView notifyDataSetChanged | +| attachLinearSnapHelper | 设置 RecyclerView LinearSnapHelper | +| attachPagerSnapHelper | 设置 RecyclerView PagerSnapHelper | +| addItemDecoration | 添加 RecyclerView ItemDecoration | +| removeItemDecoration | 移除 RecyclerView ItemDecoration | +| removeItemDecorationAt | 移除 RecyclerView ItemDecoration | +| removeAllItemDecoration | 移除 RecyclerView 全部 ItemDecoration | +| setOnScrollListener | 设置 RecyclerView ScrollListener | +| addOnScrollListener | 添加 RecyclerView ScrollListener | +| removeOnScrollListener | 移除 RecyclerView ScrollListener | +| clearOnScrollListeners | 清空 RecyclerView ScrollListener | +| setNestedScrollingEnabled | 设置 RecyclerView 嵌套滚动开关 | +| forceGetViewSize | 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) | + + +## **`dev.utils.app.image`** + + +* **Bitmap 工具类 ->** [BitmapUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/image/BitmapUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Bitmap 对象是否为 null | +| isNotEmpty | 判断 Bitmap 对象是否不为 null | +| isImage | 根据文件判断是否为图片 | +| getBitmapWidth | 获取 Bitmap 宽度 | +| getBitmapHeight | 获取 Bitmap 高度 | +| getBitmapWidthHeight | 获取 Bitmap 宽高 | +| copy | 复制 Bitmap | +| extractAlpha | 获取 Alpha 位图 ( 获取源图片的轮廓 rgb 为 0 ) | +| recode | 重新编码 Bitmap | +| recycle | Bitmap 通知回收 | +| rotate | 旋转图片 | +| getRotationDegrees | 读取图片属性, 获取图片旋转角度 | +| reverseByHorizontal | 水平翻转图片 ( 左右颠倒 ) | +| reverseByVertical | 垂直翻转图片 ( 上下颠倒 ) | +| reverse | 翻转图片 | +| zoom | 缩放图片 ( 指定所需宽高 ) | +| scale | 缩放图片 ( 比例缩放 ) | +| skew | 倾斜图片 | +| clip | 裁剪图片 | +| crop | 裁剪图片 ( 返回指定比例图片 ) | +| combine | 合并图片 | +| combineToCenter | 合并图片 ( 居中 ) | +| combineToSameSize | 合并图片 ( 转为相同大小 ) | +| reflection | 图片倒影处理 | +| roundCorner | 图片圆角处理 ( 非圆形 ) | +| roundCornerTop | 图片圆角处理 ( 非圆形, 只有 leftTop、rightTop ) | +| roundCornerBottom | 图片圆角处理 ( 非圆形, 只有 leftBottom、rightBottom ) | +| round | 图片圆形处理 | +| addCornerBorder | 添加圆角边框 | +| addCircleBorder | 添加圆形边框 | +| addBorder | 添加边框 | +| addTextWatermark | 添加文字水印 | +| addImageWatermark | 添加图片水印 | +| compressByZoom | 按缩放宽高压缩 | +| compressByScale | 按缩放比例压缩 | +| compressByQuality | 按质量压缩 | +| compressByByteSize | 按质量压缩 ( 图片大小 ) | +| compressBySampleSize | 按采样大小压缩 | +| calculateInSampleSize | 计算采样大小 | +| calculateQuality | 计算最佳压缩质量值 | +| getVideoThumbnail | 获取视频缩略图 | + + +* **图片格式转换工具类 ->** [ImageConvertUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/image/ImageConvertUtils.java) + +| 方法 | 注释 | +| :- | :- | +| convertBMP | 图片转换 BMP 格式 byte[] 数据 | + + +* **图片 ( 滤镜、效果 ) 工具类 ->** [ImageFilterUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/image/ImageFilterUtils.java) + +| 方法 | 注释 | +| :- | :- | +| blur | 图片模糊处理 ( Android RenderScript 实现, 效率最高 ) | +| fastBlur | 图片模糊处理 ( 毛玻璃化 FastBlur Java 实现 ) | +| nostalgic | 怀旧效果处理 | +| sunshine | 光照效果处理 | +| film | 底片效果处理 | +| soften | 柔化效果处理 | +| sharpen | 锐化效果处理 | +| emboss | 浮雕效果处理 | +| gray | 转为灰度图片 | +| saturation | 饱和度处理 | +| lum | 亮度处理 | +| hue | 色相处理 | +| lumHueSaturation | 亮度、色相、饱和度处理 | +| yuvLandscapeToPortrait | 将 YUV 格式的图片的源数据从横屏模式转为竖屏模式 | + + +* **Image ( Bitmap、Drawable 等 ) 工具类 ->** [ImageUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/image/ImageUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Bitmap 对象是否为 null | +| isNotEmpty | 判断 Bitmap 对象是否不为 null | +| isImageFormats | 根据文件名判断文件是否为图片 | +| getImageType | 获取图片类型 | +| isPNG | 判断是否 PNG 图片 | +| isJPEG | 判断是否 JPG 图片 | +| isBMP | 判断是否 BMP 图片 | +| isGif | 判断是否 GIF 图片 | +| isWEBP | 判断是否 WEBP 图片 | +| isICO | 判断是否 ICO 图片 | +| isTIFF | 判断是否 TIFF 图片 | +| decodeFile | 获取 Bitmap | +| decodeResource | 获取 Bitmap | +| decodeStream | 获取 Bitmap | +| decodeFileDescriptor | 获取 Bitmap | +| decodeByteArray | 获取 Bitmap | +| saveBitmapToSDCardJPEG | 保存图片到 SDCard ( JPEG ) | +| saveBitmapToSDCardPNG | 保存图片到 SDCard ( PNG ) | +| saveBitmapToSDCardWEBP | 保存图片到 SDCard ( WEBP ) | +| saveBitmapToSDCard | 保存图片到 SDCard | +| saveBitmapToStreamJPEG | 保存 JPEG 图片 | +| saveBitmapToStreamPNG | 保存 PNG 图片 | +| saveBitmapToStreamWEBP | 保存 WEBP 图片 | +| saveBitmapToStream | 保存图片 | +| get9PatchDrawable | 获取 .9 Drawable | +| setColorFilter | 图片着色 ( tint ) | +| getBitmap | 获取 Bitmap | +| getBitmapFromView | 通过 View 绘制为 Bitmap | +| getBitmapFromViewCache | 通过 View Cache 绘制为 Bitmap | +| bitmapToByte | Bitmap 转换成 byte[] | +| drawableToByte | Drawable 转换成 byte[] | +| byteToBitmap | byte[] 转 Bitmap | +| bitmapToDrawable | Bitmap 转 Drawable | +| byteToDrawable | byte[] 转 Drawable | +| drawableToBitmap | Drawable 转 Bitmap | +| setBounds | 设置 Drawable 绘制区域 | + + +## **`dev.utils.app.info`** + + +* **APK 信息 Item ->** [ApkInfoItem.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/info/ApkInfoItem.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 ApkInfoItem | +| getAppInfoBean | 获取 AppInfoBean | +| getListKeyValues | 获取 List 信息键对值集合 | +| getAppMD5 | 获取 APP MD5 签名 | +| getAppSHA1 | 获取 APP SHA1 签名 | +| getAppSHA256 | 获取 APP SHA256 签名 | +| getMinSdkVersion | 获取 APP 最低支持 Android SDK 版本 | +| getTargetSdkVersion | 获取 APP 兼容 SDK 版本 | +| getApkLength | 获取 APP 安装包大小 | +| getX509Certificate | 获取证书对象 | +| getNotBefore | 获取证书生成日期 | +| getNotAfter | 获取证书有效期 | +| isEffective | 获取证书是否过期 | +| getCertPrincipal | 获取证书发布方 | +| getCertVersion | 获取证书版本号 | +| getCertSigAlgName | 获取证书算法名称 | +| getCertSigAlgOID | 获取证书算法 OID | +| getCertSerialnumber | 获取证书机器码 | +| getCertDERCode | 获取证书 DER 编码 | + + +* **APP 信息实体类 ->** [AppInfoBean.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoBean.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 AppInfoBean | +| getAppPackName | 获取 APP 包名 | +| getAppName | 获取 APP 应用名 | +| getAppIcon | 获取 APP 图标 | +| getAppType | 获取 APP 类型 | +| getVersionCode | 获取 versionCode | +| getVersionName | 获取 versionName | +| getFirstInstallTime | 获取 APP 首次安装时间 | +| getLastUpdateTime | 获取 APP 最后更新时间 | +| getSourceDir | 获取 APK 地址 | +| getApkSize | 获取 APK 大小 | +| isSystemApp | 是否系统程序 | +| isSystemUpdateApp | 是否系统程序被手动更新后, 也成为第三方应用程序 | + + +* **APP 信息 Item ->** [AppInfoItem.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoItem.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 AppInfoItem | +| getAppInfoBean | 获取 AppInfoBean | +| getListKeyValues | 获取 List 信息键对值集合 | +| getAppMD5 | 获取 APP MD5 签名 | +| getAppSHA1 | 获取 APP SHA1 签名 | +| getAppSHA256 | 获取 APP SHA256 签名 | +| getMinSdkVersion | 获取 APP 最低支持 Android SDK 版本 | +| getTargetSdkVersion | 获取 APP 兼容 SDK 版本 | +| getApkLength | 获取 APP 安装包大小 | +| getX509Certificate | 获取证书对象 | +| getNotBefore | 获取证书生成日期 | +| getNotAfter | 获取证书有效期 | +| isEffective | 获取证书是否过期 | +| getCertPrincipal | 获取证书发布方 | +| getCertVersion | 获取证书版本号 | +| getCertSigAlgName | 获取证书算法名称 | +| getCertSigAlgOID | 获取证书算法 OID | +| getCertSerialnumber | 获取证书机器码 | +| getCertDERCode | 获取证书 DER 编码 | + + +* **APP 信息获取工具类 ->** [AppInfoUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getPackageInfoToFile | 通过 APK 路径 初始化 PackageInfo | +| getPackageInfoToPath | 通过 APK 路径 初始化 PackageInfo | +| getPackageInfo | 获取当前应用 PackageInfo | +| getAppInfoBeanToFile | 通过 APK 路径 获取 AppInfoBean | +| getAppInfoBeanToPath | 通过 APK 路径 获取 AppInfoBean | +| getAppInfoBean | 获取当前应用 AppInfoBean | +| getApkInfoItem | 获取 APK 详细信息 | +| getAppInfoItem | 获取 APP 详细信息 | +| getAppLists | 获取全部 APP 列表 | +| getAppPermissionToList | 获取 APP 注册的权限 | +| getAppPermissionToSet | 获取 APP 注册的权限 | +| getAppPermission | 获取 APP 注册的权限 | +| printAppPermission | 打印 APP 注册的权限 | + + +* **键对值实体类 ->** [KeyValue.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/info/KeyValue.java) + +| 方法 | 注释 | +| :- | :- | +| getKey | 获取 key | +| getValue | 获取 value | +| get | 通过 resId 设置 key | + + +## **`dev.utils.app.logger`** + + +* **日志操作类 ( 对外公开直接调用 ) ->** [DevLogger.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/DevLogger.java) + +| 方法 | 注释 | +| :- | :- | +| other | 使用单次其他日志配置 | +| getLogConfig | 获取日志配置信息 | +| initialize | 初始化日志配置信息 ( 使用默认配置 ) | +| d | 打印 Log.DEBUG | +| e | 打印 Log.ERROR | +| w | 打印 Log.WARN | +| i | 打印 Log.INFO | +| v | 打印 Log.VERBOSE | +| wtf | 打印 Log.ASSERT | +| json | 格式化 JSON 格式数据, 并打印 | +| xml | 格式化 XML 格式数据, 并打印 | +| dTag | 打印 Log.DEBUG | +| eTag | 打印 Log.ERROR | +| wTag | 打印 Log.WARN | +| iTag | 打印 Log.INFO | +| vTag | 打印 Log.VERBOSE | +| wtfTag | 打印 Log.ASSERT | +| jsonTag | 格式化 JSON 格式数据, 并打印 | +| xmlTag | 格式化 XML 格式数据, 并打印 | +| setPrint | 设置日志输出接口 | +| printLog | 日志打印 | + + +* **日志配置类 ->** [LogConfig.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/LogConfig.java) + +| 方法 | 注释 | +| :- | :- | +| getReleaseLogConfig | 获取 Release Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、不进行排序、默认只打印 ERROR 级别日志 ) | +| getDebugLogConfig | 获取 Debug Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、不进行排序、默认只打印 ERROR 级别日志 ) | +| getSortLogConfig | 获取 Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、并且美化日志信息、默认打印 DEBUG 级别及以上日志 ) | +| getLogConfig | 获取 Log 配置 | +| methodCount | 设置堆栈方法总数 | +| methodOffset | 设置堆栈方法索引偏移 | +| outputMethodAll | 设置是否输出全部方法 | +| displayThreadInfo | 设置是否显示日志线程信息 | +| sortLog | 设置是否排序日志 | +| logLevel | 设置日志级别 | +| tag | 设置 TAG | + + +## **`dev.utils.app.permission`** + + +* **权限请求工具类 ->** [PermissionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/permission/PermissionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isGranted | 判断是否授予了权限 | +| shouldShowRequestPermissionRationale | 获取拒绝权限询问勾选状态 | +| getDeniedPermissionStatus | 获取拒绝权限询问状态集合 | +| canRequestPackageInstalls | 是否存在 APK 安装权限 | +| getAllPermissionToSet | 获取全部权限 | +| getAllPermissionToList | 获取全部权限 | +| getAppPermissionToList | 获取 APP 注册的权限 | +| getAppPermissionToSet | 获取 APP 注册的权限 | +| getAppPermission | 获取 APP 注册的权限 | +| permission | 申请权限初始化 | +| callback | 设置回调方法 | +| setRequestPermissionsResult | 设置是否需要在 Activity 的 onRequestPermissionsResult 回调中, 调用 PermissionUtils.onRequestPermissionsResult(this); | +| request | 请求权限 | +| onRequestPermissionsResult | 请求权限回调 ( 需要在 Activity 的 onRequestPermissionsResult 回调中, 调用 PermissionUtils.onRequestPermissionsResult(this); ) | +| notifyPermissionsChange | 刷新权限改变处理 ( 清空已拒绝的权限记录 ) | +| againRequest | 再次请求处理操作 | + + +## **`dev.utils.app.player`** + + +* **MediaPlayer 统一管理类 ->** [DevMediaManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevMediaManager 实例 | +| setAudioStreamType | 设置流类型 | +| playPrepareRaw | 播放 Raw 资源 | +| playPrepareAssets | 播放 Assets 资源 | +| playPrepare | 预加载播放 ( file-path or http/rtsp URL ) http 资源、本地资源 | +| isPlaying | 是否播放中 | +| pause | 暂停操作 | +| stop | 停止操作 ( 销毁 MediaPlayer ) | +| isIgnoreWhat | 是否忽略错误类型 | +| setMediaListener | 设置 MediaPlayer 回调事件 | +| isNullMediaPlayer | 判断 MediaPlayer 是否为 null | +| isNotNullMediaPlayer | 判断 MediaPlayer 是否不为 null | +| getMediaPlayer | 获取 MediaPlayer 对象 | +| setMediaPlayer | 设置 MediaPlayer 对象 | +| setTAG | 设置日志打印 TAG | +| getVolume | 获取播放音量 | +| setVolume | 设置播放音量 | +| getPlayRawId | 获取播放资源 id | +| getPlayUri | 获取播放地址 | +| getVideoWidth | 获取视频宽度 | +| getVideoHeight | 获取视频高度 | +| getCurrentPosition | 获取播放时间 | +| getDuration | 获取资源总时间 | +| getPlayPercent | 获取播放进度百分比 | + + +* **视频播放控制器 ->** [DevVideoPlayerControl.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java) + +| 方法 | 注释 | +| :- | :- | +| setMediaListener | 设置播放监听事件 | +| pausePlayer | 暂停播放 | +| stopPlayer | 停止播放 | +| startPlayer | 开始播放 | +| getSurfaceView | 获取 SurfaceView | +| isPlaying | 是否播放中 | +| isAutoPlay | 判断是否自动播放 | +| setAutoPlay | 设置自动播放 | +| getPlayUri | 获取播放地址 | +| getVideoWidth | 获取视频宽度 | +| getVideoHeight | 获取视频高度 | +| getCurrentPosition | 获取播放时间 | +| getDuration | 获取资源总时间 | +| getPlayPercent | 获取播放进度百分比 | + + +## **`dev.utils.app.share`** + + +* **SharedPreferences 操作监听器 ->** [OnSPOperateListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/OnSPOperateListener.java) + +| 方法 | 注释 | +| :- | :- | +| onPut | put 操作回调 | +| onPutByMap | put 操作回调 ( 循环 Map 触发 ) | +| onRemove | remove 操作回调 | +| onRemoveByList | remove 操作回调 ( 循环 List 触发 ) | +| clear | 清除全部数据 | +| onGet | get 操作回调 | + + +* **SPUtils 快捷工具类 ->** [SharedUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/SharedUtils.java) + +| 方法 | 注释 | +| :- | :- | +| registerListener | 注册 SharedPreferences 操作监听器 | +| unregisterListener | 注销 SharedPreferences 操作监听器 | +| put | 保存数据 | +| putAll | 保存 Map 集合 ( 只能是 Integer、Long、Boolean、Float、String、Set ) | +| get | 根据 key 获取数据 | +| getAll | 获取全部数据 | +| remove | 移除数据 | +| removeAll | 移除集合的数据 | +| contains | 是否存在 key | +| clear | 清除全部数据 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getSet | 获取 Set 类型的数据 | + + +## **`dev.utils.app.timer`** + + +* **定时器 ->** [DevTimer.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java) + +| 方法 | 注释 | +| :- | :- | +| getTag | 获取 TAG | +| getUUID | 获取 UUID HashCode | +| getDelay | 获取延迟时间 ( 多少毫秒后开始执行 ) | +| getPeriod | 获取循环时间 ( 每隔多少毫秒执行一次 ) | +| isRunning | 判断是否运行中 | +| isMarkSweep | 是否标记清除 | +| getTriggerNumber | 获取已经触发的次数 | +| getTriggerLimit | 获取允许触发的上限次数 | +| isTriggerEnd | 是否触发结束 ( 到达最大次数 ) | +| isInfinite | 是否无限循环 | +| setHandler | 设置 UI Handler | +| setCallback | 设置回调事件 | +| start | 运行定时器 | +| stop | 关闭定时器 | +| setTag | setTag | +| setDelay | setDelay | +| setPeriod | setPeriod | +| getLimit | getLimit | +| setLimit | setLimit | +| build | build | +| callback | 触发回调方法 | + + +* **定时器管理类 ->** [TimerManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java) + +| 方法 | 注释 | +| :- | :- | +| addContainsChecker | 添加包含校验 | +| getSize | 获取全部定时器总数 | +| recycle | 回收定时器资源 | +| getTimer | 获取对应 TAG 定时器 ( 优先获取符合的 ) | +| getTimers | 获取对应 TAG 定时器集合 | +| closeAll | 关闭全部定时器 | +| closeAllNotRunning | 关闭所有未运行的定时器 | +| closeAllInfinite | 关闭所有无限循环的定时器 | +| closeAllTag | 关闭所有对应 TAG 定时器 | +| closeAllUUID | 关闭所有对应 UUID 定时器 | +| startTimer | 运行定时器 | +| stopTimer | 关闭定时器 | + + +## **`dev.utils.app.toast`** + + +* **自定义 View 着色美化 Toast 工具类 ->** [ToastTintUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/ToastTintUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reset | 重置默认参数 | +| setToastFilter | 设置 Toast 过滤器 | +| setUseHandler | 设置是否使用 Handler 显示 Toast | +| setNullText | 设置 Text 为 null 的文本 | +| setUseConfig | 设置是否使用配置 | +| setGravity | 设置 Toast 显示在屏幕上的位置 | +| setMargin | 设置边距 | +| getDefaultStyle | 获取默认样式 | +| getNormalStyle | 获取 Normal 样式 | +| getInfoStyle | 获取 Info 样式 | +| getWarningStyle | 获取 Warning 样式 | +| getErrorStyle | 获取 Error 样式 | +| getSuccessStyle | 获取 Success 样式 | +| setNormalStyle | 设置 Normal 样式 | +| setInfoStyle | 设置 Info 样式 | +| setWarningStyle | 设置 Warning 样式 | +| setErrorStyle | 设置 Error 样式 | +| setSuccessStyle | 设置 Success 样式 | +| getInfoDrawable | 获取 Info 样式 icon | +| getWarningDrawable | 获取 Warning 样式 icon | +| getErrorDrawable | 获取 Error 样式 icon | +| getSuccessDrawable | 获取 Success 样式 icon | +| normal | normal 样式 Toast | +| info | info 样式 Toast | +| warning | warning 样式 Toast | +| error | error 样式 Toast | +| success | success 样式 Toast | +| custom | custom Toast | + + +* **Simple Toast 工具类 ( 简单的 Toast 工具类, 支持子线程弹出 Toast ) ->** [ToastUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/ToastUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reset | 重置默认参数 | +| setToastFilter | 设置 Toast 过滤器 | +| setUseHandler | 设置是否使用 Handler 显示 Toast | +| setNullText | 设置 Text 为 null 的文本 | +| setUseConfig | 设置是否使用配置 | +| setGravity | 设置 Toast 显示在屏幕上的位置 | +| setMargin | 设置边距 | +| showShort | 显示 LENGTH_SHORT Toast | +| showLong | 显示 LENGTH_LONG Toast | +| showToast | 显示 Toast | +| showShortNew | 显示 new LENGTH_SHORT Toast | +| showLongNew | 显示 new LENGTH_LONG Toast | +| showToastNew | 显示新的 Toast | +| newToastText | 获取一个新的 Text Toast | +| showToastView | 显示 View Toast 方法 | +| newToastView | 获取一个新的 View Toast | + + +## **`dev.utils.app.toast.toaster`** + + +* **Toast 工具类 ( 支持子线程弹出 Toast, 处理无通知权限 ) ->** [DevToast.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DevToast.java) + +| 方法 | 注释 | +| :- | :- | +| reset | 重置默认参数 | +| setUseHandler | 设置是否使用 Handler 显示 Toast | +| setNullText | 设置 Text 为 null 的文本 | +| setTextLength | 设置 Toast 文案长度转换 显示时间 | +| initialize | 初始化调用 ( 内部已调用 ) | +| style | 使用单次 Toast 样式配置 | +| defaultStyle | 使用默认 Toast 样式 | +| getToastStyle | 获取 Toast 样式配置 | +| initStyle | 初始化 Toast 样式配置 | +| initToastFilter | 初始化 Toast 过滤器 | +| setView | 设置 Toast 显示的 View | +| show | 显示 Toast | +| cancel | 取消当前显示的 Toast | + + +## **`dev.utils.app.wifi`** + + +* **Wifi 热点工具类 ->** [WifiHotUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiHotUtils.java) + +| 方法 | 注释 | +| :- | :- | +| createWifiConfigToAp | 创建 Wifi 热点配置 ( 支持 无密码 / WPA2 PSK ) | +| startWifiAp | 开启 Wifi 热点 | +| closeWifiAp | 关闭 Wifi 热点 | +| getWifiApState | 获取 Wifi 热点状态 | +| getWifiApConfiguration | 获取 Wifi 热点配置信息 | +| setWifiApConfiguration | 设置 Wifi 热点配置信息 | +| isOpenWifiAp | 判断是否打开 Wifi 热点 | +| closeWifiApCheck | 关闭 Wifi 热点 ( 判断当前状态 ) | +| isConnectHot | 是否有设备连接热点 | +| getHotspotServiceIp | 获取热点主机 IP 地址 | +| getHotspotAllotIp | 获取连接上的子网关热点 IP ( 一个 ) | +| getConnectHotspotMsg | 获取连接的热点信息 | +| getHotspotSplitIpMask | 获取热点拼接后的 IP 网关掩码 | +| getApWifiSSID | 获取 Wifi 热点名 | +| getApWifiPwd | 获取 Wifi 热点密码 | +| setOnWifiAPListener | 设置 Wifi 热点监听事件 | + + +* **Wifi 工具类 ->** [WifiUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isOpenWifi | 判断是否打开 Wifi | +| openWifi | 打开 Wifi | +| closeWifi | 关闭 Wifi | +| toggleWifiEnabled | 自动切换 Wifi 开关状态 | +| getWifiState | 获取当前 Wifi 连接状态 | +| startScan | 开始扫描 Wifi | +| getConfiguration | 获取已配置 ( 连接过 ) 的 Wifi 配置 | +| getWifiList | 获取附近的 Wifi 列表 | +| getWifiInfo | 获取连接的 WifiInfo | +| getMacAddress | 获取 MAC 地址 | +| getBSSID | 获取连接的 BSSID | +| getIPAddress | 获取 IP 地址 | +| getNetworkId | 获取连接的 Network Id | +| getSSID | 获取 Wifi SSID | +| formatSSID | 判断是否存在 \"ssid\", 存在则裁剪返回 | +| getPassword | 获取处理后的密码 | +| isHexWepKey | 判断是否 wep 加密 | +| getWifiType | 获取加密类型 | +| getWifiTypeInt | 获取加密类型 | +| getWifiTypeStr | 获取加密类型 | +| isConnNull | 判断是否连接为 null ( unknown ssid ) | +| isConnectAPHot | 获取连接的 Wifi 热点 SSID | +| getSecurity | 获取 Wifi 加密类型 | +| isExistsPwd | 判断 Wifi 加密类型, 是否为加密类型 | +| isExists | 获取指定的 ssid 网络配置 ( 需连接保存过, 才存在 ) | +| delWifiConfig | 删除指定的 Wifi ( SSID ) 配置信息 | +| quickConnWifi | 快速连接 Wifi ( 不使用静态 IP 方式 ) | +| createWifiConfig | 创建 Wifi 配置信息 | +| removeWifiConfig | 移除 Wifi 配置信息 | +| disconnectWifi | 断开指定 networkId 的网络 | + + +## **`dev.utils.common`** + + +* **Array 数组工具类 ->** [ArrayUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ArrayUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断数组是否为 null | +| isNotEmpty | 判断数组是否不为 null | +| length | 获取数组长度 | +| isLength | 判断数组长度是否等于期望长度 | +| getCount | 获取数组长度总和 | +| getByArray | 获取数组对应索引数据 | +| get | 获取数组对应索引数据 | +| getFirst | 获取数组第一条数据 | +| getLast | 获取数组最后一条数据 | +| getPosition | 根据指定值获取 value 所在位置 + 偏移量的索引 | +| getNotNull | 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null | +| getPositionNotNull | 根据指定 value 获取索引, 不允许值为 null | +| intsToIntegers | int[] 转换 Integer[] | +| bytesToBytes | byte[] 转换 Byte[] | +| charsToCharacters | char[] 转换 Character[] | +| shortsToShorts | short[] 转换 Short[] | +| longsToLongs | long[] 转换 Long[] | +| floatsToFloats | float[] 转换 Float[] | +| doublesToDoubles | double[] 转换 Double[] | +| booleansToBooleans | boolean[] 转换 Boolean[] | +| integersToInts | Integer[] 转换 int[] | +| charactersToChars | Character[] 转换 char[] | +| asList | 转换数组为集合 | +| asListArgs | 转换数组为集合 | +| asListArgsInt | 转换数组为集合 | +| asListArgsByte | 转换数组为集合 | +| asListArgsChar | 转换数组为集合 | +| asListArgsShort | 转换数组为集合 | +| asListArgsLong | 转换数组为集合 | +| asListArgsFloat | 转换数组为集合 | +| asListArgsDouble | 转换数组为集合 | +| asListArgsBoolean | 转换数组为集合 | +| equals | 判断两个值是否一样 | +| arrayCopy | 拼接数组 | +| newArray | 创建指定长度数组 | +| subArray | 从数组上截取一段 | +| appendToString | 追加数组内容字符串 | +| getMinimumIndex | 获取数组中最小值索引 | +| getMaximumIndex | 获取数组中最大值索引 | +| getMinimum | 获取数组中最小值 | +| getMaximum | 获取数组中最大值 | +| sumArray | 计算数组总和 | + + +* **资金运算工具类 ->** [BigDecimalUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/BigDecimalUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setScale | 设置全局小数点保留位数、舍入模式 | +| getBigDecimal | 获取 BigDecimal | +| operation | 获取 Operation | +| adjustDouble | 获取自己想要的数据格式 | +| compareTo | 比较大小 | +| compareToThrow | 比较大小 ( 抛出异常 ) | +| add | 提供精确的加法运算 | +| subtract | 提供精确的减法运算 | +| multiply | 提供精确的乘法运算 | +| divide | 提供精确的除法运算 | +| remainder | 提供精确的取余运算 | +| round | 提供精确的小数位四舍五入处理 | +| addThrow | 提供精确的加法运算 ( 抛出异常 ) | +| subtractThrow | 提供精确的减法运算 ( 抛出异常 ) | +| multiplyThrow | 提供精确的乘法运算 ( 抛出异常 ) | +| divideThrow | 提供精确的除法运算 ( 抛出异常 ) | +| remainderThrow | 提供精确的取余运算 ( 抛出异常 ) | +| roundThrow | 提供精确的小数位四舍五入处理 ( 抛出异常 ) | +| getScale | 获取小数点保留位数 | +| getRoundingMode | 获取舍入模式 | +| requireNonNull | 检查 Value 是否为 null, 为 null 则抛出异常 | +| setBigDecimal | 设置 Value | +| getConfig | 获取配置信息 | +| setConfig | 设置配置信息 | +| removeConfig | 移除配置信息 | +| setScaleByConfig | 设置小数点保留位数、舍入模式 | +| isThrowError | 是否抛出异常 | +| setThrowError | 设置是否抛出异常 | +| clone | 克隆对象 | +| toString | 获取此 BigDecimal 的字符串表示形式科学记数法 | +| toPlainString | 获取此 BigDecimal 的字符串表示形式不带指数字段 | +| toEngineeringString | 获取此 BigDecimal 的字符串表示形式工程计数法 | +| intValue | 获取指定类型值 | +| floatValue | 获取指定类型值 | +| longValue | 获取指定类型值 | +| doubleValue | 获取指定类型值 | +| formatMoney | 金额分割, 四舍五入金额 | + + +* **日历工具类 ->** [CalendarUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/CalendarUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSupportLunar | 是否支持农历年份计算 | +| isSupportSolar | 是否支持公历年份计算 | +| solarToLunar | 公历转农历 | +| lunarToSolar | 农历转公历 | +| getLunarYearDays | 获取农历年份总天数 | +| getLunarLeapDays | 获取农历年份闰月天数 | +| getLunarLeapMonth | 获取农历年份哪个月是闰月 | +| getLunarMonthDays | 获取农历年份与月份总天数 | +| getLunarGanZhi | 获取干支历 | +| getLunarMonthChinese | 获取农历中文月份 | +| getLunarDayChinese | 获取农历中文天数 | +| getSolarTermsIndex | 获取二十四节气 ( 公历 ) 索引 | +| getSolarTerms | 获取二十四节气 ( 公历 ) | +| getSolarTermsDate | 获取二十四节气 ( 公历 ) 时间 | +| isFestival | 校验是否相同节日 | +| getFestival | 获取符合条件的节日信息 | +| getSolarFestival | 获取公历符合条件的节日信息 | +| getLunarFestival | 获取农历符合条件的节日信息 | +| getFestivalHook | 获取节日 Hook 接口 | +| setFestivalHook | 设置节日 Hook 接口 | + + +* **中文工具类 ->** [ChineseUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ChineseUtils.java) + +| 方法 | 注释 | +| :- | :- | +| randomWord | 随机生成汉字 | +| randomName | 随机生成名字 | +| numberToCHN | 数字转中文数值 | + + +* **类 ( Class ) 工具类 ->** [ClassUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ClassUtils.java) + +| 方法 | 注释 | +| :- | :- | +| newInstance | 根据类获取对象, 不再必须一个无参构造 | +| getDefaultPrimitiveValue | 获取 Class 原始类型值 | +| getClass | 获取 Object Class | +| isPrimitive | 判断 Class 是否为原始类型 | +| isCollection | 判断是否 Collection 类型 | +| isMap | 判断是否 Map 类型 | +| isArray | 判断是否 Array 类型 | +| isGenericParamType | 判断是否参数类型 | +| getGenericParamType | 获取参数类型 | +| getGenericSuperclass | 获取父类泛型类型 | +| getGenericInterfaces | 获取接口泛型类型 | + + +* **克隆工具类 ->** [CloneUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/CloneUtils.java) + +| 方法 | 注释 | +| :- | :- | +| deepClone | 进行克隆 | +| serializableToBytes | 通过序列化实体类, 获取对应的 byte[] 数据 | + + +* **关闭 ( IO 流 ) 工具类 ->** [CloseUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/CloseUtils.java) + +| 方法 | 注释 | +| :- | :- | +| closeIO | 关闭 IO | +| closeIOQuietly | 安静关闭 IO | +| flush | 将缓冲区数据输出 | +| flushQuietly | 安静将缓冲区数据输出 | +| flushCloseIO | 将缓冲区数据输出并关闭流 | +| flushCloseIOQuietly | 安静将缓冲区数据输出并关闭流 | + + +* **集合工具类 ( Collection - List、Set、Queue ) 等 ->** [CollectionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/CollectionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Collection 是否为 null | +| isNotEmpty | 判断 Collection 是否不为 null | +| length | 获取 Collection 长度 | +| isLength | 获取长度 Collection 是否等于期望长度 | +| greaterThan | 判断 Collection 长度是否大于指定长度 | +| greaterThanOrEqual | 判断 Collection 长度是否大于等于指定长度 | +| lessThan | 判断 Collection 长度是否小于指定长度 | +| lessThanOrEqual | 判断 Collection 长度是否小于等于指定长度 | +| getCount | 获取 Collection 数组长度总和 | +| get | 获取数据 | +| getFirst | 获取第一条数据 | +| getLast | 获取最后一条数据 | +| getPosition | 根据指定 value 获取 value 所在位置 + 偏移量的索引 | +| getPositionNotNull | 根据指定 value 获取索引, 不允许值为 null | +| getNext | 根据指定 value 获取 value 所在位置的下一个值 | +| getNextNotNull | 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null | +| getPrevious | 根据指定 value 获取 value 所在位置的上一个值 | +| getPreviousNotNull | 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null | +| add | 添加一条数据 | +| addNotNull | 添加一条数据 ( value 不允许为 null ) | +| addAll | 添加集合数据 | +| addAllNotNull | 添加集合数据 ( values 内的值不允许为 null ) | +| clearAndAddAll | 移除全部数据并添加集合数据 | +| clearAndAddAllNotNull | 移除全部数据并添加集合数据 ( values 内的值不允许为 null ) | +| remove | 移除一条数据 | +| removeAll | 移除集合数据 | +| clear | 清空集合中符合指定 value 的全部数据 | +| clearNotBelong | 保留集合中符合指定 value 的全部数据 | +| clearAll | 清空集合全部数据 | +| clearNull | 清空集合中为 null 的值 | +| isEqualCollection | 判断两个集合是否相同 | +| isEqualCollections | 判断多个集合是否相同 | +| union | 两个集合并集处理 | +| unions | 多个集合并集处理 | +| intersection | 两个集合交集处理 | +| disjunction | 两个集合交集的补集处理 | +| subtract | 两个集合差集 ( 扣除 ) 处理 | +| equals | 判断两个值是否一样 | +| toArray | 转换数组 to Object | +| toArrayT | 转换数组 to T | +| reverse | 集合翻转处理 | +| getMinimumIndexI | 获取集合中最小值索引 | +| getMinimumIndexL | 获取集合中最小值索引 | +| getMinimumIndexF | 获取集合中最小值索引 | +| getMinimumIndexD | 获取集合中最小值索引 | +| getMaximumIndexI | 获取集合中最大值索引 | +| getMaximumIndexL | 获取集合中最大值索引 | +| getMaximumIndexF | 获取集合中最大值索引 | +| getMaximumIndexD | 获取集合中最大值索引 | +| getMinimumI | 获取集合中最小值 | +| getMinimumL | 获取集合中最小值 | +| getMinimumF | 获取集合中最小值 | +| getMinimumD | 获取集合中最小值 | +| getMaximumI | 获取集合中最大值 | +| getMaximumL | 获取集合中最大值 | +| getMaximumF | 获取集合中最大值 | +| getMaximumD | 获取集合中最大值 | +| sumlistI | 计算集合总和 | +| sumlistL | 计算集合总和 | +| sumlistF | 计算集合总和 | +| sumlistD | 计算集合总和 | + + +* **颜色工具类 ( 包括常用的色值 ) ->** [ColorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ColorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| hexAlpha | 获取十六进制透明度字符串 | +| getARGB | 返回一个颜色 ARGB 色值数组 ( 返回十进制 ) | +| alpha | 返回一个颜色中的透明度值 ( 返回十进制 ) | +| alphaPercent | 返回一个颜色中的透明度百分比值 | +| red | 返回一个颜色中红色的色值 ( 返回十进制 ) | +| redPercent | 返回一个颜色中红色的百分比值 | +| green | 返回一个颜色中绿色的色值 ( 返回十进制 ) | +| greenPercent | 返回一个颜色中绿色的百分比值 | +| blue | 返回一个颜色中蓝色的色值 ( 返回十进制 ) | +| bluePercent | 返回一个颜色中蓝色的百分比值 | +| rgb | 根据对应的 red、green、blue 生成一个颜色值 | +| argb | 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) | +| isRGB | 判断颜色 RGB 是否有效 | +| isARGB | 判断颜色 ARGB 是否有效 | +| setAlpha | 设置透明度 | +| setRed | 改变颜色值中的红色色值 | +| setGreen | 改变颜色值中的绿色色值 | +| setBlue | 改变颜色值中的蓝色色值 | +| parseColor | 解析颜色字符串, 返回对应的颜色值 | +| intToRgbString | 颜色值 转换 RGB 颜色字符串 | +| intToArgbString | 颜色值 转换 ARGB 颜色字符串 | +| getRandomColor | 获取随机颜色值 | +| getRandomColorString | 获取随机颜色值字符串 | +| judgeColorString | 判断是否为 ARGB 格式的十六进制颜色, 例如: FF990587 | +| setDark | 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) | +| setLight | 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) | +| setAlphaDark | 设置透明度加深 | +| setAlphaLight | 设置透明度变浅 | +| grayLevel | 获取灰度值 | +| setParser | 设置 Color 解析器 | +| sortGray | 灰度值排序 | +| sortHUE | HSB ( HSV ) HUE 色相排序 | +| sortSaturation | HSB ( HSV ) Saturation 饱和度排序 | +| sortBrightness | HSB ( HSV ) Brightness 亮度排序 | +| blendColor | 使用给定的比例在两种 ARGB 颜色之间进行混合 | +| transitionColor | 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 | +| getKey | 获取 Key | +| getValue | 获取 Value | +| getValueParser | 获取 Value 解析后的值 ( 如: #000 => #000000 ) | +| getValueColor | 获取 ARGB/RGB color | +| getAlpha | 返回颜色中的透明度值 ( 返回十进制 ) | +| getRed | 返回颜色中红色的色值 ( 返回十进制 ) | +| getGreen | 返回颜色中绿色的色值 ( 返回十进制 ) | +| getBlue | 返回颜色中蓝色的色值 ( 返回十进制 ) | +| getGrayLevel | 获取灰度值 | +| getHue | 获取颜色色调 | +| getSaturation | 获取颜色饱和度 | +| getBrightness | 获取颜色亮度 | +| handleColor | 处理 color | + + +* **转换工具类 ( Byte、Hex 等 ) ->** [ConvertUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ConvertUtils.java) + +| 方法 | 注释 | +| :- | :- | +| convert | Object 转换所需类型对象 | +| newString | Object 转 String | +| newStringNotArrayDecode | Object 转 String ( 不进行 Array 解码转 String ) | +| toString | Object 转 String | +| toInt | Object 转 Integer | +| toBoolean | Object 转 Boolean | +| toFloat | Object 转 Float | +| toDouble | Object 转 Double | +| toLong | Object 转 Long | +| toShort | Object 转 Short | +| toChar | Object 转 Character | +| toByte | Object 转 Byte | +| toBigDecimal | Object 转 BigDecimal | +| toBigInteger | Object 转 BigInteger | +| toChars | Object 获取 char[] | +| toBytes | Object 获取 byte[] | +| toCharInt | char 转换 unicode 编码 | +| charAt | Object 获取 char ( 默认第一位 ) | +| parseInt | 字符串转换对应的进制 | +| parseLong | 字符串转换对应的进制 | +| bytesToObject | byte[] 转为 Object | +| objectToBytes | Object 转为 byte[] | +| bytesToChars | byte[] 转换 char[], 并且进行补码 | +| charsToBytes | char[] 转换 byte[] | +| intsToStrings | int[] 转换 string[] | +| doublesToStrings | double[] 转换 string[] | +| longsToStrings | long[] 转换 string[] | +| floatsToStrings | float[] 转换 string[] | +| intsToDoubles | int[] 转换 double[] | +| intsToLongs | int[] 转换 long[] | +| intsToFloats | int[] 转换 float[] | +| stringsToInts | string[] 转换 int[] | +| stringsToDoubles | string[] 转换 double[] | +| stringsToLongs | string[] 转换 long[] | +| stringsToFloats | string[] 转换 float[] | +| doublesToInts | double[] 转换 int[] | +| longsToInts | long[] 转换 int[] | +| floatsToInts | float[] 转换 int[] | +| toBinaryString | 将 字节转换 为 二进制字符串 | +| decodeBinary | 二进制字符串 转换 byte[] 解码 | +| isHex | 判断是否十六进制数据 | +| decodeHex | 将十六进制字节数组解码 | +| hexToInt | 十六进制 char 转换 int | +| toHexString | int 转换十六进制 | +| toHexChars | 将 string 转换为 十六进制 char[] | +| bytesBitwiseAND | 按位求补 byte[] 位移编解码 ( 共用同一个方法 ) | + + +* **坐标 ( GPS 纠偏 ) 相关工具类 ->** [CoordinateUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/CoordinateUtils.java) + +| 方法 | 注释 | +| :- | :- | +| bd09ToGcj02 | BD09 坐标转 GCJ02 坐标 | +| gcj02ToBd09 | GCJ02 坐标转 BD09 坐标 | +| gcj02ToWGS84 | GCJ02 坐标转 WGS84 坐标 | +| wgs84ToGcj02 | WGS84 坐标转 GCJ02 坐标 | +| bd09ToWGS84 | BD09 坐标转 WGS84 坐标 | +| wgs84ToBd09 | WGS84 坐标转 BD09 坐标 | +| outOfChina | 判断是否中国境外 | +| getDistance | 计算两个坐标相距距离 ( 单位: 米 ) | +| getAngle | 计算两个坐标的方向角度 | +| getDirection | 计算两个坐标的方向 | +| getValue | 获取中文方向值 | + + +* **日期工具类 ->** [DateUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/DateUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDefaultFormat | 获取默认 SimpleDateFormat ( yyyy-MM-dd HH:mm:ss ) | +| getSafeDateFormat | 获取对应时间格式线程安全 SimpleDateFormat | +| getCalendar | 获取 Calendar | +| getCurrentTime | 获取当前时间 Date | +| getCurrentTimeMillis | 获取当前时间毫秒 | +| getDateTime | 获取 Date Time | +| getDateNow | 获取当前时间的字符串 | +| formatDate | 将 Date 转换日期字符串 | +| formatTime | 将时间毫秒转换日期字符串 | +| parseDate | 将时间毫秒转换成 Date | +| parseLong | 解析时间字符串转换为 long 毫秒 | +| parseStringDefault | 解析时间字符串转换为指定格式字符串 | +| parseString | 解析时间字符串转换为指定格式字符串 | +| getYear | 获取年份 | +| getMonth | 获取月份 ( 0 - 11 ) + 1 | +| getDay | 获取天数 | +| getWeek | 获取星期数 ( 1 - 7、日 - 六 ) | +| get24Hour | 获取小时 ( 24 ) | +| get12Hour | 获取小时 ( 12 ) | +| getMinute | 获取分钟 | +| getSecond | 获取秒数 | +| isAM | 是否上午 | +| isPM | 是否下午 | +| isYear | 是否对应年份 | +| isMonth | 是否对应月份 | +| isDay | 是否对应天数 | +| isWeek | 是否对应星期 | +| isHour | 是否对应小时 | +| isMinute | 是否对应分钟 | +| isSecond | 是否对应秒数 | +| getSecondMultiple | 获取秒数倍数 | +| getMinuteMultiple | 获取分钟倍数 | +| getHourMultiple | 获取小时倍数 | +| getDayMultiple | 获取天数倍数 | +| getWeekMultiple | 获取周数倍数 | +| getMillisMultiple | 获取对应单位倍数 | +| getTimeDiffByCurrent | 获取时间差 ( 传入时间 - 当前时间 ) | +| getTimeDiff | 获取时间差 | +| isLeapYear | 判断是否闰年 | +| getMonthDayNumberAll | 根据年份、月份, 获取对应的天数 ( 完整天数, 无判断是否属于未来日期 ) | +| getYearMonthNumber | 根据年份, 获取对应的月份 | +| getMonthDayNumber | 根据年份、月份, 获取对应的天数 | +| timeAddZero | 时间补 0 处理 ( 小于 10, 则自动补充 0x ) | +| getArrayToHH | 生成 HH 按时间排序数组 | +| getListToHH | 生成 HH 按时间排序集合 | +| getArrayToMM | 生成 MM 按时间排序数组 | +| getListToMM | 生成 MM 按时间排序集合 | +| getArrayToHHMM | 生成 HH:mm 按间隔时间排序数组 | +| getListToHHMM | 生成 HH:mm 按间隔时间排序集合 | +| getListToHHMMPosition | 获取 HH:mm 按间隔时间排序的集合中, 指定时间所在索引 | +| millisToFitTimeSpan | 转换时间 | +| millisToTimeArrays | 转换时间为数组 | +| timeConvertByMillis | 传入时间毫秒, 获取 00:00:00 格式 ( 不处理大于一天 ) | +| timeConvertBySecond | 传入时间秒, 获取 00:00:00 格式 ( 不处理大于一天 ) | +| isInTime | 判断时间是否在 [startTime, endTime] 区间 | +| isInTimeFormat | 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) | +| isInTimeHHmm | 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) | +| isInTimeHHmmss | 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) | +| getEndTimeDiffHHmm | 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) | +| getEndTimeDiff | 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) | +| getZodiac | 获取生肖 | +| getConstellation | 获取星座 | +| getConstellationDate | 获取星座日期 | + + +* **开发常用方法工具类 ->** [DevCommonUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/DevCommonUtils.java) + +| 方法 | 注释 | +| :- | :- | +| timeRecord | 耗时时间记录 | +| getOperateTime | 获取操作时间 | +| sleepOperate | 堵塞操作 | +| isHttpRes | 判断是否网络资源 | +| whileMD5 | 循环 MD5 加密处理 | +| randomUUID | 获取随机唯一数 | +| randomUUIDToHashCode | 获取随机唯一数 HashCode | +| getRandomUUID | 获取随机规则生成 UUID | +| getRandomUUIDToString | 获取随机规则生成 UUID 字符串 | + + +* **编码工具类 ->** [EncodeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/EncodeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| base64Encode | Base64 编码 | +| base64EncodeToString | Base64 编码 | +| base64Decode | Base64 解码 | +| base64DecodeToString | Base64 解码 | +| htmlEncode | Html 字符串编码 | + + +* **变量字段工具类 ->** [FieldUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/FieldUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getField | 获取变量对象 | +| getDeclaredField | 获取变量对象 | +| getFields | 获取变量对象数组 | +| getDeclaredFields | 获取变量对象数组 | +| set | 设置字段的值 | +| get | 获取字段的值 | +| isLong | 是否 long/Long 类型 | +| isFloat | 是否 float/Float 类型 | +| isDouble | 是否 double/Double 类型 | +| isInteger | 是否 int/Integer 类型 | +| isBoolean | 是否 boolean/Boolean 类型 | +| isCharacter | 是否 char/Character 类型 | +| isByte | 是否 byte/Byte 类型 | +| isShort | 是否 short/Short 类型 | +| isString | 是否 String 类型 | +| isSerializable | 判断是否序列化 | +| isInvalid | 是否静态常量或者内部结构属性 | +| isStatic | 是否静态变量 | +| isFinal | 是否常量 | +| isStaticFinal | 是否静态变量 | +| isSynthetic | 是否内部结构属性 | +| getGenericType | 获取字段的泛型类型, 如果不带泛型返回 null | +| getComponentType | 获取数组的类型 | +| getAllDeclaredFields | 获取全部 Field, 包括父类 | + + +* **文件 ( IO 流 ) 工具类 ->** [FileIOUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/FileIOUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setBufferSize | 设置缓冲区的大小, 默认大小等于 8192 字节 | +| getFileInputStream | 获取输入流 | +| getFileOutputStream | 获取输出流 | +| writeFileFromIS | 通过输入流写入文件 | +| writeFileFromBytesByStream | 通过字节流写入文件 | +| writeFileFromBytesByChannel | 通过 FileChannel 把字节流写入文件 | +| writeFileFromBytesByMap | 通过 MappedByteBuffer 把字节流写入文件 | +| writeFileFromString | 通过字符串写入文件 | +| readFileToList | 读取文件内容, 返回换行 List | +| readFileToString | 读取文件内容, 返回字符串 | +| readFileToBytesByStream | 读取文件内容, 返回 byte[] | +| readFileToBytesByChannel | 通过 FileChannel, 读取文件内容, 返回 byte[] | +| readFileToBytesByMap | 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] | +| copyLarge | 复制 InputStream 到 OutputStream | + + +* **文件操作工具类 ->** [FileUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/FileUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getFile | 获取文件 | +| getFileByPath | 获取文件 | +| getFileCreateFolder | 获取路径, 并且进行创建目录 | +| getFilePathCreateFolder | 获取路径, 并且进行创建目录 | +| createFolder | 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) | +| createFolderByPath | 创建文件夹目录 ( 可以传入文件名 ) | +| createFolderByPaths | 创建多个文件夹, 如果不存在则创建 | +| createOrExistsDir | 判断目录是否存在, 不存在则判断是否创建成功 | +| createOrExistsFile | 判断文件是否存在, 不存在则判断是否创建成功 | +| createFileByDeleteOldFile | 判断文件是否存在, 存在则在创建之前删除 | +| createTimestampFileName | 通过文件后缀创建时间戳文件名 | +| createTimestampFileNameByName | 通过文件名创建时间戳文件名 | +| createTimestampFileNameByFile | 通过文件创建时间戳文件名 | +| createTimestampFileNameByPath | 通过文件路径创建时间戳文件名 | +| convertFiles | Path List 转 File List | +| convertPaths | File List 转 Path List | +| getPath | 获取文件路径 | +| getAbsolutePath | 获取文件绝对路径 | +| getName | 获取文件名 | +| getFileSuffix | 获取文件后缀名 ( 无 "." 单独后缀 ) | +| getFileNotSuffix | 获取文件名 ( 无后缀 ) | +| getFileNotSuffixToPath | 获取文件名 ( 无后缀 ) | +| getFileNameNoExtension | 获取路径中的不带扩展名的文件名 | +| getFileExtension | 获取路径中的文件扩展名 | +| isFileExists | 检查是否存在某个文件 | +| isFile | 判断是否文件 | +| isDirectory | 判断是否文件夹 | +| isHidden | 判断是否隐藏文件 | +| canRead | 文件是否可读 | +| canWrite | 文件是否可写 | +| canReadWrite | 文件是否可读写 | +| getFileLastModified | 获取文件最后修改的毫秒时间戳 | +| getFileCharsetSimple | 获取文件编码格式 | +| getFileLines | 获取文件行数 | +| getFileSize | 获取文件大小 | +| getDirSize | 获取目录大小 | +| getFileLength | 获取文件大小 | +| getDirLength | 获取目录全部文件大小 | +| getFileLengthNetwork | 获取文件大小 ( 网络资源 ) | +| getFileName | 获取路径中的文件名 | +| getDirName | 获取路径中的最长目录地址 | +| rename | 重命名文件 ( 同个目录下, 修改文件名 ) | +| formatFileSize | 传入文件路径, 返回对应的文件大小 | +| formatByteMemorySize | 字节数转合适内存大小 保留 3 位小数 | +| deleteFile | 删除文件 | +| deleteFiles | 删除多个文件 | +| deleteFolder | 删除文件夹 | +| saveFile | 保存文件 | +| appendFile | 追加文件 | +| readFileBytes | 读取文件 | +| readFile | 读取文件 | +| copyFile | 复制单个文件 | +| copyFolder | 复制文件夹 | +| moveFile | 移动 ( 剪切 ) 文件 | +| moveFolder | 移动 ( 剪切 ) 文件夹 | +| copyOrMoveDir | 复制或移动目录 | +| copyOrMoveFile | 复制或移动文件 | +| copyDir | 复制目录 | +| moveDir | 移动目录 | +| deleteDir | 删除目录 | +| deleteAllInDir | 删除目录下所有文件 | +| deleteFilesInDir | 删除目录下所有文件 | +| deleteFilesInDirWithFilter | 删除目录下所有过滤的文件 | +| listFilesInDir | 获取目录下所有文件 ( 不递归进子目录 ) | +| listFilesInDirWithFilter | 获取目录下所有过滤的文件 ( 不递归进子目录 ) | +| listFilesInDirBean | 获取目录下所有文件 ( 不递归进子目录 ) | +| listFilesInDirWithFilterBean | 获取目录下所有过滤的文件 ( 不递归进子目录 ) | +| listOrEmpty | 获取文件夹下的文件目录列表 ( 非全部子目录 ) | +| listFilesOrEmpty | 获取文件夹下的文件目录列表 ( 非全部子目录 ) | +| isImageFormats | 根据文件名判断文件是否为图片 | +| isAudioFormats | 根据文件名判断文件是否为音频 | +| isVideoFormats | 根据文件名判断文件是否为视频 | +| isFileFormats | 根据文件名判断文件是否为指定格式 | +| getFileMD5 | 获取文件 MD5 值 | +| getFileMD5ToHexString | 获取文件 MD5 值 | + + +* **格式化工具类 ->** [FormatUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/FormatUtils.java) + +| 方法 | 注释 | +| :- | :- | +| format | 字符串格式化 | +| unitSpanOf | 获取 UnitSpanFormatter | +| argsOf | 获取 ArgsFormatter | + + +* **循环工具类 ->** [ForUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ForUtils.java) + +| 方法 | 注释 | +| :- | :- | +| forArgs | 循环可变数组 | +| forSimpleArgs | 循环可变数组 | +| forInts | 循环可变数组 | +| forDoubles | 循环可变数组 | +| forFloats | 循环可变数组 | +| forLongs | 循环可变数组 | +| forBooleans | 循环可变数组 | +| forBytes | 循环可变数组 | +| forChars | 循环可变数组 | +| forShorts | 循环可变数组 | +| accept | 循环消费方法 | + + +* **Html 工具类 ->** [HtmlUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/HtmlUtils.java) + +| 方法 | 注释 | +| :- | :- | +| addRemovePaddingMargin | 为给定的 Html 移除 padding、margin | +| addHtmlColor | 为给定的字符串添加 HTML 颜色标记 | +| addHtmlBold | 为给定的字符串添加 HTML 加粗标记 | +| addHtmlColorAndBold | 为给定的字符串添加 HTML 颜色标记并加粗 | +| addHtmlUnderline | 为给定的字符串添加 HTML 下划线 | +| addHtmlStrikeThruLine | 为给定的字符串添加 HTML 中划线 | +| addHtmlOverLine | 为给定的字符串添加 HTML 上划线 | +| addHtmlIncline | 为给定的字符串添加 HTML 字体倾斜 | +| addHtmlSPAN | 为给定的字符串添加 HTML SPAN 标签 | +| addHtmlP | 为给定的字符串添加 HTML P 标签 | +| addHtmlIMG | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlIMGByWidth | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlIMGByHeight | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlDIV | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByMargin | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByPadding | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByMarginPadding | 为给定的字符串添加 HTML DIV 标签 | +| keywordReplaceHtmlColor | 将给定的字符串中所有给定的关键字标色 | + + +* **Http 参数工具类 ->** [HttpParamsUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/HttpParamsUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getUrlParams | 获取 Url 携带参数 | +| getUrlParamsArray | 获取 Url、携带参数 数组 | +| existsParams | 判断是否存在参数 | +| existsParamsByURL | 通过 Url 判断是否存在参数 | +| joinUrlParams | 拼接 Url 及携带参数 | +| getUrlParamsJoinSymbol | 获取 Url 及携带参数 拼接符号 | +| splitParamsByUrl | 通过 Url 拆分参数 | +| splitParams | 拆分参数 | +| joinParams | 拼接请求参数 | +| joinParamsObj | 拼接请求参数 | +| convertObjToMS | 进行转换对象处理 ( 请求发送对象 ) | +| convertObjToMO | 进行转换对象处理 ( 请求发送对象 ) | +| urlEncode | 进行 URL 编码, 默认 UTF-8 | + + +* **HttpURLConnection 网络工具类 ->** [HttpURLConnectionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/HttpURLConnectionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| doGetAsync | 异步的 Get 请求 | +| doPostAsync | 异步的 Post 请求 | +| request | 发送请求 | +| getNetTime | 获取网络时间 ( 默认使用百度链接 ) | + + +* **Map 工具类 ->** [MapUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/MapUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Map 是否为 null | +| isNotEmpty | 判断 Map 是否不为 null | +| length | 获取 Map 长度 | +| isLength | 获取长度 Map 是否等于期望长度 | +| greaterThan | 判断 Map 长度是否大于指定长度 | +| greaterThanOrEqual | 判断 Map 长度是否大于等于指定长度 | +| lessThan | 判断 Map 长度是否小于指定长度 | +| lessThanOrEqual | 判断 Map 长度是否小于等于指定长度 | +| getCount | 获取 Map 数组长度总和 | +| get | 获取 value | +| getKeyByValue | 通过 value 获取 key | +| getKeysByValue | 通过 value 获取 key 集合 ( 返回等于 value 的 key 集合 ) | +| getKeys | 通过 Map 获取 key 集合 | +| getKeysToArrays | 通过 Map 获取 key 数组 | +| getValues | 通过 Map 获取 value 集合 | +| getValuesToArrays | 通过 Map 获取 value 数组 | +| getFirst | 获取第一条数据 | +| getLast | 获取最后一条数据 | +| getNext | 根据指定 key 获取 key 所在位置的下一条数据 | +| getPrevious | 根据指定 key 获取 key 所在位置的上一条数据 | +| put | 添加一条数据 | +| putNotNull | 添加一条数据 ( 不允许 key 为 null ) | +| putAll | 添加多条数据 | +| putAllNotNull | 添加多条数据, 不允许 key 为 null | +| remove | 移除一条数据 | +| removeToKeys | 移除多条数据 | +| removeToValue | 移除等于 value 的所有数据 | +| removeToValues | 移除等于 value 的所有数据 ( Collection ) | +| equals | 判断两个值是否一样 | +| toggle | 切换保存状态 | +| isNullToValue | 判断指定 key 的 value 是否为 null | +| containsKey | 判断 Map 是否存储 key | +| containsValue | 判断 Map 是否存储 value | +| putToList | 添加一条数据 | +| removeToList | 移除一条数据 | +| removeToLists | 移除多条数据 | +| removeToMap | 移除多条数据 ( 通过 Map 进行移除 ) | +| mapToString | 键值对拼接 | + + +* **数字 ( 计算 ) 工具类 ->** [NumberUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/NumberUtils.java) + +| 方法 | 注释 | +| :- | :- | +| addZero | 补 0 处理 ( 小于 10, 则自动补充 0x ) | +| subZeroAndDot | 去掉结尾多余的 . 与 0 | +| calculateUnitD | 计算指定单位倍数 | +| calculateUnitI | 计算指定单位倍数 | +| calculateUnitL | 计算指定单位倍数 | +| calculateUnitF | 计算指定单位倍数 | +| percentD | 计算百分比值 ( 最大 100% ) | +| percentI | 计算百分比值 ( 最大 100% ) | +| percentL | 计算百分比值 ( 最大 100% ) | +| percentF | 计算百分比值 ( 最大 100% ) | +| percentD2 | 计算百分比值 ( 可超出 100% ) | +| percentI2 | 计算百分比值 ( 可超出 100% ) | +| percentL2 | 计算百分比值 ( 可超出 100% ) | +| percentF2 | 计算百分比值 ( 可超出 100% ) | +| multipleD | 获取倍数 | +| multipleI | 获取倍数 | +| multipleL | 获取倍数 | +| multipleF | 获取倍数 | +| multiple | 获取整数倍数 ( 自动补 1 ) | +| clamp | 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max | +| numberToCHN | 数字转中文数值 | +| isNumber | 检验数字 | +| isNumberDecimal | 检验数字或包含小数点 | + + +* **对象相关工具类 ->** [ObjectUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ObjectUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断对象是否为空 | +| isNotEmpty | 判断对象是否非空 | +| equals | 判断两个值是否一样 | +| requireNonNull | 检查对象是否为 null, 为 null 则抛出异常, 不为 null 则返回该对象 | +| getOrDefault | 获取非空或默认对象 | +| hashCode | 获取对象哈希值 | +| getObjectTag | 获取一个对象的独一无二的标记 | +| convert | Object 转换所需类型对象 | + + +* **随机工具类 ->** [RandomUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/RandomUtils.java) + +| 方法 | 注释 | +| :- | :- | +| nextBoolean | 获取伪随机 boolean 值 | +| nextBytes | 获取伪随机 byte[] | +| nextDouble | 获取伪随机 double 值 | +| nextGaussian | 获取伪随机高斯分布值 | +| nextFloat | 获取伪随机 float 值 | +| nextInt | 获取伪随机 int 值 | +| nextLong | 获取伪随机 long 值 | +| getRandomNumbers | 获取数字自定义长度的随机数 | +| getRandomLowerCaseLetters | 获取小写字母自定义长度的随机数 | +| getRandomCapitalLetters | 获取大写字母自定义长度的随机数 | +| getRandomLetters | 获取大小写字母自定义长度的随机数 | +| getRandomNumbersAndLetters | 获取数字、大小写字母自定义长度的随机数 | +| getRandom | 获取自定义数据自定义长度的随机数 | +| shuffle | 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) | +| shuffle2 | 洗牌算法 ( 第二种 ) 随机置换指定的数组使用的默认源的随机性 | +| nextIntRange | 获取指定范围 int 值 | +| nextLongRange | 获取指定范围 long 值 | +| nextDoubleRange | 获取指定范围 double 值 | +| ints | 获取随机 int[] | +| longs | 获取随机 long[] | +| doubles | 获取随机 double[] | + + +* **反射相关工具类 ->** [Reflect2Utils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/Reflect2Utils.java) + +| 方法 | 注释 | +| :- | :- | +| setProperty | 设置某个对象变量值 ( 可设置静态变量 ) | +| getProperty | 获取某个对象的变量 ( 可获取静态变量 ) | +| getStaticProperty | 获取某个类的静态变量 ( 只能获取静态变量 ) | +| invokeMethod | 执行某个对象方法 ( 可执行静态方法 ) | +| invokeStaticMethod | 执行某个类的静态方法 ( 只能执行静态方法 ) | +| newInstance | 新建实例 ( 构造函数创建 ) | +| isInstance | 是不是某个类的实例 | +| getArgsClass | 获取参数类型 | +| getPropertyByParent | 获取父类中的变量对象 | +| getDeclaredFieldParent | 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) | + + +* **反射相关工具类 ->** [ReflectUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ReflectUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reflect | 设置要反射的类 | +| newInstance | 实例化反射对象 | +| field | 设置反射的字段 | +| setEnumVal | 设置枚举值 | +| method | 设置反射的方法 | +| proxy | 根据类, 代理创建并返回对象 | +| type | 获取类型 | +| get | 获取反射想要获取的 | +| hashCode | 获取 HashCode | +| equals | 判断反射的两个对象是否一样 | +| toString | 获取反射获取的对象 | + + +* **计算比例工具类 ->** [ScaleUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ScaleUtils.java) + +| 方法 | 注释 | +| :- | :- | +| calcScale | 计算比例 ( 商 ) | +| calcScaleToMath | 计算比例 ( 被除数 ( 最大值 ) / 除数 ( 最小值 ) ) | +| calcScaleToWidth | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeight | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScale | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScale | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScale | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthI | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightI | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleI | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleI | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleI | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthL | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightL | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleL | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleL | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleL | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthF | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightF | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleF | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleF | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleF | 以高度为基准, 转换对应比例的宽度 | +| calcXY | 计算 XY 比 | + + +* **流操作工具类 ->** [StreamUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/StreamUtils.java) + +| 方法 | 注释 | +| :- | :- | +| inputToOutputStream | 输入流转输出流 | +| outputToInputStream | 输出流转输入流 | +| inputStreamToBytes | 输入流转 byte[] | +| bytesToInputStream | byte[] 转输出流 | +| outputStreamToBytes | 输出流转 byte[] | +| bytesToOutputStream | byte[] 转 输出流 | +| inputStreamToString | 输入流转 String | +| stringToInputStream | String 转换输入流 | +| outputStreamToString | 输出流转 String | +| stringToOutputStream | String 转 输出流 | + + +* **字符串工具类 ->** [StringUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/StringUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断字符串是否为 null | +| isEmptyClear | 判断字符串是否为 null ( 调用 clearSpaceTabLineTrim ) | +| isNotEmpty | 判断字符串是否不为 null | +| isNotEmptyClear | 判断字符串是否不为 null ( 调用 clearSpaceTabLineTrim ) | +| isNull | 判断字符串是否为 "null" | +| isNullClear | 判断字符串是否为 "null" ( 调用 clearSpaceTabLineTrim ) | +| isNotNull | 判断字符串是否不为 "null" | +| isNotNullClear | 判断字符串是否不为 "null" ( 调用 clearSpaceTabLineTrim ) | +| length | 获取字符串长度 | +| isLength | 获取字符串长度 是否等于期望长度 | +| equals | 判断两个值是否一样 | +| equalsNotNull | 判断两个值是否一样 ( 非 null 判断 ) | +| equalsIgnoreCase | 判断两个值是否一样 ( 忽略大小写 ) | +| equalsIgnoreCaseNotNull | 判断两个值是否一样 ( 忽略大小写 ) | +| isEquals | 判断多个字符串是否相等, 只有全相等才返回 true ( 对比大小写 ) | +| isOrEquals | 判断多个字符串, 只要有一个符合条件则通过 | +| isContains | 判断一堆值中, 是否存在符合该条件的 ( 包含 ) | +| isStartsWith | 判断内容, 是否属于特定字符串开头 ( 对比大小写 ) | +| isEndsWith | 判断内容, 是否属于特定字符串结尾 ( 对比大小写 ) | +| countMatches | 统计字符串匹配个数 | +| countMatches2 | 统计字符串匹配个数 | +| isSpace | 判断字符串是否为 null 或全为空白字符 | +| getBytes | 字符串 转 byte[] | +| clearSpace | 清空字符串全部空格 | +| clearTab | 清空字符串全部 Tab | +| clearLine | 清空字符串全部换行符 | +| clearLine2 | 清空字符串全部换行符 | +| clearSpaceTrim | 清空字符串前后全部空格 | +| clearTabTrim | 清空字符串前后全部 Tab | +| clearLineTrim | 清空字符串前后全部换行符 | +| clearLineTrim2 | 清空字符串前后全部换行符 | +| clearSpaceTabLine | 清空字符串全部空格、Tab、换行符 | +| clearSpaceTabLineTrim | 清空字符串前后全部空格、Tab、换行符 | +| appendSpace | 追加空格 | +| appendTab | 追加 Tab | +| appendLine | 追加换行 | +| appendLine2 | 追加换行 | +| forString | 循环指定数量字符串 | +| joinArgs | 循环拼接 | +| join | 循环拼接 | +| colonSplit | 冒号分割处理 | +| getString | 获取字符串 ( 判 null ) | +| checkValue | 检查字符串 | +| checkValues | 检查字符串 ( 多个值 ) | +| checkValuesSpace | 检查字符串 ( 多个值, 删除前后空格对比判断 ) | +| format | 字符串格式化 | +| argsFormat | 根据可变参数数量自动格式化 | +| concat | 字符串连接, 将参数列表拼接为一个字符串 | +| concatSpiltWith | 字符串连接, 将参数列表拼接为一个字符串 | +| concatSpiltWithIgnoreLast | 字符串连接, 将参数列表拼接为一个字符串 ( 最后一个不追加间隔 ) | +| appends | StringBuilder 拼接处理 | +| appendsIgnoreLast | StringBuilder 拼接处理 ( 最后一个不追加间隔 ) | +| gbkEncode | 字符串进行 GBK 编码 | +| gbk2312Encode | 字符串进行 GBK2312 编码 | +| utf8Encode | 字符串进行 UTF-8 编码 | +| strEncode | 进行字符串编码 | +| urlEncode | 进行 URL 编码, 默认 UTF-8 | +| urlDecode | 进行 URL 解码, 默认 UTF-8 | +| urlDecodeWhile | 进行 URL 解码, 默认 UTF-8 ( 循环到非 URL 编码为止 ) | +| ascii | 将字符串转移为 ASCII 码 | +| unicode | 将字符串转移为 Unicode 码 | +| unicodeString | 将字符数组转移为 Unicode 码 | +| dbc | 转化为半角字符 | +| sbc | 转化为全角字符 如: a = a, A = A | +| checkChineseToString | 检测字符串是否全是中文 | +| isChinese | 判断输入汉字 | +| upperFirstLetter | 首字母大写 | +| lowerFirstLetter | 首字母小写 | +| reverse | 反转字符串 | +| underScoreCaseToCamelCase | 下划线命名转为驼峰命名 | +| camelCaseToUnderScoreCase | 驼峰命名法转为下划线命名 | +| sqliteEscape | 字符串数据库字符转义 | +| convertHideMobile | 转换手机号 | +| convertSymbolHide | 转换符号处理 | +| subEllipsize | 裁剪超出的内容, 并且追加符号 ( 如 ... ) | +| subSymbolHide | 裁剪符号处理 | +| subSetSymbol | 裁剪内容 ( 设置符号处理 ) | +| substring | 裁剪字符串 | +| replaceSEWith | 替换特定字符串开头、结尾的字符串 | +| replaceStartsWith | 替换开头字符串 | +| replaceEndsWith | 替换结尾字符串 | +| clearSEWiths | 清空特定字符串开头、结尾的字符串 | +| clearStartsWith | 清空特定字符串开头的字符串 | +| clearEndsWith | 清空特定字符串结尾的字符串 | +| replaceAll | 替换字符串 | +| replaceAllToNull | 替换字符串 | +| replaceAlls | 替换字符串 | +| split | 拆分字符串 | + + +* **异常处理工具类 ->** [ThrowableUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ThrowableUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getThrowable | 获取异常信息 | +| getThrowableStackTrace | 获取异常栈信息 | + + +* **类型工具类 ->** [TypeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/TypeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getArrayType | 获取 Array Type | +| getListType | 获取 List Type | +| getSetType | 获取 Set Type | +| getMapType | 获取 Map Type | +| getType | 获取 Type | + + +* **压缩相关工具类 ->** [ZipUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/ZipUtils.java) + +| 方法 | 注释 | +| :- | :- | +| zipFiles | 批量压缩文件 | +| zipFile | 压缩文件 | +| unzipFile | 解压文件 | +| unzipFileByKeyword | 解压带有关键字的文件 | +| getFilesPath | 获取压缩文件中的文件路径链表 | +| getComments | 获取压缩文件中的注释链表 | + + +## **`dev.utils.common.able`** + + +## **`dev.utils.common.assist`** + + +* **均值计算 ( 用以统计平均数 ) 辅助类 ->** [Averager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/Averager.java) + +| 方法 | 注释 | +| :- | :- | +| add | 添加一个数字 | +| clear | 清除全部 | +| size | 获取参与均值计算的数字个数 | +| getAverage | 获取平均数 | +| print | 输出参与均值计算的数字 | + + +* **标记值计算存储 ( 位运算符 ) ->** [FlagsValue.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/FlagsValue.java) + +| 方法 | 注释 | +| :- | :- | +| getFlags | 获取 flags value | +| setFlags | 设置 flags value | +| addFlags | 添加 flags value | +| clearFlags | 移除 flags value | +| hasFlags | 是否存在 flags value | +| notHasFlags | 是否不存在 flags value | + + +* **时间均值计算辅助类 ->** [TimeAverager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/TimeAverager.java) + +| 方法 | 注释 | +| :- | :- | +| start | 开始计时 ( 毫秒 ) | +| end | 结束计时 ( 毫秒 ) | +| endAndRestart | 结束计时, 并重新启动新的计时 | +| average | 求全部计时均值 | +| print | 输出全部时间值 | +| clear | 清除计时数据 | + + +* **时间计时辅助类 ->** [TimeCounter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/TimeCounter.java) + +| 方法 | 注释 | +| :- | :- | +| start | 开始计时 ( 毫秒 ) | +| durationRestart | 获取持续的时间并重新启动 ( 毫秒 ) | +| duration | 获取持续的时间 ( 毫秒 ) | +| getStartTime | 获取开始时间 ( 毫秒 ) | + + +* **堵塞时间辅助类 ->** [TimeKeeper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/TimeKeeper.java) + +| 方法 | 注释 | +| :- | :- | +| waitForEndAsync | 设置等待一段时间后, 通知方法 ( 异步 ) | +| waitForEnd | 设置等待一段时间后, 通知方法 ( 同步 ) | + + +* **弱引用辅助类 ->** [WeakReferenceAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getSingleWeak | 获取单个弱引用对象 | +| getSingleWeakValue | 获取单个弱引用对象值 | +| setSingleWeakValue | 保存单个弱引用对象值 | +| removeSingleWeak | 移除单个弱引用持有对象 | +| getWeak | 获取弱引用对象 | +| getWeakValue | 获取弱引用对象值 | +| setWeakValue | 保存弱引用对象值 | +| removeWeak | 移除指定弱引用持有对象 | +| clear | 清空全部弱引用持有对象 | + + +## **`dev.utils.common.assist.record`** + + +* **文件记录分析工具类 ->** [FileRecordUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSuccessful | 校验记录方法返回字符串是否成功 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| setCallback | 设置文件记录回调 | +| getLogContent | 获取日志内容 | +| record | 记录方法 | + + +* **日志记录配置信息 ->** [RecordConfig.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordConfig.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取配置信息 | +| getStoragePath | 获取存储路径 | +| getFileName | 获取文件名 ( 固定 ) | +| getFolderName | 获取文件夹名 ( 模块名 ) | +| getFileIntervalTime | 获取文件记录间隔时间 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| isInsertHeaderData | 是否插入头数据 | +| setInsertHeaderData | 设置是否插入头数据 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| getFinalPath | 获取文件地址 | + + +* **日志记录插入信息 ->** [RecordInsert.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordInsert.java) + +| 方法 | 注释 | +| :- | :- | +| getFileInfo | getFileInfo | +| setFileInfo | setFileInfo | +| getLogHeader | getLogHeader | +| setLogHeader | setLogHeader | +| getLogTail | getLogTail | +| setLogTail | setLogTail | + + +## **`dev.utils.common.assist.search`** + + +* **文件广度优先搜索算法 ( 多线程 + 队列, 搜索某个目录下的全部文件 ) ->** [FileBreadthFirstSearchUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setSearchHandler | 设置搜索处理接口 | +| getQueueSameTimeNumber | 获取任务队列同时进行数量 | +| setQueueSameTimeNumber | 任务队列同时进行数量 | +| isRunning | 是否搜索中 | +| stop | 停止搜索 | +| isStop | 是否停止搜索 | +| getStartTime | 获取开始搜索时间 ( 毫秒 ) | +| getEndTime | 获取结束搜索时间 ( 毫秒 ) | +| getDelayTime | 获取延迟校验时间 ( 毫秒 ) | +| setDelayTime | 设置延迟校验时间 ( 毫秒 ) | +| query | 搜索目录 | + + +* **文件深度优先搜索算法 ( 递归搜索某个目录下的全部文件 ) ->** [FileDepthFirstSearchUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setSearchHandler | 设置搜索处理接口 | +| isRunning | 是否搜索中 | +| stop | 停止搜索 | +| isStop | 是否停止搜索 | +| getStartTime | 获取开始搜索时间 ( 毫秒 ) | +| getEndTime | 获取结束搜索时间 ( 毫秒 ) | +| query | 搜索目录 | + + +## **`dev.utils.common.assist.url`** + + +* **Dev 库 Java 通用 Url 解析器 ->** [DevJavaUrlParser.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java) + +| 方法 | 注释 | +| :- | :- | +| reset | reset | +| setUrl | setUrl | +| getUrl | getUrl | +| getUrlByPrefix | getUrlByPrefix | +| getUrlByParams | getUrlByParams | +| getUrlParams | getUrlParams | +| getUrlParamsDecode | getUrlParamsDecode | +| isConvertMap | isConvertMap | +| setConvertMap | setConvertMap | + + +* **Url 携带信息解析 ->** [UrlExtras.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/url/UrlExtras.java) + +| 方法 | 注释 | +| :- | :- | +| getUrl | 获取完整 Url | +| getUrlByPrefix | 获取 Url 前缀 ( 去除参数部分 ) | +| getUrlByParams | 获取 Url 参数部分字符串 | +| getUrlParams | 获取 Url Params Map | +| getUrlParamsDecode | 获取 Url Params Map ( 参数值进行 UrlDecode ) | +| getParser | 获取 Url 解析器 | +| setParser | 设置 Url 解析器 | +| reset | 重置并返回一个新的解析器 | +| setUrl | 设置完整 Url | +| isConvertMap | 是否解析、转换 Param Map | +| setConvertMap | 设置是否解析、转换 Param Map | + + +## **`dev.utils.common.cipher`** + + +* **Base64 工具类 ->** [Base64.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64.java) + +| 方法 | 注释 | +| :- | :- | +| decode | Decode the Base64-encoded data in input and return the data in | +| encodeToString | Base64-encode the given data and return a newly allocated | +| encode | Base64-encode the given data and return a newly allocated | + + +* **Base64 编解码 ( 并进行 ) 加解密 ->** [Base64Cipher.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64Cipher.java) + +| 方法 | 注释 | +| :- | :- | +| decrypt | 解码 | +| encrypt | 编码 | + + +* **加密工具类 ->** [CipherUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/cipher/CipherUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encrypt | 加密方法 | +| decrypt | 解密方法 | + + +## **`dev.utils.common.comparator`** + + +* **排序比较器工具类 ->** [ComparatorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/ComparatorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reverse | List 反转处理 | +| sort | List 排序处理 | +| sortAsc | List 升序处理 | +| sortDesc | List 降序处理 | +| sortFileLastModifiedAsc | 文件修改时间升序排序 | +| sortFileLastModifiedDesc | 文件修改时间降序排序 | +| sortFileLengthAsc | 文件大小升序排序 | +| sortFileLengthDesc | 文件大小降序排序 | +| sortFileNameAsc | 文件名升序排序 | +| sortFileNameDesc | 文件名降序排序 | +| sortFileAsc | 文件升序排序 | +| sortFileDesc | 文件降序排序 | +| sortDateAsc | Date 升序排序 | +| sortDateDesc | Date 降序排序 | +| sortDoubleAsc | Double 升序排序 | +| sortDoubleDesc | Double 降序排序 | +| sortFloatAsc | Float 升序排序 | +| sortFloatDesc | Float 降序排序 | +| sortIntAsc | Int 升序排序 | +| sortIntDesc | Int 降序排序 | +| sortLongAsc | Long 升序排序 | +| sortLongDesc | Long 降序排序 | +| sortStringAsc | String 升序排序 | +| sortStringDesc | String 降序排序 | +| sortStringWindowsSimpleAsc | String Windows 排序比较器简单实现升序排序 | +| sortStringWindowsSimpleDesc | String Windows 排序比较器简单实现降序排序 | +| sortStringWindowsSimple2Asc | String Windows 排序比较器简单实现升序排序 ( 实现方式二 ) | +| sortStringWindowsSimple2Desc | String Windows 排序比较器简单实现降序排序 ( 实现方式二 ) | +| sortWindowsExplorerFileSimpleComparatorAsc | Windows 目录资源文件升序排序 | +| sortWindowsExplorerFileSimpleComparatorDesc | Windows 目录资源文件降序排序 | +| sortWindowsExplorerFileSimpleComparator2Asc | Windows 目录资源文件升序排序 ( 实现方式二 ) | +| sortWindowsExplorerFileSimpleComparator2Desc | Windows 目录资源文件降序排序 ( 实现方式二 ) | +| sortWindowsExplorerStringSimpleComparatorAsc | Windows 目录资源文件名升序排序 | +| sortWindowsExplorerStringSimpleComparatorDesc | Windows 目录资源文件名降序排序 | +| sortWindowsExplorerStringSimpleComparator2Asc | Windows 目录资源文件名升序排序 ( 实现方式二 ) | +| sortWindowsExplorerStringSimpleComparator2Desc | Windows 目录资源文件名降序排序 ( 实现方式二 ) | + + +## **`dev.utils.common.comparator.sort`** + + +* **Date 排序值 ->** [DateSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSort.java) + +| 方法 | 注释 | +| :- | :- | +| getDateSortValue | getDateSortValue | + + +* **Date 升序排序 ->** [DateSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Date 降序排序 ->** [DateSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Double 排序值 ->** [DoubleSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java) + +| 方法 | 注释 | +| :- | :- | +| getDoubleSortValue | getDoubleSortValue | + + +* **Double 升序排序 ->** [DoubleSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Double 降序排序 ->** [DoubleSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件修改时间升序排序 ->** [FileLastModifiedSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件修改时间降序排序 ->** [FileLastModifiedSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件大小升序排序 ->** [FileLengthSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件大小降序排序 ->** [FileLengthSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件名升序排序 ->** [FileNameSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件名降序排序 ->** [FileNameSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件升序排序 ->** [FileSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件降序排序 ->** [FileSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Float 排序值 ->** [FloatSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSort.java) + +| 方法 | 注释 | +| :- | :- | +| getFloatSortValue | getFloatSortValue | + + +* **Float 升序排序 ->** [FloatSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Float 降序排序 ->** [FloatSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Int 排序值 ->** [IntSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSort.java) + +| 方法 | 注释 | +| :- | :- | +| getIntSortValue | getIntSortValue | + + +* **Int 升序排序 ->** [IntSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Int 降序排序 ->** [IntSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Long 排序值 ->** [LongSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSort.java) + +| 方法 | 注释 | +| :- | :- | +| getLongSortValue | getLongSortValue | + + +* **Long 升序排序 ->** [LongSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Long 降序排序 ->** [LongSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String 排序值 ->** [StringSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSort.java) + +| 方法 | 注释 | +| :- | :- | +| getStringSortValue | getStringSortValue | + + +* **String 升序排序 ->** [StringSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String 降序排序 ->** [StringSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String Windows 排序比较器简单实现 ->** [StringSortWindowsSimple.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String Windows 排序比较器简单实现 ->** [StringSortWindowsSimple2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件排序比较器 ->** [WindowsExplorerFileSimpleComparator.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件排序比较器 ->** [WindowsExplorerFileSimpleComparator2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件名排序比较器 ->** [WindowsExplorerStringSimpleComparator.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件名排序比较器 ->** [WindowsExplorerStringSimpleComparator2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +## **`dev.utils.common.encrypt`** + + +* **AES 对称加密工具类 ->** [AESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/AESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| initKey | 生成密钥 | +| encrypt | AES 加密 | +| decrypt | AES 解密 | + + +* **CRC 工具类 ->** [CRCUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/CRCUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getCRC32 | 获取 CRC32 值 | +| getCRC32ToHexString | 获取 CRC32 值 | +| getFileCRC32 | 获取文件 CRC32 值 | + + +* **DES 对称加密工具类 ->** [DESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/DESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDESKey | 获取可逆算法 DES 的密钥 | +| encrypt | DES 加密 | +| decrypt | DES 解密 | + + +* **加解密通用工具类 ->** [EncryptUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/EncryptUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encryptMD2 | MD2 加密 | +| encryptMD2ToHexString | MD2 加密 | +| encryptMD5 | MD5 加密 | +| encryptMD5ToHexString | MD5 加密 | +| encryptMD5File | 获取文件 MD5 值 | +| encryptMD5FileToHexString | 获取文件 MD5 值 | +| encryptSHA1 | SHA1 加密 | +| encryptSHA1ToHexString | SHA1 加密 | +| encryptSHA224 | SHA224 加密 | +| encryptSHA224ToHexString | SHA224 加密 | +| encryptSHA256 | SHA256 加密 | +| encryptSHA256ToHexString | SHA256 加密 | +| encryptSHA384 | SHA384 加密 | +| encryptSHA384ToHexString | SHA384 加密 | +| encryptSHA512 | SHA512 加密 | +| encryptSHA512ToHexString | SHA512 加密 | +| hashTemplate | Hash 加密模版方法 | +| encryptHmacMD5 | HmacMD5 加密 | +| encryptHmacMD5ToHexString | HmacMD5 加密 | +| encryptHmacSHA1 | HmacSHA1 加密 | +| encryptHmacSHA1ToHexString | HmacSHA1 加密 | +| encryptHmacSHA224 | HmacSHA224 加密 | +| encryptHmacSHA224ToHexString | HmacSHA224 加密 | +| encryptHmacSHA256 | HmacSHA256 加密 | +| encryptHmacSHA256ToHexString | HmacSHA256 加密 | +| encryptHmacSHA384 | HmacSHA384 加密 | +| encryptHmacSHA384ToHexString | HmacSHA384 加密 | +| encryptHmacSHA512 | HmacSHA512 加密 | +| encryptHmacSHA512ToHexString | HmacSHA512 加密 | +| hmacTemplate | Hmac 加密模版方法 | +| encryptDES | DES 加密 | +| encryptDESToBase64 | DES 加密 | +| encryptDESToHexString | DES 加密 | +| decryptDES | DES 解密 | +| decryptDESToBase64 | DES 解密 | +| decryptDESToHexString | DES 解密 | +| encrypt3DES | 3DES 加密 | +| encrypt3DESToBase64 | 3DES 加密 | +| encrypt3DESToHexString | 3DES 加密 | +| decrypt3DES | 3DES 解密 | +| decrypt3DESToBase64 | 3DES 解密 | +| decrypt3DESToHexString | 3DES 解密 | +| encryptAES | AES 加密 | +| encryptAESToBase64 | AES 加密 | +| encryptAESToHexString | AES 加密 | +| decryptAES | AES 解密 | +| decryptAESToBase64 | AES 解密 | +| decryptAESToHexString | AES 解密 | +| symmetricTemplate | 对称加密模版方法 | +| encryptRSA | RSA 加密 | +| encryptRSAToBase64 | RSA 加密 | +| encryptRSAToHexString | RSA 加密 | +| decryptRSA | RSA 解密 | +| decryptRSAToBase64 | RSA 解密 | +| decryptRSAToHexString | RSA 解密 | +| rsaTemplate | RSA 加解密模版方法 | + + +* **字符串 ( 编解码 ) 工具类 ->** [EscapeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/EscapeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| escape | 编码 | +| unescape | 解码 | + + +* **MD5 加密工具类 ->** [MD5Utils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/MD5Utils.java) + +| 方法 | 注释 | +| :- | :- | +| md5 | 加密内容 ( 32 位小写 MD5 ) | +| md5Upper | 加密内容 ( 32 位大写 MD5 ) | +| getFileMD5 | 获取文件 MD5 值 | +| getFileMD5ToHexString | 获取文件 MD5 值 | + + +* **SHA 加密工具类 ->** [SHAUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/SHAUtils.java) + +| 方法 | 注释 | +| :- | :- | +| sha1 | 加密内容 SHA1 | +| sha224 | 加密内容 SHA224 | +| sha256 | 加密内容 SHA256 | +| sha384 | 加密内容 SHA384 | +| sha512 | 加密内容 SHA512 | +| getFileSHA1 | 获取文件 SHA1 值 | +| getFileSHA256 | 获取文件 SHA256 值 | +| shaHex | 加密内容 SHA 模板 | +| getFileSHA | 获取文件 SHA 值 | + + +* **3DES 对称加密工具类 ->** [TripleDESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| initKey | 生成密钥 | +| encrypt | 3DES 加密 | +| decrypt | 3DES 解密 | + + +* **异或工具类 ->** [XorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/encrypt/XorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encryptAsFix | 加解密 ( 固定 Key 方式 ) 这种方式 加解密 方法共用 | +| encrypt | 加密 ( 非固定 Key 方式 ) | +| decrypt | 解密 ( 非固定 Key 方式 ) | +| xorChecksum | 数据异或校验位计算 | + + +## **`dev.utils.common.file`** + + +* **文件分片辅助类 ->** [FilePartAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/file/FilePartAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getFile | 获取文件 | +| getFileName | 获取文件名 | +| getFilePartItems | 获取文件分片信息集合 | +| getFilePartItem | 获取指定索引文件分片信息 | +| getPartCount | 获取分片总数 | +| existsPart | 是否存在分片 | +| isOnlyOne | 是否只有一个分片 | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | + + +* **文件分片信息 Item ->** [FilePartItem.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/file/FilePartItem.java) + +| 方法 | 注释 | +| :- | :- | +| isFirstItem | 判断是否 First Item | +| isLastItem | 判断是否 Last Item | +| existsPart | 是否存在分片 | +| isOnlyOne | 是否只有一个分片 | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | + + +* **文件分片工具类 ->** [FilePartUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/file/FilePartUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | +| getFilePartAssist | 获取文件分片辅助类 | +| isFilePart | 是否符合文件分片条件 | +| fileSplit | 文件拆分 | +| fileSplitSave | 文件拆分并存储 | +| fileSplitSaves | 文件拆分并存储 | +| fileSplitDelete | 删除拆分文件 | +| fileSplitDeletes | 删除拆分文件 | +| fileSplitMergePaths | 分片合并 | +| fileSplitMergeFiles | 分片合并 | +| fileSplitMerge | 分片合并 | + + +## **`dev.utils.common.format`** + + +* **可变数组格式化 ->** [ArgsFormatter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/format/ArgsFormatter.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 ArgsFormatter | +| getStartSpecifier | 获取开始占位说明符 | +| getMiddleSpecifier | 获取中间占位说明符 | +| getEndSpecifier | 获取结尾占位说明符 | +| isThrowError | 是否抛出异常 | +| getDefaultValue | 获取格式化异常默认值 | +| format | 根据可变参数数量自动格式化 | +| formatByArray | 根据可变参数数量自动格式化 | + + +* **单位数组范围格式化 ->** [UnitSpanFormatter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/format/UnitSpanFormatter.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 UnitSpanFormatter | +| getPrecision | 获取单位格式化精度 | +| isAppendZero | 是否自动补 0 | +| getDefaultValue | 获取格式化异常默认值 | +| format | 格式化 | +| formatBySpan | 计算指定单位倍数格式化 | + + +## **`dev.utils.common.random`** + + +* **随机概率采样算法 ->** [AliasMethod.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/random/AliasMethod.java) + +| 方法 | 注释 | +| :- | :- | +| next | 获取随机索引 ( 对应几率索引 ) | + + +## **`dev.utils.common.thread`** + + +* **线程池管理工具类 ->** [DevThreadManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevThreadManager 实例 | +| initConfig | 初始化配置信息 | +| putConfig | 添加配置信息 | +| removeConfig | 移除配置信息 | + + +* **线程池 ( 构建类 ) ->** [DevThreadPool.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java) + +| 方法 | 注释 | +| :- | :- | +| getThreads | 获取线程数 | +| getCalcThreads | 获取线程数 | +| execute | 加入到线程池任务队列 | +| shutdown | shutdown 会等待所有提交的任务执行完成, 不管是正在执行还是保存在任务队列中的已提交任务 | +| shutdownNow | shutdownNow 会尝试中断正在执行的任务 ( 其主要是中断一些指定方法如 sleep 方法 ) , 并且停止执行等待队列中提交的任务 | +| isShutdown | 判断线程池是否已关闭 ( isShutDown 当调用 shutdown() 方法后返回为 true ) | +| isTerminated | 若关闭后所有任务都已完成, 则返回 true | +| awaitTermination | 请求关闭、发生超时或者当前线程中断 | +| submit | 提交一个 Callable 任务用于执行 | +| invokeAll | 执行给定的任务 | +| invokeAny | 执行给定的任务 | +| schedule | 延迟执行 Runnable 命令 | +| scheduleWithFixedRate | 延迟并循环执行命令 | +| scheduleWithFixedDelay | 延迟并以固定休息时间循环执行命令 | + + +## **`dev.utils.common.validator`** + + +* **银行卡管理工具类 ->** [BankCheckUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/validator/BankCheckUtils.java) + +| 方法 | 注释 | +| :- | :- | +| checkBankCard | 校验银行卡卡号是否合法 | +| getBankCardCheckCode | 从不含校验位的银行卡卡号采用 Luhn 校验算法获取校验位 | +| getNameOfBank | 通过银行卡的 前六位确定 判断银行开户行及卡种 | + + +* **居民身份证工具类 ->** [IDCardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/validator/IDCardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| validateIdCard15 | 身份证校验规则, 验证 15 位身份编码是否合法 | +| validateIdCard18 | 身份证校验规则, 验证 18 位身份编码是否合法 | +| convert15CardTo18 | 将 15 位身份证号码转换为 18 位 | +| validateTWCard | 验证台湾身份证号码 | +| validateHKCard | 验证香港身份证号码 ( 部份特殊身份证无法检查 ) | +| validateIdCard10 | 判断 10 位数的身份证号, 是否合法 | +| validateCard | 验证身份证是否合法 | +| getAgeByIdCard | 根据身份编号获取年龄 | +| getBirthByIdCard | 根据身份编号获取生日 | +| getBirthdayByIdCard | 根据身份编号获取生日 | +| getYearByIdCard | 根据身份编号获取生日 ( 年份 ) | +| getMonthByIdCard | 根据身份编号获取生日 ( 月份 ) | +| getDateByIdCard | 根据身份编号获取生日 ( 天数 ) | +| getGenderByIdCard | 根据身份编号获取性别 | +| getProvinceByIdCard | 根据身份编号获取户籍省份 | +| getPowerSum | 将身份证的每位和对应位的加权因子相乘之后, 再获取和值 | +| getCheckCode18 | 将 POWER 和值与 11 取模获取余数进行校验码判断 | + + +* **校验工具类 ->** [ValidatorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/validator/ValidatorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| match | 通用匹配函数 | +| isNumber | 检验数字 | +| isNumberDecimal | 检验数字或包含小数点 | +| isLetter | 判断字符串是不是全是字母 | +| isContainNumber | 判断字符串是不是包含数字 | +| isNumberLetter | 判断字符串是不是只含字母和数字 | +| isSpec | 检验特殊符号 | +| isWx | 检验微信号 | +| isRealName | 检验真实姓名 | +| isNickName | 校验昵称 | +| isUserName | 校验用户名 | +| isPassword | 校验密码 | +| isEmail | 校验邮箱 | +| isUrl | 校验 URL | +| isIPAddress | 校验 IP 地址 | +| isChinese | 校验汉字 ( 无符号, 纯汉字 ) | +| isChineseAll | 判断字符串是不是全是中文 | +| isContainChinese | 判断字符串中包含中文、包括中文字符标点等 | + + +* **检验联系 ( 手机号码、座机 ) 工具类 ->** [ValiToPhoneUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isPhoneSimple | 中国手机号码格式验证 ( 简单手机号码校验 ) | +| isPhone | 是否中国手机号码 | +| isPhoneToChinaMobile | 是否中国移动手机号码 | +| isPhoneToChinaUnicom | 是否中国联通手机号码 | +| isPhoneToChinaTelecom | 是否中国电信手机号码 | +| isPhoneToChinaBroadcast | 是否中国广电手机号码 | +| isPhoneToChinaVirtual | 是否中国虚拟运营商手机号码 | +| isPhoneToChinaHkMobile | 是否中国香港手机号码 | +| isPhoneCallNum | 验证电话号码的格式 | \ No newline at end of file diff --git a/lib/DevApp/build.gradle b/lib/DevApp/build.gradle new file mode 100644 index 0000000000..67662b45b5 --- /dev/null +++ b/lib/DevApp/build.gradle @@ -0,0 +1,30 @@ +apply from: rootProject.file(files.lib_app_gradle) + +android.defaultConfig { + versionCode versions.dev_app_versionCode + versionName versions.dev_app_versionName + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" + // DevJava Module Version + buildConfigField "int", "DevJava_VersionCode", "${versions.dev_java_versionCode}" + buildConfigField "String", "DevJava_Version", "\"${versions.dev_java_version}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.androidx.design + api deps.androidx.appcompat + api deps.androidx.exifinterface +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevApp/proguard-rules.pro b/lib/DevApp/proguard-rules.pro new file mode 100644 index 0000000000..cdaf36490d --- /dev/null +++ b/lib/DevApp/proguard-rules.pro @@ -0,0 +1,43 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontwarn dev.** + +# 如果需要直接导出 ApkInfoItem、AppInfoItem、AppInfoBean、KeyValue 等实体类 JSON 字符串则在自己项目中加入以下忽略配置 +#-dontwarn android.support.** +# +## Understand the @Keep support annotation. +#-keep class android.support.annotation.Keep +# +#-keep @android.support.annotation.Keep class * {*;} +# +#-keepclasseswithmembers class * { +# @android.support.annotation.Keep ; +#} +# +#-keepclasseswithmembers class * { +# @android.support.annotation.Keep ; +#} +# +#-keepclasseswithmembers class * { +# @android.support.annotation.Keep (...); +#} \ No newline at end of file diff --git a/lib/DevApp/project.properties b/lib/DevApp/project.properties new file mode 100644 index 0000000000..c2965cefe1 --- /dev/null +++ b/lib/DevApp/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevAppX +project.groupId=io.github.afkt +project.artifactId=DevAppX +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevAppX \ No newline at end of file diff --git a/lib/DevApp/src/main/AndroidManifest.xml b/lib/DevApp/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e27e8e9c93 --- /dev/null +++ b/lib/DevApp/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/DevUtils.java b/lib/DevApp/src/main/java/dev/DevUtils.java new file mode 100644 index 0000000000..4204f4b4a8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/DevUtils.java @@ -0,0 +1,410 @@ +package dev; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.net.Uri; +import android.os.Handler; + +import androidx.core.content.FileProvider; + +import java.io.File; + +import dev.utils.BuildConfig; +import dev.utils.JCLogUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.HandlerUtils; +import dev.utils.app.UriUtils; +import dev.utils.app.assist.lifecycle.AbstractActivityLifecycle; +import dev.utils.app.assist.lifecycle.ActivityLifecycleAssist; +import dev.utils.app.assist.lifecycle.ActivityLifecycleFilter; +import dev.utils.app.assist.lifecycle.ActivityLifecycleGet; +import dev.utils.app.assist.lifecycle.ActivityLifecycleNotify; +import dev.utils.app.assist.record.AppRecordInsert; +import dev.utils.app.toast.toaster.DevToast; +import dev.utils.common.FileUtils; +import dev.utils.common.assist.record.FileRecordUtils; + +/** + * detail: 开发工具类 + * @author Ttt + *
+ *     GitHub
+ *     @see 
+ *     DevApp Api
+ *     @see 
+ *     DevAssist Api
+ *     @see 
+ *     DevBase README
+ *     @see 
+ *     DevBaseMVVM README
+ *     @see 
+ *     DevMVVM README
+ *     @see 
+ *     DevEngine README
+ *     @see 
+ *     DevHttpCapture Api
+ *     @see 
+ *     DevHttpManager Api
+ *     @see 
+ *     DevRetrofit Api
+ *     @see 
+ *     DevWidget Api
+ *     @see 
+ *     DevEnvironment Api
+ *     @see 
+ *     DevJava Api
+ *     @see 
+ * 
+ */ +public final class DevUtils { + + private DevUtils() { + } + + // 日志 TAG + public static final String TAG = DevUtils.class.getSimpleName(); + + // 全局 Application 对象 + private static Application sApplication; + // 全局 Context - getApplicationContext() + private static Context sContext; + // 是否内部 Debug 模式 + private static boolean sDebug = false; + + /** + * 初始化方法 ( 必须调用 ) + * @param context {@link Context} + */ + public static void init(final Context context) { + if (context == null) return; + + // 初始化全局 Context + initContext(context); + // 初始化全局 Application + initApplication(context); + // 注册 Activity 生命周期监听 + getLifecycleAssist().registerActivityLifecycleCallbacks(); + + // ================= + // = 初始化工具类相关 = + // ================= + + // 初始化 Record + FileRecordUtils.setRecordInsert(new AppRecordInsert(false)); + // 初始化 Toast + DevToast.initialize(context); + + // ============ + // = Java Log = + // ============ + + // 设置 Java 模块日志信息在 logcat 输出 + JCLogUtils.setPrint((logType, tag, message) -> { + switch (logType) { + case JCLogUtils.INFO: + LogPrintUtils.iTag(tag, message); + case JCLogUtils.ERROR: + LogPrintUtils.eTag(tag, message); + break; + case JCLogUtils.DEBUG: + default: + LogPrintUtils.dTag(tag, message); + break; + } + }); + } + + /** + * 初始化全局 Context + * @param context {@link Context} + */ + private static void initContext(final Context context) { + if (DevUtils.sContext == null && context != null) { + DevUtils.sContext = context.getApplicationContext(); + } + } + + /** + * 初始化全局 Application + * @param context {@link Context} + */ + private static void initApplication(final Context context) { + if (DevUtils.sApplication == null && context != null) { + try { + DevUtils.sApplication = (Application) context.getApplicationContext(); + } catch (Exception ignored) { + } + } + } + + /** + * 获取全局 Context + * @return {@link Context} + */ + public static Context getContext() { + return DevUtils.sContext; + } + + /** + * 获取 Context ( 判断 null, 视情况返回全局 Context ) + * @param context {@link Context} + * @return {@link Context} + */ + public static Context getContext(final Context context) { + return (context != null) ? context : DevUtils.sContext; + } + + /** + * 获取全局 Application + * @return {@link Application} + */ + public static Application getApplication() { + if (DevUtils.sApplication != null) return DevUtils.sApplication; + try { + Application application = getApplicationByReflect(); + init(application); // 初始化操作 + return application; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApplication"); + } + return null; + } + + // = + + /** + * 反射获取 Application + * @return {@link Application} + */ + private static Application getApplicationByReflect() { + try { + @SuppressLint("PrivateApi") + Class activityThread = Class.forName("android.app.ActivityThread"); + Object thread = activityThread.getMethod("currentActivityThread").invoke(null); + Object app = activityThread.getMethod("getApplication").invoke(thread); + if (app == null) { + throw new NullPointerException("u should init first"); + } + return (Application) app; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApplicationByReflect"); + } + throw new NullPointerException("u should init first"); + } + + // = + + /** + * 获取 Handler + * @return {@link Handler} + */ + public static Handler getHandler() { + return HandlerUtils.getMainHandler(); + } + + /** + * 获取 Handler ( 判断 null, 视情况返回全局 Handler ) + * @param handler {@link Handler} + * @return {@link Handler} + */ + public static Handler getHandler(final Handler handler) { + if (handler != null) return handler; + return HandlerUtils.getMainHandler(); + } + + /** + * 执行 UI 线程任务 + * @param runnable 线程任务 + */ + public static void runOnUiThread(final Runnable runnable) { + HandlerUtils.postRunnable(runnable); + } + + /** + * 执行 UI 线程任务 ( 延时执行 ) + * @param runnable 线程任务 + * @param delayMillis 延时执行时间 ( 毫秒 ) + */ + public static void runOnUiThread( + final Runnable runnable, + final long delayMillis + ) { + HandlerUtils.postRunnable(runnable, delayMillis); + } + + /** + * 开启日志开关 + */ + public static void openLog() { + // 专门打印 Android 日志信息 + LogPrintUtils.setPrintLog(true); + // 专门打印 Java 日志信息 + JCLogUtils.setPrintLog(true); + } + + /** + * 标记 Debug 模式 + */ + public static void openDebug() { + DevUtils.sDebug = true; + } + + /** + * 判断是否 Debug 模式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDebug() { + return sDebug; + } + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + public static int getDevAppVersionCode() { + return BuildConfig.DevApp_VersionCode; + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + public static String getDevAppVersion() { + return BuildConfig.DevApp_Version; + } + + /** + * 获取 DevJava 版本号 + * @return DevJava version + */ + public static int getDevJavaVersionCode() { + return BuildConfig.DevJava_VersionCode; + } + + /** + * 获取 DevJava 版本 + * @return DevJava version + */ + public static String getDevJavaVersion() { + return BuildConfig.DevJava_Version; + } + + // ================ + // = Activity 监听 = + // ================ + + // ActivityLifecycleAssist 实例 + private static volatile ActivityLifecycleAssist sInstance; + + /** + * 获取 ActivityLifecycleAssist 管理实例 + * @return {@link ActivityLifecycleAssist} + */ + private static ActivityLifecycleAssist getLifecycleAssist() { + if (sInstance == null) { + synchronized (ActivityLifecycleAssist.class) { + if (sInstance == null) { + sInstance = new ActivityLifecycleAssist(); + } + } + } + return sInstance; + } + + /** + * 获取 Activity 生命周期 相关信息获取接口类 + * @return {@link ActivityLifecycleGet} + */ + public static ActivityLifecycleGet getActivityLifecycleGet() { + return getLifecycleAssist().getActivityLifecycleGet(); + } + + /** + * 获取 Activity 生命周期 事件监听接口类 + * @return {@link ActivityLifecycleNotify} + */ + public static ActivityLifecycleNotify getActivityLifecycleNotify() { + return getLifecycleAssist().getActivityLifecycleNotify(); + } + + /** + * 获取 Top Activity + * @return {@link Activity} + */ + public static Activity getTopActivity() { + return getLifecycleAssist().getTopActivity(); + } + + /** + * 设置 Activity 生命周期 过滤判断接口 + * @param activityLifecycleFilter Activity 过滤判断接口 + */ + public static void setActivityLifecycleFilter(final ActivityLifecycleFilter activityLifecycleFilter) { + getLifecycleAssist().setActivityLifecycleFilter(activityLifecycleFilter); + } + + /** + * 设置 ActivityLifecycle 监听回调 + * @param abstractActivityLifecycle Activity 生命周期监听类 + */ + public static void setAbstractActivityLifecycle(final AbstractActivityLifecycle abstractActivityLifecycle) { + getLifecycleAssist().setAbstractActivityLifecycle(abstractActivityLifecycle); + } + + // ================ + // = FileProvider = + // ================ + + // 获取 lib utils fileProvider + public static final String LIB_FILE_PROVIDER = "devapp.provider"; + + /** + * 获取 FileProvider Authority + * @return FileProvider Authority + */ + public static String getAuthority() { + try { + return DevUtils.getContext().getPackageName() + "." + LIB_FILE_PROVIDER; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAuthority"); + } + return null; + } + + /** + * 获取 FileProvider File Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForFile(final File file) { + return UriUtils.getUriForFile(file, getAuthority()); + } + + /** + * 获取 FileProvider File Path Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForPath(final String filePath) { + return UriUtils.getUriForFile(FileUtils.getFileByPath(filePath), getAuthority()); + } + + /** + * detail: FileProvider + * @author Ttt + */ + public static final class FileProviderDevApp + extends FileProvider { + @Override + public boolean onCreate() { + init(getContext().getApplicationContext()); + return true; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/DevFinal.java b/lib/DevApp/src/main/java/dev/utils/DevFinal.java new file mode 100644 index 0000000000..ee49580c4f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/DevFinal.java @@ -0,0 +1,1355 @@ +package dev.utils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; + +/** + * detail: 常量类 + * @author Ttt + */ +public final class DevFinal { + + private DevFinal() { + } + + /** + * detail: 工具类内部返回值等常量定义 + * @author Ttt + */ + public static final class INNER { + + // 异常错误返回值 + public static final int ERROR_INT = -1; + } + + /** + * detail: 符号、标记字符串常量 + * @author Ttt + */ + public static final class SYMBOL { + + // 空格 字符串 + public static final String SPACE = " "; + // TAB 字符串 + public static final String TAB = "\t"; + // 回车 ( CR ) 字符串 + public static final String CR = "\r"; + // 换行 ( \n ) 字符串 ( single newline ('\n') character ) + public static final String NL = "\n"; + public static final char NL_CHAR = '\n'; + // 点 字符串 + public static final String POINT = "."; + // 横杠 字符串 + public static final String HYPHEN = "-"; + // 下划线 字符串 + public static final String UNDERSCORE = "_"; + // 冒号 字符串 + public static final String COLON = ":"; + // 逗号 字符串 + public static final String COMMA = ","; + // 顿号 字符串 + public static final String COMMA2 = "、"; + // 分号 字符串 + public static final String SEMICOLON = ";"; + // 百分号 字符串 + public static final String PERCENT = "%"; + // 反斜杠 字符串 + public static final String BACKSLASH = "\\"; + // 斜杠 字符串 + public static final String SLASH = "/"; + // 换行字符串 + public static final String NEW_LINE = System.getProperty("line.separator"); + // 换行字符串 ( 两行 ) + public static final String NEW_LINE_X2 = NEW_LINE + NEW_LINE; + // 换行字符串 ( 四行 ) + public static final String NEW_LINE_X4 = NEW_LINE_X2 + NEW_LINE_X2; + // null 对象字符串 + public static final String NULL = "null"; + // "" 对象字符串 + public static final String EMPTY = ""; + } + + /** + * detail: 编码格式字符串常量 + * @author Ttt + */ + public static final class ENCODE { + + public static final String UNICODE = "Unicode"; + public static final String US_ASCII = "US-ASCII"; + public static final String ISO_8859_1 = "ISO-8859-1"; + public static final String UTF_8 = "UTF-8"; + public static final String UTF_16BE = "UTF-16BE"; + public static final String UTF_16LE = "UTF-16LE"; + public static final String UTF_16 = "UTF-16"; + public static final String GBK = "GBK"; + public static final String GBK_2312 = "GBK-2312"; + } + + /** + * detail: 时间格式字符串常量 + * @author Ttt + */ + public static final class TIME { + + // 一分钟 60 秒 + public static final int MINUTE_S = 60; + // 一小时 60 * 60 秒 + public static final int HOUR_S = 3600; + // 一天 24 * 60 * 60 秒 + public static final int DAY_S = 86400; + // 周与秒的倍数 + public static final int WEEK_S = DAY_S * 7; + // 月与秒的倍数 + public static final int MONTH_S = DAY_S * 30; + // 年与秒的倍数 + public static final int YEAR_S = DAY_S * 365; + + // 秒与毫秒的倍数 + public static final long SECOND_MS = 1000; + // 分与毫秒的倍数 + public static final long MINUTE_MS = SECOND_MS * 60; + // 时与毫秒的倍数 + public static final long HOUR_MS = MINUTE_MS * 60; + // 天与毫秒的倍数 + public static final long DAY_MS = HOUR_MS * 24; + // 周与毫秒的倍数 + public static final long WEEK_MS = DAY_MS * 7; + // 月与毫秒的倍数 + public static final long MONTH_MS = DAY_MS * 30; + // 年与毫秒的倍数 + public static final long YEAR_MS = DAY_MS * 365; + + // ========== + // = 时间格式 = + // ========== + + public static final String yy = "yy"; + public static final String yyyy = "yyyy"; + public static final String MM = "MM"; + public static final String dd = "dd"; + public static final String HH = "HH"; + public static final String mm = "mm"; + public static final String ss = "ss"; + + public static final String yyMMdd = "yyMMdd"; + public static final String yyMMdd_POINT = "yy.MM.dd"; + public static final String yyMMdd_HYPHEN = "yy-MM-dd"; + public static final String yyMMdd_UNDERSCORE = "yy_MM_dd"; + + public static final String yyyyMMdd = "yyyyMMdd"; + public static final String yyyyMMdd_POINT = "yyyy.MM.dd"; + public static final String yyyyMMdd_HYPHEN = "yyyy-MM-dd"; + public static final String yyyyMMdd_UNDERSCORE = "yyyy_MM_dd"; + + public static final String yyyyMMddHHmm = "yyyyMMddHHmm"; + public static final String yyyyMMddHHmm_POINT = "yyyy.MM.dd HH:mm"; + public static final String yyyyMMddHHmm_HYPHEN = "yyyy-MM-dd HH:mm"; + public static final String yyyyMMddHHmm_UNDERSCORE = "yyyy_MM_dd HH:mm"; + + public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; + public static final String yyyyMMddHHmmss_POINT = "yyyy.MM.dd HH:mm:ss"; + public static final String yyyyMMddHHmmss_HYPHEN = "yyyy-MM-dd HH:mm:ss"; + public static final String yyyyMMddHHmmss_UNDERSCORE = "yyyy_MM_dd HH:mm:ss"; + + public static final String MMdd = "MMdd"; + public static final String MMdd_POINT = "MM.dd"; + public static final String MMdd_HYPHEN = "MM-dd"; + public static final String MMdd_UNDERSCORE = "MM_dd"; + + public static final String HHmm = "HHmm"; + public static final String HHmm_COLON = "HH:mm"; + public static final String HHmm_POINT = "HH.mm"; + public static final String HHmm_HYPHEN = "HH-mm"; + public static final String HHmm_UNDERSCORE = "HH_mm"; + + public static final String HHmmss = "HHmmss"; + public static final String HHmmss_COLON = "HH:mm:ss"; + public static final String HHmmss_POINT = "HH.mm.ss"; + public static final String HHmmss_HYPHEN = "HH-mm-ss"; + public static final String HHmmss_UNDERSCORE = "HH_mm_ss"; + + public static final String SPECIAL_mmddHHmmyyyyss = "MMddHHmmyyyy.ss"; + + // ============= + // = 中文时间格式 = + // ============= + + public static final String ZH_yy = "yy年"; + public static final String ZH_yyyy = "yyyy年"; + public static final String ZH_MM = "MM月"; + public static final String ZH_dd = "dd日"; + public static final String ZH_HH = "HH时"; + public static final String ZH_mm = "mm分"; + public static final String ZH_ss = "ss秒"; + + public static final String ZH_yyMMdd = "yy年MM月dd日"; + public static final String ZH_yyyyMMdd = "yyyy年MM月dd日"; + public static final String ZH_yyyyMMddHHmm = "yyyy年MM月dd日HH时mm分"; + public static final String ZH_yyyyMMddHHmmss = "yyyy年MM月dd日HH时mm分ss秒"; + public static final String ZH_MMdd = "MM月dd日"; + public static final String ZH_HHmm = "HH时mm分"; + public static final String ZH_HHmmss = "HH时mm分ss秒"; + } + + /** + * detail: 时间格式字符串常量 + * @author Ttt + *
+     *     存储历史时间格式常量, 方便更新库过渡使用
+     *     并标记已弃用, 提醒使用 {@link TIME} 类
+     *     随时删除请及时替换
+     * 
+ */ + @Deprecated + public static final class TIME_DEPRECATED { + + // 一分钟 60 秒 + public static final int MINUTE_S = TIME.MINUTE_S; + // 一小时 60 * 60 秒 + public static final int HOUR_S = TIME.HOUR_S; + // 一天 24 * 60 * 60 秒 + public static final int DAY_S = TIME.DAY_S; + // 秒与毫秒的倍数 + public static final long SECOND = TIME.SECOND_MS; + // 分与毫秒的倍数 + public static final long MINUTE = TIME.MINUTE_MS; + // 时与毫秒的倍数 + public static final long HOUR = TIME.HOUR_MS; + // 天与毫秒的倍数 + public static final long DAY = TIME.DAY_MS; + // 周与毫秒的倍数 + public static final long WEEK = TIME.WEEK_MS; + // 月与毫秒的倍数 + public static final long MONTH = TIME.MONTH_MS; + // 年与毫秒的倍数 + public static final long YEAR = TIME.YEAR_MS; + + public static final String yyyy = TIME.yyyy; + public static final String yyMMdd = TIME.yyMMdd_HYPHEN; + public static final String yyMMdd2 = TIME.yyMMdd; + public static final String yyyyMMdd = TIME.yyyyMMdd_HYPHEN; + public static final String yyyyMMdd2 = TIME.yyyyMMdd; + public static final String yyyyMMdd3 = TIME.ZH_yyyyMMdd; + public static final String yyyyMMdd4 = TIME.yyyyMMdd_UNDERSCORE; + public static final String yyyyMMdd5 = TIME.yyyyMMdd_POINT; + + public static final String yyyyMMddHHmm = TIME.yyyyMMddHHmm_HYPHEN; + public static final String yyyyMMddHHmm2 = "yyyy年M月d日 HH:mm"; + public static final String yyyyMMddHHmm3 = TIME.yyyyMMddHHmm_POINT; + + public static final String yyyyMMddHHmmss = TIME.yyyyMMddHHmmss_HYPHEN; + public static final String yyyyMMddHHmmss2 = "yyyy年M月d日 HH:mm:ss"; + public static final String yyyyMMddHHmmss3 = "yyyyMMdd_HHmmss"; + public static final String yyyyMMddHHmmss4 = "yyyyMMdd.HHmmss"; + + public static final String MMdd = TIME.MMdd_HYPHEN; + public static final String MMdd2 = TIME.ZH_MMdd; + public static final String MMdd3 = TIME.MMdd; + public static final String yy = TIME.yy; + public static final String MM = TIME.MM; + public static final String dd = TIME.dd; + public static final String hh = "hh"; + public static final String HH = TIME.HH; + public static final String mm = TIME.mm; + public static final String HHmm = TIME.HHmm_COLON; + public static final String HHmm2 = TIME.HHmm; + public static final String HHmmss = TIME.HHmmss_COLON; + public static final String HHmmss2 = TIME.HHmmss; + public static final String hhmmMMDDyyyy = "hh:mm M月d日 yyyy"; + public static final String hhmmssMMDDyyyy = "hh:mm:ss M月d日 yyyy"; + public static final String mmddHHmmyyyyss = TIME.SPECIAL_mmddHHmmyyyyss; + } + + /** + * detail: String 类型常量 + * @author Ttt + */ + public static final class STR { + + // ======= + // = 通用 = + // ======= + + public static final String DEFAULT = "default"; + public static final String NONE = "none"; + public static final String OBJECT = "object"; + public static final String UNKNOWN = "unknown"; + + public static final String BUG = "bug"; + public static final String CHANNEL = "channel"; + public static final String CHARSET = "charset"; + public static final String CMD = "cmd"; + public static final String CODE = "code"; + public static final String COMPONENT = "component"; + public static final String CORE = "core"; + public static final String ENGINE = "engine"; + public static final String FETCH = "fetch"; + public static final String FLAG = "flag"; + public static final String FROM = "from"; + public static final String GROUP = "group"; + public static final String HASH = "hash"; + public static final String LIB = "lib"; + public static final String LIBS = "libs"; + public static final String LIMIT = "limit"; + public static final String MATCH = "match"; + public static final String MODEL = "model"; + public static final String MODULE = "module"; + public static final String OBTAIN = "obtain"; + public static final String OWNER = "owner"; + public static final String PLUGIN = "plugin"; + public static final String RESET = "reset"; + public static final String ROUTER = "router"; + public static final String SAFE = "safe"; + public static final String SAFETY = "safety"; + public static final String SHARE = "share"; + public static final String STANDARD = "standard"; + public static final String TARGET = "target"; + public static final String TEMPLATE = "template"; + public static final String TO = "to"; + + public static final String DECRYPT = "decrypt"; + public static final String ENCRYPT = "encrypt"; + public static final String PREFIX = "prefix"; + public static final String SUFFIX = "suffix"; + + public static final String BASE = "base"; + public static final String BEAN = "bean"; + public static final String VO = "vo"; + + public static final String HIGH = "high"; + public static final String LOW = "low"; + public static final String MAX = "max"; + public static final String MAX_LENGTH = "max_length"; + public static final String MAX_SIZE = "max_size"; + public static final String MIN = "min"; + public static final String MIN_LENGTH = "min_length"; + public static final String MIN_SIZE = "min_size"; + + public static final String EVENT = "event"; + public static final String LINK = "link"; + public static final String LISTENER = "listener"; + public static final String LOG = "log"; + public static final String MESSAGE = "message"; + public static final String REPORT = "report"; + public static final String TRACK = "track"; + + public static final String DATABASE = "database"; + public static final String DB = "db"; + + public static final String BLANK = "blank"; + public static final String GLOBAL = "global"; + public static final String HOME = "home"; + public static final String MAIN = "main"; + public static final String PRIVACY = "privacy"; + public static final String PROFILES = "profiles"; + public static final String SETTING = "setting"; + public static final String SETTINGS = "settings"; + + // ======= + // = 其他 = + // ======= + + public static final String ATTACH = "attach"; + public static final String AUTO = "auto"; + public static final String BALANCER = "balancer"; + public static final String BANK = "bank"; + public static final String BANNER = "banner"; + public static final String BLOCK = "block"; + public static final String BUCKET = "bucket"; + public static final String CONTENT = "content"; + public static final String EDIT = "edit"; + public static final String ELASTIC = "elastic"; + public static final String FACTORY = "factory"; + public static final String GOTO = "goto"; + public static final String IMPL = "impl"; + public static final String INDENT = "indent"; + public static final String INVENTORY = "inventory"; + public static final String KIND = "kind"; + public static final String LEVEL = "level"; + public static final String LOADER = "loader"; + public static final String MENU = "menu"; + public static final String MORE = "more"; + public static final String NUMBER = "number"; + public static final String OF = "of"; + public static final String ONLY = "only"; + public static final String OPERATE = "operate"; + public static final String OPTIONS = "options"; + public static final String ORIGINAL = "original"; + public static final String OTHER = "other"; + public static final String QUICK = "quick"; + public static final String RANGE = "range"; + public static final String REMARK = "remark"; + public static final String SANDBOX = "sandbox"; + public static final String SCORE = "score"; + public static final String SKIP = "skip"; + public static final String SMS = "sms"; + public static final String TIMING = "timing"; + public static final String TITLE = "title"; + public static final String TRANSFER = "transfer"; + public static final String WITH = "with"; + + // ========== + // = 信息相关 = + // ========== + + public static final String ACCOUNT = "account"; + public static final String ADDRESS = "address"; + public static final String AREA = "area"; + public static final String CITY = "city"; + public static final String EMAIL = "email"; + public static final String INFO = "info"; + public static final String LATITUDE = "latitude"; + public static final String LONGITUDE = "longitude"; + public static final String MOBILE = "mobile"; + public static final String NAME = "name"; + public static final String PASSWORD = "password"; + public static final String PHONE = "phone"; + public static final String PROVINCE = "province"; + public static final String REGION = "region"; + public static final String SPEC = "spec"; + public static final String USER = "user"; + public static final String USER_ID = "user_id"; + + public static final String ACCESS = "access"; + public static final String ID = "id"; + public static final String IDENTITY = "identity"; + public static final String TOKEN = "token"; + public static final String UNIQUE = "unique"; + public static final String UUID = "uuid"; + + // ======= + // = 媒体 = + // ======= + + public static final String AUDIO = "audio"; + public static final String IMAGE = "image"; + public static final String IMAGES = "images"; + public static final String MEDIA = "media"; + public static final String MEDIA_TYPE = "media_type"; + public static final String TEXT = "text"; + public static final String THUMBNAIL = "thumbnail"; + public static final String VIDEO = "video"; + public static final String WATERMARK = "watermark"; + + public static final String AAC = "aac"; + public static final String AVI = "avi"; + public static final String AVIF = "avif"; + public static final String BMP = "bmp"; + public static final String GIF = "gif"; + public static final String HEIF = "heif"; + public static final String ICON = "icon"; + public static final String JPEG = "jpeg"; + public static final String JPG = "jpg"; + public static final String JSON = "json"; + public static final String MP3 = "mp3"; + public static final String MP4 = "mp4"; + public static final String PNG = "png"; + public static final String TXT = "txt"; + public static final String WEBP = "webp"; + public static final String WEBP_LOSSLESS = "webp_lossless"; + public static final String WEBP_LOSSY = "webp_lossy"; + public static final String XML = "xml"; + + public static final String ALBUM = "album"; + public static final String BEGIN_TIME = "begin_time"; + public static final String COMPRESS = "compress"; + public static final String COVER = "cover"; + public static final String CROP = "crop"; + public static final String DURATION = "duration"; + public static final String END_TIME = "end_time"; + public static final String EXIF = "exif"; + public static final String EXIF_TAG = "exif_tag"; + public static final String MIME_TYPE = "mimetype"; + public static final String PLAY_TIME = "play_time"; + public static final String PREVIEW = "preview"; + public static final String QUALITY = "quality"; + public static final String RECORD = "record"; + public static final String TIME = "time"; + public static final String TIMESTAMP = "timestamp"; + public static final String VALID_TIME = "valid_time"; + + // ========== + // = 时间相关 = + // ========== + + public static final String CALENDAR = "calendar"; + + public static final String DAY = "day"; + public static final String HOUR = "hour"; + public static final String MILLI_SECOND = "milli_second"; + public static final String MINUTE = "minute"; + public static final String MONTH = "month"; + public static final String SECOND = "second"; + public static final String WEEK = "week"; + public static final String YEAR = "year"; + + // =============== + // = 状态、操作相关 = + // =============== + + public static final String BIND = "bind"; + public static final String UN_BINDER = "un_binder"; + + public static final String ACCEPT = "accept"; + public static final String ACTIVATED = "activated"; + public static final String ACTIVE = "active"; + public static final String AFTER = "after"; + public static final String ALLOW = "allow"; + public static final String ASYNC = "async"; + public static final String BEFORE = "before"; + public static final String CANCEL = "cancel"; + public static final String CHECKABLE = "checkable"; + public static final String CLOSE = "close"; + public static final String COMPLETE = "complete"; + public static final String CONFIRM = "confirm"; + public static final String CONNECT = "connect"; + public static final String CONNECTED = "connected"; + public static final String CONNECTING = "connecting"; + public static final String CREATE = "create"; + public static final String DELAY = "delay"; + public static final String DELETE = "delete"; + public static final String DELIMITER = "delimiter"; + public static final String DENIED = "denied"; + public static final String DESTROY = "destroy"; + public static final String DISABLED = "disabled"; + public static final String DISABLING = "disabling"; + public static final String DISCONNECT = "disconnect"; + public static final String DISCONNECTED = "disconnected"; + public static final String DISK = "disk"; + public static final String DOWNLOAD = "download"; + public static final String DOWNLOADS = "downloads"; + public static final String ENABLED = "enabled"; + public static final String ENABLING = "enabling"; + public static final String END = "end"; + public static final String EXECUTE = "execute"; + public static final String FAIL = "fail"; + public static final String FINISH = "finish"; + public static final String FOUND = "found"; + public static final String GRANTED = "granted"; + public static final String ING = "ing"; + public static final String INIT = "init"; + public static final String INSERT = "insert"; + public static final String INVALID = "invalid"; + public static final String LAUNCH = "launch"; + public static final String LOAD = "load"; + public static final String LOADING = "loading"; + public static final String MARKER = "marker"; + public static final String METADATA = "metadata"; + public static final String NEED = "need"; + public static final String NEXT = "next"; + public static final String NORMAL = "normal"; + public static final String NOT_FOUND = "not_found"; + public static final String NOW = "now"; + public static final String OPEN = "open"; + public static final String OVERWRITE = "overwrite"; + public static final String PAUSE = "pause"; + public static final String PERIOD = "period"; + public static final String PLAY = "play"; + public static final String POOL = "pool"; + public static final String RECYCLE = "recycle"; + public static final String REFRESH = "refresh"; + public static final String REQUEST = "request"; + public static final String REQUIRE = "require"; + public static final String RESPONSE = "response"; + public static final String RESTART = "restart"; + public static final String RESULT = "result"; + public static final String RESUME = "resume"; + public static final String SCOPE = "scope"; + public static final String SHUTDOWN = "shutdown"; + public static final String SLEEP = "sleep"; + public static final String START = "start"; + public static final String STATE = "state"; + public static final String STOP = "stop"; + public static final String SUBMIT = "submit"; + public static final String SUCCESS = "success"; + public static final String SUSPEND = "suspend"; + public static final String SUSPENDED = "suspended"; + public static final String SYNC = "sync"; + public static final String TERMINATED = "terminated"; + public static final String TRUNCATED = "truncated"; + public static final String UNCONNECT = "unconnect"; + public static final String UPLOAD = "upload"; + public static final String VALID = "valid"; + public static final String WAITING = "waiting"; + + // ========== + // = 平台相关 = + // ========== + + public static final String ANDROID = "android"; + public static final String H5 = "h5"; + public static final String IOS = "ios"; + public static final String MIN_IPROGRAM = "min_iprogram"; + public static final String PLATFORM = "platform"; + public static final String WEB = "web"; + + // =============== + // = UI、APP 相关 = + // =============== + + public static final String BOTTOM = "bottom"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String TOP = "top"; + + public static final String BORDER_WIDTH = "border_width"; + public static final String DASH_WIDTH = "dash_width"; + public static final String HEIGHT = "height"; + public static final String MAX_WIDTH = "max_width"; + public static final String MIN_HEIGHT = "min_height"; + public static final String SCALE_HEIGHT = "scale_height"; + public static final String SCALE_WIDTH = "scale_width"; + public static final String SCREEN_HEIGHT = "screen_height"; + public static final String SCREEN_WIDTH = "screen_width"; + public static final String STROKE_WIDTH = "stroke_width"; + public static final String WIDTH = "width"; + public static final String WIDTH_HEIGHT = "width_height"; + + public static final String ANIMATION = "animation"; + public static final String BACKGROUND = "background"; + public static final String BOLD = "bold"; + public static final String CENTER = "center"; + public static final String CHECK = "check"; + public static final String CHECKBOX = "checkbox"; + public static final String CHECKED = "checked"; + public static final String CHOOSE = "choose"; + public static final String CHOOSE_MODE = "choose_mode"; + public static final String COLOR = "color"; + public static final String DOWN = "down"; + public static final String FOCUSED = "focused"; + public static final String HORIZONTAL = "horizontal"; + public static final String INFLATER = "inflater"; + public static final String LAYOUT = "layout"; + public static final String MEASURE = "measure"; + public static final String MOVE = "move"; + public static final String ORIENTATION = "orientation"; + public static final String OUTSIDE = "outside"; + public static final String PRESS = "press"; + public static final String PRESSED = "pressed"; + public static final String PROGRESS = "progress"; + public static final String SCALE = "scale"; + public static final String SCREEN = "screen"; + public static final String SCROLL = "scroll"; + public static final String SCROLLING = "scrolling"; + public static final String SELECT = "select"; + public static final String SELECTED = "selected"; + public static final String SLIDE = "slide"; + public static final String SLIDING = "sliding"; + public static final String UP = "up"; + public static final String VERTICAL = "vertical"; + public static final String WEIGHT = "weight"; + public static final String WIDGET = "widget"; + public static final String X = "x"; + public static final String Y = "y"; + + public static final String GRADIENT = "gradient"; + public static final String SHAPE = "shape"; + public static final String SOLID = "solid"; + public static final String STROKE = "stroke"; + + public static final String DEBUG = "debug"; + public static final String RELEASE = "release"; + public static final String UPGRADE = "upgrade"; + public static final String VERSION = "version"; + public static final String VERSION_CODE = "version_code"; + public static final String VERSION_NAME = "version_name"; + + public static final String ACCESSIBILITY = "accessibility"; + public static final String ACTION = "action"; + public static final String ACTIVITY = "activity"; + public static final String ADAPTER = "adapter"; + public static final String APPLICATION = "application"; + public static final String BROADCAST = "broadcast"; + public static final String BUNDLE = "bundle"; + public static final String CANVAS = "canvas"; + public static final String CATEGORY = "category"; + public static final String COMPOSE = "compose"; + public static final String CORNER = "corner"; + public static final String CURSOR = "cursor"; + public static final String DASH = "dash"; + public static final String DIALOG = "dialog"; + public static final String DIRECTION = "direction"; + public static final String DISCRETE = "discrete"; + public static final String DISTANCE = "distance"; + public static final String DRAW = "draw"; + public static final String EFFECT = "effect"; + public static final String EXTRA = "extra"; + public static final String EXTRAS = "extras"; + public static final String FILTER = "filter"; + public static final String FONT = "font"; + public static final String FONT_FAMILY = "font_family"; + public static final String FONT_STYLE = "font_style"; + public static final String FRAGMENT = "fragment"; + public static final String GALLERY = "gallery"; + public static final String GRAPHICS = "graphics"; + public static final String GRID = "grid"; + public static final String HANDLER = "handler"; + public static final String HOLDER = "holder"; + public static final String INSETS = "insets"; + public static final String INTENT = "intent"; + public static final String INTERPOLATOR = "interpolator"; + public static final String INVALIDATE = "invalidate"; + public static final String ITEM_COUNT = "item_count"; + public static final String ITEM_DECORATION = "item_decoration"; + public static final String LAUNCHER = "launcher"; + public static final String MATRIX = "matrix"; + public static final String NOTIFY = "notify"; + public static final String OFFSETS = "offsets"; + public static final String OUTLINE = "outline"; + public static final String PAINT = "paint"; + public static final String PATH_EFFECT = "path_effect"; + public static final String POINT = "point"; + public static final String POINTF = "pointf"; + public static final String POST_INVALIDATE = "post_invalidate"; + public static final String RECEIVE = "receive"; + public static final String RECT = "rect"; + public static final String RECTF = "rectf"; + public static final String RENDER = "render"; + public static final String SERVICE = "service"; + public static final String SHADER = "shader"; + public static final String SPACE = "space"; + public static final String SPAN = "span"; + public static final String SPAN_COUNT = "span_count"; + public static final String TOAST = "toast"; + public static final String TYPEFACE = "typeface"; + public static final String VIBRATE = "vibrate"; + public static final String VIEW = "view"; + + public static final String BINDING = "binding"; + public static final String LIFECYCLE = "lifecycle"; + public static final String LIVE_DATA = "live_data"; + public static final String PERMISSION = "permission"; + public static final String VIEW_MODEL = "view_model"; + + public static final String IMAGE_VIEW = "image_view"; + public static final String RECYCLE_VIEW = "recycle_view"; + public static final String SCROLL_VIEW = "scroll_view"; + public static final String TEXT_VIEW = "text_view"; + public static final String VIEW_GROUP = "view_group"; + public static final String VIEW_PAGER = "view_pager"; + + public static final String CHILD = "child"; + public static final String COMPILE = "compile"; + public static final String DEVICE = "device"; + public static final String ELEMENT = "element"; + public static final String ENVIRONMENT = "environment"; + public static final String MEMORY = "memory"; + public static final String PACKAGE = "package"; + public static final String PACKNAME = "packname"; + + public static final String ASSETS = "assets"; + public static final String ASSIST = "assist"; + public static final String CAMERA = "camera"; + public static final String CAPTURE = "capture"; + public static final String RAW = "raw"; + public static final String RES = "res"; + public static final String RICH_TEXT = "rich_text"; + public static final String SOURCE = "source"; + public static final String STYLE = "style"; + public static final String TRANSFORM = "transform"; + + public static final String BODY = "body"; + public static final String BROWSER = "browser"; + public static final String CACHE = "cache"; + public static final String CLIENT = "client"; + public static final String CONFIG = "config"; + public static final String COOKIE = "cookie"; + public static final String COPY = "copy"; + public static final String DNS = "dns"; + public static final String DOMAIN = "domain"; + public static final String EBS = "ebs"; + public static final String FOOTER = "footer"; + public static final String FOREGROUND = "foreground"; + public static final String HEAD = "head"; + public static final String HEADER = "header"; + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String LANGUAGE = "language"; + public static final String OSS = "oss"; + public static final String PASTE = "paste"; + public static final String PATCH = "patch"; + public static final String POST = "post"; + public static final String SESSION = "session"; + public static final String SLB = "slb"; + public static final String SSL = "ssl"; + public static final String TIMEOUT = "timeout"; + public static final String TRACE = "trace"; + public static final String UNLINK = "unlink"; + public static final String URI = "uri"; + public static final String URL = "url"; + public static final String WRAPPED = "wrapped"; + public static final String WWW = "www"; + + // ========== + // = 数据相关 = + // ========== + + public static final String FALSE = "false"; + public static final String TRUE = "true"; + + public static final String ARRAY = "array"; + public static final String BOOLEAN = "boolean"; + public static final String BYTE = "byte"; + public static final String CHAR = "char"; + public static final String DATE = "date"; + public static final String DOUBLE = "double"; + public static final String FLOAT = "float"; + public static final String INT = "int"; + public static final String INTEGER = "integer"; + public static final String LIST = "list"; + public static final String LONG = "long"; + public static final String MAP = "map"; + public static final String STRING = "string"; + + public static final String BINARY = "binary"; + public static final String DEC = "dec"; + public static final String DECODE = "decode"; + public static final String ENCODE = "encode"; + public static final String HEX = "hex"; + public static final String OCT = "oct"; + + public static final String AES = "aes"; + public static final String BASE64 = "base64"; + public static final String CRC32 = "crc32"; + public static final String DES = "des"; + public static final String DES3 = "des3"; + public static final String ESCAPE = "escape"; + public static final String HMACMD5 = "hmacmd5"; + public static final String HMACSHA1 = "hmacsha1"; + public static final String HMACSHA224 = "hmacsha224"; + public static final String HMACSHA256 = "hmacsha256"; + public static final String HMACSHA384 = "hmacsha384"; + public static final String HMACSHA512 = "hmacsha512"; + public static final String MD2 = "md2"; + public static final String MD5 = "md5"; + public static final String RSA = "rsa"; + public static final String SHA1 = "sha1"; + public static final String SHA224 = "sha224"; + public static final String SHA256 = "sha256"; + public static final String SHA384 = "sha384"; + public static final String SHA512 = "sha512"; + public static final String TRIPLEDES = "tripledes"; + public static final String UNESCAPE = "unescape"; + public static final String XOR = "xor"; + + // ======== + // = 关键字 = + // ======== + + public static final String CATCH = "catch"; + public static final String CRASH = "crash"; + public static final String ERROR = "error"; + public static final String EXCEPTION = "exception"; + public static final String EXIT = "exit"; + public static final String THROWABLE = "throwable"; + public static final String TRY = "try"; + + public static final String ADD = "add"; + public static final String APPEND = "append"; + public static final String ARGS = "args"; + public static final String COUNT = "count"; + public static final String CURRENT = "current"; + public static final String CYCLE = "cycle"; + public static final String DIFF = "diff"; + public static final String FIND = "find"; + public static final String FIRST = "first"; + public static final String GET = "get"; + public static final String INDEX = "index"; + public static final String ITEM = "item"; + public static final String LAST = "last"; + public static final String LOOP = "loop"; + public static final String MIDDLE = "middle"; + public static final String PAGE = "page"; + public static final String PAGER = "pager"; + public static final String POSITION = "position"; + public static final String PUT = "put"; + public static final String QUERY = "query"; + public static final String REMOVE = "remove"; + public static final String SET = "set"; + public static final String SINGLE = "single"; + public static final String SIZE = "size"; + public static final String SORT = "sort"; + public static final String SUB = "sub"; + public static final String TAB = "tab"; + public static final String TAG = "tag"; + public static final String TAKE = "take"; + public static final String UPDATE = "update"; + + public static final String AGENT = "agent"; + public static final String ALIAS = "alias"; + public static final String CUSTOM = "custom"; + public static final String DATA = "data"; + public static final String DIR = "dir"; + public static final String DIRECTORY = "directory"; + public static final String FILE = "file"; + public static final String FOLD = "fold"; + public static final String IGNORE = "ignore"; + public static final String INPUT = "input"; + public static final String KEY = "key"; + public static final String KEYWORD = "keyword"; + public static final String MISSING = "missing"; + public static final String OUTPUT = "output"; + public static final String PATH = "path"; + public static final String PRINT = "print"; + public static final String READER = "reader"; + public static final String TASK = "task"; + public static final String TEMP = "temp"; + public static final String TYPE = "type"; + public static final String VALUE = "value"; + public static final String WRAPPER = "wrapper"; + public static final String WRITER = "writer"; + + public static final String CONTROL = "control"; + public static final String CONVERT = "convert"; + public static final String INSTANCE = "instance"; + public static final String MARGIN = "margin"; + public static final String PADDING = "padding"; + public static final String PARENT = "parent"; + public static final String PARSER = "parser"; + + public static final String MARGIN_BOTTOM = "margin_bottom"; + public static final String MARGIN_LEFT = "margin_left"; + public static final String MARGIN_RIGHT = "margin_right"; + public static final String MARGIN_TOP = "margin_top"; + public static final String PADDING_BOTTOM = "padding_bottom"; + public static final String PADDING_LEFT = "padding_left"; + public static final String PADDING_RIGHT = "padding_right"; + public static final String PADDING_TOP = "padding_top"; + + public static final String ATTRIBUTE = "attribute"; + public static final String BUFFER = "buffer"; + public static final String BUILD = "build"; + public static final String BUILDER = "builder"; + public static final String CLASS = "class"; + public static final String CLONE = "clone"; + public static final String CONST = "const"; + public static final String ENUM = "enum"; + public static final String FIELD = "field"; + public static final String FINAL = "final"; + public static final String FOR = "for"; + public static final String FUNCTION = "function"; + public static final String INNER = "inner"; + public static final String INTERFACE = "interface"; + public static final String INTERNAL = "internal"; + public static final String INVOKE = "invoke"; + public static final String JOB = "job"; + public static final String METHOD = "method"; + public static final String NEW = "new"; + public static final String NULL = "null"; + public static final String PARAM = "param"; + public static final String PARAMS = "params"; + public static final String PRIVATE = "private"; + public static final String PROTECTED = "protected"; + public static final String PUBLIC = "public"; + public static final String RETURN = "return"; + public static final String RUNNABLE = "runnable"; + public static final String SCHEDULE = "schedule"; + public static final String STATIC = "static"; + public static final String STREAM = "stream"; + public static final String THREAD = "thread"; + public static final String VAL = "val"; + public static final String VAR = "var"; + public static final String VOID = "void"; + public static final String WHILE = "while"; + } + + /** + * detail: Int 类型常量 + * @author Ttt + */ + public static final class INT { + + // ============ + // = 常用操作值 = + // ============ + + public static final int BASE = 102030; + // 默认状态 ( 暂未进行操作 ) + public static final int NORMAL = BASE + 1; + // 操作中 + public static final int ING = BASE + 2; + // 操作成功 + public static final int SUCCESS = BASE + 3; + // 操作失败 + public static final int FAIL = BASE + 4; + // 操作异常 + public static final int ERROR = BASE + 5; + // 开始操作 + public static final int START = BASE + 6; + // 重新开始操作 + public static final int RESTART = BASE + 7; + // 操作结束 + public static final int END = BASE + 8; + // 操作暂停 + public static final int PAUSE = BASE + 9; + // 操作恢复 ( 继续 ) + public static final int RESUME = BASE + 10; + // 操作停止 + public static final int STOP = BASE + 11; + // 操作取消 + public static final int CANCEL = BASE + 12; + // 创建 + public static final int CREATE = BASE + 13; + // 销毁 + public static final int DESTROY = BASE + 14; + // 回收 + public static final int RECYCLE = BASE + 15; + // 初始化 + public static final int INIT = BASE + 16; + // 已打开 + public static final int ENABLED = BASE + 17; + // 正在打开 + public static final int ENABLING = BASE + 18; + // 已关闭 + public static final int DISABLED = BASE + 19; + // 正在关闭 + public static final int DISABLING = BASE + 20; + // 连接成功 + public static final int CONNECTED = BASE + 21; + // 连接中 + public static final int CONNECTING = BASE + 22; + // 连接失败、断开 + public static final int DISCONNECTED = BASE + 23; + // 暂停、延迟 + public static final int SUSPENDED = BASE + 24; + // 未知 + public static final int UNKNOWN = BASE + 25; + // 新增 + public static final int INSERT = BASE + 26; + // 删除 + public static final int DELETE = BASE + 27; + // 更新 + public static final int UPDATE = BASE + 28; + // 查询 + public static final int SELECT = BASE + 29; + // 加密 + public static final int ENCRYPT = BASE + 30; + // 解密 + public static final int DECRYPT = BASE + 31; + // 重置 + public static final int RESET = BASE + 32; + // 关闭 + public static final int CLOSE = BASE + 33; + // 打开 + public static final int OPEN = BASE + 34; + // 退出 + public static final int EXIT = BASE + 35; + // 下一步 + public static final int NEXT = BASE + 36; + // 无任何 + public static final int NONE = BASE + 37; + // 结束 + public static final int FINISH = BASE + 38; + // 等待中 + public static final int WAITING = BASE + 39; + // 完成 + public static final int COMPLETE = BASE + 40; + + // =========== + // = Request = + // =========== + + // 默认状态 ( 暂未进行操作 ) + public static final int REQUEST_NORMAL = NORMAL; + // 请求中 + public static final int REQUEST_ING = ING; + // 请求成功 + public static final int REQUEST_SUCCESS = SUCCESS; + // 请求失败 + public static final int REQUEST_FAIL = FAIL; + // 请求异常 + public static final int REQUEST_ERROR = ERROR; + // 请求开始 + public static final int REQUEST_START = START; + // 重新请求 + public static final int REQUEST_RESTART = RESTART; + // 请求结束 + public static final int REQUEST_END = END; + // 请求暂停 + public static final int REQUEST_PAUSE = PAUSE; + // 请求恢复 ( 继续 ) + public static final int REQUEST_RESUME = RESUME; + // 请求停止 + public static final int REQUEST_STOP = STOP; + // 请求取消 + public static final int REQUEST_CANCEL = CANCEL; + } + + /** + * detail: 格式化字符串常量 + * @author Ttt + */ + public static final class FORMAT { + + public static final String S2 = "%s%s"; + public static final String S2_HYPHEN = "%s-%s"; + public static final String S2_UNDERSCORE = "%s_%s"; + public static final String S2_COMMA = "%s,%s"; + public static final String S2_COMMA_SPACE = "%s, %s"; + public static final String S2_COMMA2 = "%s、%s"; + public static final String S2_SPACE = "%s %s"; + public static final String S2_SPACE_SE = " %s %s "; + + public static final String S3 = "%s%s%s"; + public static final String S3_HYPHEN = "%s-%s-%s"; + public static final String S3_UNDERSCORE = "%s_%s_%s"; + public static final String S3_COMMA = "%s,%s,%s"; + public static final String S3_COMMA_SPACE = "%s, %s, %s"; + public static final String S3_COMMA2 = "%s、%s、%s"; + public static final String S3_SPACE = "%s %s %s"; + public static final String S3_SPACE_SE = " %s %s %s "; + + public static final String S4 = "%s%s%s%s"; + public static final String S4_HYPHEN = "%s-%s-%s-%s"; + public static final String S4_UNDERSCORE = "%s_%s_%s_%s"; + public static final String S4_COMMA = "%s,%s,%s,%s"; + public static final String S4_COMMA_SPACE = "%s, %s, %s, %s"; + public static final String S4_COMMA2 = "%s、%s、%s、%s"; + public static final String S4_SPACE = "%s %s %s %s"; + public static final String S4_SPACE_SE = " %s %s %s %s "; + + public static final String BRACE = "{ %s }"; + public static final String BRACE_SPACE = " { %s } "; + + public static final String BRACKET = "[ %s ]"; + public static final String BRACKET_SPACE = " [ %s ] "; + + public static final String PARENTHESES = "( %s )"; + public static final String PARENTHESES_SPACE = " ( %s ) "; + } + + /** + * detail: 正则表达式字符串常量 + * @author Ttt + */ + public static final class REGEX { + + // 正则表达式: 空格 + public static final String SPACE = "\\s"; + + // 正则表达式: 验证数字 + public static final String NUMBER = "^[0-9]*$"; + + // 正则表达式: 验证数字或包含小数点 + public static final String NUMBER_OR_DECIMAL = "^[0-9]*[.]?[0-9]*$"; + + // 正则表达式: 验证是否包含数字 + public static final String CONTAIN_NUMBER = ".*\\d+.*"; + + // 正则表达式: 验证是否数字或者字母 + public static final String NUMBER_OR_LETTER = "^[A-Za-z0-9]+$"; + + // 正则表达式: 验证是否全是字母 + public static final String LETTER = "^[A-Za-z]+$"; + + // 正则表达式: 不能输入特殊字符 ^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ + public static final String SPECIAL = "^[\\u4E00-\\u9FA5A-Za-z0-9]+$"; + + // 正则表达式: 验证微信号 + public static final String WX = "^[a-zA-Z]{1}[-_a-zA-Z0-9]{5,19}+$"; + + // 正则表达式: 验证真实姓名 ^[\u4e00-\u9fa5]+(·[\u4e00-\u9fa5]+)*$ + public static final String REALNAME = "^[\\u4e00-\\u9fa5]+(•[\\u4e00-\\u9fa5]*)*$|^[\\u4e00-\\u9fa5]+(·[\\u4e00-\\u9fa5]*)*$"; + + // 正则表达式: 验证昵称 + public static final String NICKNAME = "^[\\u4E00-\\u9FA5A-Za-z0-9_]+$"; + + // 正则表达式: 验证用户名 ( 不包含中文和特殊字符 ) 如果用户名使用手机号码或邮箱 则结合手机号验证和邮箱验证 + public static final String USERNAME = "^[a-zA-Z]\\w{5,17}$"; + + // 正则表达式: 验证密码 ( 不包含特殊字符 ) + public static final String PASSWORD = "^[a-zA-Z0-9]{6,18}$"; + + // 正则表达式: 验证邮箱 + public static final String EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + + // 正则表达式: 验证 URL + public static final String URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?"; + + // 正则表达式: 验证 IP 地址 + public static final String IP_ADDRESS = "(2[5][0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"; + + // 正则表达式: 验证汉字 + public static final String CHINESE = "^[\u4e00-\u9fa5]+$"; + + // 正则表达式: 验证汉字 ( 含双角符号 ) + public static final String CHINESE_ALL = "^[\u0391-\uFFE5]+$"; + + // 正则表达式: 验证汉字 ( 含双角符号 ) + public static final String CHINESE_ALL2 = "[\u0391-\uFFE5]"; + } + + /** + * detail: 数组常量 + * @author Ttt + */ + public static final class ARRAY { + + // 用于建立十六进制字符的输出的小写字符数组 + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // 用于建立十六进制字符的输出的大写字符数组 + private static final char[] HEX_DIGITS_UPPER = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + // 0123456789 + private static final char[] NUMBERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 + }; + + // abcdefghijklmnopqrstuvwxyz + private static final char[] LOWER_CASE_LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 + }; + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] CAPITAL_LETTERS = { + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90 + }; + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] NUMBERS_AND_LETTERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // 生肖数组 + private static final String[] ZODIAC = { + "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊" + }; + + // 星座对应日期 + private static final String[] CONSTELLATION_DATE = { + "01.20-02.18", "02.19-03.20", "03.21-04.19", "04.20-05.20", "05.21-06.21", "06.22-07.22", + "07.23-08.22", "08.23-09.22", "09.23-10.23", "10.24-11.22", "11.23-12.21", "12.22-01.19" + }; + + // 星座数组 + private static final String[] CONSTELLATION = { + "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", + "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座" + }; + + // ========== + // = 对外公开 = + // ========== + + // 用于建立十六进制字符的输出的小写字符数组 + public static char[] HEX_DIGITS() { + return Arrays.copyOf(HEX_DIGITS, HEX_DIGITS.length); + } + + // 用于建立十六进制字符的输出的大写字符数组 + public static char[] HEX_DIGITS_UPPER() { + return Arrays.copyOf(HEX_DIGITS_UPPER, HEX_DIGITS_UPPER.length); + } + + // 0123456789 + public static char[] NUMBERS() { + return Arrays.copyOf(NUMBERS, NUMBERS.length); + } + + // abcdefghijklmnopqrstuvwxyz + public static char[] LOWER_CASE_LETTERS() { + return Arrays.copyOf(LOWER_CASE_LETTERS, LOWER_CASE_LETTERS.length); + } + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] CAPITAL_LETTERS() { + return Arrays.copyOf(CAPITAL_LETTERS, CAPITAL_LETTERS.length); + } + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] LETTERS() { + return Arrays.copyOf(LETTERS, LETTERS.length); + } + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] NUMBERS_AND_LETTERS() { + return Arrays.copyOf(NUMBERS_AND_LETTERS, NUMBERS_AND_LETTERS.length); + } + + // 生肖数组 + public static String[] ZODIAC() { + return Arrays.copyOf(ZODIAC, ZODIAC.length); + } + + // 星座对应日期 + public static String[] CONSTELLATION_DATE() { + return Arrays.copyOf(CONSTELLATION_DATE, CONSTELLATION_DATE.length); + } + + // 星座数组 + public static String[] CONSTELLATION() { + return Arrays.copyOf(CONSTELLATION, CONSTELLATION.length); + } + } + + /** + * detail: 校验接受常量 + * @author Ttt + */ + public static final class ACCEPT { + + public static final String NUMBERS = "0123456789"; + + public static final String LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"; + + public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final String LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final String NUMBERS_AND_LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + + /** + * detail: 默认值常量 + * @author Ttt + */ + public static final class DEFAULT { + + public static final int ERROR_INT = -1; + public static final long ERROR_LONG = -1L; + public static final float ERROR_FLOAT = -1F; + public static final double ERROR_DOUBLE = -1D; + public static final boolean ERROR_BOOLEAN = false; + public static final short ERROR_SHORT = -1; + public static final char ERROR_CHAR = (char) -1; + public static final byte ERROR_BYTE = (byte) -1; + public static final BigDecimal ERROR_BIG_DECIMAL = BigDecimal.valueOf(-1L); + public static final BigInteger ERROR_BIG_INTEGER = BigInteger.valueOf(-1L); + public static final String ERROR_STRING = null; + + public static final int INT = 0; + public static final long LONG = 0L; + public static final float FLOAT = 0F; + public static final double DOUBLE = 0D; + public static final boolean BOOLEAN = false; + public static final short SHORT = 0; + public static final char CHAR = (char) 0; + public static final byte BYTE = (byte) 0; + public static final BigDecimal BIG_DECIMAL = BigDecimal.ZERO; + public static final BigInteger BIG_INTEGER = BigInteger.ZERO; + public static final String STRING = ""; + + public static final Object ENTITY = null; + public static final Object OBJECT = null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/JCLogUtils.java b/lib/DevApp/src/main/java/dev/utils/JCLogUtils.java new file mode 100644 index 0000000000..d3808c201b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/JCLogUtils.java @@ -0,0 +1,328 @@ +package dev.utils; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * detail: Java Common 日志打印工具类 ( 简化版 ) + * @author Ttt + *
+ *     项目内部使用 ( 主要打印 Java 日志 )
+ * 
+ */ +public final class JCLogUtils { + + private JCLogUtils() { + } + + // 是否打印日志 线上 (release) = false, 开发 (debug) = true + private static boolean JUDGE_PRINT_LOG = false; + // 判断是否控制台打印信息 + private static boolean JUDGE_CONTROL_PRINT_LOG = false; + // 默认 DEFAULT_TAG + private static final String DEFAULT_TAG = JCLogUtils.class.getSimpleName(); + + // ========== + // = 日志类型 = + // ========== + + // INFO 模式 + public static final int INFO = 0; + // DEBUG 模式 + public static final int DEBUG = 1; + // ERROR 模式 + public static final int ERROR = 2; + + /** + * 判断是否打印日志 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrintLog() { + return JUDGE_PRINT_LOG; + } + + /** + * 设置是否打印日志 + * @param judgePrintLog 是否允许打印日志 + */ + public static void setPrintLog(final boolean judgePrintLog) { + JUDGE_PRINT_LOG = judgePrintLog; + } + + /** + * 设置是否在控制台打印日志 + * @param judgeControlPrintLog 是否允许控制台打印日志 + */ + public static void setControlPrintLog(final boolean judgeControlPrintLog) { + JUDGE_CONTROL_PRINT_LOG = judgeControlPrintLog; + } + + /** + * 判断字符串是否为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + private static boolean isEmpty(final String str) { + return (str == null || str.length() == 0); + } + + // = + + /** + * 最终打印日志方法 ( 全部调用此方法 ) + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + private static void printLog( + final int logType, + final String tag, + final String message + ) { + if (JCLogUtils.sPrint != null) { + JCLogUtils.sPrint.printLog(logType, tag, message); + } + + if (JUDGE_CONTROL_PRINT_LOG) { + // 打印信息 + if (isEmpty(tag)) { + System.out.println(message); + } else { + System.out.println(tag + " : " + message); + } + } + } + + /** + * 处理信息 + * @param message 日志信息 + * @param args 占位符替换 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String createMessage( + final String message, + final Object... args + ) { + String result; + try { + if (message != null) { + if (args == null) { + // 动态参数为 null + result = "params is null"; + } else { + // 格式化字符串 + result = (args.length == 0 ? message : String.format(message, args)); + } + } else { + // 打印内容为 null + result = "message is null"; + } + } catch (Exception e) { + // 出现异常 + result = e.toString(); + } + return result; + } + + /** + * 拼接错误信息 + * @param throwable 异常 + * @param message 需要打印的消息 + * @param args 动态参数 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String concatErrorMessage( + final Throwable throwable, + final String message, + final Object... args + ) { + String result; + try { + if (throwable != null) { + if (message != null) { + result = createMessage(message, args) + " : " + throwable.toString(); + } else { + result = throwable.toString(); + } + } else { + result = createMessage(message, args); + } + } catch (Exception e) { + result = e.toString(); + } + return result; + } + + // ============================= + // = 对外公开方法 ( 使用默认 TAG ) = + // ============================= + + public static void d( + final String message, + final Object... args + ) { + dTag(DEFAULT_TAG, message, args); + } + + public static void e(final Throwable throwable) { + eTag(DEFAULT_TAG, throwable); + } + + public static void e( + final String message, + final Object... args + ) { + e(null, message, args); + } + + public static void e( + final Throwable throwable, + final String message, + final Object... args + ) { + eTag(DEFAULT_TAG, throwable, message, args); + } + + public static void i( + final String message, + final Object... args + ) { + iTag(DEFAULT_TAG, message, args); + } + + public static void xml(final String xml) { + xmlTag(DEFAULT_TAG, xml); + } + + // ============================ + // = 对外公开方法 ( 日志打印方法 ) = + // ============================ + + public static void dTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(DEBUG, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, concatErrorMessage(throwable, null)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, concatErrorMessage(throwable, message, args)); + } + } + + public static void iTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(INFO, tag, createMessage(message, args)); + } + } + + public static void xmlTag( + final String tag, + final String xml + ) { + if (JUDGE_PRINT_LOG) { + // 判断传入 XML 格式信息是否为 null + if (isEmpty(xml)) { + printLog(ERROR, tag, "Empty/Null xml content"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(xmlInput, xmlOutput); + // 获取打印消息 + String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); + // 打印信息 + printLog(DEBUG, tag, message); + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + printLog(ERROR, tag, errorInfo + DevFinal.SYMBOL.NEW_LINE + xml); + } + } + } + + // ========== + // = 通知输出 = + // ========== + + private static Print sPrint; + + /** + * 设置日志输出接口 + * @param print 日志输出接口 + */ + public static void setPrint(final Print print) { + JCLogUtils.sPrint = print; + } + + /** + * detail: 日志输出接口 + * @author Ttt + */ + public interface Print { + + /** + * 日志打印 + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + void printLog( + int logType, + String tag, + String message + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/LogPrintUtils.java b/lib/DevApp/src/main/java/dev/utils/LogPrintUtils.java new file mode 100644 index 0000000000..e41dbe4976 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/LogPrintUtils.java @@ -0,0 +1,432 @@ +package dev.utils; + +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * detail: Android 日志打印工具类 ( 简化版 ) + * @author Ttt + *
+ *     项目内部使用 ( 主要打印 Android 日志 )
+ * 
+ */ +public final class LogPrintUtils { + + private LogPrintUtils() { + } + + // JSON 格式内容缩进 + private static final int JSON_INDENT = 4; + // 是否打印日志 线上 (release) = false, 开发 (debug) = true + private static boolean JUDGE_PRINT_LOG = false; + // 默认 DEFAULT_TAG + private static final String DEFAULT_TAG = LogPrintUtils.class.getSimpleName(); + + /** + * 判断是否打印日志 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrintLog() { + return JUDGE_PRINT_LOG; + } + + /** + * 设置是否打印日志 + * @param judgePrintLog 是否允许打印日志 + */ + public static void setPrintLog(final boolean judgePrintLog) { + JUDGE_PRINT_LOG = judgePrintLog; + } + + /** + * 判断字符串是否为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + private static boolean isEmpty(final String str) { + return (str == null || str.length() == 0); + } + + // = + + /** + * 最终打印日志方法 ( 全部调用此方法 ) + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + private static void printLog( + final int logType, + final String tag, + final String message + ) { + if (LogPrintUtils.sPrint != null) { + LogPrintUtils.sPrint.printLog(logType, tag, message); + } + } + + /** + * 处理信息 + * @param message 日志信息 + * @param args 占位符替换 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String createMessage( + final String message, + final Object... args + ) { + String result; + try { + if (message != null) { + if (args == null) { + // 动态参数为 null + result = "params is null"; + } else { + // 格式化字符串 + result = (args.length == 0 ? message : String.format(message, args)); + } + } else { + // 打印内容为 null + result = "message is null"; + } + } catch (Exception e) { + // 出现异常 + result = e.toString(); + } + return result; + } + + /** + * 拼接错误信息 + * @param throwable 异常 + * @param message 需要打印的消息 + * @param args 动态参数 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String concatErrorMessage( + final Throwable throwable, + final String message, + final Object... args + ) { + String result; + try { + if (throwable != null) { + if (message != null) { + result = createMessage(message, args) + " : " + throwable.toString(); + } else { + result = throwable.toString(); + } + } else { + result = createMessage(message, args); + } + } catch (Exception e) { + result = e.toString(); + } + return result; + } + + // ============================= + // = 对外公开方法 ( 使用默认 TAG ) = + // ============================= + + public static void d( + final String message, + final Object... args + ) { + dTag(DEFAULT_TAG, message, args); + } + + public static void e(final Throwable throwable) { + eTag(DEFAULT_TAG, throwable, null); + } + + public static void e( + final String message, + final Object... args + ) { + e(null, message, args); + } + + public static void e( + final Throwable throwable, + final String message, + final Object... args + ) { + eTag(DEFAULT_TAG, throwable, message, args); + } + + public static void w( + final String message, + final Object... args + ) { + wTag(DEFAULT_TAG, message, args); + } + + public static void i( + final String message, + final Object... args + ) { + iTag(DEFAULT_TAG, message, args); + } + + public static void v( + final String message, + final Object... args + ) { + vTag(DEFAULT_TAG, message, args); + } + + public static void wtf( + final String message, + final Object... args + ) { + wtfTag(DEFAULT_TAG, message, args); + } + + public static void json(final String json) { + jsonTag(DEFAULT_TAG, json); + } + + public static void xml(final String xml) { + xmlTag(DEFAULT_TAG, xml); + } + + // ============================ + // = 对外公开方法 ( 日志打印方法 ) = + // ============================ + + public static void dTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.DEBUG, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.ERROR, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.ERROR, tag, concatErrorMessage(throwable, null)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.ERROR, tag, concatErrorMessage(throwable, message, args)); + } + } + + public static void wTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.WARN, tag, createMessage(message, args)); + } + } + + public static void iTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.INFO, tag, createMessage(message, args)); + } + } + + public static void vTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.VERBOSE, tag, createMessage(message, args)); + } + } + + public static void wtfTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(Log.ASSERT, tag, createMessage(message, args)); + } + } + + public static void jsonTag( + final String tag, + final String json + ) { + if (JUDGE_PRINT_LOG) { + // 判断传入 JSON 格式信息是否为 null + if (isEmpty(json)) { + printLog(Log.ERROR, tag, "Empty/Null json content"); + return; + } + try { + // 属于对象的 JSON 格式信息 + if (json.startsWith("{")) { + JSONObject jsonObject = new JSONObject(json); + // 进行缩进 + String message = jsonObject.toString(JSON_INDENT); + // 打印信息 + printLog(Log.DEBUG, tag, message); + } else if (json.startsWith("[")) { + // 属于数据的 JSON 格式信息 + JSONArray jsonArray = new JSONArray(json); + // 进行缩进 + String message = jsonArray.toString(JSON_INDENT); + // 打印信息 + printLog(Log.DEBUG, tag, message); + } else { + // 打印信息 + printLog(Log.DEBUG, tag, "json content format error"); + } + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + printLog(Log.ERROR, tag, errorInfo + DevFinal.SYMBOL.NEW_LINE + json); + } + } + } + + public static void xmlTag( + final String tag, + final String xml + ) { + if (JUDGE_PRINT_LOG) { + // 判断传入 XML 格式信息是否为 null + if (isEmpty(xml)) { + printLog(Log.ERROR, tag, "Empty/Null xml content"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(xmlInput, xmlOutput); + // 获取打印消息 + String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); + // 打印信息 + printLog(Log.DEBUG, tag, message); + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + printLog(Log.ERROR, tag, errorInfo + DevFinal.SYMBOL.NEW_LINE + xml); + } + } + } + + // ========== + // = 通知输出 = + // ========== + + // 默认日志输出接口 + private static Print sPrint = (logType, tag, message) -> { + // 防止 null 处理 + if (message == null) return; + // 获取日志类型 + switch (logType) { + case Log.VERBOSE: + Log.v(tag, message); + break; + case Log.DEBUG: + Log.d(tag, message); + break; + case Log.INFO: + Log.i(tag, message); + break; + case Log.WARN: + Log.w(tag, message); + break; + case Log.ERROR: + Log.e(tag, message); + break; + case Log.ASSERT: + default: + Log.wtf(tag, message); + break; + } + }; + + /** + * 设置日志输出接口 + * @param print 日志输出接口 + */ + public static void setPrint(final Print print) { + LogPrintUtils.sPrint = print; + } + + /** + * detail: 日志输出接口 + * @author Ttt + */ + public interface Print { + + /** + * 日志打印 + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + void printLog( + int logType, + String tag, + String message + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ADBUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ADBUtils.java new file mode 100644 index 0000000000..c51dea47d7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ADBUtils.java @@ -0,0 +1,2503 @@ +package dev.utils.app; + +import android.Manifest; +import android.content.Intent; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.IntRange; +import androidx.annotation.RequiresPermission; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.CollectionUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; + +/** + * detail: ADB shell 工具类 + * @author Ttt + *
+ *     Awesome ADB 一份超全超详细的 ADB 用法大全
+ *     @see 
+ *     

+ * adb shell input + * @see
+ *

+ * grep 是 linux 下的命令, windows 用 findstr + * 开启 Thread 执行, 非主线程, 否则无响应并无效 + *

+ * 导出 ANR 日志如果无 ROOT 权限可通过 adb bugreport 导出压缩包 ( 文件存储在 adb 文件目录下 ) + *
+ */ +public final class ADBUtils { + + private ADBUtils() { + } + + // 日志 TAG + private static final String TAG = ADBUtils.class.getSimpleName(); + + /** + * 判断设备是否 root + * @return {@code true} yes, {@code false} no + */ + public static boolean isDeviceRooted() { + String su = "su"; + String[] locations = { + "/system/bin/", "/system/xbin/", "/sbin/", + "/system/sd/xbin/", "/system/bin/failsafe/", + "/data/local/xbin/", "/data/local/bin/", "/data/local/" + }; + for (String location : locations) { + if (new File(location + su).exists()) { + return true; + } + } + return false; + } + + /** + * 请求 Root 权限 + * @return {@code true} success, {@code false} fail + */ + public static boolean requestRoot() { + return ShellUtils.execCmd("exit", true).isSuccess(); + } + + /** + * 判断 APP 是否授权 Root 权限 + * @return {@code true} yes, {@code false} no + */ + public static boolean isGrantedRoot() { + return ShellUtils.execCmd("exit", true).isSuccess2(); + } + + // ========== + // = 应用管理 = + // ========== + + // ========== + // = 应用列表 = + // ========== + + /** + * 获取 APP 列表 ( 包名 ) + * @param type options + * @return 对应选项的应用包名列表 + */ + public static List getAppList(final String type) { + // adb shell pm list packages [options] + String typeStr = TextUtils.isEmpty(type) ? "" : " " + type; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "pm list packages" + typeStr, false + ); + if (result.isSuccess3()) { + try { + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + return Arrays.asList(arrays); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppList type: %s", typeStr); + } + } + return null; + } + + /** + * 获取 APP 安装列表 ( 包名 ) + * @return APP 安装列表 ( 包名 ) + */ + public static List getInstallAppList() { + return getAppList(null); + } + + /** + * 获取用户安装的应用列表 ( 包名 ) + * @return 用户安装的应用列表 ( 包名 ) + */ + public static List getUserAppList() { + return getAppList("-3"); + } + + /** + * 获取系统应用列表 ( 包名 ) + * @return 系统应用列表 ( 包名 ) + */ + public static List getSystemAppList() { + return getAppList("-s"); + } + + /** + * 获取启用的应用列表 ( 包名 ) + * @return 启用的应用列表 ( 包名 ) + */ + public static List getEnableAppList() { + return getAppList("-e"); + } + + /** + * 获取禁用的应用列表 ( 包名 ) + * @return 禁用的应用列表 ( 包名 ) + */ + public static List getDisableAppList() { + return getAppList("-d"); + } + + /** + * 获取包名包含字符串 xxx 的应用列表 + * @param filter 过滤获取字符串 + * @return 包名包含字符串 xxx 的应用列表 + */ + public static List getAppListToFilter(final String filter) { + if (TextUtils.isEmpty(filter)) return null; + return getAppList("| grep " + filter.trim()); + } + + /** + * 判断是否安装应用 + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInstalledApp(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + return ShellUtils.execCmd( + "pm path " + packageName, false + ).isSuccess3(); + } + + /** + * 查看应用安装路径 + * @param packageName 应用包名 + * @return 应用安装路径 + */ + public static String getAppInstallPath(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "pm path " + packageName, false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 清除应用数据与缓存 ( 相当于在设置里的应用信息界面点击了「清除缓存」和「清除数据」 ) + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAppDataCache(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + // adb shell pm clear + String cmd = "pm clear %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, packageName), true + ); + return result.isSuccess4("success"); + } + + // ========== + // = 应用信息 = + // ========== + + /** + * 查看应用详细信息 + *
+     *     输出中包含很多信息, 包括 Activity Resolver Table、Registered ContentProviders、
+     *     包名、userId、安装后的文件资源代码等路径、版本信息、权限信息和授予状态、签名版本信息等
+     * 
+ * @param packageName 应用包名 + * @return 应用详细信息 + */ + public static String getAppMessage(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys package " + packageName, true + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 APP versionCode + * @param packageName 应用包名 + * @return versionCode + */ + public static int getVersionCode(final String packageName) { + if (TextUtils.isEmpty(packageName)) return 0; + try { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys package " + packageName + " | grep version", + true + ); + if (result.isSuccess3()) { + String[] arrays = result.successMsg.split(DevFinal.REGEX.SPACE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + try { + String[] splitArray = value.split("="); + if (splitArray.length == 2) { + if ("versionCode".equalsIgnoreCase(splitArray[0])) { + return Integer.parseInt(splitArray[1]); + } + } + } catch (Exception ignored) { + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVersionCode"); + } + return 0; + } + + /** + * 获取 APP versionName + * @param packageName 应用包名 + * @return versionName + */ + public static String getVersionName(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + try { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys package " + packageName + " | grep version", + true + ); + if (result.isSuccess3()) { + String[] arrays = result.successMsg.split(DevFinal.REGEX.SPACE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + try { + String[] splitArray = value.split("="); + if (splitArray.length == 2) { + if ("versionName".equalsIgnoreCase(splitArray[0])) { + return splitArray[1]; + } + } + } catch (Exception ignored) { + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVersionName"); + } + return null; + } + + // ============ + // = 安装、卸载 = + // ============ + + /** + * 安装应用 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp(final String filePath) { + return installApp(filePath, "-rtsd"); + } + + /** + * 安装应用 + *
+     *     -l 将应用安装到保护目录 /mnt/asec
+     *     -r 允许覆盖安装
+     *     -t 允许安装 AndroidManifest.xml 里 application 指定 android:testOnly="true" 的应用
+     *     -s 将应用安装到 sdcard
+     *     -d 允许降级覆盖安装
+     *     -g 授予所有运行时权限
+     * 
+ * @param filePath 文件路径 + * @param params 安装选项 + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp( + final String filePath, + final String params + ) { + if (TextUtils.isEmpty(params)) return false; + boolean isRoot = isDeviceRooted(); + // adb install [-lrtsdg] + String cmd = "adb install %s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, params, filePath), isRoot + ); + // 判断是否成功 + return result.isSuccess4("success"); + } + + /** + * 静默安装应用 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent(final String filePath) { + return installAppSilent(FileUtils.getFileByPath(filePath), null); + } + + /** + * 静默安装应用 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent(final File file) { + return installAppSilent(file, null); + } + + /** + * 静默安装应用 + * @param filePath 文件路径 + * @param params 安装选项 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final String filePath, + final String params + ) { + return installAppSilent( + FileUtils.getFileByPath(filePath), + params, isDeviceRooted() + ); + } + + /** + * 静默安装应用 + * @param file 文件 + * @param params 安装选项 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final File file, + final String params + ) { + return installAppSilent(file, params, isDeviceRooted()); + } + + /** + * 静默安装应用 + * @param file 文件 + * @param params 安装选项 + * @param isRooted 是否 root + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final File file, + final String params, + final boolean isRooted + ) { + if (!FileUtils.isFileExists(file)) return false; + String filePath = '"' + file.getAbsolutePath() + '"'; + String command = "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm install " + (params == null ? "" : params + " ") + filePath; + ShellUtils.CommandResult result = ShellUtils.execCmd(command, isRooted); + return result.isSuccess4("success"); + } + + // = + + /** + * 卸载应用 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallApp(final String packageName) { + return uninstallApp(packageName, false); + } + + /** + * 卸载应用 + * @param packageName 应用包名 + * @param isKeepData -k 参数可选, 表示卸载应用但保留数据和缓存目录 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallApp( + final String packageName, + final boolean isKeepData + ) { + if (TextUtils.isEmpty(packageName)) return false; + boolean isRoot = isDeviceRooted(); + // adb uninstall [-k] + String cmd = "adb uninstall "; + if (isKeepData) { + cmd += " -k "; + } + cmd += packageName; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, isRoot); + // 判断是否成功 + return result.isSuccess4("success"); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent(final String packageName) { + return uninstallAppSilent(packageName, false, isDeviceRooted()); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @param isKeepData -k 参数可选, 表示卸载应用但保留数据和缓存目录 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent( + final String packageName, + final boolean isKeepData + ) { + return uninstallAppSilent(packageName, isKeepData, isDeviceRooted()); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @param isKeepData -k 参数可选, 表示卸载应用但保留数据和缓存目录 + * @param isRooted 是否 root + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent( + final String packageName, + final boolean isKeepData, + final boolean isRooted + ) { + if (TextUtils.isEmpty(packageName)) return false; + String command = "LD_LIBRARY_PATH=/vendor/lib*:/system/lib* pm uninstall " + (isKeepData ? "-k " : "") + packageName; + ShellUtils.CommandResult result = ShellUtils.execCmd(command, isRooted); + return result.isSuccess4("success"); + } + + // =========== + // = dumpsys = + // =========== + + /** + * 获取对应包名应用启动的 Activity + *
+     *     android.intent.category.LAUNCHER (android.intent.action.MAIN)
+     * 
+ * @param packageName 应用包名 + * @return package.xx.Activity.className + */ + public static String getActivityToLauncher(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + String cmd = "dumpsys package %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, packageName), true + ); + if (result.isSuccess3()) { + String mainStr = "android.intent.action.MAIN:"; + int start = result.successMsg.indexOf(mainStr); + // 防止都为 null + if (start != -1) { + try { + // 进行裁剪字符串 + String subData = result.successMsg.substring(start + mainStr.length()); + // 进行拆分 + String[] arrays = subData.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + // 存在包名才处理 + if (value.contains(packageName)) { + String[] splitArrays = value.split(DevFinal.REGEX.SPACE); + for (String itemValue : splitArrays) { + if (!TextUtils.isEmpty(itemValue)) { + // 属于 packageName/ 前缀的 + if (itemValue.contains(packageName + "/")) { + // 防止属于 packageName/.xx.Main_Activity + if (itemValue.contains("/.")) { + // packageName/.xx.Main_Activity + // packageName/packageName.xx.Main_Activity + itemValue = itemValue.replace( + "/", "/" + packageName + ); + } + return itemValue; + } + } + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityToLauncher %s", packageName); + } + } + } + return null; + } + + // ================= + // = 获取当前 Window = + // ================= + + /** + * 获取当前显示的 Window + *
+     *     adb shell dumpsys window -h
+     * 
+ * @return package.xx.Activity.className + */ + public static String getWindowCurrent() { + String cmd = "dumpsys window w | grep \\/ | grep name="; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + try { + String nameStr = "name="; + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + int start = value.indexOf(nameStr); + if (start != -1) { + try { + String subData = value.substring(start + nameStr.length()); + if (subData.indexOf(')') != -1) { + return subData.substring(0, subData.length() - 1); + } + return subData; + } catch (Exception ignored) { + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWindowCurrent"); + } + } + return null; + } + + /** + * 获取当前显示的 Window + * @return package/package.xx.Activity.className + */ + public static String getWindowCurrent2() { + String cmd = "dumpsys window windows | grep Current"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + try { + // 拆分换行, 并循环 + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + String[] splitArrays = value.split(DevFinal.REGEX.SPACE); + if (splitArrays.length != 0) { + for (String itemValue : splitArrays) { + if (!TextUtils.isEmpty(itemValue)) { + int start = itemValue.indexOf('/'); + int lastIndex = itemValue.lastIndexOf('}'); + if (start != -1 && lastIndex != -1) { + // 获取裁剪数据 + String strData = itemValue.substring(0, lastIndex); + // 防止属于 packageName/.xx.Main_Activity + if (strData.contains("/.")) { + // packageName/.xx.Main_Activity + // packageName/packageName.xx.Main_Activity + strData = strData.replace( + "/", "/" + itemValue.substring(0, start) + ); + } + return strData; + } + } + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWindowCurrent2"); + } + } + return null; + } + + /** + * 获取对应包名显示的 Window + * @param packageName 应用包名 + * @return package/package.xx.Activity.className + */ + public static String getWindowCurrentToPackage(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + String cmd = "dumpsys window windows | grep %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, packageName), true + ); + if (result.isSuccess3()) { + try { + // 拆分换行, 并循环 + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + String[] splitArrays = value.split(DevFinal.REGEX.SPACE); + if (splitArrays.length != 0) { + for (String itemValue : splitArrays) { + if (!TextUtils.isEmpty(itemValue)) { + int start = itemValue.indexOf('/'); + int lastIndex = itemValue.lastIndexOf('}'); + if (start != -1 && lastIndex != -1 + && itemValue.indexOf(packageName) == 0) { + // 获取裁剪数据 + String strData = itemValue.substring(0, lastIndex); + // 防止属于 packageName/.xx.Main_Activity + if (strData.contains("/.")) { + // packageName/.xx.Main_Activity + // packageName/packageName.xx.Main_Activity + strData = strData.replace( + "/", "/" + packageName + ); + } + return strData; + } + } + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWindowCurrentToPackage"); + } + } + return null; + } + + // =================== + // = 获取当前 Activity = + // =================== + + /** + * 获取当前显示的 Activity + * @return package.xx.Activity.className + */ + public static String getActivityCurrent() { + String cmd = "dumpsys activity activities | grep mFocusedActivity"; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + cmd = "dumpsys activity activities | grep mResumedActivity"; + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + try { + // 拆分换行, 并循环 + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + String[] splitArrays = value.split(DevFinal.REGEX.SPACE); + if (splitArrays.length != 0) { + for (String itemValue : splitArrays) { + if (!TextUtils.isEmpty(itemValue)) { + int start = itemValue.indexOf('/'); + if (start != -1) { + // 获取裁剪数据 + String strData = itemValue; + // 防止属于 packageName/.xx.Main_Activity + if (strData.contains("/.")) { + // packageName/.xx.Main_Activity + // packageName/packageName.xx.Main_Activity + strData = strData.replace( + "/", "/" + itemValue.substring(0, start) + ); + } + return strData; + } + } + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityCurrent"); + } + } + return null; + } + + /** + * 获取 Activity 栈 + * @return 当前全部 Activity 栈信息 + */ + public static String getActivitys() { + return getActivitys(null); + } + + /** + * 获取 Activity 栈 + * @param append 追加筛选条件 + * @return 当前全部 Activity 栈信息 + */ + public static String getActivitys(final String append) { + String cmd = "dumpsys activity activities"; + if (!TextUtils.isEmpty(append)) { + cmd += " " + append.trim(); + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取对应包名的 Activity 栈 + * @param packageName 应用包名 + * @return 对应包名的 Activity 栈信息 + */ + public static String getActivitysToPackage(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + return getActivitys("| grep " + packageName); + } + + /** + * 获取对应包名的 Activity 栈 ( 最新的 Activity 越靠后 ) + * @param packageName 应用包名 + * @return 对应包名的 Activity 栈信息集合 + */ + public static List getActivitysToPackageLists(final String packageName) { + // 获取对应包名的 Activity 数据结果 + String result = getActivitysToPackage(packageName); + // 防止数据为 null + if (!TextUtils.isEmpty(result)) { + try { + List lists = new ArrayList<>(); + String[] arrays = result.split(DevFinal.SYMBOL.NEW_LINE); + // 拆分后, 数据长度 + int splitLength = arrays.length; + // 获取 Activity 栈字符串 + String activities = null; + // 判断最后一行是否符合条件 + if (arrays[splitLength - 1].contains("Activities=")) { + activities = arrays[splitLength - 1]; + } else { + for (String value : arrays) { + if (value.contains("Activities=")) { + activities = value; + break; + } + } + } + // 进行特殊处理 Activities=[ActivityRecord{xx},ActivityRecord{xx}]; + int startIndex = activities.indexOf("Activities=["); + activities = activities.substring( + startIndex + "Activities=[".length(), + activities.length() - 1 + ); + // 再次进行拆分 + String[] activityArrays = activities.split("ActivityRecord"); + for (String value : activityArrays) { + try { + String[] splitArrays = value.split(DevFinal.REGEX.SPACE); + if (splitArrays.length != 0) { + for (String itemValue : splitArrays) { + int start = itemValue.indexOf(packageName + "/"); + if (start != -1) { + // 获取裁剪数据 + String strData = itemValue; + // 防止属于 packageName/.xx.XxxActivity + if (strData.contains("/.")) { + // packageName/.xx.XxxActivity + // packageName/packageName.xx.XxxActivity + strData = strData.replace( + "/", "/" + itemValue.substring(0, start) + ); + } + // 保存数据 + lists.add(strData); + } + } + } + } catch (Exception ignored) { + } + } + return lists; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivitysToPackageLists"); + } + } + return null; + } + + // = + + /** + * 判断 Activity 栈顶是否重复 + * @param packageName 应用包名 + * @param activity Activity Name + * @return {@code true} yes, {@code false} no + */ + public static boolean isActivityTopRepeat( + final String packageName, + final String activity + ) { + if (TextUtils.isEmpty(packageName)) { + return false; + } else if (TextUtils.isEmpty(activity)) { + return false; + } + // 获取 + List lists = getActivitysToPackageLists(packageName); + // 数据长度 + int length = CollectionUtils.length(lists); + // 防止数据为 null + if (length >= 2) { // 两个页面以上, 才能够判断是否重复 + try { + String value = lists.get(length - 1); + if (value != null && value.endsWith(activity)) { + // 倒序遍历, 越后面是 Activity 栈顶 + for (int i = length - 2; i >= 0; i--) { + String data = lists.get(i); + // 判断是否该页面结尾 + if (data.endsWith(activity)) { + return true; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isActivityTopRepeat"); + } + } + return false; + } + + /** + * 判断 Activity 栈顶是否重复 + * @param packageName 应用包名 + * @param activitys Activity Name 集合 + * @return {@code true} yes, {@code false} no + */ + public static boolean isActivityTopRepeat( + final String packageName, + final List activitys + ) { + if (TextUtils.isEmpty(packageName)) { + return false; + } else if (activitys == null || activitys.size() == 0) { + return false; + } + // 获取 + List lists = getActivitysToPackageLists(packageName); + // 数据长度 + int length = CollectionUtils.length(lists); + // 防止数据为 null + if (length >= 2) { // 两个页面以上, 才能够判断是否重复 + // 循环判断 + for (String activity : activitys) { + try { + String value = lists.get(length - 1); + if (value != null && value.endsWith(activity)) { + // 倒序遍历, 越后面是 Activity 栈顶 + for (int i = length - 2; i >= 0; i--) { + String data = lists.get(i); + // 判断是否该页面结尾 + if (data.endsWith(activity)) { + return true; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isActivityTopRepeat"); + } + } + } + return false; + } + + // = + + /** + * 获取 Activity 栈顶重复总数 + * @param packageName 应用包名 + * @param activity Activity Name + * @return 指定 Activity 在栈顶重复总数 + */ + public static int getActivityTopRepeatCount( + final String packageName, + final String activity + ) { + if (TextUtils.isEmpty(packageName)) { + return 0; + } else if (TextUtils.isEmpty(activity)) { + return 0; + } + // 重复数量 + int number = 0; + // 获取 + List lists = getActivitysToPackageLists(packageName); + // 数据长度 + int length = CollectionUtils.length(lists); + // 防止数据为 null + if (length >= 2) { // 两个页面以上, 才能够判断是否重复 + try { + String value = lists.get(length - 1); + if (value != null && value.endsWith(activity)) { + // 倒序遍历, 越后面是 Activity 栈顶 + for (int i = length - 2; i >= 0; i--) { + String data = lists.get(i); + // 判断是否该页面结尾 + if (data.endsWith(activity)) { + number++; + } else { + break; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityTopRepeatCount"); + } + } + return number; + } + + /** + * 获取 Activity 栈顶重复总数 + * @param packageName 应用包名 + * @param activitys Activity Name 集合 + * @return 指定 Activity 在栈顶重复总数 + */ + public static int getActivityTopRepeatCount( + final String packageName, + final List activitys + ) { + if (TextUtils.isEmpty(packageName)) { + return 0; + } else if (activitys == null || activitys.size() == 0) { + return 0; + } + // 获取 + List lists = getActivitysToPackageLists(packageName); + // 数据长度 + int length = CollectionUtils.length(lists); + // 防止数据为 null + if (length >= 2) { // 两个页面以上, 才能够判断是否重复 + // 循环判断 + for (String activity : activitys) { + try { + // 重复数量 + int number = 0; + // 判断是否对应页面结尾 + String value = lists.get(length - 1); + if (value != null && value.endsWith(activity)) { + // 倒序遍历, 越后面是 Activity 栈顶 + for (int i = length - 2; i >= 0; i--) { + String data = lists.get(i); + // 判断是否该页面结尾 + if (data.endsWith(activity)) { + number++; + } else { + break; + } + } + // 进行判断处理 + return number; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityTopRepeatCount"); + } + } + } + return 0; + } + + // ===================== + // = 正在运行的 Services = + // ===================== + + /** + * 查看正在运行的 Services + * @return 运行中的 Services 信息 + */ + public static String getServices() { + return getServices(null); + } + + /** + * 查看正在运行的 Services + * @param packageName 应用包名, 参数不是必须的, 指定 表示查看与某个包名相关的 Services, + * 不指定表示查看所有 Services, 不一定要给出完整的包名, + * 比如运行 adb shell dumpsys activity services org.mazhuang + * 那么包名 org.mazhuang.demo1、org.mazhuang.demo2 和 org.mazhuang123 等相关的 Services 都会列出来 + * @return 运行中的 Services 信息 + */ + public static String getServices(final String packageName) { + String cmd = "dumpsys activity services" + ((TextUtils.isEmpty(packageName) ? "" : " " + packageName)); + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + // ====== + // = am = + // ====== + + /** + * 启动自身应用 + * @return {@code true} success, {@code false} fail + */ + public static boolean startSelfApp() { + return startSelfApp(false); + } + + /** + * 启动自身应用 + * @param closeActivity 是否关闭 Activity 所属的 APP 进程后再启动 Activity + * @return {@code true} success, {@code false} fail + */ + public static boolean startSelfApp(final boolean closeActivity) { + try { + // 获取包名 + String packageName = AppUtils.getPackageName(); + // 获取 Launcher Activity + String activity = ActivityUtils.getLauncherActivity(); + // 跳转应用启动页 ( 启动应用 ) + return startActivity(packageName + "/" + activity, closeActivity); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startSelfApp"); + } + return false; + } + + /** + * 跳转页面 Activity + * @param packageAndLauncher package/package.xx.Activity.className + * @param closeActivity 是否关闭 Activity 所属的 APP 进程后再启动 Activity + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivity( + final String packageAndLauncher, + final boolean closeActivity + ) { + return startActivity(packageAndLauncher, null, closeActivity); + } + + /** + * 跳转页面 Activity + * @param packageAndLauncher package/package.xx.Activity.className + * @param append 追加的信息, 例如传递参数等 + * @param closeActivity 是否关闭 Activity 所属的 APP 进程后再启动 Activity + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivity( + final String packageAndLauncher, + final String append, + final boolean closeActivity + ) { + if (TextUtils.isEmpty(packageAndLauncher)) return false; + try { + // am start [options] + String cmd = "am start %s"; + if (closeActivity) { + cmd = String.format(cmd, "-S " + packageAndLauncher); + } else { + cmd = String.format(cmd, packageAndLauncher); + } + // 判断是否追加 + if (!TextUtils.isEmpty(append)) { + cmd += " " + append.trim(); + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startActivity"); + } + return false; + } + + /** + * 启动服务 + * @param packageAndService package/package.xx.Service.className + * @return {@code true} success, {@code false} fail + */ + public static boolean startService(final String packageAndService) { + return startService(packageAndService, null); + } + + /** + * 启动服务 + * @param packageAndService package/package.xx.Service.className + * @param append 追加的信息, 例如传递参数等 + * @return {@code true} success, {@code false} fail + */ + public static boolean startService( + final String packageAndService, + final String append + ) { + if (TextUtils.isEmpty(packageAndService)) return false; + try { + // am startservice [options] + String cmd = "am startservice %s"; + // 进行格式化 + cmd = String.format(cmd, packageAndService); + // 判断是否追加 + if (!TextUtils.isEmpty(append)) { + cmd += " " + append.trim(); + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startService"); + } + return false; + } + + /** + * 停止服务 + * @param packageAndService package/package.xx.Service.className + * @return {@code true} success, {@code false} fail + */ + public static boolean stopService(final String packageAndService) { + return stopService(packageAndService, null); + } + + /** + * 停止服务 + * @param packageAndService package/package.xx.Service.className + * @param append 追加的信息, 例如传递参数等 + * @return {@code true} success, {@code false} fail + */ + public static boolean stopService( + final String packageAndService, + final String append + ) { + if (TextUtils.isEmpty(packageAndService)) return false; + try { + // am stopservice [options] + String cmd = "am stopservice %s"; + // 进行格式化 + cmd = String.format(cmd, packageAndService); + // 判断是否追加 + if (!TextUtils.isEmpty(append)) { + cmd += " " + append.trim(); + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + return result.isSuccess3(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopService"); + } + return false; + } + + /** + * 发送广播 ( 向所有组件发送 ) + *
+     *     向所有组件广播 BOOT_COMPLETED
+     *     adb shell am broadcast -a android.intent.action.BOOT_COMPLETED
+     * 
+ * @param broadcast 广播 INTENT + * @return {@code true} success, {@code false} fail + */ + public static boolean sendBroadcastToAll(final String broadcast) { + if (TextUtils.isEmpty(broadcast)) return false; + try { + // am broadcast [options] + String cmd = "am broadcast -a %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, broadcast), true + ); + return result.isSuccess3(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendBroadcastAll"); + } + return false; + } + + /** + * 发送广播 + *
+     *     只向 org.mazhuang.boottimemeasure/.BootCompletedReceiver 广播 BOOT_COMPLETED
+     *     adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n org.mazhuang.boottimemeasure/.BootCompletedReceiver
+     * 
+ * @param packageAndBroadcast package/package.xx.Receiver.className + * @param broadcast 广播 INTENT + * @return {@code true} success, {@code false} fail + */ + public static boolean sendBroadcast( + final String packageAndBroadcast, + final String broadcast + ) { + if (TextUtils.isEmpty(packageAndBroadcast)) return false; + if (TextUtils.isEmpty(broadcast)) return false; + try { + // am broadcast [options] + String cmd = "am broadcast -a %s -n %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, broadcast, packageAndBroadcast), + true + ); + return result.isSuccess3(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendBroadcast"); + } + return false; + } + + // = + + /** + * 销毁进程 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean kill(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + String cmd = "am force-stop %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, packageName), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "kill"); + } + return false; + } + + /** + * 收紧内存 + * @param pid 进程 ID + * @param level HIDDEN、RUNNING_MODERATE、BACKGROUND、RUNNING_LOW、MODERATE、RUNNING_CRITICAL、COMPLETE + * @return {@code true} success, {@code false} fail + */ + public static boolean sendTrimMemory( + final int pid, + final String level + ) { + if (TextUtils.isEmpty(level)) return false; + try { + String cmd = "am send-trim-memory %s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, pid, level), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendTrimMemory"); + } + return false; + } + +// // ========== +// // = 文件管理 = +// // ========== +// +// /** +// * 复制设备里的文件到电脑 +// * @param remote 设备里的文件路径 +// * @param local 电脑上的目录 +// * @return {@code true} success, {@code false} fail +// */ +// public static boolean pull(final String remote, final String local) { +// if (TextUtils.isEmpty(remote)) return false; +// try { +// // adb pull <设备里的文件路径> [电脑上的目录] +// String cmd = "adb pull %s"; +// // 判断是否存到默认地址 +// if (!TextUtils.isEmpty(local)) { +// cmd += " " + local; +// } +// // 执行 shell +// ShellUtils.CommandResult result = ShellUtils.execCmd(String.format(cmd, remote), true); +// return result.isSuccess2(); +// } catch (Exception e) { +// LogPrintUtils.eTag(TAG, e, "pull"); +// } +// return false; +// } +// +// /** +// * 复制电脑里的文件到设备 +// * @param local 电脑上的文件路径 +// * @param remote 设备里的目录 +// * @return {@code true} success, {@code false} fail +// */ +// public static boolean push(final String local, final String remote) { +// if (TextUtils.isEmpty(local)) return false; +// if (TextUtils.isEmpty(remote)) return false; +// try { +// // adb push <电脑上的文件路径> <设备里的目录> +// String cmd = "adb push %s %s"; +// // 执行 shell +// ShellUtils.CommandResult result = ShellUtils.execCmd(String.format(cmd, local, remote), true); +// return result.isSuccess2(); +// } catch (Exception e) { +// LogPrintUtils.eTag(TAG, e, "push"); +// } +// return false; +// } + + // ========= + // = Input = + // ========= + + // ============================== + // = tap ( 模拟 touch 屏幕的事件 ) = + // ============================== + + /** + * 点击某个区域 + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public static boolean tap( + final float x, + final float y + ) { + try { + // input [touchscreen|touchpad|touchnavigation] tap + // input [ 屏幕、触摸板、导航键 ] tap + String cmd = "input touchscreen tap %s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, (int) x, (int) y), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "tap"); + } + return false; + } + + // ==================== + // = swipe ( 滑动事件 ) = + // ==================== + + /** + * 按压某个区域 ( 点击 ) + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public static boolean swipeClick( + final float x, + final float y + ) { + return swipe(x, y, x, y, 100L); + } + + /** + * 按压某个区域 time 大于一定时间变成长按 + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @param millis 按压时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean swipeClick( + final float x, + final float y, + final long millis + ) { + return swipe(x, y, x, y, millis); + } + + /** + * 滑动到某个区域 + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @param toX 滑动到 X 轴坐标 + * @param toY 滑动到 Y 轴坐标 + * @param millis 滑动时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean swipe( + final float x, + final float y, + final float toX, + final float toY, + final long millis + ) { + try { + // input [touchscreen|touchpad|touchnavigation] swipe [duration(ms)] + String cmd = "input touchscreen swipe %s %s %s %s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format( + cmd, (int) x, (int) y, + (int) toX, (int) toY, millis + ), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "swipe"); + } + return false; + } + + // =================== + // = text ( 模拟输入 ) = + // =================== + + /** + * 输入文本 ( 不支持中文 ) + * @param txt 文本内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean text(final String txt) { + if (TextUtils.isEmpty(txt)) return false; + try { + // input text + String cmd = "input text %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, txt), true + ); // false 可以执行 + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "text"); + } + return false; + } + + // ======================= + // = keyevent ( 按键操作 ) = + // ======================= + + /** + * 触发某些按键 + * @param keyCode KeyEvent.xxx 如: KeyEvent.KEYCODE_BACK ( 返回键 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean keyevent(final int keyCode) { + try { + // input keyevent + String cmd = "input keyevent %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, keyCode), true + ); // false 可以执行 + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "keyevent"); + } + return false; + } + + // ========== + // = 实用功能 = + // ========== + + /** + * 屏幕截图 + * @param path 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean screencap(final String path) { + return screencap(path, 0); + } + + /** + * 屏幕截图 + * @param path 存储路径 + * @param displayId -d display-id 指定截图的显示屏编号 ( 有多显示屏的情况下 ) 默认 0 + * @return {@code true} success, {@code false} fail + */ + public static boolean screencap( + final String path, + final int displayId + ) { + if (TextUtils.isEmpty(path)) return false; + try { + String cmd = "screencap -p -d %s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format( + cmd, Math.max(displayId, 0), path + ), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "screencap"); + } + return false; + } + + /** + * 录制屏幕 ( 以 mp4 格式保存 ) + * @param path 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean screenrecord(final String path) { + return screenrecord(path, null, -1, -1); + } + + /** + * 录制屏幕 ( 以 mp4 格式保存 ) + * @param path 存储路径 + * @param time 录制时长, 单位秒 ( 默认 / 最长 180 秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean screenrecord( + final String path, + final int time + ) { + return screenrecord(path, null, -1, time); + } + + /** + * 录制屏幕 ( 以 mp4 格式保存到 ) + * @param path 存储路径 + * @param size 视频的尺寸, 比如 1280x720, 默认是屏幕分辨率 + * @param time 录制时长, 单位秒 ( 默认 / 最长 180 秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean screenrecord( + final String path, + final String size, + final int time + ) { + return screenrecord(path, size, -1, time); + } + + /** + * 录制屏幕 ( 以 mp4 格式保存到 ) + * @param path 存储路径 + * @param size 视频的尺寸, 比如 1280x720, 默认是屏幕分辨率 + * @param bitRate 视频的比特率, 默认是 4Mbps + * @param time 录制时长, 单位秒 ( 默认 / 最长 180 秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean screenrecord( + final String path, + final String size, + final int bitRate, + final int time + ) { + if (TextUtils.isEmpty(path)) return false; + try { + StringBuilder builder = new StringBuilder(); + builder.append("screenrecord"); + if (!TextUtils.isEmpty(size)) { + builder.append(" --size ").append(size); + } + if (bitRate > 0) { + builder.append(" --bit-rate ").append(bitRate); + } + if (time > 0) { + builder.append(" --time-limit ").append(time); + } + builder.append(" ").append(path); + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + builder.toString(), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "screenrecord"); + } + return false; + } + + /** + * 查看连接过的 Wifi 密码 + * @return 连接过的 Wifi 密码 + */ + public static String wifiConf() { + try { + String cmd = "cat /data/misc/wifi/*.conf"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmd, true); + if (result.isSuccess3()) { + return result.successMsg; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "wifiConf"); + } + return null; + } + + /** + * 开启 / 关闭 Wifi + * @param open 是否开启 + * @return {@code true} success, {@code false} fail + */ + public static boolean wifiSwitch(final boolean open) { + String cmd = "svc wifi %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, open ? "enable" : "disable"), true + ); + return result.isSuccess2(); + } + + /** + * 设置系统时间 + * @param time yyyyMMdd.HHmmss 20160823.131500 + * 表示将系统日期和时间更改为 2016 年 08 月 23 日 13 点 15 分 00 秒 + * @return {@code true} success, {@code false} fail + */ + public static boolean setSystemTime(final String time) { + if (TextUtils.isEmpty(time)) return false; + try { + String cmd = "date -s %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, time), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setSystemTime"); + } + return false; + } + + /** + * 设置系统时间 + * @param time MMddHHmmyyyy.ss 082313152016.00 + * 表示将系统日期和时间更改为 2016 年 08 月 23 日 13 点 15 分 00 秒 + * @return {@code true} success, {@code false} fail + */ + public static boolean setSystemTime2(final String time) { + if (TextUtils.isEmpty(time)) return false; + try { + String cmd = "date %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, time), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setSystemTime2"); + } + return false; + } + + /** + * 设置系统时间 + * @param millis 时间毫秒转换 MMddHHmmyyyy.ss + * @return {@code true} success, {@code false} fail + */ + public static boolean setSystemTime2(final long millis) { + if (millis < 0) return false; + try { + String cmd = "date %s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, DateUtils.formatTime( + millis, DevFinal.TIME.SPECIAL_mmddHHmmyyyyss + )), true + ); + return result.isSuccess2(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setSystemTime2"); + } + return false; + } + + // ============= + // = 刷机相关命令 = + // ============= + + /** + * 关机 ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean shutdown() { + try { + ShellUtils.execCmd("reboot -p", true); + Intent intent = new Intent("android.intent.action.ACTION_REQUEST_SHUTDOWN"); + intent.putExtra("android.intent.extra.KEY_CONFIRM", false); + return AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "shutdown"); + } + return false; + } + + /** + * 重启设备 ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean reboot() { + try { + ShellUtils.execCmd("reboot", true); + Intent intent = new Intent(Intent.ACTION_REBOOT); + intent.putExtra("nowait", 1); + intent.putExtra("interval", 1); + intent.putExtra("window", 0); + return AppUtils.sendBroadcast(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "reboot"); + } + return false; + } + + /** + * 重启设备 ( 需要 root 权限 ) + * @param reason 传递给内核来请求特殊的引导模式, 如 "recovery" + * 重启到 Fastboot 模式 bootloader + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.REBOOT) + public static boolean reboot(final String reason) { + if (TextUtils.isEmpty(reason)) return false; + try { + AppUtils.getPowerManager().reboot(reason); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "reboot"); + } + return false; + } + + /** + * 重启引导到 recovery ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean rebootToRecovery() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "reboot recovery", true + ); + return result.isSuccess2(); + } + + /** + * 重启引导到 bootloader ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean rebootToBootloader() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "reboot bootloader", true + ); + return result.isSuccess2(); + } + + // ========== + // = 滑动方法 = + // ========== + + /** + * 发送事件滑动 + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @param toX 滑动到 X 轴坐标 + * @param toY 滑动到 Y 轴坐标 + * @param number 循环次数 + */ + public static void sendEventSlide( + final float x, + final float y, + final float toX, + final float toY, + final int number + ) { + List lists = new ArrayList<>(); + // = 开头 = + lists.add("sendevent /dev/input/event1 3 57 109"); + lists.add("sendevent /dev/input/event1 3 53 " + x); + lists.add("sendevent /dev/input/event1 3 54 " + y); + // 发送 touch 事件 ( 必须使用 0 0 0 配对 ) + lists.add("sendevent /dev/input/event1 1 330 1"); + lists.add("sendevent /dev/input/event1 0 0 0"); + + // 判断方向 ( 手势是否从左到右, View 往左滑, 手势操作往右滑 ) + boolean isLeftToRight = toX > x; + // 判断方向 ( 手势是否从上到下, View 往上滑, 手势操作往下滑 ) + boolean isTopToBottom = toY > y; + + // 计算差数 + float diffX = isLeftToRight ? (toX - x) : (x - toX); + float diffY = isTopToBottom ? (toY - y) : (y - toY); + + if (!isLeftToRight) { + diffX = -diffX; + } + + if (!isTopToBottom) { + diffY = -diffY; + } + + // 平均值 + float averageX = diffX / number; + float averageY = diffY / number; + // 上次位置 + int oldX = (int) x; + int oldY = (int) y; + + // 循环处理 + for (int i = 0; i <= number; i++) { + if (averageX != 0F) { + // 进行判断处理 + int calcX = (int) (x + averageX * i); + if (oldX != calcX) { + oldX = calcX; + lists.add("sendevent /dev/input/event1 3 53 " + calcX); + } + } + + if (averageY != 0F) { + // 进行判断处理 + int calcY = (int) (y + averageY * i); + if (oldY != calcY) { + oldY = calcY; + lists.add("sendevent /dev/input/event1 3 54 " + calcY); + } + } + // 每次操作结束发送 + lists.add("sendevent /dev/input/event1 0 0 0"); + } + // = 结尾 = + lists.add("sendevent /dev/input/event1 3 57 4294967295"); + // 释放 touch 事件 ( 必须使用 0 0 0 配对 ) + lists.add("sendevent /dev/input/event1 1 330 0"); + lists.add("sendevent /dev/input/event1 0 0 0"); + + // 执行 shell + ShellUtils.execCmd(lists, true); + } + + // ============= + // = 查看设备信息 = + // ============= + + /** + * 获取 SDK 版本 + * @return SDK 版本 + */ + public static String getSDKVersion() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.build.version.sdk", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 Android 系统版本 + * @return Android 系统版本 + */ + public static String getAndroidVersion() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.build.version.release", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取设备型号 ( 如 RedmiNote4X ) + * @return 设备型号 + */ + public static String getModel() { + // android.os.Build 内部有信息 android.os.Build.MODEL + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.product.model", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取设备品牌 + * @return 设备品牌 + */ + public static String getBrand() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.product.brand", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取设备名 + * @return 设备名 + */ + public static String getDeviceName() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.product.name", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 CPU 支持的 abi 列表 + * @return CPU 支持的 abi 列表 + */ + public static String getCpuAbiList() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "cat /system/build.prop | grep ro.product.cpu.abi", + false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取每个应用程序的内存上限 + * @return 每个应用程序的内存上限 + */ + public static String getAppHeapsize() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop dalvik.vm.heapsize", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取电池状况 + * @return 电池状况 + */ + public static String getBattery() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys battery", true + ); + if (result.isSuccess3()) { // scale 代表最大电量, level 代表当前电量 + return result.successMsg; + } + return null; + } + + /** + * 获取屏幕密度 + * @return 屏幕密度 + */ + public static String getDensity() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop ro.sf.lcd_density", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取屏幕分辨率 + * @return 屏幕分辨率 + */ + public static String getScreenSize() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "wm size", true + ); + if (result.isSuccess3()) { + // 正常返回 Physical size: 1080 x 1920 + // 如果使用命令修改过, 那输出可能是 + // Physical size: 1080 x 1920 + // Override size: 480 x 1024 + // 表明设备的屏幕分辨率原本是 1080px * 1920px, 当前被修改为 480px * 1024px + return result.successMsg; + } + return null; + } + + /** + * 获取显示屏参数 + * @return 显示屏参数 + */ + public static String getDisplays() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys window displays", true + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 Android id + * @return Android id + */ + public static String getAndroidId() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings get secure android_id", true + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 IMEI 码 + * @return IMEI 码 + */ + public static String getIMEI() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "service call iphonesubinfo 1", true + ); + if (result.isSuccess3()) { + try { + StringBuilder builder = new StringBuilder(); + String subStr = result.successMsg.replaceAll("\\.", ""); + subStr = subStr.substring(subStr.indexOf('\'') + 1, subStr.indexOf("')")); + // 添加数据 + builder.append(subStr.substring(0, subStr.indexOf('\''))); + // 从指定索引开始 + int index = subStr.indexOf("'", builder.length() + 1); + // 再次裁剪 + subStr = subStr.substring(index + 1); + // 添加数据 + builder.append(subStr.substring(0, subStr.indexOf("'"))); + // 从指定索引开始 + index = subStr.indexOf("'", builder.length() + 1); + // 再次裁剪 + subStr = subStr.substring(index + 1); + // 最后进行添加 + builder.append(subStr.split(DevFinal.REGEX.SPACE)[0]); + // 返回对应的数据 + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIMEI"); + } + } + } else { + // 在 Android 4.4 及以下版本可通过如下命令获取 IMEI + ShellUtils.CommandResult result = ShellUtils.execCmd( + "dumpsys iphonesubinfo", true + ); + if (result.isSuccess3()) { // 返回值中的 Device ID 就是 IMEI + try { + String[] arrays = result.successMsg.split(DevFinal.SYMBOL.NEW_LINE); + for (String value : arrays) { + if (!TextUtils.isEmpty(value)) { + if (value.toLowerCase().contains("device")) { + // 进行拆分 + String[] splitArray = value.split(DevFinal.REGEX.SPACE); + return splitArray[splitArray.length - 1]; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIMEI"); + } + } + } + return null; + } + + /** + * 获取 IP 地址 + * @return IP 地址 + */ + public static String getIPAddress() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "ifconfig | grep Mask", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } else { // 如果设备连着 Wifi, 可以使用如下命令来查看局域网 IP + result = ShellUtils.execCmd("ifconfig wlan0", false); + if (result.isSuccess3()) { + return result.successMsg; + } else { + // 可以看到网络连接名称、启用状态、IP 地址和 Mac 地址等信息 + result = ShellUtils.execCmd("netcfg", false); + if (result.isSuccess3()) { + return result.successMsg; + } + } + } + return null; + } + + /** + * 获取 Mac 地址 + * @return Mac 地址 + */ + public static String getMac() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "cat /sys/class/net/wlan0/address", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取 CPU 信息 + * @return CPU 信息 + */ + public static String getCPU() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "cat /proc/cpuinfo", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 获取内存信息 + * @return 内存信息 + */ + public static String getMemInfo() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "cat /proc/meminfo", false + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + // ========== + // = 修改设置 = + // ========== + + /** + * 设置屏幕大小 + * @param width 屏幕宽度 + * @param height 屏幕高度 + * @return {@code true} success, {@code false} fail + */ + public static boolean setScreenSize( + final int width, + final int height + ) { + String cmd = "wm size %sx%s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, width, height), true + ); + return result.isSuccess2(); + } + + /** + * 恢复原分辨率命令 + * @return {@code true} success, {@code false} fail + */ + public static boolean resetScreen() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "wm size reset", true + ); + return result.isSuccess2(); + } + + /** + * 设置屏幕密度 + * @param density 屏幕密度 + * @return {@code true} success, {@code false} fail + */ + public static boolean setDensity(final int density) { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "wm density " + density, true + ); + return result.isSuccess2(); + } + + /** + * 恢复原屏幕密度 + * @return {@code true} success, {@code false} fail + */ + public static boolean resetDensity() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "wm density reset", true + ); + return result.isSuccess2(); + } + + /** + * 显示区域 ( 设置留白边距 ) + * @param left left padding + * @param top top padding + * @param right right padding + * @param bottom bottom padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setOverscan( + final int left, + final int top, + final int right, + final int bottom + ) { + String cmd = "wm overscan %s,%s,%s,%s"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format(cmd, left, top, right, bottom), true + ); + return result.isSuccess2(); + } + + /** + * 恢复原显示区域 + * @return {@code true} success, {@code false} fail + */ + public static boolean resetOverscan() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "wm overscan reset", true + ); + return result.isSuccess2(); + } + + /** + * 获取亮度是否为自动获取 ( 自动调节亮度 ) + * @return 1 开启、0 未开启、-1 未知 + */ + public static int getScreenBrightnessMode() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings get system screen_brightness_mode", true + ); + if (result.isSuccess3()) { + try { + return Integer.parseInt(result.successMsg); + } catch (Exception ignored) { + } + } + return -1; + } + + /** + * 设置亮度是否为自动获取 ( 自动调节亮度 ) + * @param isAuto 是否自动调节 + * @return {@code true} success, {@code false} fail + */ + public static boolean setScreenBrightnessMode(final boolean isAuto) { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings put system screen_brightness_mode " + (isAuto ? 1 : 0), + true + ); + return result.isSuccess3(); + } + + /** + * 获取屏幕亮度值 + * @return 屏幕亮度值 + */ + public static String getScreenBrightness() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings get system screen_brightness", + true + ); + if (result.isSuccess3()) { + String suc = result.successMsg; + if (suc.startsWith("\"")) { + suc = suc.substring(1); + } + if (suc.endsWith("\"")) { + suc = suc.substring(0, suc.length() - 1); + } + return suc; + } + return null; + } + + /** + * 更改屏幕亮度值 ( 亮度值在 0-255 之间 ) + * @param brightness 亮度值 + * @return {@code true} success, {@code false} fail + */ + public static boolean setScreenBrightness(@IntRange(from = 0, to = 255) final int brightness) { + if (brightness < 0) { + return false; + } else if (brightness > 255) { + return false; + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings put system screen_brightness " + brightness, + true + ); + return result.isSuccess2(); + } + + /** + * 获取自动锁屏休眠时间 ( 单位毫秒 ) + * @return 自动锁屏休眠时间 + */ + public static String getScreenOffTimeout() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings get system screen_off_timeout", + true + ); + if (result.isSuccess3()) { + return result.successMsg; + } + return null; + } + + /** + * 设置自动锁屏休眠时间 ( 单位毫秒 ) + *
+     *     设置永不休眠 Integer.MAX_VALUE
+     * 
+ * @param millis 休眠时间 ( 单位毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean setScreenOffTimeout(final long millis) { + if (millis <= 0) { + return false; + } + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings put system screen_off_timeout " + millis, + true + ); + return result.isSuccess2(); + } + + /** + * 获取日期时间选项中通过网络获取时间的状态 + * @return 1 允许、0 不允许、-1 未知 + */ + public static int getGlobalAutoTime() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings get global auto_time", + true + ); + if (result.isSuccess3()) { + try { + return Integer.parseInt(result.successMsg); + } catch (Exception ignored) { + } + } + return -1; + } + + /** + * 修改日期时间选项中通过网络获取时间的状态, 设置是否开启 + * @param isOpen 是否设置通过网络获取时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean setGlobalAutoTime(final boolean isOpen) { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings put global auto_time " + (isOpen ? 1 : 0), + true + ); + return result.isSuccess3(); + } + + /** + * 关闭 USB 调试模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean disableADB() { + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd( + "settings put global adb_enabled 0", + true + ); + return result.isSuccess2(); + } + + /** + * 允许访问非 SDK API + *
+     *     不需要设备获得 Root 权限
+     * 
+ * @return 执行结果 + */ + public static int putHiddenApi() { + String[] cmds = new String[2]; + cmds[0] = "settings put global hidden_api_policy_pre_p_apps 1"; + cmds[1] = "settings put global hidden_api_policy_p_apps 1"; + // 执行 shell + return ShellUtils.execCmd(cmds, true).result; + } + + /** + * 禁止访问非 SDK API + *
+     *     不需要设备获得 Root 权限
+     * 
+ * @return 执行结果 + */ + public static int deleteHiddenApi() { + String[] cmds = new String[2]; + cmds[0] = "settings delete global hidden_api_policy_pre_p_apps"; + cmds[1] = "settings delete global hidden_api_policy_p_apps"; + // 执行 shell + return ShellUtils.execCmd(cmds, true).result; + } + + /** + * 开启无障碍辅助功能 + * @param packageName 应用包名 + * @param accessibilityServiceName 无障碍服务名 + * @return {@code true} success, {@code false} fail + */ + public static boolean openAccessibility( + final String packageName, + final String accessibilityServiceName + ) { + if (TextUtils.isEmpty(packageName)) return false; + if (TextUtils.isEmpty(accessibilityServiceName)) return false; + + String cmd = "settings put secure enabled_accessibility_services %s/%s"; + // 格式化 shell 命令 + String[] cmds = new String[2]; + cmds[0] = String.format(cmd, packageName, accessibilityServiceName); + cmds[1] = "settings put secure accessibility_enabled 1"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmds, true); + return result.isSuccess2(); + } + + /** + * 关闭无障碍辅助功能 + * @param packageName 应用包名 + * @param accessibilityServiceName 无障碍服务名 + * @return {@code true} success, {@code false} fail + */ + public static boolean closeAccessibility( + final String packageName, + final String accessibilityServiceName + ) { + if (TextUtils.isEmpty(packageName)) return false; + if (TextUtils.isEmpty(accessibilityServiceName)) return false; + + String cmd = "settings put secure enabled_accessibility_services %s/%s"; + // 格式化 shell 命令 + String[] cmdArrays = new String[2]; + cmdArrays[0] = String.format(cmd, packageName, accessibilityServiceName); + cmdArrays[1] = "settings put secure accessibility_enabled 0"; + // 执行 shell + ShellUtils.CommandResult result = ShellUtils.execCmd(cmdArrays, true); + return result.isSuccess2(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/AccessibilityUtils.java b/lib/DevApp/src/main/java/dev/utils/app/AccessibilityUtils.java new file mode 100644 index 0000000000..eff14d0efd --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/AccessibilityUtils.java @@ -0,0 +1,1931 @@ +package dev.utils.app; + +import android.accessibilityservice.AccessibilityService; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.GestureDescription; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; + +import androidx.annotation.RequiresApi; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 无障碍功能工具类 + * @author Ttt + *
+ *     @see 
+ *     @see 
+ *     @see 
+ *     

+ * AccessibilityService 在 API < 18 的时候使用 AccessibilityService + * 所需权限 + * + *

+ * // 如果允许服务监听按键操作, 该方法是按键事件的回调, 需要注意这个过程发生在系统处理按键事件之前 + * AccessibilityService#onKeyEvent(KeyEvent event) + *
+ */ +public final class AccessibilityUtils { + + private AccessibilityUtils() { + } + + // 日志 TAG + private static final String TAG = AccessibilityUtils.class.getSimpleName(); + + // AccessibilityService 对象 + private static AccessibilityService sService = null; + + /** + * 获取 AccessibilityService 对象 + * @return {@link AccessibilityService} + */ + public static AccessibilityService getService() { + return sService; + } + + /** + * 设置 AccessibilityService 对象 + * @param service {@link AccessibilityService} + */ + public static void setService(final AccessibilityService service) { + AccessibilityUtils.sService = service; + } + + // = + + /** + * 检查是否开启无障碍功能 + *
+     *     未开启则跳转至辅助功能设置页面
+     * 
+ * @return {@code true} open, {@code false} close + */ + public static boolean checkAccessibility() { + return checkAccessibility(AppUtils.getPackageName()); + } + + /** + * 检查是否开启无障碍功能 + *
+     *     未开启则跳转至辅助功能设置页面
+     * 
+ * @param packageName 应用包名 + * @return {@code true} open, {@code false} close + */ + public static boolean checkAccessibility(final String packageName) { + if (!isAccessibilitySettingsOn(packageName)) { + // 跳转至辅助功能设置页面 + AppUtils.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); + return false; + } + return true; + } + + /** + * 判断是否开启无障碍功能 + * @param packageName 应用包名 + * @return {@code true} open, {@code false} close + */ + public static boolean isAccessibilitySettingsOn(final String packageName) { + if (packageName == null) return false; + // 无障碍功能开启状态 + int accessibilityEnabled = 0; + try { + accessibilityEnabled = Settings.Secure.getInt( + ResourceUtils.getContentResolver(), + Settings.Secure.ACCESSIBILITY_ENABLED + ); + } catch (Settings.SettingNotFoundException e) { + LogPrintUtils.eTag( + TAG, e, + "isAccessibilitySettingsOn - Settings.Secure.ACCESSIBILITY_ENABLED" + ); + } + if (accessibilityEnabled == 1) { + try { + String services = Settings.Secure.getString( + ResourceUtils.getContentResolver(), + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ); + if (services != null) { + return services.toLowerCase().contains(packageName.toLowerCase()); + } + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, + "isAccessibilitySettingsOn - Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES" + ); + } + } + return false; + } + + // =========== + // = Service = + // =========== + + /** + * 禁用无障碍服务 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean disableSelf() { + return disableSelf(sService); + } + + /** + * 禁用无障碍服务 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean disableSelf(final AccessibilityService service) { + if (service != null) { + try { + service.disableSelf(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "disableSelf"); + } + } + return false; + } + + /** + * 获取无障碍服务信息 + * @return AccessibilityServiceInfo + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static AccessibilityServiceInfo getServiceInfo() { + return getServiceInfo(sService); + } + + /** + * 获取无障碍服务信息 + * @param service {@link AccessibilityService} + * @return AccessibilityServiceInfo + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static AccessibilityServiceInfo getServiceInfo(final AccessibilityService service) { + if (service != null) { + try { + return service.getServiceInfo(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getServiceInfo"); + } + } + return null; + } + + /** + * 设置无障碍服务信息 ( 动态配置方式 ) + * @param service {@link AccessibilityService} + * @param info 无障碍服务信息 + * @return {@code true} success, {@code false} fail + */ + public static boolean setServiceInfo( + final AccessibilityService service, + final AccessibilityServiceInfo info + ) { + if (service != null && info != null) { + try { + service.setServiceInfo(info); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setServiceInfo"); + } + } + return false; + } + + // ========== + // = 节点获取 = + // ========== + + /** + * 获取根节点 + * @return 根节点 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static AccessibilityNodeInfo getRootInActiveWindow() { + return getRootInActiveWindow(sService); + } + + /** + * 获取根节点 + * @param service {@link AccessibilityService} + * @return 根节点 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static AccessibilityNodeInfo getRootInActiveWindow(final AccessibilityService service) { + if (service != null) { + try { + return service.getRootInActiveWindow(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRootInActiveWindow"); + } + } + return null; + } + + // ======== + // = 包装类 = + // ======== + + /** + * detail: AccessibilityNodeInfo 筛选接口 + * @author Ttt + */ + public interface NodeFilter { + + /** + * 是否允许添加 + * @param nodeInfo 待校验 AccessibilityNodeInfo + * @return {@code true} 允许, {@code false} 不允许 + */ + boolean accept(AccessibilityNodeInfo nodeInfo); + } + + // ============= + // = Operation = + // ============= + + /** + * 获取 Operation + * @param nodeInfo {@link AccessibilityNodeInfo} + * @return {@link Operation} + */ + public static Operation operation(final AccessibilityNodeInfo nodeInfo) { + return new Operation(nodeInfo); + } + + /** + * 获取 Operation ( Root Window Node ) + * @return {@link Operation} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static Operation operation() { + return new Operation(getRootInActiveWindow()); + } + + /** + * 获取 Operation ( Root Window Node ) + * @param service {@link AccessibilityService} + * @return {@link Operation} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static Operation operation(final AccessibilityService service) { + return new Operation(getRootInActiveWindow(service)); + } + + // ======== + // = Node = + // ======== + + /** + * 获取 Node + * @param nodeInfo {@link AccessibilityNodeInfo} + * @return {@link Node} + */ + public static Node node(final AccessibilityNodeInfo nodeInfo) { + return new Node(nodeInfo); + } + + /** + * 获取 Node ( Root Window Node ) + * @return {@link Node} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static Node node() { + return new Node(getRootInActiveWindow()); + } + + /** + * 获取 Node ( Root Window Node ) + * @param service {@link AccessibilityService} + * @return {@link Node} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static Node node(final AccessibilityService service) { + return new Node(getRootInActiveWindow(service)); + } + + // ============ + // = 包装类实现 = + // ============ + + /** + * detail: 无障碍节点操作包装类 + * @author Ttt + */ + public static final class Operation { + + // 无障碍节点包装类 + private Node mNode; + + private Operation(final AccessibilityNodeInfo nodeInfo) { + this.mNode = AccessibilityUtils.node(nodeInfo); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Node + * @return {@link Node} + */ + public Node node() { + return mNode; + } + + /** + * 获取无障碍节点 + * @return {@link AccessibilityNodeInfo} + */ + public AccessibilityNodeInfo getNodeInfo() { + return mNode.getNodeInfo(); + } + + // ======= + // = 操作 = + // ======= + + /** + * 模拟对应 Action 操作 + * @param action 操作意图 + * @return {@code true} success, {@code false} fail + */ + public boolean performAction(final int action) { + return mNode.performAction(action); + } + + /** + * 模拟对应 Action 操作 + * @param action 操作意图 + * @param arguments 操作参数 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performAction( + final int action, + final Bundle arguments + ) { + return mNode.performAction(action, arguments); + } + + // ======= + // = 点击 = + // ======= + + /** + * 点击指定节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick() { + return mNode.performClick(); + } + + /** + * 点击指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick(final boolean clickParent) { + return mNode.performClick(clickParent); + } + + /** + * 点击指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick( + final boolean clickParent, + final boolean clickAll + ) { + return mNode.performClick(clickParent, clickAll); + } + + // ======= + // = 长按 = + // ======= + + /** + * 长按指定节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick() { + return mNode.performLongClick(); + } + + /** + * 长按指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick(final boolean clickParent) { + return mNode.performLongClick(clickParent); + } + + /** + * 长按指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick( + final boolean clickParent, + final boolean clickAll + ) { + return mNode.performLongClick(clickParent, clickAll); + } + + // ========== + // = 输入内容 = + // ========== + + /** + * 指定节点输入文本 + * @param text 文本内容 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean inputText(final CharSequence text) { + return mNode.inputText(text); + } + + // ========== + // = 节点获取 = + // ========== + + /** + * 查找符合条件的节点 + * @param focus 焦点类型 + * @return Operation + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public Operation findFocus(final int focus) { + return operation(mNode.findFocus(focus)); + } + + /** + * 查找符合条件的节点 + * @param focus 焦点类型 + * @param className 节点所属的类 ( 类名 ) + * @return Operation + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public Operation findFocus( + final int focus, + final String className + ) { + return operation(mNode.findFocus(focus, className)); + } + + /** + * 查找符合条件的节点 + * @param text 文本内容 ( 搜索包含该文本内容的节点 ) + * @return Operation List + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public List findAccessibilityNodeInfosByText(final String text) { + List lists = new ArrayList<>(); + List nodes = mNode.findAccessibilityNodeInfosByText(text); + for (int i = 0, len = nodes.size(); i < len; i++) { + lists.add(operation(nodes.get(i))); + } + return lists; + } + + /** + * 查找符合条件的节点 + * @param text 文本内容 ( 搜索包含该文本内容的节点 ) + * @param className 节点所属的类 ( 类名 ) + * @return Operation List + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public List findAccessibilityNodeInfosByText( + final String text, + final String className + ) { + List lists = new ArrayList<>(); + List nodes = mNode.findAccessibilityNodeInfosByText( + text, className + ); + for (int i = 0, len = nodes.size(); i < len; i++) { + lists.add(operation(nodes.get(i))); + } + return lists; + } + + /** + * 查找符合条件的节点 + * @param id viewId + * @return Operation List + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + public List findAccessibilityNodeInfosByViewId(final String id) { + List lists = new ArrayList<>(); + List nodes = mNode.findAccessibilityNodeInfosByViewId(id); + for (int i = 0, len = nodes.size(); i < len; i++) { + lists.add(operation(nodes.get(i))); + } + return lists; + } + + /** + * 查找符合条件的节点 + * @param id viewId + * @param className 节点所属的类 ( 类名 ) + * @return Operation List + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + public List findAccessibilityNodeInfosByViewId( + final String id, + final String className + ) { + List lists = new ArrayList<>(); + List nodes = mNode.findAccessibilityNodeInfosByViewId( + id, className + ); + for (int i = 0, len = nodes.size(); i < len; i++) { + lists.add(operation(nodes.get(i))); + } + return lists; + } + + // ========== + // = 循环查找 = + // ========== + + /** + * 查找全部子节点并进行筛选 + * @param filter AccessibilityNodeInfo 筛选接口 + * @return Operation List + */ + public List findByFilter(final NodeFilter filter) { + List lists = new ArrayList<>(); + List nodes = mNode.findByFilter(filter); + for (int i = 0, len = nodes.size(); i < len; i++) { + lists.add(operation(nodes.get(i))); + } + return lists; + } + } + + /** + * detail: 无障碍节点包装类 + * @author Ttt + */ + public static final class Node { + + // 无障碍节点 + private AccessibilityNodeInfo mNodeInfo; + // 无障碍节点操作包装类 + private Operation mOperation; + + private Node(final AccessibilityNodeInfo nodeInfo) { + this.mNodeInfo = nodeInfo; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Operation + * @return {@link Operation} + */ + public Operation operation() { + if (mOperation == null) { + mOperation = AccessibilityUtils.operation(mNodeInfo); + } + return mOperation; + } + + /** + * 获取无障碍节点 + * @return {@link AccessibilityNodeInfo} + */ + public AccessibilityNodeInfo getNodeInfo() { + return mNodeInfo; + } + + // ======= + // = 操作 = + // ======= + + /** + * 模拟对应 Action 操作 + * @param action 操作意图 + * @return {@code true} success, {@code false} fail + */ + public boolean performAction(final int action) { + return performAction(mNodeInfo, action); + } + + /** + * 模拟对应 Action 操作 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param action 操作意图 + * @return {@code true} success, {@code false} fail + */ + private boolean performAction( + final AccessibilityNodeInfo nodeInfo, + final int action + ) { + if (nodeInfo != null) { + try { + return nodeInfo.performAction(action); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "performAction"); + } + } + return false; + } + + /** + * 模拟对应 Action 操作 + * @param action 操作意图 + * @param arguments 操作参数 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performAction( + final int action, + final Bundle arguments + ) { + return performAction(mNodeInfo, action, arguments); + } + + /** + * 模拟对应 Action 操作 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param action 操作意图 + * @param arguments 操作参数 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private boolean performAction( + final AccessibilityNodeInfo nodeInfo, + final int action, + final Bundle arguments + ) { + if (nodeInfo != null && arguments != null) { + try { + return nodeInfo.performAction(action, arguments); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "performAction - arguments"); + } + } + return false; + } + + // ======= + // = 点击 = + // ======= + + /** + * 点击指定节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick() { + return performClick(mNodeInfo); + } + + /** + * 点击指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick(final boolean clickParent) { + return performClick(mNodeInfo, clickParent, false); + } + + /** + * 点击指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performClick( + final boolean clickParent, + final boolean clickAll + ) { + return performClick(mNodeInfo, clickParent, clickAll); + } + + // = + + /** + * 点击指定节点 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private boolean performClick(final AccessibilityNodeInfo nodeInfo) { + if (nodeInfo != null && nodeInfo.isClickable()) { + return performAction(nodeInfo, AccessibilityNodeInfo.ACTION_CLICK); + } + return false; + } + + /** + * 点击指定节点 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private boolean performClick( + final AccessibilityNodeInfo nodeInfo, + final boolean clickParent, + final boolean clickAll + ) { + if (nodeInfo == null) return false; + if (clickParent) { + if (nodeInfo.isClickable()) { + return performAction(nodeInfo, AccessibilityNodeInfo.ACTION_CLICK); + } else { + AccessibilityNodeInfo parent = nodeInfo.getParent(); + while (parent != null) { + if (performClick(parent)) { + if (!clickAll) { + return true; + } + } + parent = parent.getParent(); + } + return true; + } + } else { + return performClick(nodeInfo); + } + } + + // ======= + // = 长按 = + // ======= + + /** + * 长按指定节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick() { + return performLongClick(mNodeInfo); + } + + /** + * 长按指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick(final boolean clickParent) { + return performLongClick(mNodeInfo, clickParent, false); + } + + /** + * 长按指定节点 + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public boolean performLongClick( + final boolean clickParent, + final boolean clickAll + ) { + return performLongClick(mNodeInfo, clickParent, clickAll); + } + + // = + + /** + * 长按指定节点 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private boolean performLongClick(final AccessibilityNodeInfo nodeInfo) { + if (nodeInfo != null && nodeInfo.isClickable()) { + return performAction(nodeInfo, AccessibilityNodeInfo.ACTION_LONG_CLICK); + } + return false; + } + + /** + * 长按指定节点 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param clickParent 如果当前节点不可点击, 是否往上追溯点击父节点, 直到点击成功或没有父节点 + * @param clickAll 判断是否点击全部节点 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + private boolean performLongClick( + final AccessibilityNodeInfo nodeInfo, + final boolean clickParent, + final boolean clickAll + ) { + if (nodeInfo == null) return false; + if (clickParent) { + if (nodeInfo.isClickable()) { + return performAction(nodeInfo, AccessibilityNodeInfo.ACTION_LONG_CLICK); + } else { + AccessibilityNodeInfo parent = nodeInfo.getParent(); + while (parent != null) { + if (performLongClick(parent)) { + if (!clickAll) { + return true; + } + } + parent = parent.getParent(); + } + return true; + } + } else { + return performLongClick(nodeInfo); + } + } + + // ========== + // = 输入内容 = + // ========== + + /** + * 指定节点输入文本 + * @param text 文本内容 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean inputText(final CharSequence text) { + if (mNodeInfo != null && text != null) { + Bundle arguments = new Bundle(); + arguments.putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text + ); + return performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); + } + return false; + } + + // ========== + // = 节点获取 = + // ========== + + /** + * 查找符合条件的节点 + * @param focus 焦点类型 + * @return 拥有特定焦点类型的节点 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public AccessibilityNodeInfo findFocus(final int focus) { + if (mNodeInfo != null) { + try { + // 通过指定的焦点类型找到当前的节点 + return mNodeInfo.findFocus(focus); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findFocus"); + } + } + return null; + } + + /** + * 查找符合条件的节点 + * @param focus 焦点类型 + * @param className 节点所属的类 ( 类名 ) + * @return 拥有特定焦点类型的节点 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public AccessibilityNodeInfo findFocus( + final int focus, + final String className + ) { + if (mNodeInfo != null && className != null) { + AccessibilityNodeInfo node = findFocus(focus); + if (node != null && className.equals(node.getClassName())) { + return node; + } + } + return null; + } + + /** + * 查找符合条件的节点 + * @param text 文本内容 ( 搜索包含该文本内容的节点 ) + * @return 包含该文本内容的节点集合 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public List findAccessibilityNodeInfosByText(final String text) { + if (mNodeInfo != null && text != null) { + try { + // 通过文字找到当前的节点 + List nodes = mNodeInfo.findAccessibilityNodeInfosByText(text); + if (nodes != null) return nodes; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findAccessibilityNodeInfosByText"); + } + } + return Collections.emptyList(); + } + + /** + * 查找符合条件的节点 + * @param text 文本内容 ( 搜索包含该文本内容的节点 ) + * @param className 节点所属的类 ( 类名 ) + * @return 包含该文本内容, 且属于指定类的节点集合 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public List findAccessibilityNodeInfosByText( + final String text, + final String className + ) { + List lists = new ArrayList<>(); + if (mNodeInfo != null && text != null && className != null) { + List nodes = findAccessibilityNodeInfosByText(text); + for (int i = 0, len = nodes.size(); i < len; i++) { + AccessibilityNodeInfo node = nodes.get(i); + if (node != null && className.equals(node.getClassName())) { + lists.add(node); + } + } + } + return lists; + } + + /** + * 查找符合条件的节点 + * @param id viewId + * @return 等于 viewId 的节点集合 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + public List findAccessibilityNodeInfosByViewId(final String id) { + if (mNodeInfo != null && id != null) { + try { + // 通过 id 找到当前的节点 + List nodes = mNodeInfo.findAccessibilityNodeInfosByViewId(id); + if (nodes != null) return nodes; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findAccessibilityNodeInfosByViewId"); + } + } + return Collections.emptyList(); + } + + /** + * 查找符合条件的节点 + * @param id viewId + * @param className 节点所属的类 ( 类名 ) + * @return 等于 viewId, 且属于指定类的节点集合 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) + public List findAccessibilityNodeInfosByViewId( + final String id, + final String className + ) { + List lists = new ArrayList<>(); + if (mNodeInfo != null && id != null && className != null) { + List nodes = findAccessibilityNodeInfosByViewId(id); + for (int i = 0, len = nodes.size(); i < len; i++) { + AccessibilityNodeInfo node = nodes.get(i); + if (node != null && className.equals(node.getClassName())) { + lists.add(node); + } + } + } + return lists; + } + + // ========== + // = 循环查找 = + // ========== + + /** + * 查找全部子节点并进行筛选 + * @param filter AccessibilityNodeInfo 筛选接口 + * @return 筛选后的节点集合 + */ + public List findByFilter(final NodeFilter filter) { + LinkedHashSet sets = new LinkedHashSet<>(); + if (mNodeInfo != null && filter != null) { + recursiveNodeChild(mNodeInfo, sets, filter); + } + return new ArrayList<>(sets); + } + + /** + * 递归 AccessibilityNodeInfo 子节点并进行过滤添加 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param data 符合条件数据源存储 + * @param filter AccessibilityNodeInfo 筛选接口 + */ + private void recursiveNodeChild( + final AccessibilityNodeInfo nodeInfo, + final LinkedHashSet data, + final NodeFilter filter + ) { + if (nodeInfo != null) { + if (nodeInfo.getChildCount() == 0) { + if (filter.accept(nodeInfo)) { + data.add(nodeInfo); + } + } else { + for (int i = 0, len = nodeInfo.getChildCount(); i < len; i++) { + recursiveNodeChild(nodeInfo.getChild(i), data, filter); + } + } + } + } + } + + // ============= + // = 全局操作方法 = + // ============= + + /** + * 模拟全局对应 Action 操作 + * @param action 操作意图 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performGlobalAction(final int action) { + return performGlobalAction(sService, action); + } + + /** + * 模拟全局对应 Action 操作 + *
+     *     可自行传入滑动手势
+     *     如:
+     *     向上滑动
+     *     {@link AccessibilityService#GESTURE_SWIPE_UP}
+     * 
+ * @param service {@link AccessibilityService} + * @param action 操作意图 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performGlobalAction( + final AccessibilityService service, + final int action + ) { + if (service != null) { + try { + return service.performGlobalAction(action); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "performGlobalAction"); + } + } + return false; + } + + /** + * 模拟手势操作 + * @param gesture 模拟手势 + * @param callback 操作结果回调 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean dispatchGesture( + final GestureDescription gesture, + final AccessibilityService.GestureResultCallback callback + ) { + return dispatchGesture(sService, gesture, callback, DevUtils.getHandler()); + } + + /** + * 模拟手势操作 + * @param gesture 模拟手势 + * @param callback 操作结果回调 + * @param handler 是否通过 Handler 回调 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean dispatchGesture( + final GestureDescription gesture, + final AccessibilityService.GestureResultCallback callback, + final Handler handler + ) { + return dispatchGesture(sService, gesture, callback, handler); + } + + /** + * 模拟手势操作 + *
+     *     需要设置 android:canPerformGestures="true"
+     * 
+ * @param service {@link AccessibilityService} + * @param gesture 模拟手势 + * @param callback 操作结果回调 + * @param handler 是否通过 Handler 回调 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean dispatchGesture( + final AccessibilityService service, + final GestureDescription gesture, + final AccessibilityService.GestureResultCallback callback, + final Handler handler + ) { + if (service != null) { + try { + return service.dispatchGesture(gesture, callback, handler); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "dispatchGesture"); + } + } + return false; + } + + // ============= + // = 全局快捷方法 = + // ============= + + /** + * 触发返回键 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionBack() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_BACK); + } + + /** + * 触发返回键 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionBack(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_BACK); + } + + /** + * 触发 Home 键 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionHome() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_HOME); + } + + /** + * 触发 Home 键 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionHome(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_HOME); + } + + /** + * 启动长按电源按钮 Dialog + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static boolean performActionPowerDialog() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_POWER_DIALOG); + } + + /** + * 启动长按电源按钮 Dialog + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static boolean performActionPowerDialog(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_POWER_DIALOG); + } + + /** + * 锁定屏幕 ( 非锁屏 ) + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public static boolean performActionLockScreen() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); + } + + /** + * 锁定屏幕 ( 非锁屏 ) + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public static boolean performActionLockScreen(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); + } + + /** + * 截屏 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public static boolean performActionTakeScreenshot() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); + } + + /** + * 截屏 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public static boolean performActionTakeScreenshot(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); + } + + /** + * 打开通知栏 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionNotifications() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); + } + + /** + * 打开通知栏 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionNotifications(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS); + } + + /** + * 最近打开应用列表 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionRecents() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_RECENTS); + } + + /** + * 最近打开应用列表 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean performActionRecents(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_RECENTS); + } + + /** + * 打开设置 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean performActionQuickSettings() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS); + } + + /** + * 打开设置 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean performActionQuickSettings(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS); + } + + /** + * 分屏 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean performActionSplitScreen() { + return performGlobalAction(sService, AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN); + } + + /** + * 分屏 + * @param service {@link AccessibilityService} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean performActionSplitScreen(final AccessibilityService service) { + return performGlobalAction(service, AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN); + } + + // ========== + // = 打印方法 = + // ========== + + /** + * detail: 无障碍日志打印 + * @author Ttt + *
+     *     该类仅限开发阶段方便调试使用
+     *     视情况使用 {@link Print#logComplete(AccessibilityEvent, AccessibilityService)}
+     *     防止日志量过多 ( 一直循环子节点 )
+     * 
+ */ + public static final class Print { + + // ========================== + // = AccessibilityEvent Log = + // ========================== + + /** + * 拼接 AccessibilityEvent 信息日志 + * @param event {@link AccessibilityEvent} + * @return AccessibilityEvent 信息日志 + */ + public static String logEvent(final AccessibilityEvent event) { + return logEvent(event, true); + } + + /** + * 拼接 AccessibilityEvent 信息日志 + * @param event {@link AccessibilityEvent} + * @param printSource 是否打印 Source NodeInfo + * @return AccessibilityEvent 信息日志 + */ + public static String logEvent( + final AccessibilityEvent event, + final boolean printSource + ) { + if (event == null) return null; + + try { + StringBuilder builder = new StringBuilder(); + + // 响应事件的应用包名 + builder.append("packageName: "); + builder.append(event.getPackageName()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // 响应事件类, 如 android.widget.TextView + builder.append("className: "); + builder.append(event.getClassName()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // 事件类型 + int eventType = event.getEventType(); + builder.append("eventType: "); + builder.append(eventType); + builder.append(" ( "); + builder.append(AccessibilityEvent.eventTypeToString(eventType)); + builder.append(" )"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // 事件时间 + builder.append("eventTime: "); + builder.append(event.getEventTime()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // 响应事件窗口 + builder.append("windowId: 0x"); + builder.append(Long.toHexString(event.getWindowId())); + builder.append(" ( "); + builder.append(event.getWindowId()); + builder.append(" )"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + builder.append("movementGranularity: "); + builder.append(event.getMovementGranularity()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + builder.append("action: "); + builder.append(event.getAction()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + int contentChangeTypes = event.getContentChangeTypes(); + builder.append("contentChangeTypes: "); + builder.append(contentChangeTypes); + builder.append(" ( "); + builder.append(contentChangeTypesToString(contentChangeTypes)); + builder.append(" )"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + int windowChanges = event.getWindowChanges(); + builder.append("windowChanges: "); + builder.append(windowChanges); + builder.append(" ( "); + builder.append(windowChangeTypesToString(windowChanges)); + builder.append(" )"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + for (CharSequence text : event.getText()) { + // 输出当前事件包含的文本信息 + builder.append("text: "); + builder.append(text); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (printSource) { + String sourceLog = logNodeInfo( + event.getSource(), + StringUtils.appendSpace(2) + ); + if (sourceLog != null) { + builder.append("source NodeInfo:"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append(sourceLog); + } + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "Print.logEvent"); + return null; + } + } + + // ============================= + // = AccessibilityNodeInfo Log = + // ============================= + + /** + * 拼接 AccessibilityNodeInfo 信息日志 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @return AccessibilityNodeInfo 信息日志 + */ + public static String logNodeInfo(final AccessibilityNodeInfo nodeInfo) { + return logNodeInfo(nodeInfo, ""); + } + + /** + * 拼接 AccessibilityNodeInfo 信息日志 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param delimiter 拼接符号 + * @return AccessibilityNodeInfo 信息日志 + */ + public static String logNodeInfo( + final AccessibilityNodeInfo nodeInfo, + final String delimiter + ) { + if (nodeInfo == null) return null; + try { + StringBuilder builder = new StringBuilder(); + + builder.append(delimiter); + builder.append("windowId: 0x"); + builder.append(Long.toHexString(nodeInfo.getWindowId())); + builder.append(" ( "); + builder.append(nodeInfo.getWindowId()); + builder.append(" )"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("childCount: "); + builder.append(nodeInfo.getChildCount()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + Rect boundsInParent = new Rect(); + nodeInfo.getBoundsInParent(boundsInParent); + builder.append(delimiter); + builder.append("boundsInParent: "); + builder.append(boundsInParent); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + Rect boundsInScreen = new Rect(); + nodeInfo.getBoundsInScreen(boundsInScreen); + builder.append(delimiter); + builder.append("boundsInScreen: "); + builder.append(boundsInScreen); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // ========== + // = 分割开始 = + // ========== + + builder.append(delimiter); + builder.append(DevFinal.SYMBOL.HYPHEN); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // ========== + // = 分割结束 = + // ========== + + builder.append(delimiter); + builder.append("packageName: "); + builder.append(nodeInfo.getPackageName()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("className: "); + builder.append(nodeInfo.getClassName()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("text: "); + builder.append(nodeInfo.getText()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.append(delimiter); + builder.append("error: "); + builder.append(nodeInfo.getError()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.append(delimiter); + builder.append("maxTextLength: "); + builder.append(nodeInfo.getMaxTextLength()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + builder.append(delimiter); + builder.append("stateDescription: "); + builder.append(nodeInfo.getStateDescription()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + builder.append(delimiter); + builder.append("contentDescription: "); + builder.append(nodeInfo.getContentDescription()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + builder.append(delimiter); + builder.append("tooltipText: "); + builder.append(nodeInfo.getTooltipText()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + builder.append(delimiter); + builder.append("viewIdResName: "); + builder.append(nodeInfo.getViewIdResourceName()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + int granularities = nodeInfo.getMovementGranularities(); + builder.append(delimiter); + builder.append("movementGranularities: "); + builder.append(getMovementGranularitySymbolicName(granularities)); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + // ========== + // = 分割开始 = + // ========== + + builder.append(delimiter); + builder.append(DevFinal.SYMBOL.HYPHEN); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // ========== + // = 分割结束 = + // ========== + + builder.append(delimiter); + builder.append("checkable: "); + builder.append(nodeInfo.isCheckable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("checked: "); + builder.append(nodeInfo.isChecked()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("focusable: "); + builder.append(nodeInfo.isFocusable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("focused: "); + builder.append(nodeInfo.isFocused()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("selected: "); + builder.append(nodeInfo.isSelected()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("clickable: "); + builder.append(nodeInfo.isClickable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("longClickable: "); + builder.append(nodeInfo.isLongClickable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + builder.append(delimiter); + builder.append("contextClickable: "); + builder.append(nodeInfo.isContextClickable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + builder.append(delimiter); + builder.append("enabled: "); + builder.append(nodeInfo.isEnabled()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("password: "); + builder.append(nodeInfo.isPassword()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + builder.append(delimiter); + builder.append("scrollable: "); + builder.append(nodeInfo.isScrollable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + builder.append(delimiter); + builder.append("editable: "); + builder.append(nodeInfo.isEditable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + builder.append(delimiter); + builder.append("visible: "); + builder.append(nodeInfo.isVisibleToUser()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + builder.append(delimiter); + builder.append("canOpenPopup: "); + builder.append(nodeInfo.canOpenPopup()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + builder.append(delimiter); + builder.append("dismiss: "); + builder.append(nodeInfo.isDismissable()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + builder.append(delimiter); + builder.append("importantForAccessibility: "); + builder.append(nodeInfo.isImportantForAccessibility()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + builder.append(delimiter); + builder.append("accessibilityFocused: "); + builder.append(nodeInfo.isAccessibilityFocused()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.append(delimiter); + builder.append("actions: "); + builder.append(nodeInfo.getActionList()); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "Print.logNodeInfo"); + return null; + } + } + + // ============= + // = 快捷全面方法 = + // ============= + + /** + * 拼接 AccessibilityEvent、AccessibilityService 完整信息日志 + *
+         *     打印包含
+         *     AccessibilityEvent Source Node、Source Node All Child
+         *     AccessibilityService RootInActiveWindow or WindowList
+         * 
+ * @param event {@link AccessibilityEvent} + * @param service {@link AccessibilityService} + * @return AccessibilityEvent、AccessibilityService 完整信息日志 + */ + public static String logComplete( + final AccessibilityEvent event, + final AccessibilityService service + ) { + if (event == null || service == null) return null; + StringBuilder builder = new StringBuilder(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (service.getRootInActiveWindow() != null) { + builder.append("logComplete - rootInActiveWindow:"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + logNodeInfoChild(service.getRootInActiveWindow(), builder); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + List windows = service.getWindows(); + if (windows != null) { + for (int i = 0, len = windows.size(); i < len; i++) { + builder.append("logComplete - windows child ("); + builder.append(i).append("):"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + logNodeInfoChild(service.getRootInActiveWindow(), builder); + } + } + } + } + } + builder.append("logComplete - logEvent:"); + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append(logEvent(event, false)); + return builder.toString(); + } + + /** + * 拼接 AccessibilityNodeInfo 以及 Child 信息日志 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param builder 拼接 Builder + */ + public static void logNodeInfoChild( + final AccessibilityNodeInfo nodeInfo, + final StringBuilder builder + ) { + logNodeInfoChild(nodeInfo, builder, 0); + } + + /** + * 拼接 AccessibilityNodeInfo 以及 Child 信息日志 + * @param nodeInfo {@link AccessibilityNodeInfo} + * @param builder 拼接 Builder + * @param spaceNumber 空格数量 + */ + public static void logNodeInfoChild( + final AccessibilityNodeInfo nodeInfo, + final StringBuilder builder, + final int spaceNumber + ) { + if (nodeInfo != null) { + if (nodeInfo.getChildCount() == 0) { + String nodeLog = logNodeInfo( + nodeInfo, StringUtils.appendSpace(spaceNumber) + ); + builder.append(nodeLog); + } else { + for (int i = 0, len = nodeInfo.getChildCount(); i < len; i++) { + try { + logNodeInfoChild( + nodeInfo.getChild(i), + builder, spaceNumber + 2 + ); + } catch (Exception ignored) { + } + } + } + } + } + + // ========== + // = 系统方法 = + // ========== + + /** + * copy AccessibilityEvent singleContentChangeTypeToString + * @param type Event ContentChangeTypes + * @return type String + */ + public static String contentChangeTypesToString(final int type) { + switch (type) { + case AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION: + return "CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION: + return "CONTENT_CHANGE_TYPE_STATE_DESCRIPTION"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE: + return "CONTENT_CHANGE_TYPE_SUBTREE"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT: + return "CONTENT_CHANGE_TYPE_TEXT"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_TITLE: + return "CONTENT_CHANGE_TYPE_PANE_TITLE"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED: + return "CONTENT_CHANGE_TYPE_UNDEFINED"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED: + return "CONTENT_CHANGE_TYPE_PANE_APPEARED"; + case AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED: + return "CONTENT_CHANGE_TYPE_PANE_DISAPPEARED"; + default: + return Integer.toHexString(type); + } + } + + /** + * copy AccessibilityEvent singleWindowChangeTypeToString + * @param type Event WindowChanges + * @return type String + */ + public static String windowChangeTypesToString(final int type) { + switch (type) { + case AccessibilityEvent.WINDOWS_CHANGE_ADDED: + return "WINDOWS_CHANGE_ADDED"; + case AccessibilityEvent.WINDOWS_CHANGE_REMOVED: + return "WINDOWS_CHANGE_REMOVED"; + case AccessibilityEvent.WINDOWS_CHANGE_TITLE: + return "WINDOWS_CHANGE_TITLE"; + case AccessibilityEvent.WINDOWS_CHANGE_BOUNDS: + return "WINDOWS_CHANGE_BOUNDS"; + case AccessibilityEvent.WINDOWS_CHANGE_LAYER: + return "WINDOWS_CHANGE_LAYER"; + case AccessibilityEvent.WINDOWS_CHANGE_ACTIVE: + return "WINDOWS_CHANGE_ACTIVE"; + case AccessibilityEvent.WINDOWS_CHANGE_FOCUSED: + return "WINDOWS_CHANGE_FOCUSED"; + case AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED: + return "WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED"; + case AccessibilityEvent.WINDOWS_CHANGE_PARENT: + return "WINDOWS_CHANGE_PARENT"; + case AccessibilityEvent.WINDOWS_CHANGE_CHILDREN: + return "WINDOWS_CHANGE_CHILDREN"; + case AccessibilityEvent.WINDOWS_CHANGE_PIP: + return "WINDOWS_CHANGE_PIP"; + default: + return Integer.toHexString(type); + } + } + + /** + * copy AccessibilityNodeInfo getMovementGranularitySymbolicName + * @param granularity NodeInfo granularity + * @return granularity String + */ + public static String movementGranularitiesToString(final int granularity) { + switch (granularity) { + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER: + return "MOVEMENT_GRANULARITY_CHARACTER"; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD: + return "MOVEMENT_GRANULARITY_WORD"; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: + return "MOVEMENT_GRANULARITY_LINE"; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH: + return "MOVEMENT_GRANULARITY_PARAGRAPH"; + case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: + return "MOVEMENT_GRANULARITY_PAGE"; + default: + return null; + } + } + + /** + * 封装 AccessibilityNodeInfo#toString() granularity 拼接代码 + * @param granularities NodeInfo movementGranularities + * @return movementGranularities 拼接代码 + */ + public static String getMovementGranularitySymbolicName(final int granularities) { + StringBuilder builder = new StringBuilder(); + int temp = granularities; + + builder.append("["); + while (temp != 0) { + final int granularity = 1 << Integer.numberOfTrailingZeros(temp); + temp &= ~granularity; + builder.append(movementGranularitiesToString(granularity)); + if (temp != 0) { + builder.append(", "); + } + } + builder.append("]"); + return builder.toString(); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ActivityResultUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ActivityResultUtils.java new file mode 100644 index 0000000000..134e3f14f3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ActivityResultUtils.java @@ -0,0 +1,269 @@ +package dev.utils.app; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultCaller; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.ActivityResultRegistry; +import androidx.activity.result.contract.ActivityResultContract; +import androidx.core.app.ActivityOptionsCompat; +import androidx.lifecycle.LifecycleOwner; + +import dev.utils.LogPrintUtils; +import dev.utils.app.activity_result.ActivityResultAssist; +import dev.utils.app.activity_result.DefaultActivityResult; + +/** + * detail: Activity Result 工具类 + * @author Ttt + *
+ *     Activity Result API
+ *     @see 
+ *     

+ * 只是为了拆分原始 onActivityResult 实现方式以及新的 ActivityResult API 实现方式 + * 可不封装 ( 内部捕获异常并返回操作是否成功 ) + *

+ * 关于 registerForActivityResult 创建的 ActivityResultLauncher 可以看 + * {@link androidx.activity.result.ActivityResultRegistry#register} + * 正常在 Activity 中使用顺序是 + * registerForActivityResult().launch(input) + * 可考虑使用 + * {@link ActivityResultAssist} + *
+ */ +public final class ActivityResultUtils { + + private ActivityResultUtils() { + } + + // 日志 TAG + private static final String TAG = ActivityResultUtils.class.getSimpleName(); + + // =================================== + // = 默认实现 ( 原始 onActivityResult ) = + // =================================== + + /** + * 获取默认实现 ( 原始 onActivityResult ) 封装辅助类 + * @return DefaultActivityResult + */ + public static DefaultActivityResult getDefault() { + return DefaultActivityResult.getInstance(); + } + + // ========================= + // = 新 Activity Result API = + // ========================= + + // ========================== + // = ActivityResultLauncher = + // ========================== + + /** + * 执行 ActivityResultContract createIntent 并进行跳转 + * @param launcher ActivityResultLauncher + * @param input 输入参数 + * @param 启动所需输入参数类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean launch( + final ActivityResultLauncher launcher, + final I input + ) { + if (launcher != null) { + try { + // 虽然内部也是调用 launch(input, options) 防止后期迭代更新适配, 分开调用 + launcher.launch(input); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "launch"); + } + } + return false; + } + + /** + * 执行 ActivityResultContract createIntent 并进行跳转 + * @param launcher ActivityResultLauncher + * @param input 输入参数 + * @param options Activity 启动选项 + * @param 启动所需输入参数类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean launch( + final ActivityResultLauncher launcher, + final I input, + final ActivityOptionsCompat options + ) { + if (launcher != null) { + try { + launcher.launch(input, options); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "launch - options"); + } + } + return false; + } + + /** + * 取消启动器注册, 并释放回调监听 + * @param launcher ActivityResultLauncher + * @param 启动所需输入参数类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean unregister(final ActivityResultLauncher launcher) { + if (launcher != null) { + try { + launcher.unregister(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregister"); + } + } + return false; + } + + /** + * 获取创建启动器对应 ActivityResultContract + * @param launcher ActivityResultLauncher + * @param 启动所需输入参数类型 + * @return ActivityResultContract + */ + public static ActivityResultContract getContract(final ActivityResultLauncher launcher) { + if (launcher != null) { + try { + return launcher.getContract(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getContract"); + } + } + return null; + } + + // ======================== + // = ActivityResultCaller = + // ======================== + + /** + * 注册创建跳转回传值启动器并返回 + * @param caller ActivityResultCaller ( 只要属于继承 Fragment、FragmentActivity 传入 this 即可 ) + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + * @param 启动所需输入参数类型 + * @param 回传结果解析值类型 + * @return ActivityResultLauncher + */ + public static ActivityResultLauncher registerForActivityResult( + final ActivityResultCaller caller, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + if (caller != null && contract != null && callback != null) { + try { + return caller.registerForActivityResult(contract, callback); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "ActivityResultCaller registerForActivityResult" + ); + } + } + return null; + } + + /** + * 注册创建跳转回传值启动器并返回 + * @param caller ActivityResultCaller ( 只要属于继承 Fragment、FragmentActivity 传入 this 即可 ) + * @param contract ActivityResultContract + * @param registry ActivityResultRegistry + * @param callback ActivityResultCallback 回传回调 + * @param 启动所需输入参数类型 + * @param 回传结果解析值类型 + * @return ActivityResultLauncher + */ + public static ActivityResultLauncher registerForActivityResult( + final ActivityResultCaller caller, + final ActivityResultContract contract, + final ActivityResultRegistry registry, + final ActivityResultCallback callback + ) { + if (caller != null && contract != null && registry != null && callback != null) { + try { + return caller.registerForActivityResult(contract, registry, callback); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "ActivityResultCaller registerForActivityResult" + ); + } + } + return null; + } + + // ========================== + // = ActivityResultRegistry = + // ========================== + + /** + * 注册创建跳转回传值启动器并返回 + *
+     *     ActivityResultRegistry ( 可通过 ComponentActivity、Fragment getActivityResultRegistry() 获取 )
+     * 
+ * @param registry ActivityResultRegistry + * @param key 唯一值字符串 + * @param lifecycleOwner 生命周期监听 + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + * @param 启动所需输入参数类型 + * @param 回传结果解析值类型 + * @return ActivityResultLauncher + */ + public static ActivityResultLauncher register( + final ActivityResultRegistry registry, + final String key, + final LifecycleOwner lifecycleOwner, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + if (registry != null && key != null && lifecycleOwner != null + && contract != null && callback != null) { + try { + return registry.register(key, lifecycleOwner, contract, callback); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "ActivityResultRegistry register" + ); + } + } + return null; + } + + /** + * 注册创建跳转回传值启动器并返回 + *
+     *     ActivityResultRegistry ( 可通过 ComponentActivity、Fragment getActivityResultRegistry() 获取 )
+     * 
+ * @param registry ActivityResultRegistry + * @param key 唯一值字符串 + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + * @param 启动所需输入参数类型 + * @param 回传结果解析值类型 + * @return ActivityResultLauncher + */ + public static ActivityResultLauncher register( + final ActivityResultRegistry registry, + final String key, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + if (registry != null && key != null && contract != null && callback != null) { + try { + return registry.register(key, contract, callback); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "ActivityResultRegistry register" + ); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ActivityUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ActivityUtils.java new file mode 100644 index 0000000000..9dbd2a31cf --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ActivityUtils.java @@ -0,0 +1,606 @@ +package dev.utils.app; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityOptionsCompat; +import androidx.core.util.Pair; + +import java.util.List; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.assist.ActivityManagerAssist; + +/** + * detail: Activity 工具类 ( 包含 Activity 控制管理 ) + * @author Ttt + *
+ *     转场动画
+ *     @see 
+ *     @see 
+ *     ActivityOptionsCompat.makeScaleUpAnimation(source, startX, startY, startWidth, startHeight)
+ *     @see 
+ * 
+ */ +public final class ActivityUtils { + + private ActivityUtils() { + } + + // 日志 TAG + private static final String TAG = ActivityUtils.class.getSimpleName(); + + // =================== + // = Activity 判断处理 = + // =================== + + /** + * 通过 Context 获取 Activity + * @param context {@link Context} + * @return {@link Activity} + */ + public static Activity getActivity(final Context context) { + if (context != null) { + try { + Context temp = context; + while (temp instanceof ContextWrapper) { + if (temp instanceof Activity) { + return (Activity) temp; + } + temp = ((ContextWrapper) temp).getBaseContext(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivity"); + } + } + return null; + } + + /** + * 获取 View context 所属的 Activity + * @param view {@link View} + * @return {@link Activity} + */ + public static Activity getActivity(final View view) { + if (view == null) return null; + return getActivity(view.getContext()); + } + + // = + + /** + * 判断 Activity 是否关闭 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFinishing(final Activity activity) { + if (activity != null) { + return activity.isFinishing(); + } + return true; + } + + /** + * 判断 Activity 是否关闭 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFinishing(final Context context) { + if (context != null) { + try { + return isFinishing((Activity) context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isFinishing"); + } + } + return true; + } + + /** + * 判断 Activity 是否未关闭 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotFinishing(final Activity activity) { + if (activity != null) { + return !activity.isFinishing(); + } + return false; + } + + /** + * 判断 Activity 是否未关闭 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotFinishing(final Context context) { + if (context != null) { + try { + return isNotFinishing((Activity) context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isNotFinishing"); + } + } + return false; + } + + /** + * 判断 Activity 是否销毁 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isDestroyed(final Activity activity) { + if (activity != null) { + return activity.isDestroyed(); + } + return true; + } + + /** + * 判断 Activity 是否销毁 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isDestroyed(final Context context) { + if (context != null) { + try { + return isDestroyed((Activity) context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isDestroyed"); + } + } + return true; + } + + /** + * 判断 Activity 是否未销毁 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isNotDestroyed(final Activity activity) { + if (activity != null) { + return !activity.isDestroyed(); + } + return false; + } + + /** + * 判断 Activity 是否未销毁 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isNotDestroyed(final Context context) { + if (context != null) { + try { + return isNotDestroyed((Activity) context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isNotDestroyed"); + } + } + return false; + } + + // = + + /** + * 判断 Activity 是否有效 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean assertValidActivity(final Activity activity) { + if (activity != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return !activity.isFinishing() && !activity.isDestroyed(); + } else { + return !activity.isFinishing(); + } + } + return false; + } + + /** + * 判断 Activity 是否有效 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean assertValidActivity(final Context context) { + if (context != null) { + try { + return assertValidActivity((Activity) context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "assertValidActivity"); + } + } + return false; + } + + // = + + /** + * 判断是否存在指定的 Activity + * @param className Activity.class.getCanonicalName() + * @return {@code true} 存在, {@code false} 不存在 + */ + public static boolean isActivityExists(final String className) { + return isActivityExists(AppUtils.getPackageName(), className); + } + + /** + * 判断是否存在指定的 Activity + * @param packageName 应用包名 + * @param className Activity.class.getCanonicalName() + * @return {@code true} 存在, {@code false} 不存在 + */ + public static boolean isActivityExists( + final String packageName, + final String className + ) { + if (packageName == null || className == null) return false; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return false; + boolean result = true; + try { + Intent intent = new Intent(); + intent.setClassName(packageName, className); + if (packageManager.resolveActivity(intent, 0) == null) { + result = false; + } else if (intent.resolveActivity(packageManager) == null) { + result = false; + } else { + List lists = packageManager.queryIntentActivities( + intent, 0 + ); + if (lists.size() == 0) { + result = false; + } + } + } catch (Exception e) { + result = false; + LogPrintUtils.eTag(TAG, e, "isActivityExists"); + } + return result; + } + + // =================== + // = Activity 获取操作 = + // =================== + + /** + * 回到桌面 ( 同点击 Home 键效果 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean startHomeActivity() { + try { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + return AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startHomeActivity"); + } + return false; + } + + /** + * 获取 Launcher activity + * @return package.xx.Activity.className + */ + public static String getLauncherActivity() { + try { + return getLauncherActivity(AppUtils.getPackageName()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLauncherActivity"); + } + return null; + } + + /** + * 获取 Launcher activity + * @param packageName 应用包名 + * @return package.xx.Activity.className + */ + public static String getLauncherActivity(final String packageName) { + if (packageName == null) return null; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + List lists = packageManager.queryIntentActivities(intent, 0); + for (ResolveInfo resolveInfo : lists) { + if (resolveInfo != null && resolveInfo.activityInfo != null) { + if (packageName.equals(resolveInfo.activityInfo.packageName)) { + return resolveInfo.activityInfo.name; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLauncherActivity"); + } + return null; + } + + /** + * 获取 Activity 对应的 icon + * @param clazz Activity.class + * @return {@link Drawable} Activity 对应的 icon + */ + public static Drawable getActivityIcon(final Class clazz) { + if (clazz == null) return null; + try { + return getActivityIcon(new ComponentName(DevUtils.getContext(), clazz)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityIcon"); + } + return null; + } + + /** + * 获取 Activity 对应的 icon + * @param componentName {@link ComponentName} + * @return {@link Drawable} Activity 对应的 icon + */ + public static Drawable getActivityIcon(final ComponentName componentName) { + if (componentName == null) return null; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + return packageManager.getActivityIcon(componentName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityIcon"); + } + return null; + } + + /** + * 获取 Activity 对应的 logo + * @param clazz Activity.class + * @return {@link Drawable} Activity 对应的 logo + */ + public static Drawable getActivityLogo(final Class clazz) { + if (clazz == null) return null; + try { + return getActivityLogo(new ComponentName(DevUtils.getContext(), clazz)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityLogo"); + } + return null; + } + + /** + * 获取 Activity 对应的 logo + * @param componentName {@link ComponentName} + * @return {@link Drawable} Activity 对应的 logo + */ + public static Drawable getActivityLogo(final ComponentName componentName) { + if (componentName == null) return null; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + return packageManager.getActivityLogo(componentName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityLogo"); + } + return null; + } + + /** + * 获取对应包名应用启动的 Activity + * @return package.xx.Activity.className + */ + public static String getActivityToLauncher() { + return getActivityToLauncher(AppUtils.getPackageName()); + } + + /** + * 获取对应包名应用启动的 Activity + *
+     *     android.intent.category.LAUNCHER (android.intent.action.MAIN)
+     * 
+ * @param packageName 应用包名 + * @return package.xx.Activity.className + */ + public static String getActivityToLauncher(final String packageName) { + if (packageName == null) return null; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + // 创建一个类别为 CATEGORY_LAUNCHER 的该包名的 Intent + Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null); + resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER); + resolveIntent.setPackage(packageName); + // 通过 AppUtils.getPackageManager() 的 queryIntentActivities 方法遍历 + List lists = packageManager.queryIntentActivities( + resolveIntent, 0 + ); + for (ResolveInfo resolveInfo : lists) { + if (resolveInfo != null && resolveInfo.activityInfo != null) { + // resolveInfo.activityInfo.packageName; // packageName + // 这个就是该 APP 的 LAUNCHER 的 Activity [ 组织形式: packageName.mainActivityName ] + return resolveInfo.activityInfo.name; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActivityToLauncher"); + } + return null; + } + + /** + * 获取系统桌面信息 + * @return {@link ResolveInfo} + */ + public static ResolveInfo getLauncherCategoryHomeToResolveInfo() { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return packageManager.resolveActivity(intent, 0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLauncherCategoryHomeToResolveInfo"); + } + return null; + } + + /** + * 获取系统桌面信息 ( packageName ) + *
+     *     注: 存在多个桌面时且未指定默认桌面时, 该方法返回 Null, 使用时需处理这个情况
+     * 
+ * @return packageName + */ + public static String getLauncherCategoryHomeToPackageName() { + ResolveInfo resolveInfo = getLauncherCategoryHomeToResolveInfo(); + if (resolveInfo != null && resolveInfo.activityInfo != null) { + // 有多个桌面程序存在, 且未指定默认项时 + if ("android".equals(resolveInfo.activityInfo.packageName)) { + return null; + } else { + return resolveInfo.activityInfo.packageName; + } + } + return null; + } + + /** + * 获取系统桌面信息 ( activityName ) + * @return activityName + */ + public static String getLauncherCategoryHomeToActivityName() { + ResolveInfo resolveInfo = getLauncherCategoryHomeToResolveInfo(); + if (resolveInfo != null && resolveInfo.activityInfo != null) { + // 有多个桌面程序存在, 且未指定默认项时 + if ("android".equals(resolveInfo.activityInfo.packageName)) { + return null; + } else { + return resolveInfo.activityInfo.name; + } + } + return null; + } + + /** + * 获取系统桌面信息 ( package/activityName ) + * @return package/activityName + */ + public static String getLauncherCategoryHomeToPackageAndName() { + ResolveInfo resolveInfo = getLauncherCategoryHomeToResolveInfo(); + if (resolveInfo != null && resolveInfo.activityInfo != null) { + // 有多个桌面程序存在, 且未指定默认项时 + if ("android".equals(resolveInfo.activityInfo.packageName)) { + return null; + } else { + // 判断是否. 开头 + String name = resolveInfo.activityInfo.name; + if (name != null) { + // 判断是否 . 开头 + if (name.startsWith(".")) { + name = resolveInfo.activityInfo.packageName + name; + } + return resolveInfo.activityInfo.packageName + "/" + name; + } + } + } + return null; + } + + // ========== + // = 转场动画 = + // ========== + + /** + * 设置跳转动画 + * @param context {@link Context} + * @param enterAnim 进入动画 + * @param exitAnim 退出动画 + * @return {@link Bundle} + */ + public static Bundle getOptionsBundle( + final Context context, + final int enterAnim, + final int exitAnim + ) { + try { + return ActivityOptionsCompat.makeCustomAnimation( + context, enterAnim, exitAnim + ).toBundle(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getOptionsBundle"); + } + return null; + } + + /** + * 设置跳转动画 + * @param activity {@link Activity} + * @param sharedElements 转场动画 View + * @return {@link Bundle} + */ + public static Bundle getOptionsBundle( + final Activity activity, + final View[] sharedElements + ) { + if (activity == null) return null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + int len = sharedElements.length; + @SuppressWarnings("unchecked") + Pair[] pairs = new Pair[len]; + for (int i = 0; i < len; i++) { + pairs[i] = Pair.create( + sharedElements[i], + sharedElements[i].getTransitionName() + ); + } + return ActivityOptionsCompat.makeSceneTransitionAnimation( + activity, pairs + ).toBundle(); + } + return ActivityOptionsCompat.makeSceneTransitionAnimation( + activity, null, null + ).toBundle(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getOptionsBundle"); + } + return null; + } + + // =================== + // = Activity 管理控制 = + // =================== + + // ActivityManagerAssist 实例 + private static volatile ActivityManagerAssist sInstance; + + /** + * 获取 ActivityManagerAssist 管理实例 + * @return {@link ActivityManagerAssist} + */ + public static ActivityManagerAssist getManager() { + if (sInstance == null) { + synchronized (ActivityManagerAssist.class) { + if (sInstance == null) { + sInstance = new ActivityManagerAssist(); + } + } + } + return sInstance; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/AlarmUtils.java b/lib/DevApp/src/main/java/dev/utils/app/AlarmUtils.java new file mode 100644 index 0000000000..2c40b1f3c5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/AlarmUtils.java @@ -0,0 +1,412 @@ +package dev.utils.app; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import androidx.annotation.RequiresApi; + +import dev.utils.LogPrintUtils; + +/** + * detail: AlarmManager ( 全局定时器、闹钟 ) 工具类 + * @author Ttt + *
+ *     指定时长或以周期形式执行某项操作
+ *     @see 
+ *     关于使用 AlarmManager 的注意事项
+ *     @see 
+ * 
+ */ +public final class AlarmUtils { + + private AlarmUtils() { + } + + // 日志 TAG + private static final String TAG = AlarmUtils.class.getSimpleName(); + + // ========== + // = 开启闹钟 = + // ========== + + /** + * 开启一次性闹钟 + * @param triggerAtMillis 执行时间 + * @param pendingIntent {@link PendingIntent} 响应动作 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmIntent( + final long triggerAtMillis, + final PendingIntent pendingIntent + ) { + return startAlarmIntent(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); + } + + /** + * 开启一次性闹钟 + * @param type 闹钟类型, 常用的有 5 个值: + * AlarmManager.ELAPSED_REALTIME、 + * AlarmManager.ELAPSED_REALTIME_WAKEUP、 + * AlarmManager.RTC、 + * AlarmManager.RTC_WAKEUP、 + * AlarmManager.POWER_OFF_WAKEUP + * @param triggerAtMillis 执行时间 + * @param pendingIntent {@link PendingIntent} 响应动作 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmIntent( + final int type, + final long triggerAtMillis, + final PendingIntent pendingIntent + ) { + try { + AlarmManager manager = AppUtils.getAlarmManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + manager.setExactAndAllowWhileIdle(type, triggerAtMillis, pendingIntent); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + manager.setExact(type, triggerAtMillis, pendingIntent); + } else { + manager.set(type, triggerAtMillis, pendingIntent); + } + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmIntent"); + } + return false; + } + + // ========== + // = 关闭闹钟 = + // ========== + + /** + * 关闭闹钟 + * @param pendingIntent {@link PendingIntent} 响应动作 + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean stopAlarmIntent(final PendingIntent pendingIntent) { + try { + AppUtils.getAlarmManager().cancel(pendingIntent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmIntent"); + } + return false; + } + + // =============== + // = Service 闹钟 = + // =============== + + /** + * 开启 Service 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param clazz Class + * @param action Intent Action + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmService( + final Context context, + final long triggerAtMillis, + final Class clazz, + final String action + ) { + try { + Intent intent = new Intent(context, clazz); + intent.setAction(action); + return startAlarmService(context, triggerAtMillis, intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmService"); + } + return false; + } + + /** + * 开启 Service 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param intent {@link Intent + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmService( + final Context context, + final long triggerAtMillis, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getService( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return startAlarmIntent(triggerAtMillis, pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmService"); + } + return false; + } + + // = + + /** + * 关闭 Service 闹钟 + * @param context {@link Context} + * @param clazz Class + * @param action Intent Action + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean stopAlarmService( + final Context context, + final Class clazz, + final String action + ) { + try { + Intent intent = new Intent(context, clazz); + intent.setAction(action); + return stopAlarmService(context, intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmService"); + } + return false; + } + + /** + * 关闭 Service 闹钟 + * @param context {@link Context} + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean stopAlarmService( + final Context context, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getService( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return stopAlarmIntent(pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmService"); + } + return false; + } + + // ===================== + // = ForegroundService = + // ===================== + + /** + * 开启 ForegroundService 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param clazz Class + * @param action Intent Action + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static boolean startAlarmForegroundService( + final Context context, + final long triggerAtMillis, + final Class clazz, + final String action + ) { + try { + Intent intent = new Intent(context, clazz); + intent.setAction(action); + return startAlarmForegroundService(context, triggerAtMillis, intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmForegroundService"); + } + return false; + } + + /** + * 开启 ForegroundService 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static boolean startAlarmForegroundService( + final Context context, + final long triggerAtMillis, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getForegroundService( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return startAlarmIntent(triggerAtMillis, pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmForegroundService"); + } + return false; + } + + // = + + /** + * 关闭 ForegroundService 闹钟 + * @param context {@link Context} + * @param clazz Class + * @param action Intent Action + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static boolean stopAlarmForegroundService( + final Context context, + final Class clazz, + final String action + ) { + try { + Intent intent = new Intent(context, clazz); + intent.setAction(action); + return stopAlarmForegroundService(context, intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmForegroundService"); + } + return false; + } + + /** + * 关闭 ForegroundService 闹钟 + * @param context {@link Context} + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static boolean stopAlarmForegroundService( + final Context context, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getForegroundService( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return stopAlarmIntent(pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmForegroundService"); + } + return false; + } + + // ========================== + // = Broadcast Receiver 闹钟 = + // ========================== + + /** + * 开启 Receiver 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmBroadcast( + final Context context, + final long triggerAtMillis, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return startAlarmIntent(triggerAtMillis, pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmBroadcast"); + } + return false; + } + + // = + + /** + * 关闭 Receiver 闹钟 + * @param context {@link Context} + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean stopAlarmBroadcast( + final Context context, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return stopAlarmIntent(pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmBroadcast"); + } + return false; + } + + // ================ + // = Activity 闹钟 = + // ================ + + /** + * 开启 Activity 闹钟 + * @param context {@link Context} + * @param triggerAtMillis 执行时间 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean startAlarmActivity( + final Context context, + final long triggerAtMillis, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getActivity( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return startAlarmIntent(triggerAtMillis, pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAlarmActivity"); + } + return false; + } + + /** + * 关闭 Activity 闹钟 + * @param context {@link Context} + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static boolean stopAlarmActivity( + final Context context, + final Intent intent + ) { + try { + PendingIntent pendingIntent = PendingIntent.getActivity( + context, 0, intent, + PendingIntent.FLAG_UPDATE_CURRENT + ); + return stopAlarmIntent(pendingIntent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopAlarmActivity"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/AppUtils.java b/lib/DevApp/src/main/java/dev/utils/app/AppUtils.java new file mode 100644 index 0000000000..1224335f6b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/AppUtils.java @@ -0,0 +1,1851 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.app.KeyguardManager; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; +import android.app.usage.UsageStatsManager; +import android.content.BroadcastReceiver; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutManager; +import android.content.pm.Signature; +import android.graphics.drawable.Drawable; +import android.hardware.SensorManager; +import android.hardware.SensorPrivacyManager; +import android.location.LocationManager; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.PowerManager; +import android.os.Vibrator; +import android.os.storage.StorageManager; +import android.provider.Settings; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.view.WindowManager; +import android.view.WindowMetrics; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.util.List; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.activity_result.DefaultActivityResult; +import dev.utils.common.ConvertUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.encrypt.EncryptUtils; + +/** + * detail: APP ( Android ) 工具类 + * @author Ttt + *
+ *     MimeType
+ *     @see 
+ *     存储后缀根据 MIME_TYPE 决定, 值类型 libcore.net.MimeUtils
+ *     @see 
+ *     

+ * 所需权限 + * + *
+ */ +public final class AppUtils { + + private AppUtils() { + } + + // 日志 TAG + private static final String TAG = AppUtils.class.getSimpleName(); + + /** + * 获取 SystemService + * @param name 服务名 + * @param 泛型 + * @return SystemService Object + */ + public static T getSystemService(final String name) { + return getSystemService(DevUtils.getContext(), name); + } + + /** + * 获取 SystemService + * @param context Context + * @param name 服务名 + * @param 泛型 + * @return SystemService Object + */ + public static T getSystemService( + final Context context, + final String name + ) { + if (context == null) return null; + if (TextUtils.isEmpty(name)) return null; + try { + return (T) context.getSystemService(name); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSystemService"); + } + return null; + } + + /** + * 获取 SystemService + * @param clazz 服务类名 + * @param 泛型 + * @return SystemService Object + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static T getSystemService(final Class clazz) { + return getSystemService(DevUtils.getContext(), clazz); + } + + /** + * 获取 SystemService + * @param context Context + * @param clazz 服务类名 + * @param 泛型 + * @return SystemService Object + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static T getSystemService( + final Context context, + final Class clazz + ) { + if (context == null) return null; + if (clazz == null) return null; + try { + return context.getSystemService(clazz); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSystemService"); + } + return null; + } + + // = + + /** + * 获取 WindowManager + * @return {@link WindowManager} + */ + public static WindowManager getWindowManager() { + return getSystemService(Context.WINDOW_SERVICE); + } + + /** + * 获取 WindowManager + * @param context Context + * @return {@link WindowManager} + */ + public static WindowManager getWindowManager(final Context context) { + return getSystemService(context, Context.WINDOW_SERVICE); + } + + /** + * 获取 AudioManager + * @return {@link AudioManager} + */ + public static AudioManager getAudioManager() { + return getSystemService(Context.AUDIO_SERVICE); + } + + /** + * 获取 AudioManager + * @param context Context + * @return {@link AudioManager} + */ + public static AudioManager getAudioManager(final Context context) { + return getSystemService(context, Context.AUDIO_SERVICE); + } + + /** + * 获取 StatusBarManager + * @return {@link StatusBarManager} + */ + public static StatusBarManager getStatusBarManager() { + return getSystemService("statusbar"); + } + + /** + * 获取 StatusBarManager + * @param context Context + * @return {@link StatusBarManager} + */ + public static StatusBarManager getStatusBarManager(final Context context) { + return getSystemService(context, "statusbar"); + } + + /** + * 获取 SensorManager + * @return {@link SensorManager} + */ + public static SensorManager getSensorManager() { + return getSystemService(Context.SENSOR_SERVICE); + } + + /** + * 获取 SensorManager + * @param context Context + * @return {@link SensorManager} + */ + public static SensorManager getSensorManager(final Context context) { + return getSystemService(context, Context.SENSOR_SERVICE); + } + + /** + * 获取 StorageManager + * @return {@link StorageManager} + */ + public static StorageManager getStorageManager() { + return getSystemService(Context.STORAGE_SERVICE); + } + + /** + * 获取 StorageManager + * @param context Context + * @return {@link StorageManager} + */ + public static StorageManager getStorageManager(final Context context) { + return getSystemService(context, Context.STORAGE_SERVICE); + } + + /** + * 获取 WifiManager + * @return {@link WifiManager} + */ + @SuppressLint("WifiManagerLeak") + public static WifiManager getWifiManager() { + return getSystemService(Context.WIFI_SERVICE); + } + + /** + * 获取 WifiManager + * @param context Context + * @return {@link WifiManager} + */ + @SuppressLint("WifiManagerLeak") + public static WifiManager getWifiManager(final Context context) { + return getSystemService(context, Context.WIFI_SERVICE); + } + + /** + * 获取 ConnectivityManager + * @return {@link ConnectivityManager} + */ + public static ConnectivityManager getConnectivityManager() { + return getSystemService(Context.CONNECTIVITY_SERVICE); + } + + /** + * 获取 ConnectivityManager + * @param context Context + * @return {@link ConnectivityManager} + */ + public static ConnectivityManager getConnectivityManager(final Context context) { + return getSystemService(context, Context.CONNECTIVITY_SERVICE); + } + + /** + * 获取 TelephonyManager + * @return {@link TelephonyManager} + */ + public static TelephonyManager getTelephonyManager() { + return getSystemService(Context.TELEPHONY_SERVICE); + } + + /** + * 获取 TelephonyManager + * @param context Context + * @return {@link TelephonyManager} + */ + public static TelephonyManager getTelephonyManager(final Context context) { + return getSystemService(context, Context.TELEPHONY_SERVICE); + } + + /** + * 获取 AppOpsManager + * @return {@link AppOpsManager} + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static AppOpsManager getAppOpsManager() { + return getSystemService(Context.APP_OPS_SERVICE); + } + + /** + * 获取 AppOpsManager + * @param context Context + * @return {@link AppOpsManager} + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static AppOpsManager getAppOpsManager(final Context context) { + return getSystemService(context, Context.APP_OPS_SERVICE); + } + + /** + * 获取 NotificationManager + * @return {@link NotificationManager} + */ + public static NotificationManager getNotificationManager() { + return getSystemService(Context.NOTIFICATION_SERVICE); + } + + /** + * 获取 NotificationManager + * @param context Context + * @return {@link NotificationManager} + */ + public static NotificationManager getNotificationManager(final Context context) { + return getSystemService(context, Context.NOTIFICATION_SERVICE); + } + + /** + * 获取 ShortcutManager + * @return {@link ShortcutManager} + */ + @RequiresApi(api = Build.VERSION_CODES.N_MR1) + public static ShortcutManager getShortcutManager() { + return getSystemService(Context.SHORTCUT_SERVICE); + } + + /** + * 获取 ShortcutManager + * @param context Context + * @return {@link ShortcutManager} + */ + @RequiresApi(api = Build.VERSION_CODES.N_MR1) + public static ShortcutManager getShortcutManager(final Context context) { + return getSystemService(context, Context.SHORTCUT_SERVICE); + } + + /** + * 获取 ActivityManager + * @return {@link ActivityManager} + */ + public static ActivityManager getActivityManager() { + return getSystemService(Context.ACTIVITY_SERVICE); + } + + /** + * 获取 ActivityManager + * @param context Context + * @return {@link ActivityManager} + */ + public static ActivityManager getActivityManager(final Context context) { + return getSystemService(context, Context.ACTIVITY_SERVICE); + } + + /** + * 获取 PowerManager + * @return {@link PowerManager} + */ + public static PowerManager getPowerManager() { + return getSystemService(Context.POWER_SERVICE); + } + + /** + * 获取 PowerManager + * @param context Context + * @return {@link PowerManager} + */ + public static PowerManager getPowerManager(final Context context) { + return getSystemService(context, Context.POWER_SERVICE); + } + + /** + * 获取 KeyguardManager + * @return {@link KeyguardManager} + */ + public static KeyguardManager getKeyguardManager() { + return getSystemService(Context.KEYGUARD_SERVICE); + } + + /** + * 获取 KeyguardManager + * @param context Context + * @return {@link KeyguardManager} + */ + public static KeyguardManager getKeyguardManager(final Context context) { + return getSystemService(context, Context.KEYGUARD_SERVICE); + } + + /** + * 获取 InputMethodManager + * @return {@link InputMethodManager} + */ + public static InputMethodManager getInputMethodManager() { + return getSystemService(Context.INPUT_METHOD_SERVICE); + } + + /** + * 获取 InputMethodManager + * @param context Context + * @return {@link InputMethodManager} + */ + public static InputMethodManager getInputMethodManager(final Context context) { + return getSystemService(context, Context.INPUT_METHOD_SERVICE); + } + + /** + * 获取 ClipboardManager + * @return {@link ClipboardManager} + */ + public static ClipboardManager getClipboardManager() { + return getSystemService(Context.CLIPBOARD_SERVICE); + } + + /** + * 获取 ClipboardManager + * @param context Context + * @return {@link ClipboardManager} + */ + public static ClipboardManager getClipboardManager(final Context context) { + return getSystemService(context, Context.CLIPBOARD_SERVICE); + } + + /** + * 获取 UsageStatsManager + * @return {@link UsageStatsManager} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) + public static UsageStatsManager getUsageStatsManager() { + return getSystemService(Context.USAGE_STATS_SERVICE); + } + + /** + * 获取 UsageStatsManager + * @param context Context + * @return {@link UsageStatsManager} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) + public static UsageStatsManager getUsageStatsManager(final Context context) { + return getSystemService(context, Context.USAGE_STATS_SERVICE); + } + + /** + * 获取 AlarmManager + * @return {@link AlarmManager} + */ + public static AlarmManager getAlarmManager() { + return getSystemService(Context.ALARM_SERVICE); + } + + /** + * 获取 AlarmManager + * @param context Context + * @return {@link AlarmManager} + */ + public static AlarmManager getAlarmManager(final Context context) { + return getSystemService(context, Context.ALARM_SERVICE); + } + + /** + * 获取 LocationManager + * @return {@link LocationManager} + */ + public static LocationManager getLocationManager() { + return getSystemService(Context.LOCATION_SERVICE); + } + + /** + * 获取 LocationManager + * @param context Context + * @return {@link LocationManager} + */ + public static LocationManager getLocationManager(final Context context) { + return getSystemService(context, Context.LOCATION_SERVICE); + } + + /** + * 获取 Vibrator + * @return {@link Vibrator} + */ + public static Vibrator getVibrator() { + return getSystemService(Context.VIBRATOR_SERVICE); + } + + /** + * 获取 Vibrator + * @param context Context + * @return {@link Vibrator} + */ + public static Vibrator getVibrator(final Context context) { + return getSystemService(context, Context.VIBRATOR_SERVICE); + } + + /** + * 获取 DevicePolicyManager + * @return {@link DevicePolicyManager} + */ + public static DevicePolicyManager getDevicePolicyManager() { + return getSystemService(Context.DEVICE_POLICY_SERVICE); + } + + /** + * 获取 DevicePolicyManager + * @param context Context + * @return {@link DevicePolicyManager} + */ + public static DevicePolicyManager getDevicePolicyManager(final Context context) { + return getSystemService(context, Context.DEVICE_POLICY_SERVICE); + } + + /** + * 获取 SensorPrivacyManager + * @return {@link SensorPrivacyManager} + */ + @RequiresApi(api = Build.VERSION_CODES.S) + public static SensorPrivacyManager getSensorPrivacyManager() { + return getSystemService(SensorPrivacyManager.class); + } + + /** + * 获取 SensorPrivacyManager + * @param context Context + * @return {@link SensorPrivacyManager} + */ + @RequiresApi(api = Build.VERSION_CODES.S) + public static SensorPrivacyManager getSensorPrivacyManager(final Context context) { + return getSystemService(context, SensorPrivacyManager.class); + } + + /** + * 获取 WallpaperManager + * @return {@link WallpaperManager} + */ + public static WallpaperManager getWallpaperManager() { + return getWallpaperManager(DevUtils.getContext()); + } + + /** + * 获取 WallpaperManager + * @param context Context + * @return {@link WallpaperManager} + */ + public static WallpaperManager getWallpaperManager(final Context context) { + if (context == null) return null; + try { + return WallpaperManager.getInstance(context); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWallpaperManager"); + } + return null; + } + + /** + * 获取 PackageManager + * @return {@link PackageManager} + */ + public static PackageManager getPackageManager() { + return getPackageManager(DevUtils.getContext()); + } + + /** + * 获取 PackageManager + * @param context Context + * @return {@link PackageManager} + */ + public static PackageManager getPackageManager(final Context context) { + if (context == null) return null; + try { + return context.getPackageManager(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPackageManager"); + } + return null; + } + + /** + * 获取 Current WindowMetrics + * @return {@link WindowMetrics} + */ + public static WindowMetrics getCurrentWindowMetrics() { + return getCurrentWindowMetrics(DevUtils.getContext()); + } + + /** + * 获取 Current WindowMetrics + * @param context Context + * @return {@link WindowMetrics} + */ + public static WindowMetrics getCurrentWindowMetrics(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowManager windowManager = getWindowManager(context); + if (windowManager != null) { + try { + return windowManager.getCurrentWindowMetrics(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCurrentWindowMetrics"); + } + } + } + return null; + } + + /** + * 获取 Maximum WindowMetrics + * @return {@link WindowMetrics} + */ + public static WindowMetrics getMaximumWindowMetrics() { + return getMaximumWindowMetrics(DevUtils.getContext()); + } + + /** + * 获取 Maximum WindowMetrics + * @param context Context + * @return {@link WindowMetrics} + */ + public static WindowMetrics getMaximumWindowMetrics(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowManager windowManager = getWindowManager(context); + if (windowManager != null) { + try { + return windowManager.getMaximumWindowMetrics(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMaximumWindowMetrics"); + } + } + } + return null; + } + + /** + * 获取 ApplicationInfo + * @return {@link ApplicationInfo} + */ + public static ApplicationInfo getApplicationInfo() { + try { + return DevUtils.getContext().getApplicationInfo(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApplicationInfo"); + } + return null; + } + + /** + * 获取 ApplicationInfo + * @param packageName 应用包名 + * @param flags application flags + * @return {@link ApplicationInfo} + */ + public static ApplicationInfo getApplicationInfo( + final String packageName, + final int flags + ) { + try { + return DevUtils.getContext().getPackageManager() + .getApplicationInfo(packageName, flags); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApplicationInfo %s", packageName); + } + return null; + } + + /** + * 获取 PackageInfo + * @param flags package flags + * @return {@link ApplicationInfo} + */ + public static PackageInfo getPackageInfo(final int flags) { + return getPackageInfo(getPackageName(), flags); + } + + /** + * 获取 PackageInfo + * @param packageName 应用包名 + * @param flags package flags + * @return {@link ApplicationInfo} + */ + public static PackageInfo getPackageInfo( + final String packageName, + final int flags + ) { + try { + return DevUtils.getContext().getPackageManager() + .getPackageInfo(packageName, flags); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPackageInfo %s", packageName); + } + return null; + } + + /** + * 获取 SharedPreferences + * @param fileName 文件名 + * @return {@link SharedPreferences} + */ + public static SharedPreferences getSharedPreferences(final String fileName) { + return getSharedPreferences(fileName, Context.MODE_PRIVATE); + } + + /** + * 获取 SharedPreferences + * @param fileName 文件名 + * @param mode SharedPreferences 操作模式 + * @return {@link SharedPreferences} + */ + public static SharedPreferences getSharedPreferences( + final String fileName, + final int mode + ) { + try { + return DevUtils.getContext().getSharedPreferences(fileName, mode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSharedPreferences %s", fileName); + } + return null; + } + + // =========== + // = APP 相关 = + // =========== + + /** + * 根据名称清除数据库 + * @param dbName 数据库名 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteDatabase(final String dbName) { + return DBUtils.deleteDatabase(dbName); + } + + /** + * 获取 APP 包名 + * @return APP 包名 + */ + public static String getPackageName() { + try { + return DevUtils.getContext().getPackageName(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPackageName"); + } + return null; + } + + /** + * 获取 APP 图标 + * @return {@link Drawable} + */ + public static Drawable getAppIcon() { + return getAppIcon(getPackageName()); + } + + /** + * 获取 APP 图标 + * @param packageName 应用包名 + * @return {@link Drawable} + */ + public static Drawable getAppIcon(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + PackageManager packageManager = getPackageManager(); + if (packageManager == null) return null; + try { + PackageInfo packageInfo = packageManager.getPackageInfo( + packageName, 0 + ); + if (packageInfo == null) return null; + return packageInfo.applicationInfo.loadIcon(packageManager); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppIcon"); + return null; + } + } + + /** + * 获取 APP 应用名 + * @return APP 应用名 + */ + public static String getAppName() { + return getAppName(getPackageName()); + } + + /** + * 获取 APP 应用名 + * @param packageName 应用包名 + * @return APP 应用名 + */ + public static String getAppName(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + PackageManager packageManager = getPackageManager(); + if (packageManager == null) return null; + try { + PackageInfo packageInfo = packageManager.getPackageInfo( + packageName, 0 + ); + if (packageInfo == null) return null; + return packageInfo.applicationInfo.loadLabel(packageManager).toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppName"); + return null; + } + } + + /** + * 获取 APP versionName + * @return APP versionName + */ + public static String getAppVersionName() { + return getAppVersionName(getPackageName()); + } + + /** + * 获取 APP versionName + * @param packageName 应用包名 + * @return APP versionName + */ + public static String getAppVersionName(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + try { + PackageInfo packageInfo = getPackageInfo( + packageName, PackageManager.GET_SIGNATURES + ); + return packageInfo == null ? null : packageInfo.versionName; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppVersionName"); + return null; + } + } + + /** + * 获取 APP versionCode + * @return APP versionCode + */ + public static long getAppVersionCode() { + return getAppVersionCode(getPackageName()); + } + + /** + * 获取 APP versionCode + * @param packageName 应用包名 + * @return APP versionCode + */ + public static long getAppVersionCode(final String packageName) { + if (TextUtils.isEmpty(packageName)) return -1L; + PackageInfo packageInfo = getPackageInfo( + packageName, PackageManager.GET_SIGNATURES + ); + if (packageInfo == null) return -1L; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + return packageInfo.getLongVersionCode(); + } else { + return packageInfo.versionCode; + } + } + + /** + * 获取 APP 安装包路径 /data/data/packageName/.apk + * @return APP 安装包路径 + */ + public static String getAppPath() { + return getAppPath(getPackageName()); + } + + /** + * 获取 APP 安装包路径 /data/data/packageName/.apk + * @param packageName 应用包名 + * @return APP 安装包路径 + */ + public static String getAppPath(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + try { + PackageInfo packageInfo = getPackageInfo(packageName, 0); + return packageInfo == null ? null : packageInfo.applicationInfo.sourceDir; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppPath"); + return null; + } + } + + // = + + /** + * 获取 APP Signature + * @return {@link Signature} 数组 + */ + public static Signature[] getAppSignature() { + return getAppSignature(getPackageName()); + } + + /** + * 获取 APP Signature + * @param packageName 应用包名 + * @return {@link Signature} 数组 + */ + public static Signature[] getAppSignature(final String packageName) { + if (TextUtils.isEmpty(packageName)) return null; + try { + PackageInfo packageInfo = getPackageInfo( + packageName, PackageManager.GET_SIGNATURES + ); + return packageInfo == null ? null : packageInfo.signatures; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppSignature"); + return null; + } + } + + // = + + /** + * 获取 APP 签名 MD5 值 + * @return APP 签名 MD5 值 + */ + public static String getAppSignatureMD5() { + return getAppSignatureMD5(getPackageName()); + } + + /** + * 获取 APP 签名 MD5 值 + * @param packageName 应用包名 + * @return APP 签名 MD5 值 + */ + public static String getAppSignatureMD5(final String packageName) { + return getAppSignatureHash(packageName, "MD5"); + } + + /** + * 获取 APP 签名 SHA1 值 + * @return APP 签名 SHA1 值 + */ + public static String getAppSignatureSHA1() { + return getAppSignatureSHA1(getPackageName()); + } + + /** + * 获取 APP 签名 SHA1 值 + * @param packageName 应用包名 + * @return APP 签名 SHA1 值 + */ + public static String getAppSignatureSHA1(final String packageName) { + return getAppSignatureHash(packageName, "SHA1"); + } + + /** + * 获取 APP 签名 SHA256 值 + * @return APP 签名 SHA256 值 + */ + public static String getAppSignatureSHA256() { + return getAppSignatureSHA256(getPackageName()); + } + + /** + * 获取 APP 签名 SHA256 值 + * @param packageName 应用包名 + * @return APP 签名 SHA256 值 + */ + public static String getAppSignatureSHA256(final String packageName) { + return getAppSignatureHash(packageName, "SHA256"); + } + + /** + * 获取应用签名 Hash 值 + * @param packageName 应用包名 + * @param algorithm 算法 + * @return 对应算法处理后的签名信息 + */ + public static String getAppSignatureHash( + final String packageName, + final String algorithm + ) { + if (TextUtils.isEmpty(packageName)) return null; + try { + Signature[] signature = getAppSignature(packageName); + if (signature == null || signature.length == 0) return null; + return StringUtils.colonSplit( + ConvertUtils.toHexString( + EncryptUtils.hashTemplate(signature[0].toByteArray(), algorithm) + ) + ); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, + "getAppSignatureHash - packageName: %s, algorithm: %s", + packageName, algorithm + ); + return null; + } + } + + // = + + /** + * 判断 APP 是否 debug 模式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppDebug() { + return isAppDebug(getPackageName()); + } + + /** + * 判断 APP 是否 debug 模式 + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppDebug(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + ApplicationInfo appInfo = getApplicationInfo(packageName, 0); + return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAppDebug"); + return false; + } + } + + /** + * 判断 APP 是否 release 模式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppRelease() { + return isAppRelease(getPackageName()); + } + + /** + * 判断 APP 是否 release 模式 + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppRelease(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + ApplicationInfo appInfo = getApplicationInfo(packageName, 0); + return !(appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAppRelease"); + return false; + } + } + + // = + + /** + * 判断 APP 是否系统 app + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppSystem() { + return isAppSystem(getPackageName()); + } + + /** + * 判断 APP 是否系统 app + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppSystem(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + ApplicationInfo appInfo = getApplicationInfo(packageName, 0); + return appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAppSystem"); + return false; + } + } + + /** + * 判断 APP 是否在前台 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppForeground() { + return isAppForeground(getPackageName()); + } + + /** + * 判断 APP 是否在前台 + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAppForeground(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + List lists = getActivityManager().getRunningAppProcesses(); + if (lists != null && lists.size() > 0) { + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return packageName.equals(appProcess.processName); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAppForeground"); + } + return false; + } + + // = + + /** + * 判断是否安装了 APP + * @param action Action + * @param category Category + * @return {@code true} yes, {@code false} no + */ + public static boolean isInstalledApp( + final String action, + final String category + ) { + PackageManager packageManager = getPackageManager(); + if (packageManager == null) return false; + try { + Intent intent = new Intent(action); + intent.addCategory(category); + ResolveInfo resolveInfo = packageManager.resolveActivity(intent, 0); + return resolveInfo != null; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isInstalledApp"); + return false; + } + } + + /** + * 判断是否安装了 APP + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + @SuppressWarnings("unused") + public static boolean isInstalledApp(final String packageName) { + if (TextUtils.isEmpty(packageName)) return false; + try { + ApplicationInfo appInfo = getApplicationInfo( + packageName, PackageManager.GET_UNINSTALLED_PACKAGES + ); + return appInfo != null; + } catch (Exception e) { // 未安装, 则会抛出异常 + LogPrintUtils.eTag(TAG, e, "isInstalledApp"); + return false; + } + } + + /** + * 判断是否安装了 APP + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInstalledApp2(final String packageName) { + return IntentUtils.getLaunchAppIntent(packageName) != null; + } + + // ================ + // = Activity 跳转 = + // ================ + + /** + * Activity 跳转 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivity(final Intent intent) { + return startActivity( + DevUtils.getContext(), + IntentUtils.getIntent(intent, true) + ); + } + + /** + * Activity 跳转 + * @param context Context + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivity( + final Context context, + final Intent intent + ) { + if (context == null || intent == null) return false; + try { + context.startActivity(intent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startActivity"); + } + return false; + } + + // = + + /** + * Activity 跳转回传 + * @param activity {@link Activity} + * @param intent {@link Intent} + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivityForResult( + final Activity activity, + final Intent intent, + final int requestCode + ) { + if (activity == null || intent == null) return false; + try { + activity.startActivityForResult(intent, requestCode); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startActivityForResult"); + } + return false; + } + + /** + * Activity 跳转回传 + * @param callback Activity 跳转回传回调 + * @return {@code true} success, {@code false} fail + */ + public static boolean startActivityForResult(final DefaultActivityResult.ResultCallback callback) { + return ActivityResultUtils.getDefault().startActivityForResult(callback); + } + + /** + * Activity 请求权限跳转回传 + * @param activity {@link Activity} + * @param pendingIntent {@link PendingIntent} + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean startIntentSenderForResult( + final Activity activity, + final PendingIntent pendingIntent, + final int requestCode + ) { + if (activity == null || pendingIntent == null) return false; + try { + activity.startIntentSenderForResult( + pendingIntent.getIntentSender(), requestCode, + null, 0, 0, 0 + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startIntentSenderForResult"); + } + return false; + } + + // ======= + // = 广播 = + // ======= + + /** + * 注册广播监听 + * @param receiver {@link BroadcastReceiver} + * @param filter {@link IntentFilter} + * @return {@code true} success, {@code false} fail + */ + public static boolean registerReceiver( + final BroadcastReceiver receiver, + final IntentFilter filter + ) { + if (receiver == null || filter == null) return false; + try { + DevUtils.getContext().registerReceiver(receiver, filter); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "registerReceiver"); + } + return false; + } + + /** + * 注销广播监听 + * @param receiver {@link BroadcastReceiver} + * @return {@code true} success, {@code false} fail + */ + public static boolean unregisterReceiver(final BroadcastReceiver receiver) { + if (receiver == null) return false; + try { + DevUtils.getContext().unregisterReceiver(receiver); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregisterReceiver"); + } + return false; + } + + // ========== + // = 发送广播 = + // ========== + + /** + * 发送广播 ( 无序 ) + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean sendBroadcast(final Intent intent) { + if (intent == null) return false; + try { + DevUtils.getContext().sendBroadcast(intent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendBroadcast"); + } + return false; + } + + /** + * 发送广播 ( 无序 ) + * @param intent {@link Intent} + * @param receiverPermission 广播权限 + * @return {@code true} success, {@code false} fail + */ + public static boolean sendBroadcast( + final Intent intent, + final String receiverPermission + ) { + if (intent == null || receiverPermission == null) return false; + try { + DevUtils.getContext().sendBroadcast(intent, receiverPermission); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendBroadcast"); + } + return false; + } + + /** + * 发送广播 ( 有序 ) + * @param intent {@link Intent} + * @param receiverPermission 广播权限 + * @return {@code true} success, {@code false} fail + */ + public static boolean sendOrderedBroadcast( + final Intent intent, + final String receiverPermission + ) { + if (intent == null || receiverPermission == null) return false; + try { + DevUtils.getContext().sendOrderedBroadcast(intent, receiverPermission); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendOrderedBroadcast"); + } + return false; + } + + // ======= + // = 服务 = + // ======= + + /** + * 启动服务 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean startService(final Intent intent) { + if (intent == null) return false; + try { + DevUtils.getContext().startService(intent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startService"); + } + return false; + } + + /** + * 停止服务 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean stopService(final Intent intent) { + if (intent == null) return false; + try { + DevUtils.getContext().stopService(intent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopService"); + } + return false; + } + + // =========== + // = 安装、卸载 = + // =========== + + /** + * 安装 APP ( 支持 8.0 ) 的意图 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp(final String filePath) { + return installApp(FileUtils.getFileByPath(filePath)); + } + + /** + * 安装 APP ( 支持 8.0 ) 的意图 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp(final File file) { + return startActivity(IntentUtils.getInstallAppIntent(file)); + } + + /** + * 安装 APP ( 支持 8.0 ) 的意图 + * @param activity {@link Activity} + * @param filePath 文件路径 + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp( + final Activity activity, + final String filePath, + final int requestCode + ) { + return installApp(activity, FileUtils.getFileByPath(filePath), requestCode); + } + + /** + * 安装 APP ( 支持 8.0 ) 的意图 + * @param activity {@link Activity} + * @param file 文件 + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean installApp( + final Activity activity, + final File file, + final int requestCode + ) { + return startActivityForResult( + activity, IntentUtils.getInstallAppIntent(file), + requestCode + ); + } + + // = + + /** + * 静默安装应用 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent(final String filePath) { + return ADBUtils.installAppSilent(filePath); + } + + /** + * 静默安装应用 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent(final File file) { + return ADBUtils.installAppSilent(file); + } + + /** + * 静默安装应用 + * @param filePath 文件路径 + * @param params 安装参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final String filePath, + final String params + ) { + return ADBUtils.installAppSilent(filePath, params); + } + + /** + * 静默安装应用 + * @param file 文件 + * @param params 安装参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final File file, + final String params + ) { + return ADBUtils.installAppSilent(file, params); + } + + /** + * 静默安装应用 + * @param file 文件 + * @param params 安装参数 + * @param isRooted 是否 root + * @return {@code true} success, {@code false} fail + */ + public static boolean installAppSilent( + final File file, + final String params, + final boolean isRooted + ) { + return ADBUtils.installAppSilent(file, params, isRooted); + } + + // = + + /** + * 卸载应用 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallApp(final String packageName) { + return startActivity(IntentUtils.getUninstallAppIntent(packageName)); + } + + /** + * 卸载应用 + * @param activity {@link Activity} + * @param packageName 应用包名 + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallApp( + final Activity activity, + final String packageName, + final int requestCode + ) { + return startActivityForResult( + activity, IntentUtils.getUninstallAppIntent(packageName), + requestCode + ); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent(final String packageName) { + return ADBUtils.uninstallAppSilent(packageName); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @param isKeepData true 表示卸载应用但保留数据和缓存目录 + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent( + final String packageName, + final boolean isKeepData + ) { + return ADBUtils.uninstallAppSilent(packageName, isKeepData); + } + + /** + * 静默卸载应用 + * @param packageName 应用包名 + * @param isKeepData true 表示卸载应用但保留数据和缓存目录 + * @param isRooted 是否 root + * @return {@code true} success, {@code false} fail + */ + public static boolean uninstallAppSilent( + final String packageName, + final boolean isKeepData, + final boolean isRooted + ) { + return ADBUtils.uninstallAppSilent(packageName, isKeepData, isRooted); + } + + // ========== + // = 操作相关 = + // ========== + + /** + * 打开 APP + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean launchApp(final String packageName) { + return startActivity(IntentUtils.getLaunchAppIntent(packageName)); + } + + /** + * 打开 APP + * @param activity {@link Activity} + * @param packageName 应用包名 + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean launchApp( + final Activity activity, + final String packageName, + final int requestCode + ) { + return startActivityForResult( + activity, IntentUtils.getLaunchAppIntent(packageName), + requestCode + ); + } + + // = + + /** + * 跳转到 APP 设置详情页面 + * @return {@code true} success, {@code false} fail + */ + public static boolean launchAppDetailsSettings() { + return launchAppDetailsSettings(getPackageName()); + } + + /** + * 跳转到 APP 设置详情页面 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + public static boolean launchAppDetailsSettings(final String packageName) { + return startActivity(IntentUtils.getLaunchAppDetailsSettingsIntent(packageName)); + } + + /** + * 跳转到 APP 应用商城详情页面 + * @param marketPkg 应用商店包名, 如果为 "" 则由系统弹出应用商店列表供用户选择, 否则调转到目标市场的应用详情界面, 某些应用商店可能会失败 + * @return {@code true} success, {@code false} fail + */ + public static boolean launchAppDetails(final String marketPkg) { + return launchAppDetails(getPackageName(), marketPkg); + } + + /** + * 跳转到 APP 应用商城详情页面 + * @param packageName 应用包名 + * @param marketPkg 应用商店包名, 如果为 "" 则由系统弹出应用商店列表供用户选择, 否则调转到目标市场的应用详情界面, 某些应用商店可能会失败 + * @return {@code true} success, {@code false} fail + */ + public static boolean launchAppDetails( + final String packageName, + final String marketPkg + ) { + return startActivity(IntentUtils.getLaunchAppDetailIntent(packageName, marketPkg)); + } + + // ========== + // = 其他功能 = + // ========== + + /** + * 打开文件 + * @param filePath 文件路径 + * @param dataType 数据类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean openFile( + final String filePath, + final String dataType + ) { + return openFile(FileUtils.getFileByPath(filePath), dataType); + } + + /** + * 打开文件 + * @param file 文件 + * @param dataType 数据类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean openFile( + final File file, + final String dataType + ) { + if (!FileUtils.isFileExists(file)) return false; + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 临时授权 ( 必须 ) + intent.setDataAndType(UriUtils.getUriForFile(file), dataType); + return startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openFile"); + } + return false; + } + + // = + + /** + * 打开文件 ( 指定应用 ) + * @param filePath 文件路径 + * @param packageName 应用包名 + * @param className Activity.class.getCanonicalName() + * @return {@code true} success, {@code false} fail + */ + public static boolean openFileByApp( + final String filePath, + final String packageName, + final String className + ) { + return openFileByApp(FileUtils.getFileByPath(filePath), packageName, className); + } + + /** + * 打开文件 ( 指定应用 ) + * @param file 文件 + * @param packageName 应用包名 + * @param className Activity.class.getCanonicalName() + * @return {@code true} success, {@code false} fail + */ + public static boolean openFileByApp( + final File file, + final String packageName, + final String className + ) { + if (!FileUtils.isFileExists(file)) return false; + try { + Intent intent = new Intent(); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setData(DevUtils.getUriForFile(file)); + intent.setClassName(packageName, className); + return startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openFile"); + } + return false; + } + + // = + + /** + * 打开 PDF 文件 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean openPDFFile(final String filePath) { + return openPDFFile(FileUtils.getFileByPath(filePath)); + } + + /** + * 打开 PDF 文件 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean openPDFFile(final File file) { + return openFile(file, "application/pdf"); + } + + // = + + /** + * 打开 Word 文件 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean openWordFile(final String filePath) { + return openWordFile(FileUtils.getFileByPath(filePath)); + } + + /** + * 打开 Word 文件 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean openWordFile(final File file) { + return openFile(file, "application/msword"); + } + + // = + + /** + * 调用 WPS 打开 office 文档 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean openOfficeByWPS(final String filePath) { + return openOfficeByWPS(FileUtils.getFileByPath(filePath)); + } + + /** + * 调用 WPS 打开 office 文档 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean openOfficeByWPS(final File file) { + String wpsPackage = "cn.wps.moffice_eng"; // 普通版与英文版一样 + // String wpsActivity = "cn.wps.moffice.documentmanager.PreStartActivity"; + String wpsActivity2 = "cn.wps.moffice.documentmanager.PreStartActivity2"; + // 打开文件 + return openFileByApp(file, wpsPackage, wpsActivity2); + } + + // ========== + // = 系统页面 = + // ========== + + /** + * 跳转到系统设置页面 + * @return {@code true} success, {@code false} fail + */ + public static boolean startSysSetting() { + return startActivity(new Intent(Settings.ACTION_SETTINGS)); + } + + /** + * 跳转到系统设置页面 + * @param activity {@link Activity} + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean startSysSetting( + final Activity activity, + final int requestCode + ) { + return startActivityForResult( + activity, new Intent(Settings.ACTION_SETTINGS), + requestCode + ); + } + + /** + * 打开网络设置界面 + * @return {@code true} success, {@code false} fail + */ + public static boolean openWirelessSettings() { + return startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS)); + } + + /** + * 打开网络设置界面 + * @param activity {@link Activity} + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean openWirelessSettings( + final Activity activity, + final int requestCode + ) { + return startActivityForResult( + activity, new Intent(Settings.ACTION_WIRELESS_SETTINGS), + requestCode + ); + } + + /** + * 打开 GPS 设置界面 + * @return {@code true} success, {@code false} fail + */ + public static boolean openGpsSettings() { + return startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)); + } + + /** + * 打开 GPS 设置界面 + * @param activity {@link Activity} + * @param requestCode 请求 code + * @return {@code true} success, {@code false} fail + */ + public static boolean openGpsSettings( + final Activity activity, + final int requestCode + ) { + return startActivityForResult( + activity, new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), + requestCode + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/AudioManagerUtils.java b/lib/DevApp/src/main/java/dev/utils/app/AudioManagerUtils.java new file mode 100644 index 0000000000..d842cb298f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/AudioManagerUtils.java @@ -0,0 +1,787 @@ +package dev.utils.app; + +import android.app.NotificationManager; +import android.content.Intent; +import android.media.AudioManager; +import android.os.Build; + +import dev.utils.LogPrintUtils; + +/** + * detail: 音频管理工具类 + * @author Ttt + *
+ *     AudioManager 的作用: 调整音量和控制响铃模式
+ *     @see 
+ *     

+ * 声音分类 + * STREAM_VOICE_CALL: 通话声音 + * STREAM_SYSTEM: 系统声音, 包括按键声音等 + * STREAM_RING: 来电响铃 + * STREAM_MUSIC: 媒体声音 ( 包括音乐、视频、游戏声音 ) + * STREAM_ALARM: 闹钟声音 + * STREAM_NOTIFICATION: 通知声音 + *

+ * 声音模式分类 + * RINGER_MODE_NORMAL: 正常模式 + * 所有声音都正常, 包括系统声音、来电响铃、媒体声音、闹钟、通知声音都有 + * RINGER_MODE_SILENT: 静音模式 + * 该模式下, 来电响铃、通知、系统声音和震动都没有, 闹钟、通话声音保持, 大部分手机媒体声音依然有 + * 但是小米和少部分 oppo 手机在设置静音的同时会将媒体声音自动调整为 0, 此时没有媒体声音 + * RINGER_MODE_VIBRATE: 震动模式 + * 该模式下, 来电、通知保持震动没有声音, 但是媒体、闹钟依然有声音, 不过小米手机只有正常模式和静音模式, 没有震动模式 + *

+ * 所需权限 + * + *
+ */ +public final class AudioManagerUtils { + + private AudioManagerUtils() { + } + + // 日志 TAG + private static final String TAG = AudioManagerUtils.class.getSimpleName(); + + // ========== + // = 音量大小 = + // ========== + + /** + * 获取指定声音流最大音量大小 + * @param streamType 流类型 + * @return 最大音量大小 + */ + public static int getStreamMaxVolume(final int streamType) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getStreamMaxVolume(streamType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStreamMaxVolume"); + } + } + return 0; + } + + /** + * 获取指定声音流音量大小 + * @param streamType 流类型 + * @return 音量大小 + */ + public static int getStreamVolume(final int streamType) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getStreamVolume(streamType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStreamVolume"); + } + } + return 0; + } + + /** + * 设置指定声音流音量大小 + * @param streamType 流类型 + * @param index 音量大小 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamVolume( + final int streamType, + final int index + ) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setStreamVolume(streamType, index, 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStreamVolume"); + } + } + return false; + } + + // = + + /** + * 控制手机音量, 调小一个单位 + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustVolumeLower() { + return adjustVolume(AudioManager.ADJUST_LOWER); + } + + /** + * 控制手机音量, 调大一个单位 + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustVolumeRaise() { + return adjustVolume(AudioManager.ADJUST_RAISE); + } + + /** + * 控制手机音量, 调大或者调小一个单位 + *
+     *     AudioManager.ADJUST_LOWER 可调小一个单位
+     *     AudioManager.ADJUST_RAISE 可调大一个单位
+     * 
+ * @param direction 音量方向 ( 调大、调小 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustVolume(final int direction) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.adjustVolume(direction, 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "adjustVolume"); + } + } + return false; + } + + // = + + /** + * 控制指定声音流音量, 调小一个单位 + * @param streamType 流类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustStreamVolumeLower(final int streamType) { + return adjustStreamVolume(streamType, AudioManager.ADJUST_LOWER); + } + + /** + * 控制指定声音流音量, 调大一个单位 + * @param streamType 流类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustStreamVolumeRaise(final int streamType) { + return adjustStreamVolume(streamType, AudioManager.ADJUST_RAISE); + } + + /** + * 控制指定声音流音量, 调大或者调小一个单位 + *
+     *     AudioManager.ADJUST_LOWER 可调小一个单位
+     *     AudioManager.ADJUST_RAISE 可调大一个单位
+     * 
+ * @param streamType 流类型 + * @param direction 音量方向 ( 调大、调小 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustStreamVolume( + final int streamType, + final int direction + ) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.adjustStreamVolume(streamType, direction, 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "adjustStreamVolume"); + } + } + return false; + } + + // ========== + // = 静音状态 = + // ========== + + /** + * 设置媒体声音静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteByMusic(final boolean state) { + return setStreamMute(AudioManager.STREAM_MUSIC, state); + } + + /** + * 设置通话声音静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteByVoiceCall(final boolean state) { + return setStreamMute(AudioManager.STREAM_VOICE_CALL, state); + } + + /** + * 设置系统声音静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteBySystem(final boolean state) { + return setStreamMute(AudioManager.STREAM_SYSTEM, state); + } + + /** + * 设置来电响铃静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteByRing(final boolean state) { + return setStreamMute(AudioManager.STREAM_RING, state); + } + + /** + * 设置闹钟声音静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteByAlarm(final boolean state) { + return setStreamMute(AudioManager.STREAM_ALARM, state); + } + + /** + * 设置通知声音静音状态 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMuteByNotification(final boolean state) { + return setStreamMute(AudioManager.STREAM_NOTIFICATION, state); + } + + /** + * 设置指定声音流静音状态 + * @param streamType 流类型 + * @param state {@code true} 静音, {@code false} 非静音 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStreamMute( + final int streamType, + final boolean state + ) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setStreamMute(streamType, state); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStreamMute"); + } + } + return false; + } + + // ======= + // = 模式 = + // ======= + + /** + * 获取当前的音频模式 + *
+     *     返回值有下述几种模式:
+     *     MODE_NORMAL( 普通 )
+     *     MODE_RINGTONE( 铃声 )
+     *     MODE_IN_CALL( 打电话 )
+     *     MODE_IN_COMMUNICATION( 通话 )
+     * 
+ * @return 当前的音频模式 + */ + public static int getMode() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getMode(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMode"); + } + } + return AudioManager.MODE_NORMAL; + } + + /** + * 设置当前的音频模式 + *
+     *     有下述几种模式:
+     *     MODE_NORMAL( 普通 )
+     *     MODE_RINGTONE( 铃声 )
+     *     MODE_IN_CALL( 打电话 )
+     *     MODE_IN_COMMUNICATION( 通话 )
+     * 
+ * @param mode 音频模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setMode(final int mode) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setMode(mode); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setMode"); + } + } + return false; + } + + // ========== + // = 铃声模式 = + // ========== + + /** + * 获取当前的铃声模式 + *
+     *     返回值有下述几种模式:
+     *     RINGER_MODE_NORMAL( 普通 )
+     *     RINGER_MODE_SILENT( 静音 )
+     *     RINGER_MODE_VIBRATE( 震动 )
+     * 
+ * @return 当前的铃声模式 + */ + public static int getRingerMode() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getRingerMode(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRingerMode"); + } + } + return AudioManager.RINGER_MODE_NORMAL; + } + + /** + * 获取当前的铃声模式 + * @param ringerMode 铃声模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setRingerMode(final int ringerMode) { + return setRingerMode(ringerMode, true); + } + + /** + * 获取当前的铃声模式 + *
+     *     有下述几种模式:
+     *     RINGER_MODE_NORMAL( 普通 )
+     *     RINGER_MODE_SILENT( 静音 )
+     *     RINGER_MODE_VIBRATE( 震动 )
+     * 
+ * @param ringerMode 铃声模式 + * @param setting 如果没授权, 是否跳转到设置页面 + * @return {@code true} success, {@code false} fail + */ + public static boolean setRingerMode( + final int ringerMode, + final boolean setting + ) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + if (isDoNotDisturb(setting)) { + audioManager.setRingerMode(ringerMode); + return true; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setRingerMode"); + } + } + return false; + } + + // = + + /** + * 设置静音模式 ( 静音, 且无振动 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean ringerSilent() { + return setRingerMode(AudioManager.RINGER_MODE_SILENT); + } + + /** + * 设置震动模式 ( 静音, 但有振动 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean ringerVibrate() { + return setRingerMode(AudioManager.RINGER_MODE_VIBRATE); + } + + /** + * 设置正常模式 ( 正常声音, 振动开关由 setVibrateSetting 决定 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean ringerNormal() { + return setRingerMode(AudioManager.RINGER_MODE_NORMAL); + } + + // = + + /** + * 判断是否授权 Do not disturb 权限 + *
+     *     授权 Do not disturb 权限, 才可进行音量操作
+     * 
+ * @param setting 如果没授权, 是否跳转到设置页面 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDoNotDisturb(final boolean setting) { + try { + NotificationManager notificationManager = AppUtils.getNotificationManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N + && !notificationManager.isNotificationPolicyAccessGranted()) { + if (setting) { + Intent intent = new Intent( + android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS + ); + AppUtils.startActivity(intent); + } + } else { + return true; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isDoNotDisturb"); + } + return false; + } + + // = + + /** + * 设置是否打开扩音器 ( 扬声器 ) + * @param on {@code true} yes, {@code false} no + * @return {@code true} success, {@code false} fail + */ + public static boolean setSpeakerphoneOn(final boolean on) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setSpeakerphoneOn(on); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setSpeakerphoneOn"); + } + } + return false; + } + + /** + * 设置是否让麦克风静音 + * @param on {@code true} yes, {@code false} no + * @return {@code true} success, {@code false} fail + */ + public static boolean setMicrophoneMute(final boolean on) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setMicrophoneMute(on); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setMicrophoneMute"); + } + } + return false; + } + + /** + * 判断是否打开扩音器 ( 扬声器 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isSpeakerphoneOn() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isSpeakerphoneOn(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isSpeakerphoneOn"); + } + } + return false; + } + + /** + * 判断麦克风是否静音 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMicrophoneMute() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isMicrophoneMute(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isMicrophoneMute"); + } + } + return false; + } + + /** + * 判断是否有音乐处于活跃状态 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMusicActive() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isMusicActive(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isMusicActive"); + } + } + return false; + } + + /** + * 判断是否插入了耳机 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWiredHeadsetOn() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isWiredHeadsetOn(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isWiredHeadsetOn"); + } + } + return false; + } + + /** + * 检查蓝牙 A2DP 音频外设是否已连接 + * @return {@code true} yes, {@code false} no + */ + public static boolean isBluetoothA2dpOn() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isBluetoothA2dpOn(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isBluetoothA2dpOn"); + } + } + return false; + } + + /** + * 检查当前平台是否支持使用 SCO 的关闭调用用例 + * @return {@code true} yes, {@code false} no + */ + public static boolean isBluetoothScoAvailableOffCall() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isBluetoothScoAvailableOffCall(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isBluetoothScoAvailableOffCall"); + } + } + return false; + } + + /** + * 检查通信是否使用蓝牙 SCO + * @return {@code true} yes, {@code false} no + */ + public static boolean isBluetoothScoOn() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.isBluetoothScoOn(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isBluetoothScoOn"); + } + } + return false; + } + + /** + * 设置是否使用蓝牙 SCO 耳机进行通讯 + * @param on {@code true} yes, {@code false} no + * @return {@code true} success, {@code false} fail + */ + public static boolean setBluetoothScoOn(final boolean on) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.setBluetoothScoOn(on); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBluetoothScoOn"); + } + } + return false; + } + + /** + * 启动蓝牙 SCO 音频连接 + * @return {@code true} success, {@code false} fail + */ + public static boolean startBluetoothSco() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.startBluetoothSco(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startBluetoothSco"); + } + } + return false; + } + + /** + * 停止蓝牙 SCO 音频连接 + * @return {@code true} success, {@code false} fail + */ + public static boolean stopBluetoothSco() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.stopBluetoothSco(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopBluetoothSco"); + } + } + return false; + } + + // = + + /** + * 加载音效 + * @return {@code true} success, {@code false} fail + */ + public static boolean loadSoundEffects() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.loadSoundEffects(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "loadSoundEffects"); + } + } + return false; + } + + /** + * 卸载音效 + * @return {@code true} success, {@code false} fail + */ + public static boolean unloadSoundEffects() { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.unloadSoundEffects(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unloadSoundEffects"); + } + } + return false; + } + + /** + * 播放音效 + * @param effectType {@link AudioManager#FX_KEY_CLICK}, + * {@link AudioManager#FX_FOCUS_NAVIGATION_UP}, + * {@link AudioManager#FX_FOCUS_NAVIGATION_DOWN}, + * {@link AudioManager#FX_FOCUS_NAVIGATION_LEFT}, + * {@link AudioManager#FX_FOCUS_NAVIGATION_RIGHT}, + * {@link AudioManager#FX_KEYPRESS_STANDARD}, + * {@link AudioManager#FX_KEYPRESS_SPACEBAR}, + * {@link AudioManager#FX_KEYPRESS_DELETE}, + * {@link AudioManager#FX_KEYPRESS_RETURN}, + * {@link AudioManager#FX_KEYPRESS_INVALID}, + * @param volume 音量大小 + * @return {@code true} success, {@code false} fail + */ + public static boolean playSoundEffect( + final int effectType, + final float volume + ) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.playSoundEffect(effectType, volume); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "playSoundEffect"); + } + } + return false; + } + + // = + + /** + * 放弃音频焦点, 使上一个焦点所有者 ( 如果有 ) 接收焦点 + * @param listener 焦点监听事件 + * @return {@code true} success, {@code false} fail + */ + public static boolean abandonAudioFocus(final AudioManager.OnAudioFocusChangeListener listener) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.abandonAudioFocus(listener); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "abandonAudioFocus"); + } + } + return false; + } + + /** + * 调整最相关的流的音量, 或者给定的回退流 + * @param direction 调整参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean adjustSuggestedStreamVolume(final int direction) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + audioManager.adjustSuggestedStreamVolume( + direction, AudioManager.USE_DEFAULT_STREAM_TYPE, 0 + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "adjustSuggestedStreamVolume"); + } + } + return false; + } + + /** + * 获取音频硬件指定 key 的参数值 + * @param keys Key + * @return 音频硬件指定 key 的参数值 + */ + public static String getParameters(final String keys) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getParameters(keys); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getParameters"); + } + } + return null; + } + + /** + * 获取用户对振动类型的振动设置 + * @param vibrateType {@link AudioManager#VIBRATE_TYPE_NOTIFICATION} or {@link AudioManager#VIBRATE_TYPE_RINGER} + * @return {@link AudioManager#VIBRATE_SETTING_ON}, {@link AudioManager#VIBRATE_SETTING_OFF}, {@link AudioManager#VIBRATE_SETTING_ONLY_SILENT} + */ + public static int getVibrateSetting(final int vibrateType) { + AudioManager audioManager = AppUtils.getAudioManager(); + if (audioManager != null) { + try { + return audioManager.getVibrateSetting(vibrateType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVibrateSetting"); + } + } + return -1; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/BarUtils.java b/lib/DevApp/src/main/java/dev/utils/app/BarUtils.java new file mode 100644 index 0000000000..cf71ef211a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/BarUtils.java @@ -0,0 +1,877 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Insets; +import android.graphics.Point; +import android.os.Build; +import android.util.TypedValue; +import android.view.Display; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.drawerlayout.widget.DrawerLayout; + +import java.lang.reflect.Method; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: Bar 相关工具类 + * @author Blankj + * @author Ttt + *
+ *     所需权限
+ *     
+ *     Android 顶部状态栏和底部导航栏的沉浸式效果设置
+ *     @see 
+ * 
+ */ +public final class BarUtils { + + private BarUtils() { + } + + // 日志 TAG + private static final String TAG = BarUtils.class.getSimpleName(); + + // ============== + // = status bar = + // ============== + + private static final String TAG_STATUS_BAR = "TAG_STATUS_BAR"; + private static final String TAG_OFFSET = "TAG_OFFSET"; + private static final int KEY_OFFSET = -123; + + /** + * 获取 StatusBar 高度 + * @return StatusBar 高度 + */ + public static int getStatusBarHeight() { + try { + Resources resources = Resources.getSystem(); + int id = resources.getIdentifier( + "status_bar_height", "dimen", "android" + ); + if (id != 0) { + return resources.getDimensionPixelSize(id); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStatusBarHeight"); + } + return 0; + } + + /** + * 获取 StatusBar 高度 + * @return StatusBar 高度 + */ + public static int getStatusBarHeight2() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + WindowMetrics windowMetrics = AppUtils.getCurrentWindowMetrics(); + if (windowMetrics != null) { + try { + WindowInsets windowInsets = windowMetrics.getWindowInsets(); + Insets insets = windowInsets.getInsetsIgnoringVisibility( + WindowInsets.Type.navigationBars() + | WindowInsets.Type.displayCutout() + ); + return insets.top; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStatusBarHeight2"); + } + } + } + return getStatusBarHeight(); + } + + /** + * 判断 StatusBar 是否显示 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStatusBarVisible(final Activity activity) { + if (activity != null) { + try { + int flags = activity.getWindow().getAttributes().flags; + return (flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isStatusBarVisible"); + } + } + return true; + } + + /** + * 设置 StatusBar 是否显示 + * @param activity {@link Activity} + * @param isVisible 是否显示 StatusBar + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarVisibility( + final Activity activity, + final boolean isVisible + ) { + return setStatusBarVisibility(WindowUtils.getWindow(activity), isVisible); + } + + /** + * 设置 StatusBar 是否显示 + * @param window {@link Window} + * @param isVisible 是否显示 StatusBar + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarVisibility( + final Window window, + final boolean isVisible + ) { + if (window != null) { + if (isVisible) { + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + showStatusBarView(window); + addMarginTopEqualStatusBarHeight(window); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + hideStatusBarView(window); + subtractMarginTopEqualStatusBarHeight(window); + } + return true; + } + return false; + } + + /** + * 设置 StatusBar 是否高亮模式 + * @param activity {@link Activity} + * @param isLightMode 是否高亮模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarLightMode( + final Activity activity, + final boolean isLightMode + ) { + return setStatusBarLightMode(WindowUtils.getWindow(activity), isLightMode); + } + + /** + * 设置 StatusBar 是否高亮模式 + * @param window {@link Window} + * @param isLightMode 是否高亮模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarLightMode( + final Window window, + final boolean isLightMode + ) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + View decorView = window.getDecorView(); + int vis = decorView.getSystemUiVisibility(); + if (isLightMode) { + vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + decorView.setSystemUiVisibility(vis); + return true; + } + return false; + } + + /** + * 获取 StatusBar 是否高亮模式 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStatusBarLightMode(final Activity activity) { + return isStatusBarLightMode(WindowUtils.getWindow(activity)); + } + + /** + * 获取 StatusBar 是否高亮模式 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStatusBarLightMode(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + View decorView = window.getDecorView(); + int vis = decorView.getSystemUiVisibility(); + return (vis & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0; + } + return false; + } + + // = + + /** + * 添加 View 向上 StatusBar 同等高度边距 + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public static boolean addMarginTopEqualStatusBarHeight(final View view) { + if (view != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + view.setTag(TAG_OFFSET); + Object haveSetOffset = view.getTag(KEY_OFFSET); + if (haveSetOffset != null && (Boolean) haveSetOffset) return false; + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + layoutParams.setMargins( + layoutParams.leftMargin, + layoutParams.topMargin + getStatusBarHeight(), + layoutParams.rightMargin, + layoutParams.bottomMargin + ); + view.setLayoutParams(layoutParams); + view.setTag(KEY_OFFSET, true); + return true; + } + return false; + } + + /** + * 移除 View 向上 StatusBar 同等高度边距 + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public static boolean subtractMarginTopEqualStatusBarHeight(final View view) { + if (view != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Object haveSetOffset = view.getTag(KEY_OFFSET); + if (haveSetOffset == null || !(Boolean) haveSetOffset) return false; + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + layoutParams.setMargins( + layoutParams.leftMargin, + layoutParams.topMargin - getStatusBarHeight(), + layoutParams.rightMargin, + layoutParams.bottomMargin + ); + view.setLayoutParams(layoutParams); + view.setTag(KEY_OFFSET, false); + return true; + } + return false; + } + + /** + * 添加 View 向上 StatusBar 同等高度边距 + * @param window {@link Window} + */ + private static void addMarginTopEqualStatusBarHeight(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET); + if (withTag == null) return; + addMarginTopEqualStatusBarHeight(withTag); + } + } + + /** + * 移除 View 向上 StatusBar 同等高度边距 + * @param window {@link Window} + */ + private static void subtractMarginTopEqualStatusBarHeight(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + View withTag = window.getDecorView().findViewWithTag(TAG_OFFSET); + if (withTag == null) return; + subtractMarginTopEqualStatusBarHeight(withTag); + } + } + + // = + + /** + * 设置 StatusBar 颜色 + * @param activity {@link Activity} + * @param color 背景颜色 + */ + public static View setStatusBarColor( + final Activity activity, + final int color + ) { + return setStatusBarColor(activity, color, false); + } + + /** + * 设置 StatusBar 颜色 + * @param activity {@link Activity} + * @param color 背景颜色 + * @param isDecor {@code true} add DecorView, {@code false} add ContentView + */ + public static View setStatusBarColor( + final Activity activity, + final int color, + final boolean isDecor + ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null; + transparentStatusBar(activity); + return applyStatusBarColor(activity, color, isDecor); + } + + /** + * 设置 StatusBar 颜色 + * @param window {@link Window} + * @param color 背景颜色 + */ + public static View setStatusBarColor( + final Window window, + final int color + ) { + return setStatusBarColor(window, color, false); + } + + /** + * 设置 StatusBar 颜色 + * @param window {@link Window} + * @param color 背景颜色 + * @param isDecor {@code true} add DecorView, {@code false} add ContentView + */ + public static View setStatusBarColor( + final Window window, + final int color, + final boolean isDecor + ) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return null; + transparentStatusBar(window); + return applyStatusBarColor(window, color, isDecor); + } + + /** + * 设置 StatusBar 颜色 + * @param fakeStatusBar StatusBar View + * @param color 背景颜色 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarColor( + final View fakeStatusBar, + final int color + ) { + if (fakeStatusBar != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Activity activity = ActivityUtils.getActivity(fakeStatusBar.getContext()); + if (activity == null) return false; + transparentStatusBar(activity); + fakeStatusBar.setVisibility(View.VISIBLE); + ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams(); + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.height = getStatusBarHeight(); + fakeStatusBar.setLayoutParams(layoutParams); + fakeStatusBar.setBackgroundColor(color); + return true; + } + return false; + } + + /** + * 设置自定义 StatusBar View + * @param fakeStatusBar StatusBar View + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarCustom(final View fakeStatusBar) { + if (fakeStatusBar != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Activity activity = ActivityUtils.getActivity(fakeStatusBar.getContext()); + if (activity == null) return false; + transparentStatusBar(activity); + fakeStatusBar.setVisibility(View.VISIBLE); + ViewGroup.LayoutParams layoutParams = fakeStatusBar.getLayoutParams(); + if (layoutParams == null) { + layoutParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + getStatusBarHeight() + ); + } else { + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.height = getStatusBarHeight(); + } + fakeStatusBar.setLayoutParams(layoutParams); + return true; + } + return false; + } + + /** + * 设置 DrawerLayout StatusBar 颜色 + * @param drawer DrawLayout + * @param fakeStatusBar StatusBar View + * @param color 背景颜色 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarColorDrawer( + final DrawerLayout drawer, + final View fakeStatusBar, + final int color + ) { + return setStatusBarColorDrawer(drawer, fakeStatusBar, color, false); + } + + /** + * 设置 DrawerLayout StatusBar 颜色 + *
+     *     DrawLayout 必须添加
+     *     {@code android:fitsSystemWindows="true"}
+     * 
+ * @param drawer DrawLayout + * @param fakeStatusBar StatusBar View + * @param color 背景颜色 + * @param isTop 是否设置 DrawerLayout 为顶层 + * @return {@code true} success, {@code false} fail + */ + public static boolean setStatusBarColorDrawer( + final DrawerLayout drawer, + final View fakeStatusBar, + final int color, + final boolean isTop + ) { + if (drawer != null && fakeStatusBar != null && + Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Activity activity = ActivityUtils.getActivity(fakeStatusBar.getContext()); + if (activity == null) return false; + transparentStatusBar(activity); + drawer.setFitsSystemWindows(false); + setStatusBarColor(fakeStatusBar, color); + for (int i = 0, len = drawer.getChildCount(); i < len; i++) { + drawer.getChildAt(i).setFitsSystemWindows(false); + } + if (isTop) { + hideStatusBarView(activity); + } else { + setStatusBarColor(activity, color, false); + } + return true; + } + return false; + } + + // = + + /** + * 设置透明 StatusBar + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean transparentStatusBar(final Activity activity) { + return transparentStatusBar(WindowUtils.getWindow(activity)); + } + + /** + * 设置透明 StatusBar + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public static boolean transparentStatusBar(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + int option = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + int vis = window.getDecorView().getSystemUiVisibility(); + window.getDecorView().setSystemUiVisibility(option | vis); + window.setStatusBarColor(Color.TRANSPARENT); + } else { + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + return true; + } + return false; + } + + /** + * 应用 StatusBar View + * @param activity {@link Activity} + * @param color 背景颜色 + * @param isDecor 是否添加在 DecorView 上 + * @return StatusBar View + */ + private static View applyStatusBarColor( + final Activity activity, + final int color, + final boolean isDecor + ) { + return applyStatusBarColor(WindowUtils.getWindow(activity), color, isDecor); + } + + /** + * 应用 StatusBar View + * @param window {@link Window} + * @param color 背景颜色 + * @param isDecor 是否添加在 DecorView 上 + * @return StatusBar View + */ + private static View applyStatusBarColor( + final Window window, + final int color, + final boolean isDecor + ) { + if (window == null) return null; + ViewGroup parent = isDecor ? (ViewGroup) window.getDecorView() : (ViewGroup) window.findViewById(android.R.id.content); + View fakeStatusBarView = parent.findViewWithTag(TAG_STATUS_BAR); + if (fakeStatusBarView != null) { + if (fakeStatusBarView.getVisibility() == View.GONE) { + fakeStatusBarView.setVisibility(View.VISIBLE); + } + fakeStatusBarView.setBackgroundColor(color); + } else { + fakeStatusBarView = createStatusBarView(window.getContext(), color); + parent.addView(fakeStatusBarView); + } + return fakeStatusBarView; + } + + /** + * 隐藏 StatusBar View + * @param activity {@link Activity} + */ + private static void hideStatusBarView(final Activity activity) { + hideStatusBarView(WindowUtils.getWindow(activity)); + } + + /** + * 隐藏 StatusBar View + * @param window {@link Window} + */ + private static void hideStatusBarView(final Window window) { + ViewGroup decorView = (ViewGroup) window.getDecorView(); + View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR); + if (fakeStatusBarView == null) return; + fakeStatusBarView.setVisibility(View.GONE); + } + + /** + * 显示 StatusBar View + * @param window {@link Window} + */ + private static void showStatusBarView(final Window window) { + ViewGroup decorView = (ViewGroup) window.getDecorView(); + View fakeStatusBarView = decorView.findViewWithTag(TAG_STATUS_BAR); + if (fakeStatusBarView == null) return; + fakeStatusBarView.setVisibility(View.VISIBLE); + } + + /** + * 创建 StatusBar View + * @param context {@link Context} + * @param color 背景颜色 + * @return StatusBar View + */ + private static View createStatusBarView( + final Context context, + final int color + ) { + View statusBarView = new View(context); + statusBarView.setLayoutParams(new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, getStatusBarHeight() + )); + statusBarView.setBackgroundColor(color); + statusBarView.setTag(TAG_STATUS_BAR); + return statusBarView; + } + + // ============== + // = action bar = + // ============== + + /** + * 获取 ActionBar 高度 + * @return ActionBar 高度 + */ + public static int getActionBarHeight() { + return getActionBarHeight(DevUtils.getContext()); + } + + /** + * 获取 ActionBar 高度 + * @param context {@link Context} + * @return ActionBar 高度 + */ + public static int getActionBarHeight(final Context context) { + TypedValue tv = new TypedValue(); + try { + if (ResourceUtils.getTheme(context).resolveAttribute( + android.R.attr.actionBarSize, tv, true)) { + return TypedValue.complexToDimensionPixelSize( + tv.data, Resources.getSystem().getDisplayMetrics() + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActionBarHeight"); + } + return 0; + } + + // ==================== + // = notification bar = + // ==================== + + /** + * 设置 Notification Bar 是否显示 + * @param isVisible 是否显示 Notification Bar + * @return {@code true} success, {@code false} fail + */ + public static boolean setNotificationBarVisibility(final boolean isVisible) { + String methodName; + if (isVisible) { + methodName = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) ? "expand" : "expandNotificationsPanel"; + } else { + methodName = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) ? "collapse" : "collapsePanels"; + } + try { + @SuppressLint("WrongConstant") + Object service = AppUtils.getSystemService("statusbar"); + @SuppressLint("PrivateApi") + Class statusBarManager = Class.forName("android.app.StatusBarManager"); + Method expand = statusBarManager.getMethod(methodName); + expand.invoke(service); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setNotificationBarVisibility"); + } + return false; + } + + // ================== + // = navigation bar = + // ================== + + /** + * 获取 Navigation Bar 高度 + * @return Navigation Bar 高度 + */ + public static int getNavBarHeight() { + try { + Resources resources = Resources.getSystem(); + int id = resources.getIdentifier( + "navigation_bar_height", "dimen", "android" + ); + if (id != 0) { + return resources.getDimensionPixelSize(id); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNavBarHeight"); + } + return 0; + } + + /** + * 设置 Navigation Bar 是否可见 + * @param activity {@link Activity} + * @param isVisible 是否显示 Navigation Bar + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarVisibility( + final Activity activity, + final boolean isVisible + ) { + return setNavBarVisibility(WindowUtils.getWindow(activity), isVisible); + } + + /** + * 设置 Navigation Bar 是否可见 + * @param window {@link Window} + * @param isVisible 是否显示 Navigation Bar + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarVisibility( + final Window window, + final boolean isVisible + ) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + final ViewGroup decorView = (ViewGroup) window.getDecorView(); + for (int i = 0, len = decorView.getChildCount(); i < len; i++) { + final View child = decorView.getChildAt(i); + final int id = child.getId(); + if (id != View.NO_ID) { + String resourceEntryName = Resources.getSystem().getResourceEntryName(id); + if ("navigationBarBackground".equals(resourceEntryName)) { + child.setVisibility(isVisible ? View.VISIBLE : View.INVISIBLE); + } + } + } + final int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + if (isVisible) { + decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() & ~uiOptions); + } else { + decorView.setSystemUiVisibility(decorView.getSystemUiVisibility() | uiOptions); + } + return true; + } + return false; + } + + /** + * 判断 Navigation Bar 是否可见 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNavBarVisible(final Activity activity) { + return isNavBarVisible(WindowUtils.getWindow(activity)); + } + + /** + * 判断 Navigation Bar 是否可见 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNavBarVisible(final Window window) { + if (window != null) { + boolean isVisible = false; + ViewGroup decorView = (ViewGroup) window.getDecorView(); + for (int i = 0, len = decorView.getChildCount(); i < len; i++) { + final View child = decorView.getChildAt(i); + final int id = child.getId(); + if (id != View.NO_ID) { + String resourceEntryName = Resources.getSystem().getResourceEntryName(id); + if ("navigationBarBackground".equals(resourceEntryName) + && child.getVisibility() == View.VISIBLE) { + isVisible = true; + break; + } + } + } + if (isVisible) { + int visibility = decorView.getSystemUiVisibility(); + isVisible = (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0; + } + return isVisible; + } + return false; + } + + /** + * 判断是否支持 Navigation Bar + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportNavBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + WindowManager windowManager = AppUtils.getWindowManager(); + if (windowManager == null) return false; + Display display = windowManager.getDefaultDisplay(); + Point size = new Point(); + Point realSize = new Point(); + display.getSize(size); + display.getRealSize(realSize); + return realSize.y != size.y || realSize.x != size.x; + } + boolean menu = ViewConfiguration.get(DevUtils.getContext()).hasPermanentMenuKey(); + boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); + return !menu && !back; + } + + /** + * 设置 Navigation Bar 颜色 + * @param activity {@link Activity} + * @param color Navigation Bar 颜色 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarColor( + final Activity activity, + final int color + ) { + return setNavBarColor(WindowUtils.getWindow(activity), color); + } + + /** + * 设置 Navigation Bar 颜色 + * @param window {@link Window} + * @param color Navigation Bar 颜色 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarColor( + final Window window, + final int color + ) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setNavigationBarColor(color); + return true; + } + return false; + } + + /** + * 获取 Navigation Bar 颜色 + * @param activity {@link Activity} + * @return Navigation Bar 颜色 + */ + public static int getNavBarColor(final Activity activity) { + return getNavBarColor(WindowUtils.getWindow(activity)); + } + + /** + * 获取 Navigation Bar 颜色 + * @param window {@link Window} + * @return Navigation Bar 颜色 + */ + public static int getNavBarColor(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return window.getNavigationBarColor(); + } + return -1; + } + + /** + * 设置 Navigation Bar 是否高亮模式 + * @param activity {@link Activity} + * @param isLightMode 是否高亮模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarLightMode( + final Activity activity, + final boolean isLightMode + ) { + return setNavBarLightMode(WindowUtils.getWindow(activity), isLightMode); + } + + /** + * 设置 Navigation Bar 是否高亮模式 + * @param window {@link Window} + * @param isLightMode 是否高亮模式 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNavBarLightMode( + final Window window, + final boolean isLightMode + ) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + View decorView = window.getDecorView(); + int vis = decorView.getSystemUiVisibility(); + if (isLightMode) { + vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } else { + vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + } + decorView.setSystemUiVisibility(vis); + return true; + } + return false; + } + + /** + * 获取 Navigation Bar 是否高亮模式 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNavBarLightMode(final Activity activity) { + return isNavBarLightMode(WindowUtils.getWindow(activity)); + } + + /** + * 获取 Navigation Bar 是否高亮模式 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNavBarLightMode(final Window window) { + if (window != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + View decorView = window.getDecorView(); + int vis = decorView.getSystemUiVisibility(); + return (vis & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/BrightnessUtils.java b/lib/DevApp/src/main/java/dev/utils/app/BrightnessUtils.java new file mode 100644 index 0000000000..09f18a0a4a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/BrightnessUtils.java @@ -0,0 +1,148 @@ +package dev.utils.app; + +import android.content.ContentResolver; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.view.Window; + +import androidx.annotation.IntRange; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 亮度相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public final class BrightnessUtils { + + private BrightnessUtils() { + } + + // 日志 TAG + private static final String TAG = BrightnessUtils.class.getSimpleName(); + + /** + * 判断是否开启自动调节亮度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAutoBrightnessEnabled() { + try { + int mode = Settings.System.getInt( + ResourceUtils.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE + ); + return mode == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAutoBrightnessEnabled"); + return false; + } + } + + /** + * 设置是否开启自动调节亮度 + * @param enabled {@code true} 打开, {@code false} 关闭 + * @return {@code true} success, {@code false} fail + */ + public static boolean setAutoBrightnessEnabled(final boolean enabled) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + !Settings.System.canWrite(DevUtils.getContext())) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); + intent.setData(Uri.parse("package:" + AppUtils.getPackageName())); + AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAutoBrightnessEnabled"); + } + return false; + } + try { + return Settings.System.putInt( + ResourceUtils.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + enabled ? Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC : + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAutoBrightnessEnabled"); + } + return false; + } + + /** + * 获取屏幕亮度 0-255 + * @return 屏幕亮度 + */ + public static int getBrightness() { + try { + return Settings.System.getInt( + ResourceUtils.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBrightness"); + return 0; + } + } + + /** + * 设置屏幕亮度 + * @param brightness 亮度值 + * @return {@code true} success, {@code false} fail + */ + public static boolean setBrightness(@IntRange(from = 0, to = 255) final int brightness) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + !Settings.System.canWrite(DevUtils.getContext())) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); + intent.setData(Uri.parse("package:" + AppUtils.getPackageName())); + AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBrightness"); + } + return false; + } + try { + ContentResolver resolver = ResourceUtils.getContentResolver(); + boolean result = Settings.System.putInt( + resolver, Settings.System.SCREEN_BRIGHTNESS, brightness + ); + resolver.notifyChange( + Settings.System.getUriFor("screen_brightness"), + null + ); + return result; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBrightness"); + } + return false; + } + + /** + * 设置窗口亮度 + * @param window {@link Window} + * @param brightness 亮度值 + * @return {@code true} success, {@code false} fail + */ + public static boolean setWindowBrightness( + final Window window, + @IntRange(from = 0, to = 255) final int brightness + ) { + return WindowUtils.get().setWindowBrightness(window, brightness); + } + + /** + * 获取窗口亮度 + * @param window {@link Window} + * @return 屏幕亮度 0-255 + */ + public static int getWindowBrightness(final Window window) { + return WindowUtils.get().getWindowBrightness(window); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/CPUUtils.java b/lib/DevApp/src/main/java/dev/utils/app/CPUUtils.java new file mode 100644 index 0000000000..e52e8a85e4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/CPUUtils.java @@ -0,0 +1,268 @@ +package dev.utils.app; + +import android.text.format.Formatter; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.util.regex.Pattern; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: 获取 CPU 信息工具类 + * @author Ttt + */ +public final class CPUUtils { + + private CPUUtils() { + } + + // 日志 TAG + private static final String TAG = CPUUtils.class.getSimpleName(); + + /** + * 获取处理器的 Java 虚拟机的数量 + * @return 处理器的 Java 虚拟机的数量 + */ + public static int getProcessorsCount() { + return Runtime.getRuntime().availableProcessors(); + } + + /** + * 获取手机 CPU 序列号 + * @return CPU 序列号 (16 位 ) 读取失败为 "0000000000000000" + */ + public static String getSysCPUSerialNum() { + String str, cpuSerialNum = "0000000000000000"; + try { + // 读取 CPU 信息 + Process pp = Runtime.getRuntime().exec("cat/proc/cpuinfo"); + InputStreamReader isr = new InputStreamReader(pp.getInputStream()); + LineNumberReader input = new LineNumberReader(isr); + // 查找 CPU 序列号 + for (int i = 1; i < 100; i++) { + str = input.readLine(); + if (str != null) { + // 查找到序列号所在行 + if (str.contains("Serial")) { + // 提取序列号 + cpuSerialNum = str.substring(str.indexOf(':') + 1).trim(); + break; + } + } else { + break; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSysCPUSerialNum"); + } + return cpuSerialNum; + } + + /** + * 获取 CPU 信息 + * @return CPU 信息 + */ + public static String getCpuInfo() { + try { + FileReader fr = new FileReader("/proc/cpuinfo"); + BufferedReader br = new BufferedReader(fr); + return br.readLine(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCpuInfo"); + } + return ""; + } + + /** + * 获取 CPU 型号 + * @return CPU 型号 + */ + public static String getCpuModel() { + try { + FileReader fr = new FileReader("/proc/cpuinfo"); + BufferedReader br = new BufferedReader(fr); + String text = br.readLine(); + return text.split(":\\s+", 2)[1]; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCpuModel"); + } + return ""; + } + + /** + * 获取 CPU 最大频率 ( 单位 KHZ ) + * @return CPU 最大频率 ( 单位 KHZ ) + */ + public static String getMaxCpuFreq() { + ProcessBuilder cmd; + InputStream is = null; + try { + StringBuilder builder = new StringBuilder(); + String[] args = { + "/system/bin/cat", + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" + }; + cmd = new ProcessBuilder(args); + Process process = cmd.start(); + is = process.getInputStream(); + byte[] re = new byte[24]; + while (is.read(re) != -1) { + builder.append(new String(re)); + } + return Formatter.formatFileSize( + DevUtils.getContext(), + Long.parseLong(builder.toString().trim()) * 1024 + ) + " Hz"; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMaxCpuFreq"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return "unknown"; + } + + /** + * 获取 CPU 最小频率 ( 单位 KHZ ) + * @return CPU 最小频率 ( 单位 KHZ ) + */ + public static String getMinCpuFreq() { + ProcessBuilder cmd; + InputStream is = null; + try { + StringBuilder builder = new StringBuilder(); + String[] args = { + "/system/bin/cat", + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq" + }; + cmd = new ProcessBuilder(args); + Process process = cmd.start(); + is = process.getInputStream(); + byte[] re = new byte[24]; + while (is.read(re) != -1) { + builder.append(new String(re)); + } + return Formatter.formatFileSize( + DevUtils.getContext(), + Long.parseLong(builder.toString().trim()) * 1024 + ) + " Hz"; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMinCpuFreq"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return "unknown"; + } + + /** + * 获取 CPU 当前频率 ( 单位 KHZ ) + * @return CPU 当前频率 ( 单位 KHZ ) + */ + public static String getCurCpuFreq() { + try { + FileReader fr = new FileReader( + "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" + ); + BufferedReader br = new BufferedReader(fr); + String text = br.readLine(); + return Formatter.formatFileSize( + DevUtils.getContext(), + Long.parseLong(text.trim()) * 1024 + ) + " Hz"; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCurCpuFreq"); + } + return "unknown"; + } + + /** + * 获取 CPU 核心数 + * @return CPU 核心数 + */ + public static int getCoresNumbers() { + // Private Class to display only CPU devices in the directory listing + class CpuFilter + implements FileFilter { + @Override + public boolean accept(File pathname) { + // Check if filename is "cpu", followed by a single digit number + return Pattern.matches("cpu[0-9]+", pathname.getName()); + } + } + // CPU 核心数 + int CPU_CORES = 0; + try { + // Get directory containing CPU info + File dir = new File("/sys/devices/system/cpu/"); + // Filter to only list the devices we care about + File[] files = dir.listFiles(new CpuFilter()); + // Return the number of cores ( virtual CPU devices ) + CPU_CORES = files.length; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCoresNumbers"); + } + if (CPU_CORES < 1) { + CPU_CORES = Runtime.getRuntime().availableProcessors(); + } + if (CPU_CORES < 1) { + CPU_CORES = 1; + } + return CPU_CORES; + } + + /** + * 获取 CPU 名字 + * @return CPU 名字 + */ + public static String getCpuName() { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("/proc/cpuinfo"), 8192); + String line = br.readLine(); + String[] array = line.split(":\\s+", 2); + if (array.length > 1) { + return array[1]; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCpuName"); + } finally { + CloseUtils.closeIOQuietly(br); + } + return null; + } + + /** + * 获取 CMD 指令回调数据 + * @param strings 指令集 + * @return 执行结果 + */ + public static String getCMDOutputString(final String[] strings) { + InputStream is = null; + try { + ProcessBuilder cmd = new ProcessBuilder(strings); + Process process = cmd.start(); + is = process.getInputStream(); + StringBuilder builder = new StringBuilder(); + byte[] re = new byte[64]; + int len; + while ((len = is.read(re)) != -1) { + builder.append(new String(re, 0, len)); + } + CloseUtils.closeIOQuietly(is); + process.destroy(); + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCMDOutputString"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/CapturePictureUtils.java b/lib/DevApp/src/main/java/dev/utils/app/CapturePictureUtils.java new file mode 100644 index 0000000000..7abb6bb4a8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/CapturePictureUtils.java @@ -0,0 +1,1519 @@ +package dev.utils.app; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Picture; +import android.graphics.Rect; +import android.os.Build; +import android.view.View; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.widget.GridView; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.ScrollView; + +import androidx.annotation.ColorInt; +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +import dev.utils.LogPrintUtils; +import dev.utils.app.image.BitmapUtils; +import dev.utils.common.ArrayUtils; +import dev.utils.common.NumberUtils; + +/** + * detail: 截图工具类 + * @author Ttt + *
+ *     截图
+ *     @see 
+ *     WebView 截长图解决方案
+ *     @see 
+ *     X5 WebView 使用 snapshotWholePage 方法清晰截图
+ *     @see 
+ *     针对 SurfaceView、Surface、Window 截图可使用
+ *     {@link android.view.PixelCopy} 进行实现
+ * 
+ */ +public final class CapturePictureUtils { + + private CapturePictureUtils() { + } + + // 日志 TAG + private static final String TAG = CapturePictureUtils.class.getSimpleName(); + + // Bitmap Config + private static Bitmap.Config BITMAP_CONFIG = Bitmap.Config.RGB_565; + // Canvas 背景色 + private static int BACKGROUND_COLOR = Color.TRANSPARENT; + // 画笔 + private static Paint PAINT = new Paint(); + + // ========== + // = 配置相关 = + // ========== + + /** + * 设置 Bitmap Config + * @param config {@link Bitmap.Config} + */ + public static void setBitmapConfig(final Bitmap.Config config) { + if (config == null) return; + BITMAP_CONFIG = config; + } + + /** + * 设置 Canvas 背景色 + * @param backgroundColor 背景色 + */ + public static void setBackgroundColor(@ColorInt final int backgroundColor) { + BACKGROUND_COLOR = backgroundColor; + } + + /** + * 设置画笔 + * @param paint {@link Paint} + */ + public static void setPaint(final Paint paint) { + if (paint == null) return; + PAINT = paint; + } + + // ======= + // = 截图 = + // ======= + + // ============ + // = Activity = + // ============ + + /** + * 获取当前屏幕截图, 包含状态栏 ( 顶部灰色 TitleBar 高度, 没有设置 android:theme 的 NoTitleBar 时会显示 ) + * @param activity {@link Activity} + * @return 当前屏幕截图, 包含状态栏 + */ + public static Bitmap snapshotWithStatusBar(final Activity activity) { + try { + View view = activity.getWindow().getDecorView(); + view.setDrawingCacheEnabled(true); + // 重新创建绘图缓存, 此时的背景色是黑色 + view.buildDrawingCache(); + // 获取绘图缓存, 注意这里得到的只是一个图像的引用 + Bitmap cacheBitmap = view.getDrawingCache(); + if (cacheBitmap == null) return null; + // 获取屏幕宽度 + int[] widthHeight = ScreenUtils.getScreenWidthHeight(); + + Rect frame = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); + // 创建新的图片 + Bitmap bitmap = Bitmap.createBitmap( + cacheBitmap, 0, 0, + widthHeight[0], widthHeight[1] + ); + // 释放绘图资源所使用的缓存 + view.destroyDrawingCache(); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotWithStatusBar"); + } + return null; + } + + /** + * 获取当前屏幕截图, 不包含状态栏 ( 如果 android:theme 全屏, 则截图无状态栏 ) + * @param activity {@link Activity} + * @return 当前屏幕截图, 不包含状态栏 + */ + public static Bitmap snapshotWithoutStatusBar(final Activity activity) { + try { + View view = activity.getWindow().getDecorView(); + view.setDrawingCacheEnabled(true); + // 重新创建绘图缓存, 此时的背景色是黑色 + view.buildDrawingCache(); + // 获取绘图缓存, 注意这里得到的只是一个图像的引用 + Bitmap cacheBitmap = view.getDrawingCache(); + if (cacheBitmap == null) return null; + // 获取屏幕宽度 + int[] widthHeight = ScreenUtils.getScreenWidthHeight(); + // 获取状态栏高度 + int statusBarHeight = BarUtils.getStatusBarHeight(); + + Rect frame = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); + // 创建新的图片 + Bitmap bitmap = Bitmap.createBitmap( + cacheBitmap, 0, statusBarHeight, widthHeight[0], + widthHeight[1] - statusBarHeight + ); + // 释放绘图资源所使用的缓存 + view.destroyDrawingCache(); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotWithoutStatusBar"); + } + return null; + } + + // =========== + // = WebView = + // =========== + + /** + * 关闭 WebView 优化 + *
+     *     推荐在 setContentView 前调用
+     *     {@link CapturePictureUtils#snapshotByWebView}
+     * 
+ */ + public static void enableSlowWholeDocumentDraw() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + android.webkit.WebView.enableSlowWholeDocumentDraw(); + } + } + + /** + * 截图 WebView + * @param webView {@link WebView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByWebView(final WebView webView) { + return snapshotByWebView(webView, Integer.MAX_VALUE, BITMAP_CONFIG, 0F); + } + + /** + * 截图 WebView + * @param webView {@link WebView} + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap snapshotByWebView( + final WebView webView, + final int maxHeight + ) { + return snapshotByWebView(webView, maxHeight, BITMAP_CONFIG, 0F); + } + + /** + * 截图 WebView + * @param webView {@link WebView} + * @param scale 缩放比例 + * @return {@link Bitmap} + */ + public static Bitmap snapshotByWebView( + final WebView webView, + final float scale + ) { + return snapshotByWebView(webView, Integer.MAX_VALUE, BITMAP_CONFIG, scale); + } + + /** + * 截图 WebView + * @param webView {@link WebView} + * @param maxHeight 最大高度 + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByWebView( + final WebView webView, + final int maxHeight, + final Bitmap.Config config + ) { + return snapshotByWebView(webView, maxHeight, config, 0F); + } + + /** + * 截图 WebView + *
+     *     TODO
+     *      在 Android 5.0 及以上版本, Android 对 WebView 进行了优化, 为了减少内存使用和提高性能
+     *      使用 WebView 加载网页时只绘制显示部分, 如果我们不做处理, 就会出现只截到屏幕内显示的 WebView 内容, 其它部分是空白的情况
+     *      通过调用 WebView.enableSlowWholeDocumentDraw() 方法可以关闭这种优化, 但要注意的是, 该方法需要在 WebView 实例被创建前就要调用,
+     *      否则没有效果, 所以我们在 WebView 实例被创建前加入代码
+     *     {@link CapturePictureUtils#enableSlowWholeDocumentDraw}
+     * 
+ * @param webView {@link WebView} + * @param maxHeight 最大高度 + * @param config {@link Bitmap.Config} + * @param scale 缩放比例 + * @return {@link Bitmap} + */ + public static Bitmap snapshotByWebView( + final WebView webView, + final int maxHeight, + final Bitmap.Config config, + final float scale + ) { + if (webView != null && config != null) { + // Android 5.0 以上 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + float newScale = scale; + if (newScale <= 0) { + // 该方法已抛弃, 可通过 setWebViewClient + // onScaleChanged(WebView view, float oldScale, float newScale) + // 存储并传入 newScale + newScale = webView.getScale(); + } + int width = webView.getWidth(); + int height = (int) (webView.getContentHeight() * newScale + 0.5); + // 重新设置高度 + height = Math.min(height, maxHeight); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + webView.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByWebView SDK_INT >= 21(5.0)"); + } + } else { + try { + Picture picture = webView.capturePicture(); + int width = picture.getWidth(); + int height = picture.getHeight(); + if (width > 0 && height > 0) { + // 重新设置高度 + height = Math.min(height, maxHeight); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + picture.draw(canvas); + return bitmap; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByWebView SDK_INT < 21(5.0)"); + } + } + } + return null; + } + + // ======== + // = View = + // ======== + + /** + * 通过 View 绘制为 Bitmap + * @param view {@link View} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByView(final View view) { + return snapshotByView(view, BITMAP_CONFIG); + } + + /** + * 通过 View 绘制为 Bitmap + *
+     *     如果 View 是动态创建 ( 且没有添加到任何 ViewGroup 中 ) 想进行截图
+     *     需要设置完控件值, 如: text、background 等
+     *     再调用以下两个方法其中一个, 进行测量 View
+     *     {@link WidgetUtils#measureView(View, int)}
+     *     {@link WidgetUtils#measureView(View, int, int)}
+     *     接着调用截图方法
+     *     {@link #snapshotByView(View)}
+     *     {@link #snapshotByViewCache(View)}
+     * 
+ * @param view {@link View} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByView( + final View view, + final Bitmap.Config config + ) { + if (view == null || config == null) return null; + try { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + view.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByView"); + } + return null; + } + + /** + * 通过 View Cache 绘制为 Bitmap + *
+     *     如果 View 是动态创建 ( 且没有添加到任何 ViewGroup 中 ) 想进行截图
+     *     需要设置完控件值, 如: text、background 等
+     *     再调用以下两个方法其中一个, 进行测量 View
+     *     {@link WidgetUtils#measureView(View, int)}
+     *     {@link WidgetUtils#measureView(View, int, int)}
+     *     接着调用截图方法
+     *     {@link #snapshotByView(View)}
+     *     {@link #snapshotByViewCache(View)}
+     * 
+ * @param view {@link View} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByViewCache(final View view) { + if (view == null) return null; + try { + // 清除视图焦点 + view.clearFocus(); + // 将视图设为不可点击 + view.setPressed(false); + + // 获取视图是否可以保存画图缓存 + boolean willNotCache = view.willNotCacheDrawing(); + view.setWillNotCacheDrawing(false); + + // 获取绘制缓存位图的背景颜色 + int color = view.getDrawingCacheBackgroundColor(); + // 设置绘图背景颜色 + view.setDrawingCacheBackgroundColor(0); + if (color != 0) { // 获取的背景不是黑色的则释放以前的绘图缓存 + view.destroyDrawingCache(); // 释放绘图资源所使用的缓存 + } + + // 重新创建绘图缓存, 此时的背景色是黑色 + view.buildDrawingCache(); + // 获取绘图缓存, 注意这里得到的只是一个图像的引用 + Bitmap cacheBitmap = view.getDrawingCache(); + if (cacheBitmap == null) return null; + + Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); + // 释放位图内存 + view.destroyDrawingCache(); + // 回滚以前的缓存设置、缓存颜色设置 + view.setWillNotCacheDrawing(willNotCache); + view.setDrawingCacheBackgroundColor(color); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByViewCache"); + } + return null; + } + + // ================ + // = LinearLayout = + // ================ + + /** + * 通过 LinearLayout 绘制为 Bitmap + * @param linearLayout {@link LinearLayout} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByLinearLayout(final LinearLayout linearLayout) { + return snapshotByLinearLayout(linearLayout, BITMAP_CONFIG); + } + + /** + * 通过 LinearLayout 绘制为 Bitmap + *
+     *     LinearLayout 容器中不能有诸如 ListView、GridView、WebView 这样的高度可变的控件
+     * 
+ * @param linearLayout {@link LinearLayout} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByLinearLayout( + final LinearLayout linearLayout, + final Bitmap.Config config + ) { + return snapshotByView(linearLayout, config); + } + + // =============== + // = FrameLayout = + // =============== + + /** + * 通过 FrameLayout 绘制为 Bitmap + * @param frameLayout {@link FrameLayout} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByFrameLayout(final FrameLayout frameLayout) { + return snapshotByFrameLayout(frameLayout, BITMAP_CONFIG); + } + + /** + * 通过 FrameLayout 绘制为 Bitmap + *
+     *     FrameLayout 容器中不能有诸如 ListView、GridView、WebView 这样的高度可变的控件
+     * 
+ * @param frameLayout {@link FrameLayout} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByFrameLayout( + final FrameLayout frameLayout, + final Bitmap.Config config + ) { + return snapshotByView(frameLayout, config); + } + + // ================== + // = RelativeLayout = + // ================== + + /** + * 通过 RelativeLayout 绘制为 Bitmap + * @param relativeLayout {@link RelativeLayout} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRelativeLayout(final RelativeLayout relativeLayout) { + return snapshotByRelativeLayout(relativeLayout, BITMAP_CONFIG); + } + + /** + * 通过 RelativeLayout 绘制为 Bitmap + *
+     *     RelativeLayout 容器中不能有诸如 ListView、GridView、WebView 这样的高度可变的控件
+     * 
+ * @param relativeLayout {@link RelativeLayout} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRelativeLayout( + final RelativeLayout relativeLayout, + final Bitmap.Config config + ) { + return snapshotByView(relativeLayout, config); + } + + // ============== + // = ScrollView = + // ============== + + /** + * 通过 ScrollView 绘制为 Bitmap + * @param scrollView {@link ScrollView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByScrollView(final ScrollView scrollView) { + return snapshotByScrollView(scrollView, BITMAP_CONFIG); + } + + /** + * 通过 ScrollView 绘制为 Bitmap + *
+     *     ScrollView 容器中不能有诸如 ListView、GridView、WebView 这样的高度可变的控件
+     * 
+ * @param scrollView {@link ScrollView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByScrollView( + final ScrollView scrollView, + final Bitmap.Config config + ) { + if (scrollView == null || config == null) return null; + try { + View view = scrollView.getChildAt(0); + int width = view.getWidth(); + int height = view.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + scrollView.layout(0, 0, scrollView.getMeasuredWidth(), + scrollView.getMeasuredHeight() + ); + scrollView.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByScrollView"); + } + return null; + } + + // ======================== + // = HorizontalScrollView = + // ======================== + + /** + * 通过 HorizontalScrollView 绘制为 Bitmap + * @param scrollView {@link HorizontalScrollView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByHorizontalScrollView(final HorizontalScrollView scrollView) { + return snapshotByHorizontalScrollView(scrollView, BITMAP_CONFIG); + } + + /** + * 通过 HorizontalScrollView 绘制为 Bitmap + * @param scrollView {@link HorizontalScrollView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByHorizontalScrollView( + final HorizontalScrollView scrollView, + final Bitmap.Config config + ) { + if (scrollView == null || config == null) return null; + try { + View view = scrollView.getChildAt(0); + int width = view.getWidth(); + int height = view.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + scrollView.layout(0, 0, scrollView.getMeasuredWidth(), + scrollView.getMeasuredHeight() + ); + scrollView.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByHorizontalScrollView"); + } + return null; + } + + // ==================== + // = NestedScrollView = + // ==================== + + /** + * 通过 NestedScrollView 绘制为 Bitmap + * @param scrollView {@link NestedScrollView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByNestedScrollView(final NestedScrollView scrollView) { + return snapshotByNestedScrollView(scrollView, BITMAP_CONFIG); + } + + /** + * 通过 NestedScrollView 绘制为 Bitmap + * @param scrollView {@link NestedScrollView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByNestedScrollView( + final NestedScrollView scrollView, + final Bitmap.Config config + ) { + if (scrollView == null || config == null) return null; + try { + View view = scrollView.getChildAt(0); + int width = view.getWidth(); + int height = view.getHeight(); + + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + scrollView.layout(0, 0, scrollView.getMeasuredWidth(), + scrollView.getMeasuredHeight() + ); + scrollView.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByNestedScrollView"); + } + return null; + } + + // ============ + // = ListView = + // ============ + + /** + * 通过 ListView 绘制为 Bitmap + * @param listView {@link ListView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByListView(final ListView listView) { + return snapshotByListView(listView, BITMAP_CONFIG); + } + + /** + * 通过 ListView 绘制为 Bitmap + * @param listView {@link ListView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByListView( + final ListView listView, + final Bitmap.Config config + ) { + if (listView == null || config == null) return null; + try { + // Adapter + ListAdapter listAdapter = listView.getAdapter(); + // Item 总条数 + int itemCount = listAdapter.getCount(); + // 没数据则直接跳过 + if (itemCount == 0) return null; + // 高度 + int height = 0; + // 获取子项间分隔符占用的高度 + int dividerHeight = listView.getDividerHeight(); + // View Bitmaps + Bitmap[] bitmaps = new Bitmap[itemCount]; + + // 循环绘制每个 Item 并保存 Bitmap + for (int i = 0; i < itemCount; i++) { + View childView = listAdapter.getView(i, null, listView); + WidgetUtils.measureView(childView, listView.getWidth()); + bitmaps[i] = canvasBitmap(childView, config); + height += childView.getMeasuredHeight(); + } + // 追加子项间分隔符占用的高度 + height += (dividerHeight * (itemCount - 1)); + int width = listView.getMeasuredWidth(); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加高度 + int appendHeight = 0; + for (Bitmap bmp : bitmaps) { + canvas.drawBitmap(bmp, 0, appendHeight, PAINT); + appendHeight += (bmp.getHeight() + dividerHeight); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByListView"); + } + return null; + } + + // ============ + // = GridView = + // ============ + + /** + * 通过 GridView 绘制为 Bitmap + * @param gridView {@link GridView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByGridView(final GridView gridView) { + return snapshotByGridView(gridView, BITMAP_CONFIG, false); + } + + /** + * 通过 GridView 绘制为 Bitmap + * @param gridView {@link GridView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByGridView( + final GridView gridView, + final Bitmap.Config config + ) { + return snapshotByGridView(gridView, config, false); + } + + /** + * 通过 GridView 绘制为 Bitmap + * @param gridView {@link GridView} + * @param config {@link Bitmap.Config} + * @param listViewEffect 是否保存 ListView 效果 ( 每个 Item 铺满 ) + * @return {@link Bitmap} + */ + public static Bitmap snapshotByGridView( + final GridView gridView, + final Bitmap.Config config, + final boolean listViewEffect + ) { + if (gridView == null || config == null) return null; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return null; + try { + // Adapter + ListAdapter listAdapter = gridView.getAdapter(); + // Item 总条数 + int itemCount = listAdapter.getCount(); + // 没数据则直接跳过 + if (itemCount == 0) return null; + // 高度 + int height = 0; + // 获取一共多少列 + int numColumns = gridView.getNumColumns(); + // 每列之间的间隔 | + int horizontalSpacing = gridView.getHorizontalSpacing(); + // 每行之间的间隔 - + int verticalSpacing = gridView.getVerticalSpacing(); + // View Bitmaps + Bitmap[] bitmaps = new Bitmap[itemCount]; + + // 效果处理 ( ListView 效果 Item 铺满 ) + if (listViewEffect) { + // 循环绘制每个 Item 并保存 Bitmap + for (int i = 0; i < itemCount; i++) { + View childView = listAdapter.getView(i, null, gridView); + WidgetUtils.measureView(childView, gridView.getWidth()); + bitmaps[i] = canvasBitmap(childView, config); + height += childView.getMeasuredHeight(); + } + // 追加子项间分隔符占用的高度 + height += (verticalSpacing * (itemCount - 1)); + int width = gridView.getMeasuredWidth(); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加高度 + int appendHeight = 0; + for (Bitmap bmp : bitmaps) { + canvas.drawBitmap(bmp, 0, appendHeight, PAINT); + appendHeight += (bmp.getHeight() + verticalSpacing); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } else { + // 获取倍数 ( 行数 ) + int lineNumber = NumberUtils.multiple(itemCount, numColumns); + // 计算总共的宽度 (GridView 宽度 - 列分割间距 ) / numColumns + int childWidth = (gridView.getWidth() - (numColumns - 1) * horizontalSpacing) / numColumns; + + // 记录每行最大高度 + int[] rowHeightArrays = new int[lineNumber]; + // 临时高度 ( 保存行中最高的列高度 ) + int tempHeight; + // 循环每一行绘制每个 Item 并保存 Bitmap + for (int i = 0; i < lineNumber; i++) { + // 清空高度 + tempHeight = 0; + // 循环列数 + for (int j = 0; j < numColumns; j++) { + // 获取对应的索引 + int position = i * numColumns + j; + // 小于总数才处理 + if (position < itemCount) { + View childView = listAdapter.getView(position, null, gridView); + WidgetUtils.measureView(childView, childWidth); + bitmaps[position] = canvasBitmap(childView, config); + + int itemHeight = childView.getMeasuredHeight(); + // 保留最大高度 + tempHeight = Math.max(itemHeight, tempHeight); + } + + // 记录高度并累加 + if (j == numColumns - 1) { + height += tempHeight; + rowHeightArrays[i] = tempHeight; + } + } + } + // 追加子项间分隔符占用的高度 + height += (verticalSpacing * (lineNumber - 1)); + int width = gridView.getMeasuredWidth(); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加高度 + int appendHeight = 0; + // 循环每一行绘制每个 Item Bitmap + for (int i = 0; i < lineNumber; i++) { + // 获取每一行最长列的高度 + int itemHeight = rowHeightArrays[i]; + // 循环列数 + for (int j = 0; j < numColumns; j++) { + // 获取对应的索引 + int position = i * numColumns + j; + // 小于总数才处理 + if (position < itemCount) { + Bitmap bmp = bitmaps[position]; + // 计算边距 + int left = j * (horizontalSpacing + childWidth); + Matrix matrix = new Matrix(); + matrix.postTranslate(left, appendHeight); + // 绘制到 Bitmap + canvas.drawBitmap(bmp, matrix, PAINT); + // 释放资源 + BitmapUtils.recycle(bmp); + } + + // 记录高度并累加 + if (j == numColumns - 1) { + appendHeight += itemHeight + verticalSpacing; + } + } + } + return bitmap; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByGridView"); + } + return null; + } + + // ================ + // = RecyclerView = + // ================ + + /** + * 通过 RecyclerView 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRecyclerView(final RecyclerView recyclerView) { + return snapshotByRecyclerView(recyclerView, BITMAP_CONFIG, 0, 0); + } + + /** + * 通过 RecyclerView 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRecyclerView( + final RecyclerView recyclerView, + final Bitmap.Config config + ) { + return snapshotByRecyclerView(recyclerView, config, 0, 0); + } + + /** + * 通过 RecyclerView 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param spacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRecyclerView( + final RecyclerView recyclerView, + final int spacing + ) { + return snapshotByRecyclerView(recyclerView, BITMAP_CONFIG, spacing, spacing); + } + + /** + * 通过 RecyclerView 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @param spacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRecyclerView( + final RecyclerView recyclerView, + final Bitmap.Config config, + final int spacing + ) { + return snapshotByRecyclerView(recyclerView, config, spacing, spacing); + } + + /** + * 通过 RecyclerView 绘制为 Bitmap + *
+     *     不支持含 ItemDecoration 截图
+     *     如果数据太多推荐 copy 代码, 修改为保存每个 Item Bitmap 到本地, 并在绘制时获取绘制
+     * 
+ * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @param verticalSpacing 每行之间的间隔 - + * @param horizontalSpacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + public static Bitmap snapshotByRecyclerView( + final RecyclerView recyclerView, + final Bitmap.Config config, + final int verticalSpacing, + final int horizontalSpacing + ) { + if (recyclerView == null || config == null) return null; + try { + // 获取适配器 + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + // 获取布局管理器 + RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); + if (layoutManager != null && adapter != null) { + // 判断布局类型 + if (layoutManager instanceof GridLayoutManager) { + return snapshotByRecyclerView_GridLayoutManager( + recyclerView, config, verticalSpacing, horizontalSpacing + ); + } else if (layoutManager instanceof LinearLayoutManager) { + return snapshotByRecyclerView_LinearLayoutManager( + recyclerView, config, verticalSpacing, horizontalSpacing + ); + } else if (layoutManager instanceof StaggeredGridLayoutManager) { + return snapshotByRecyclerView_StaggeredGridLayoutManager( + recyclerView, config, verticalSpacing, horizontalSpacing + ); + } + throw new Exception( + String.format( + "Not Supported %s LayoutManager", + layoutManager.getClass().getSimpleName() + ) + ); + } else { + throw new Exception("Adapter or LayoutManager is Null"); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView"); + } + return null; + } + + // ================= + // = LayoutManager = + // ================= + + /** + * 通过 RecyclerView GridLayoutManager 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @param verticalSpacing 每行之间的间隔 - + * @param horizontalSpacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + private static Bitmap snapshotByRecyclerView_GridLayoutManager( + final RecyclerView recyclerView, + final Bitmap.Config config, + final int verticalSpacing, + final int horizontalSpacing + ) { + // 计算思路 + // = 竖屏 = + // 每个 Item 宽度最大值固定为 (RecyclerView 宽度 - ( 列数 - 1) * 每列边距 ) / 列数 + // 循环保存每行最大高度, 并累加每行之间的间隔, 用于 Bitmap 高度, 宽度用 RecyclerView 宽度 + // = 横屏 = + // 循环保存每一行宽度以及每一行 ( 横着一行 ) 最大高度, 并且累加每行、每列之间的间隔 + // 用于 Bitmap 高度, 宽度用 ( 每一行宽度累加值 ) 最大值 + try { + // 获取适配器 + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + // Item 总条数 + int itemCount = adapter.getItemCount(); + // 没数据则直接跳过 + if (itemCount == 0) return null; + // 宽高 + int width = 0, height = 0; + // View Bitmaps + Bitmap[] bitmaps = new Bitmap[itemCount]; + // 获取布局管理器 ( 判断横竖布局 ) + GridLayoutManager gridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager(); + boolean vertical = RecyclerViewUtils.canScrollVertically(gridLayoutManager); + // 获取一共多少列 + int spanCount = gridLayoutManager.getSpanCount(); + // 获取倍数 ( 行数 ) + int lineNumber = NumberUtils.multiple(itemCount, spanCount); + if (vertical) { + + // ========== + // = 竖向滑动 = + // ========== + + // 计算总共的宽度 (GridView 宽度 - 列分割间距 ) / spanCount + int childWidth = (recyclerView.getWidth() - (spanCount - 1) * horizontalSpacing) / spanCount; + // 记录每行最大高度 + int[] rowHeightArrays = new int[lineNumber]; + // 临时高度 ( 保存行中最高的列高度 ) + int tempHeight; + for (int i = 0; i < lineNumber; i++) { + // 清空高度 + tempHeight = 0; + // 循环列数 + for (int j = 0; j < spanCount; j++) { + // 获取对应的索引 + int position = i * spanCount + j; + // 小于总数才处理 + if (position < itemCount) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(position) + ); + adapter.onBindViewHolder(holder, position); + View childView = holder.itemView; + WidgetUtils.measureView(childView, childWidth); + bitmaps[position] = canvasBitmap(childView, config); + int itemHeight = childView.getMeasuredHeight(); + // 保留最大高度 + tempHeight = Math.max(itemHeight, tempHeight); + } + + // 记录高度并累加 + if (j == spanCount - 1) { + height += tempHeight; + rowHeightArrays[i] = tempHeight; + } + } + } + + // 追加子项间分隔符占用的高度 + height += (verticalSpacing * (lineNumber - 1)); + width = recyclerView.getMeasuredWidth(); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加高度 + int appendHeight = 0; + for (int i = 0; i < lineNumber; i++) { + // 获取每行中最高的列高度 + int rowHeight = rowHeightArrays[i]; + // 循环列数 + for (int j = 0; j < spanCount; j++) { + // 获取对应的索引 + int position = i * spanCount + j; + // 小于总数才处理 + if (position < itemCount) { + Bitmap bmp = bitmaps[position]; + // 计算边距 + int left = j * (horizontalSpacing + childWidth); + Matrix matrix = new Matrix(); + matrix.postTranslate(left, appendHeight); + // 绘制到 Bitmap + canvas.drawBitmap(bmp, matrix, PAINT); + // 释放资源 + BitmapUtils.recycle(bmp); + } + + // 记录高度并累加 + if (j == spanCount - 1) { + appendHeight += (rowHeight + verticalSpacing); + } + } + } + return bitmap; + } else { + + // ========== + // = 横向滑动 = + // ========== + + // 获取行数 + lineNumber = Math.min(spanCount, itemCount); + // 记录每一行宽度 + int[] rowWidthArrays = new int[lineNumber]; + // 记录每一行高度 + int[] rowHeightArrays = new int[lineNumber]; + // 获取一共多少列 + int numColumns = NumberUtils.multiple(itemCount, lineNumber); + // 临时高度 ( 保存行中最高的列高度 ) + int tempHeight; + for (int i = 0; i < lineNumber; i++) { + // 清空高度 + tempHeight = 0; + // 循环列数 + for (int j = 0; j < numColumns; j++) { + // 获取对应的索引 + int position = j * lineNumber + i; + // 小于总数才处理 + if (position < itemCount) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(position) + ); + adapter.onBindViewHolder(holder, position); + View childView = holder.itemView; + WidgetUtils.measureView(childView, 0); + bitmaps[position] = canvasBitmap(childView, config); + rowWidthArrays[i] += childView.getMeasuredWidth(); + int itemHeight = childView.getMeasuredHeight(); + // 保留最大高度 + tempHeight = Math.max(itemHeight, tempHeight); + } + + // 最后记录处理 + if (j == numColumns - 1) { + height += tempHeight; + width = Math.max(width, rowWidthArrays[i]); + rowHeightArrays[i] = tempHeight; + } + } + } + + // 追加子项间分隔符占用的高、宽 + height += (verticalSpacing * (lineNumber - 1)); + width += (horizontalSpacing * (numColumns - 1)); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加宽、高 + int appendWidth = 0, appendHeight = 0; + for (int i = 0; i < lineNumber; i++) { + // 获取每行中最高的列高度 + int rowHeight = rowHeightArrays[i]; + // 循环列数 + for (int j = 0; j < numColumns; j++) { + // 获取对应的索引 + int position = j * lineNumber + i; + // 小于总数才处理 + if (position < itemCount) { + Bitmap bmp = bitmaps[position]; + // 计算边距 + int left = appendWidth + (j * horizontalSpacing); + Matrix matrix = new Matrix(); + matrix.postTranslate(left, appendHeight); + // 绘制到 Bitmap + canvas.drawBitmap(bmp, matrix, PAINT); + // 累加 Bitmap 宽度 + appendWidth += bmp.getWidth(); + // 释放资源 + BitmapUtils.recycle(bmp); + } + + // 记录高度并累加 + if (j == numColumns - 1) { + appendWidth = 0; + appendHeight += (rowHeight + verticalSpacing); + } + } + } + return bitmap; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_GridLayoutManager"); + } + return null; + } + + /** + * 通过 RecyclerView LinearLayoutManager 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @param verticalSpacing 每行之间的间隔 - + * @param horizontalSpacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + private static Bitmap snapshotByRecyclerView_LinearLayoutManager( + final RecyclerView recyclerView, + final Bitmap.Config config, + final int verticalSpacing, + final int horizontalSpacing + ) { + // 计算思路 + // = 竖屏 = + // 循环保存每一个 Item View 高度, 并累加每行之间的间隔, + // 用于 Bitmap 高度, 宽度用 RecyclerView 宽度 + // = 横屏 = + // 循环保存每一个 Item View 宽度, 并累加每列之间的间隔, 且记录最高的列 + // 用于 Bitmap 高度, 宽度用累加出来的值 + try { + // 获取适配器 + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + // Item 总条数 + int itemCount = adapter.getItemCount(); + // 没数据则直接跳过 + if (itemCount == 0) return null; + // 宽高 + int width = 0, height = 0; + // View Bitmaps + Bitmap[] bitmaps = new Bitmap[itemCount]; + // 获取布局管理器 ( 判断横竖布局 ) + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + boolean vertical = RecyclerViewUtils.canScrollVertically(linearLayoutManager); + if (vertical) { + + // ========== + // = 竖向滑动 = + // ========== + + for (int i = 0; i < itemCount; i++) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(i) + ); + adapter.onBindViewHolder(holder, i); + View childView = holder.itemView; + WidgetUtils.measureView(childView, recyclerView.getWidth()); + bitmaps[i] = canvasBitmap(childView, config); + height += childView.getMeasuredHeight(); + } + + // 追加子项间分隔符占用的高度 + height += (verticalSpacing * (itemCount - 1)); + width = recyclerView.getMeasuredWidth(); + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加高度 + int appendHeight = 0; + for (int i = 0; i < itemCount; i++) { + Bitmap bmp = bitmaps[i]; + canvas.drawBitmap(bmp, 0, appendHeight, PAINT); + appendHeight += (bmp.getHeight() + verticalSpacing); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } else { + + // ========== + // = 横向滑动 = + // ========== + + // 临时高度 ( 保存行中最高的列高度 ) + int tempHeight = 0; + for (int i = 0; i < itemCount; i++) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(i) + ); + adapter.onBindViewHolder(holder, i); + View childView = holder.itemView; + WidgetUtils.measureView(childView, 0); + bitmaps[i] = canvasBitmap(childView, config); + width += childView.getMeasuredWidth(); + int itemHeight = childView.getMeasuredHeight(); + // 保留最大高度 + tempHeight = Math.max(itemHeight, tempHeight); + } + + // 追加子项间分隔符占用的宽度 + width += (horizontalSpacing * (itemCount - 1)); + height = tempHeight; + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 累加宽度 + int appendWidth = 0; + for (int i = 0; i < itemCount; i++) { + Bitmap bmp = bitmaps[i]; + canvas.drawBitmap(bmp, appendWidth, 0, PAINT); + appendWidth += (bmp.getWidth() + horizontalSpacing); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_LinearLayoutManager"); + } + return null; + } + + /** + * 通过 RecyclerView StaggeredGridLayoutManager 绘制为 Bitmap + * @param recyclerView {@link RecyclerView} + * @param config {@link Bitmap.Config} + * @param verticalSpacing 每行之间的间隔 - + * @param horizontalSpacing 每列之间的间隔 | + * @return {@link Bitmap} + */ + private static Bitmap snapshotByRecyclerView_StaggeredGridLayoutManager( + final RecyclerView recyclerView, + final Bitmap.Config config, + final int verticalSpacing, + final int horizontalSpacing + ) { + // 计算思路 + // = 竖屏 = + // 每个 Item 宽度最大值固定为 (RecyclerView 宽度 - ( 列数 - 1) * 每列边距 ) / 列数 + // 循环保存每一个 Item View 高度, 并创建数组记录每一列待绘制高度, 实现瀑布流高度补差 + // 并通过该数组 ( 每列待绘制高度数组 ) 获取最大值, 用做 Bitmap 高度, 绘制则还是按以上规则高度补差累加 + // = 横屏 = + // 循环保存每一个 Item View 宽度、高度, 并创建数组记录每一列待绘制宽度, 实现瀑布流高度补差 + // 并通过该数组 ( 每列待绘制宽度数组 ) 获取最大值, 用做 Bitmap 高度, 绘制则还是按以上规则宽度补差累加 + try { + // 获取适配器 + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + // Item 总条数 + int itemCount = adapter.getItemCount(); + // 没数据则直接跳过 + if (itemCount == 0) return null; + // 宽高 + int width, height = 0; + // View Bitmaps + Bitmap[] bitmaps = new Bitmap[itemCount]; + // 获取布局管理器 ( 判断横竖布局 ) + StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) recyclerView.getLayoutManager(); + boolean vertical = RecyclerViewUtils.canScrollVertically(staggeredGridLayoutManager); + // 获取一共多少列 + int spanCount = staggeredGridLayoutManager.getSpanCount(); + // 获取倍数 ( 行数 ) + int lineNumber = NumberUtils.multiple(itemCount, spanCount); + if (vertical) { + + // ========== + // = 竖向滑动 = + // ========== + + // 计算总共的宽度 - (GridView 宽度 - 列分割间距 ) / spanCount + int childWidth = (recyclerView.getWidth() - (spanCount - 1) * horizontalSpacing) / spanCount; + // 记录每个 Item 高度 + int[] itemHeightArrays = new int[itemCount]; + for (int i = 0; i < itemCount; i++) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(i) + ); + adapter.onBindViewHolder(holder, i); + View childView = holder.itemView; + WidgetUtils.measureView(childView, childWidth); + bitmaps[i] = canvasBitmap(childView, config); + itemHeightArrays[i] = childView.getMeasuredHeight(); + } + + // 记录每列 Item 个数 + int[] columnsItemNumberArrays = new int[spanCount]; + // 记录每列总高度 + int[] columnsHeightArrays = new int[spanCount]; + // 循环高度, 计算绘制位置 + for (int i = 0; i < itemCount; i++) { + // 获取最小高度索引 + int minIndex = ArrayUtils.getMinimumIndex(columnsHeightArrays); + // 累加高度 + columnsHeightArrays[minIndex] += itemHeightArrays[i]; + // 累加数量 + columnsItemNumberArrays[minIndex] += 1; + } + + // 计算高度 ( 追加子项间分隔符占用的高度 ) + if (lineNumber >= 2) { + // 循环追加子项间分隔符占用的高度 + for (int i = 0; i < spanCount; i++) { + columnsHeightArrays[i] += (columnsItemNumberArrays[i] - 1) * verticalSpacing; + } + } + + // 获取列最大高度索引 + int columnsHeightMaxIndex = ArrayUtils.getMaximumIndex(columnsHeightArrays); + // 获取最大高度值 + int maxColumnsHeight = columnsHeightArrays[columnsHeightMaxIndex]; + // 使用最大值 + height = maxColumnsHeight; + width = recyclerView.getMeasuredWidth(); + + // 清空绘制时累加计算 + columnsHeightArrays = new int[spanCount]; + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 循环绘制 + for (int i = 0; i < itemCount; i++) { + // 获取最小高度索引 + int minIndex = ArrayUtils.getMinimumIndex(columnsHeightArrays); + // 计算边距 + int left = minIndex * (horizontalSpacing + childWidth); + Matrix matrix = new Matrix(); + matrix.postTranslate(left, columnsHeightArrays[minIndex]); + // 绘制到 Bitmap + Bitmap bmp = bitmaps[i]; + canvas.drawBitmap(bmp, matrix, PAINT); + // 累加高度 + columnsHeightArrays[minIndex] += (itemHeightArrays[i] + verticalSpacing); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } else { + + // ========== + // = 横向滑动 = + // ========== + + // 获取行数 + lineNumber = Math.min(spanCount, itemCount); + // 记录每个 Item 宽度 + int[] itemWidthArrays = new int[itemCount]; + // 记录每个 Item 高度 + int[] itemHeightArrays = new int[itemCount]; + for (int i = 0; i < itemCount; i++) { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(i) + ); + adapter.onBindViewHolder(holder, i); + View childView = holder.itemView; + WidgetUtils.measureView(childView, 0); + bitmaps[i] = canvasBitmap(childView, config); + itemWidthArrays[i] = childView.getMeasuredWidth(); + itemHeightArrays[i] = childView.getMeasuredHeight(); + } + + // 记录每行向上距离 + int[] columnsTopArrays = new int[lineNumber]; + // 记录每行 Item 个数 + int[] columnsItemNumberArrays = new int[lineNumber]; + // 记录每行总宽度 + int[] columnsWidthArrays = new int[lineNumber]; + // 记录每行最大高度 + int[] columnsHeightArrays = new int[lineNumber]; + // 循环宽度, 计算绘制位置 + for (int i = 0; i < itemCount; i++) { + // 获取最小宽度索引 + int minIndex = ArrayUtils.getMinimumIndex(columnsWidthArrays); + // 累加宽度 + columnsWidthArrays[minIndex] += itemWidthArrays[i]; + // 累加数量 + columnsItemNumberArrays[minIndex] += 1; + // 保存每行最大高度 + columnsHeightArrays[minIndex] = Math.max(itemHeightArrays[i], columnsHeightArrays[minIndex]); + } + + // 循环追加子项间分隔符占用的宽度 + for (int i = 0; i < lineNumber; i++) { + if (columnsItemNumberArrays[i] > 1) { + columnsWidthArrays[i] += (columnsItemNumberArrays[i] - 1) * horizontalSpacing; + } + if (i > 0) { + columnsTopArrays[i] = height + (i * verticalSpacing); + } + // 累加每行高度 + height += columnsHeightArrays[i]; + } + + // 获取最大宽值 + int maxColumnsWidth = columnsWidthArrays[ArrayUtils.getMaximumIndex(columnsWidthArrays)]; + // 使用最大值 + height += (lineNumber - 1) * verticalSpacing; + width = maxColumnsWidth; + // 清空绘制时累加计算 + columnsWidthArrays = new int[lineNumber]; + // 创建位图 + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + // 循环绘制 + for (int i = 0; i < itemCount; i++) { + // 获取最小宽度索引 + int minIndex = ArrayUtils.getMinimumIndex(columnsWidthArrays); + Matrix matrix = new Matrix(); + matrix.postTranslate(columnsWidthArrays[minIndex], columnsTopArrays[minIndex]); + // 绘制到 Bitmap + Bitmap bmp = bitmaps[i]; + canvas.drawBitmap(bmp, matrix, PAINT); + // 累加宽度 + columnsWidthArrays[minIndex] += (itemWidthArrays[i] + horizontalSpacing); + // 释放资源 + BitmapUtils.recycle(bmp); + } + return bitmap; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "snapshotByRecyclerView_StaggeredGridLayoutManager"); + } + return null; + } + + // ============= + // = 内部私有方法 = + // ============= + + /** + * 绘制 Bitmap + * @param childView {@link View} + * @param config {@link Bitmap.Config} + * @return {@link Bitmap} + */ + private static Bitmap canvasBitmap( + final View childView, + final Bitmap.Config config + ) { + Bitmap bitmap = Bitmap.createBitmap( + childView.getMeasuredWidth(), + childView.getMeasuredHeight(), + config + ); + Canvas canvas = new Canvas(bitmap); + canvas.drawColor(BACKGROUND_COLOR); + childView.draw(canvas); + return bitmap; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/CleanUtils.java b/lib/DevApp/src/main/java/dev/utils/app/CleanUtils.java new file mode 100644 index 0000000000..15dba66efe --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/CleanUtils.java @@ -0,0 +1,134 @@ +package dev.utils.app; + +import java.io.File; + +import dev.utils.LogPrintUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 本应用数据清除管理工具类 + * @author Ttt + *
+ *     主要功能有清除内 / 外缓存、清除数据库、清除 SharedPreferences、清除 files 和清除自定义目录
+ * 
+ */ +public final class CleanUtils { + + private CleanUtils() { + } + + // 日志 TAG + private static final String TAG = CleanUtils.class.getSimpleName(); + + /** + * 清除外部缓存 ( path /storage/emulated/0/android/data/package/cache ) + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanCache() { + return FileUtils.deleteFilesInDir(PathUtils.getAppExternal().getAppCachePath()); + } + + /** + * 清除内部缓存 ( path /data/data/package/cache ) + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppCache() { + return FileUtils.deleteFilesInDir(PathUtils.getInternal().getAppCachePath()); + } + + /** + * 清除内部文件 ( path /data/data/package/files ) + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppFiles() { + return FileUtils.deleteFilesInDir(PathUtils.getInternal().getAppFilesPath()); + } + + /** + * 清除内部 SP ( path /data/data/package/shared_prefs ) + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppSp() { + return FileUtils.deleteFilesInDir(PathUtils.getInternal().getAppSpPath()); + } + + /** + * 清除内部 SP ( path /data/data/package/shared_prefs ) + * @param spName SP 文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppSp(final String spName) { + return FileUtils.deleteFilesInDir(PathUtils.getInternal().getAppSpPath(spName)); + } + + /** + * 清除内部数据库 ( path /data/data/package/databases ) + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppDbs() { + return FileUtils.deleteFilesInDir(PathUtils.getInternal().getAppDbsPath()); + } + + /** + * 根据名称清除数据库 ( path /data/data/package/databases/dbName ) + * @param dbName 数据库名 + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanAppDbByName(final String dbName) { + return DBUtils.deleteDatabase(dbName); + } + + // = + + /** + * 清除自定义路径下的文件 + *
+     *     使用需小心请不要误删, 而且只支持目录下的文件删除
+     * 
+ * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanCustomDir(final String filePath) { + return FileUtils.deleteFilesInDir(filePath); + } + + /** + * 清除自定义路径下的文件 + *
+     *     使用需小心请不要误删, 而且只支持目录下的文件删除
+     * 
+ * @param file 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanCustomDir(final File file) { + return FileUtils.deleteFilesInDir(file); + } + + /** + * 清除本应用所有的数据 + * @param filePaths 文件路径数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean cleanApplicationData(final String... filePaths) { + boolean result = true; + try { + cleanCache(); + cleanAppCache(); + cleanAppFiles(); + cleanAppSp(); + cleanAppDbs(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cleanApplicationData"); + result = false; + } + try { + for (String path : filePaths) { + cleanCustomDir(path); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cleanApplicationData"); + result = false; + } + return result; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ClickUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ClickUtils.java new file mode 100644 index 0000000000..50f12eb12f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ClickUtils.java @@ -0,0 +1,852 @@ +package dev.utils.app; + +import android.graphics.Rect; +import android.view.TouchDelegate; +import android.view.View; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import dev.utils.LogPrintUtils; + +/** + * detail: 点击 ( 双击 ) 工具类 + * @author Ttt + */ +public final class ClickUtils { + + private ClickUtils() { + } + + // 日志 TAG + private static final String TAG = ClickUtils.class.getSimpleName(); + + // 通用间隔时间 + public static final long INTERVAL_TIME = 1000L; + // 是否校验 viewId + private static boolean sCheckViewId = true; + // 双击间隔时间 + private static long sGlobalIntervalTime = 1000L; + // 全局共用的点击辅助类 + private static final ClickAssist sGlobalClickAssist = new ClickAssist(); + // 功能模块 ClickAssist Maps + private static final Map sClickAssistMaps = new HashMap<>(); + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param view 待添加点击范围 View + * @param range 点击范围 + * @return {@code true} success, {@code false} fail + */ + public static boolean addTouchArea( + final View view, + final int range + ) { + return addTouchArea(view, range, range, range, range); + } + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param view 待添加点击范围 View + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @return {@code true} success, {@code false} fail + */ + public static boolean addTouchArea( + final View view, + final int left, + final int top, + final int right, + final int bottom + ) { + if (view != null) { + try { + final View parent = (View) view.getParent(); + parent.post(() -> { + try { + Rect bounds = new Rect(); + view.getHitRect(bounds); + + // 设置范围 + bounds.left -= left; + bounds.top -= top; + bounds.right += right; + bounds.bottom += bottom; + + TouchDelegate touchDelegate = new TouchDelegate(bounds, view); + if (view.getParent() instanceof View) { + ((View) view.getParent()).setTouchDelegate(touchDelegate); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addTouchArea - runnable"); + } + }); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addTouchArea"); + } + } + return false; + } + + /** + * 设置全局是否校验 viewId + * @param checkViewId 是否校验 viewId + */ + public static void setCheckViewId(final boolean checkViewId) { + ClickUtils.sCheckViewId = checkViewId; + } + + /** + * 获取全局双击间隔时间 + * @return 全局双击间隔时间 + */ + public static long getGlobalIntervalTime() { + return ClickUtils.sGlobalIntervalTime; + } + + /** + * 设置全局双击间隔时间 + * @param globalIntervalTime 全局双击间隔时间 + */ + public static void setGlobalIntervalTime(final long globalIntervalTime) { + ClickUtils.sGlobalIntervalTime = globalIntervalTime; + } + + // ================== + // = 功能模块辅助类操作 = + // ================== + + /** + * 获取对应功能模块点击辅助类 + * @param object key by Object + * @return {@link ClickAssist} + */ + public static ClickAssist get(final Object object) { + // 获取对应模块点击辅助类 + ClickAssist clickAssist = sClickAssistMaps.get(object); + if (clickAssist != null) { + return clickAssist; + } + clickAssist = new ClickAssist(); + sClickAssistMaps.put(object, clickAssist); + return clickAssist; + } + + /** + * 移除对应功能模块点击辅助类 + * @param object key by Object + */ + public static void remove(final Object object) { + sClickAssistMaps.remove(object); + } + + // ================== + // = 全局点击辅助类操作 = + // ================== + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isFastDoubleClick() { + return sGlobalClickAssist.isFastDoubleClick(); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param tagId id + * @return {@code true} yes, {@code false} no + */ + public static boolean isFastDoubleClick(final int tagId) { + return sGlobalClickAssist.isFastDoubleClick(tagId); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param tagId id + * @param intervalTime 双击时间间隔 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFastDoubleClick( + final int tagId, + final long intervalTime + ) { + return sGlobalClickAssist.isFastDoubleClick(tagId, intervalTime); + } + + // = + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param object key by Object + * @param configKey 时间间隔配置 Key + * @return {@code true} yes, {@code false} no + */ + public static boolean isFastDoubleClick( + final Object object, + final String configKey + ) { + return sGlobalClickAssist.isFastDoubleClick(object, configKey); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param object key by Object + * @param intervalTime 双击时间间隔 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFastDoubleClick( + final Object object, + final long intervalTime + ) { + return sGlobalClickAssist.isFastDoubleClick(object, intervalTime); + } + + // = + + /** + * 初始化配置信息 + * @param mapConfigs config Maps + * @return {@link ClickAssist} + */ + public static ClickAssist initConfig(final Map mapConfigs) { + return sGlobalClickAssist.initConfig(mapConfigs); + } + + /** + * 添加配置信息 + * @param key config Key + * @param value config Value + * @return {@link ClickAssist} + */ + public static ClickAssist putConfig( + final String key, + final Long value + ) { + return sGlobalClickAssist.putConfig(key, value); + } + + /** + * 移除配置信息 + * @param key config Key + * @return {@link ClickAssist} + */ + public static ClickAssist removeConfig(final String key) { + return sGlobalClickAssist.removeConfig(key); + } + + /** + * 获取配置时间 + * @param key config Key + * @return 配置时间 + */ + public static Long getConfigTime(final String key) { + return sGlobalClickAssist.getConfigTime(key); + } + + // = + + /** + * 移除点击记录 + * @param key tag Key + * @return {@link ClickAssist} + */ + public static ClickAssist removeRecord(final String key) { + return sGlobalClickAssist.removeRecord(key); + } + + /** + * 清空全部点击记录 + * @return {@link ClickAssist} + */ + public static ClickAssist clearRecord() { + return sGlobalClickAssist.clearRecord(); + } + + // = + + /** + * 获取默认点击时间间隔 + * @return 双击时间间隔 + */ + public static long getIntervalTime() { + return sGlobalClickAssist.getIntervalTime(); + } + + /** + * 设置默认点击时间间隔 + * @param intervalTime 双击时间间隔 + * @return {@link ClickAssist} + */ + public static ClickAssist setIntervalTime(final long intervalTime) { + return sGlobalClickAssist.setIntervalTime(intervalTime); + } + + /** + * 重置处理 + * @return {@link ClickAssist} + */ + public static ClickAssist reset() { + return sGlobalClickAssist.reset(); + } + + // = + + /** + * detail: 点击 ( 双击 ) 辅助类 + * @author Ttt + *
+     *     ps: 该辅助类, 主要避免全局共用一个双击控制类, 容易出现冲突, 方便控制某个 Activity 或功能模块 双击处理
+     *     使用 Key(Tag-Object) 获取指定的 {@link ClickAssist}, 能够实现不同 Activity 或功能模块 单独使用 {@link ClickAssist}
+     *     并且可以进行销毁处理
+     * 
+ */ + public static class ClickAssist { + + // 最后一次点击的标识 id + private int mLastTagId = -1; + // 最后一次点击时间 + private long mLastClickTime = 0L; + // 双击间隔时间 + private long mIntervalTime; + // 配置数据 + private final Map mConfigMaps = new HashMap<>(); + // 点击记录数据 + private final Map mRecordMaps = new HashMap<>(); + + public ClickAssist() { + this(ClickUtils.sGlobalIntervalTime); + } + + /** + * 构造函数 + * @param intervalTime 双击间隔时间 + */ + public ClickAssist(final long intervalTime) { + this.mIntervalTime = intervalTime; + } + + // = + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isFastDoubleClick() { + return isFastDoubleClick(-1, mIntervalTime); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param tagId id + * @return {@code true} yes, {@code false} no + */ + public boolean isFastDoubleClick(final int tagId) { + return isFastDoubleClick(tagId, mIntervalTime); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param tagId id + * @param intervalTime 双击间隔时间 + * @return {@code true} yes, {@code false} no + */ + public boolean isFastDoubleClick( + final int tagId, + final long intervalTime + ) { + long curTime = System.currentTimeMillis(); + long diffTime = curTime - mLastClickTime; + // 判断时间是否超过 + if (mLastTagId == tagId && mLastClickTime > 0 && diffTime < intervalTime) { + LogPrintUtils.dTag( + TAG, + "isFastDoubleClick 无效点击 tagId: %s, intervalTime: %s", + tagId, intervalTime + ); + return true; + } + LogPrintUtils.dTag( + TAG, + "isFastDoubleClick 有效点击 tagId: %s, intervalTime: %s", + tagId, intervalTime + ); + mLastTagId = tagId; + mLastClickTime = curTime; + return false; + } + + // = + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param object key by Object + * @param configKey 时间间隔配置 Key + * @return {@code true} yes, {@code false} no + */ + public boolean isFastDoubleClick( + final Object object, + final String configKey + ) { + return isFastDoubleClick(object, getConfigTime(configKey)); + } + + /** + * 判断是否双击 ( 无效点击, 短时间内多次点击 ) + * @param object key by Object + * @param intervalTime 双击时间间隔 + * @return {@code true} yes, {@code false} no + */ + public boolean isFastDoubleClick( + final Object object, + final long intervalTime + ) { + // 获取 TAG + String tag = ((object != null) ? ("obj_" + object.hashCode()) : "obj_null"); + // 获取上次点击的时间 + Long lastTime = mRecordMaps.get(tag); + if (lastTime == null) { + lastTime = 0L; + } + long curTime = System.currentTimeMillis(); + long diffTime = curTime - lastTime; + // 判断时间是否超过 + if (lastTime > 0 && diffTime < intervalTime) { + LogPrintUtils.dTag( + TAG, + "isFastDoubleClick 无效点击 object: %s, intervalTime: %s", + object, intervalTime + ); + return true; + } + LogPrintUtils.dTag( + TAG, + "isFastDoubleClick 有效点击 object: %s, intervalTime: %s", + object, intervalTime + ); + // 保存上次点击时间 + mRecordMaps.put(tag, curTime); + return false; + } + + // = + + /** + * 初始化配置信息 + * @param mapConfigs config Maps + * @return {@link ClickAssist} + */ + public ClickAssist initConfig(final Map mapConfigs) { + if (mapConfigs != null) { + mConfigMaps.putAll(mapConfigs); + } + return this; + } + + /** + * 添加配置信息 + * @param key config Key + * @param value config Value + * @return {@link ClickAssist} + */ + public ClickAssist putConfig( + final String key, + final Long value + ) { + mConfigMaps.put(key, value); + return this; + } + + /** + * 移除配置信息 + * @param key config Key + * @return {@link ClickAssist} + */ + public ClickAssist removeConfig(final String key) { + mConfigMaps.remove(key); + return this; + } + + /** + * 获取配置时间 + * @param key config Key + * @return 配置时间 + */ + public Long getConfigTime(final String key) { + // 获取配置时间 + Long configTime = mConfigMaps.get(key); + // 判断是否为 null + return (configTime != null) ? configTime : mIntervalTime; + } + + // = + + /** + * 移除点击记录 + * @param key tag Key + * @return {@link ClickAssist} + */ + public ClickAssist removeRecord(final String key) { + mRecordMaps.remove(key); + return this; + } + + /** + * 清空全部点击记录 + * @return {@link ClickAssist} + */ + public ClickAssist clearRecord() { + mRecordMaps.clear(); + return this; + } + + // = + + /** + * 获取默认点击时间间隔 + * @return 双击时间间隔 + */ + public long getIntervalTime() { + return this.mIntervalTime; + } + + /** + * 设置默认点击时间间隔 + * @param intervalTime 双击时间间隔 + * @return {@link ClickAssist} + */ + public ClickAssist setIntervalTime(final long intervalTime) { + this.mIntervalTime = intervalTime; + return this; + } + + /** + * 重置处理 + * @return {@link ClickAssist} + */ + public ClickAssist reset() { + // 重置最后一次点击的标识 id + mLastTagId = -1; + // 重置最后一次点击时间 + mLastClickTime = 0L; + // 重置双击间隔时间 + mIntervalTime = ClickUtils.sGlobalIntervalTime; + // 清空配置信息 + mConfigMaps.clear(); + // 清空点击记录 + mRecordMaps.clear(); + return this; + } + } + + // ========== + // = 快捷方法 = + // ========== + + // 空实现 View.OnClickListener + public static final View.OnClickListener EMPTY_CLICK = new View.OnClickListener() { + @Override + public void onClick(View view) { + LogPrintUtils.dTag(TAG, "EMPTY_CLICK viewId: %s", view.getId()); + } + }; + + /** + * detail: 双击点击事件 + * @author Ttt + */ + public static abstract class OnDebouncingClickListener + implements View.OnClickListener { + + // 是否校验 viewId + private final boolean mCheckViewId; + // 点击辅助类 + private final ClickAssist mClickAssist; + + // ========== + // = 构造函数 = + // ========== + + public OnDebouncingClickListener() { + this(ClickUtils.sGlobalClickAssist, ClickUtils.sCheckViewId); + } + + public OnDebouncingClickListener(boolean checkViewId) { + this(ClickUtils.sGlobalClickAssist, checkViewId); + } + + public OnDebouncingClickListener(ClickAssist clickAssist) { + this(clickAssist, ClickUtils.sCheckViewId); + } + + public OnDebouncingClickListener( + ClickAssist clickAssist, + boolean checkViewId + ) { + this.mClickAssist = clickAssist; + this.mCheckViewId = checkViewId; + } + + // ========== + // = 内部方法 = + // ========== + + @Override + public final void onClick(View view) { + if (mClickAssist.isFastDoubleClick(mCheckViewId ? view.getId() : -1)) { + doInvalidClick(view); + } else { + doClick(view); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 有效点击 ( 单击 ) + * @param view {@link View} + */ + public abstract void doClick(View view); + + /** + * 无效点击 ( 双击 ) + * @param view {@link View} + */ + public void doInvalidClick(View view) { + } + } + + /** + * detail: 计数点击事件 + * @author Ttt + */ + public static abstract class OnCountClickListener + implements View.OnClickListener { + + // 点击辅助类 + private final ClickAssist mClickAssist; + // 总点击数 + private final AtomicInteger mCount = new AtomicInteger(); + // 无效点击总次数 + private final AtomicInteger mInvalidCount = new AtomicInteger(); + // 每个周期无效点击次数 ( 周期 ( 有效 - 无效 - 有效 ) ) + private final AtomicInteger mInvalidCycleNumber = new AtomicInteger(); + + // ========== + // = 构造函数 = + // ========== + + public OnCountClickListener() { + this(ClickUtils.sGlobalClickAssist); + } + + public OnCountClickListener(ClickAssist clickAssist) { + this.mClickAssist = clickAssist; + } + + // ========== + // = 内部方法 = + // ========== + + @Override + public final void onClick(View view) { + // 累加总点击数 + mCount.incrementAndGet(); + + if (mClickAssist.isFastDoubleClick(view.getId())) { + // 累加无效点击总次数 + mInvalidCount.incrementAndGet(); + // 累加每个周期无效点击次数 ( 周期 ( 有效 - 无效 - 有效 ) ) + int invalidCycleNumber = mInvalidCycleNumber.incrementAndGet(); + + doInvalidClick(view, this, invalidCycleNumber); + } else { + // 重置周期无效点击次数 + mInvalidCycleNumber.set(0); + + doClick(view, this); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 有效点击 ( 单击 ) + * @param view {@link View} + * @param listener {@link OnCountClickListener} + */ + public abstract void doClick( + View view, + OnCountClickListener listener + ); + + /** + * 无效点击 ( 双击 ) + * @param view {@link View} + * @param listener {@link OnCountClickListener} + * @param invalidCycleNumber 每个周期无效点击次数 ( 周期 ( 有效 - 无效 - 有效 ) ) + */ + public void doInvalidClick( + View view, + OnCountClickListener listener, + int invalidCycleNumber + ) { + } + + // = + + /** + * 获取总点击数 + * @return 总点击数 + */ + public final int getCount() { + return mCount.get(); + } + + /** + * 获取无效点击总次数 + * @return 无效点击总次数 + */ + public final int getInvalidCount() { + return mInvalidCount.get(); + } + + /** + * 获取每个周期无效点击次数 + * @return 每个周期无效点击次数 + */ + public final int getInvalidCycleNumber() { + return mInvalidCycleNumber.get(); + } + } + + /** + * detail: 多次点击触发事件 + * @author Ttt + *
+     *     因为双击后才进入计数, 所以 {@link #getInvalidCycleNumber()} 得 + 1 才是准确的点击数
+     * 
+ */ + public static abstract class OnMultiClickListener + extends OnCountClickListener { + + // 多次点击次数 + private final int multiClickNumber; + + /** + * 构造函数 + * @param number 多次点击次数 + */ + public OnMultiClickListener(final int number) { + this(number, INTERVAL_TIME); + } + + /** + * 构造函数 + * @param number 多次点击次数 + * @param intervalTime 双击间隔时间 + */ + public OnMultiClickListener( + final int number, + final long intervalTime + ) { + super(new ClickAssist(intervalTime)); + this.multiClickNumber = number; + } + + @Override + public final void doClick( + View view, + OnCountClickListener listener + ) { + } + + @Override + public void doInvalidClick( + View view, + OnCountClickListener listener, + int invalidCycleNumber + ) { + if (invalidCycleNumber + 1 >= multiClickNumber) { + doMultiClick(view, invalidCycleNumber + 1); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 多次点击触发 + * @param view {@link View} + * @param clickNumber 多次点击次数 + */ + public abstract void doMultiClick( + View view, + int clickNumber + ); + } + + // ========== + // = 常见事件 = + // ========== + + /** + * 设置点击事件 + * @param view {@link View} + * @param listener {@link View.OnClickListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnClick( + final View view, + final View.OnClickListener listener + ) { + if (view != null) { + view.setOnClickListener(listener); + return true; + } + return false; + } + + /** + * 设置长按事件 + * @param view {@link View} + * @param listener {@link View.OnLongClickListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnLongClick( + final View view, + final View.OnLongClickListener listener + ) { + if (view != null) { + view.setOnLongClickListener(listener); + return true; + } + return false; + } + + /** + * 设置触摸事件 + * @param view {@link View} + * @param listener {@link View.OnTouchListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnTouch( + final View view, + final View.OnTouchListener listener + ) { + if (view != null) { + view.setOnTouchListener(listener); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ClipboardUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ClipboardUtils.java new file mode 100644 index 0000000000..294addb100 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ClipboardUtils.java @@ -0,0 +1,132 @@ +package dev.utils.app; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Intent; +import android.net.Uri; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 剪贴板相关工具类 + * @author Ttt + */ +public final class ClipboardUtils { + + private ClipboardUtils() { + } + + // 日志 TAG + private static final String TAG = ClipboardUtils.class.getSimpleName(); + + /** + * 复制文本到剪贴板 + * @param text 文本 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyText(final CharSequence text) { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + // 复制的数据 + ClipData clipData = ClipData.newPlainText("text", text); + // 设置复制的数据 + clipManager.setPrimaryClip(clipData); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "copyText"); + } + return false; + } + + /** + * 获取剪贴板文本 + * @return 剪贴板文本 + */ + public static CharSequence getText() { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + ClipData clipData = clipManager.getPrimaryClip(); + if (clipData != null && clipData.getItemCount() > 0) { + return clipData.getItemAt(0).coerceToText(DevUtils.getContext()); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getText"); + } + return null; + } + + /** + * 复制 URI 到剪贴板 + * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + public static boolean copyUri(final Uri uri) { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + // 复制的数据 + ClipData clipData = ClipData.newUri( + ResourceUtils.getContentResolver(), "", uri + ); + // 设置复制的数据 + clipManager.setPrimaryClip(clipData); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "copyUri"); + } + return false; + } + + /** + * 获取剪贴板 URI + * @return 剪贴板 URI + */ + public static Uri getUri() { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + ClipData clipData = clipManager.getPrimaryClip(); + if (clipData != null && clipData.getItemCount() > 0) { + return clipData.getItemAt(0).getUri(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUri"); + } + return null; + } + + /** + * 复制意图到剪贴板 + * @param intent {@link Intent} + * @return {@code true} success, {@code false} fail + */ + public static boolean copyIntent(final Intent intent) { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + // 复制的数据 + ClipData clipData = ClipData.newIntent("intent", intent); + // 设置复制的数据 + clipManager.setPrimaryClip(clipData); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "copyIntent"); + } + return false; + } + + /** + * 获取剪贴板意图 + * @return 剪贴板意图 + */ + public static Intent getIntent() { + try { + ClipboardManager clipManager = AppUtils.getClipboardManager(); + ClipData clipData = clipManager.getPrimaryClip(); + if (clipData != null && clipData.getItemCount() > 0) { + return clipData.getItemAt(0).getIntent(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIntent"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ContentResolverUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ContentResolverUtils.java new file mode 100644 index 0000000000..3f056002ac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ContentResolverUtils.java @@ -0,0 +1,599 @@ +package dev.utils.app; + +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.util.Arrays; + +import dev.utils.LogPrintUtils; +import dev.utils.common.ArrayUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.FileUtils; + +/** + * detail: ContentResolver 工具类 + * @author Ttt + *
+ *     @see 
+ *     

+ * 例: content://xxxx/49 + * 可通过 {@link ContentUris#parseId(Uri)} 获取 49 + *
+ */ +public final class ContentResolverUtils { + + private ContentResolverUtils() { + } + + // 日志 TAG + private static final String TAG = ContentResolverUtils.class.getSimpleName(); + + // 卷名 + private static final String VOLUME_EXTERNAL = PathUtils.EXTERNAL; + // 文件 URI + public static final Uri FILES_URI = MediaStore.Files.getContentUri(VOLUME_EXTERNAL); + + /** + * 获取 Uri Cursor 对应条件的数据行 data 字段 + * @param uri {@link Uri} + * @param selection 查询条件 + * @param selectionArgs 查询条件的参数 + * @return 对应条件的数据行 data 字段 + */ + public static String getDataColumn( + final Uri uri, + final String selection, + final String[] selectionArgs + ) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + try { + cursor = ResourceUtils.getContentResolver().query( + uri, projection, selection, selectionArgs, null + ); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDataColumn"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return null; + } + + /** + * 获取 Uri Cursor 对应条件的数据行 display_name 字段 + * @param uri {@link Uri} + * @return 对应条件的数据行 display_name 字段 + */ + public static String getDisplayNameColumn(final Uri uri) { + Cursor cursor = null; + final String column = OpenableColumns.DISPLAY_NAME; + final String[] projection = {column}; + try { + cursor = ResourceUtils.getContentResolver().query( + uri, projection, null, null, null + ); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDisplayNameColumn"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return null; + } + + /** + * 删除多媒体资源 + * @param uri {@link Uri} + * @param where 删除条件 + * @param selectionArgs 删除条件参数 + * @return 删除条数 + */ + public static int delete( + final Uri uri, + final String where, + final String[] selectionArgs + ) { + try { + return ResourceUtils.getContentResolver().delete( + uri, where, selectionArgs + ); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "delete where: %s, args: %s", + where, Arrays.toString(selectionArgs) + ); + } + return 0; + } + + /** + * 更新多媒体资源 + * @param uri {@link Uri} + * @param values 更新值 + * @param where 更新条件 + * @param selectionArgs 更新条件参数 + * @return 更新条数 + */ + public static int update( + final Uri uri, + final ContentValues values, + final String where, + final String[] selectionArgs + ) { + try { + return ResourceUtils.getContentResolver().update( + uri, values, where, selectionArgs + ); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "update where: %s, args: %s", + where, Arrays.toString(selectionArgs) + ); + } + return 0; + } + + // = + + /** + * 删除文件 + *
+     *     {@link IntentUtils#getCreateDocumentIntent(String, String)}
+     *     {@link IntentUtils#getOpenDocumentIntent()}
+     *     通过这两个方法跳转回传的 Uri 删除
+     * 
+ * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static boolean deleteDocument(final Uri uri) { + try { + return DocumentsContract.deleteDocument( + ResourceUtils.getContentResolver(), uri + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "deleteDocument"); + } + return false; + } + + // ========= + // = Query = + // ========= + + /** + * 获取 Uri Cursor + * @param uri 具体 Uri + * @return {@link Cursor} + */ + public static Cursor query(final Uri uri) { + return query(uri, null, null, null, null); + } + + /** + * 获取 Uri Cursor + *
+     *     // 获取指定类型文件
+     *     uri => MediaStore.media-type.Media.EXTERNAL_CONTENT_URI
+     *     // 全部文件搜索
+     *     MediaStore.Files.getContentUri("external")
+     * 
+ * @param uri {@link Uri} + * @param projection 查询的字段 + * @param selection 查询条件 + * @param selectionArgs 查询条件的参数 + * @param sortOrder 排序方式 + * @return {@link Cursor} + */ + public static Cursor query( + final Uri uri, + final String[] projection, + final String selection, + final String[] selectionArgs, + final String sortOrder + ) { + try { + return ResourceUtils.getContentResolver().query( + uri, projection, selection, selectionArgs, sortOrder + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "query"); + } + return null; + } + + /** + * 通过 File 获取 Media Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri(final File file) { + return getMediaUri(FILES_URI, file); + } + + /** + * 通过 File 获取 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri( + final Uri uri, + final File file + ) { + return getMediaUri(uri, FileUtils.getAbsolutePath(file)); + } + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri(final String filePath) { + return getMediaUri(FILES_URI, filePath); + } + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri( + final Uri uri, + final String filePath + ) { + String[] result = mediaQuery(uri, filePath, MEDIA_QUERY_URI); + if (ArrayUtils.isLength(result, 2)) { + try { + // 返回外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content:// + return MediaStore.Files.getContentUri( + result[1], ConvertUtils.toLong(result[0]) + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMediaUri %s", filePath); + } + } + return null; + } + + // = + + /** + * 通过 File 获取 Media 信息 + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + public static String[] mediaQuery( + final File file, + final MediaQuery mediaQuery + ) { + return mediaQuery(FILES_URI, file, mediaQuery); + } + + /** + * 通过 File 获取 Media 信息 + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + public static String[] mediaQuery( + final Uri uri, + final File file, + final MediaQuery mediaQuery + ) { + return mediaQuery(uri, FileUtils.getAbsolutePath(file), mediaQuery); + } + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + public static String[] mediaQuery( + final String filePath, + final MediaQuery mediaQuery + ) { + return mediaQuery(FILES_URI, filePath, mediaQuery); + } + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + public static String[] mediaQuery( + final Uri uri, + final String filePath, + final MediaQuery mediaQuery + ) { + if (uri == null || TextUtils.isEmpty(filePath) || mediaQuery == null) return null; + Cursor cursor = null; + try { + cursor = query( + uri, + mediaQuery.getProjection(uri, filePath), + mediaQuery.getSelection(uri, filePath), + mediaQuery.getSelectionArgs(uri, filePath), + mediaQuery.getSortOrder(uri, filePath) + ); + if (cursor != null && cursor.moveToFirst()) { + return mediaQuery.getResult(uri, filePath, cursor); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "mediaQuery %s", filePath); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return null; + } + + // =============== + // = Media Query = + // =============== + + // 多媒体查询获取 Uri 处理 + private static final MediaQueryUri MEDIA_QUERY_URI = new MediaQueryUri(); + // 多媒体查询信息处理 + public static final MediaQueryInfo MEDIA_QUERY_INFO = new MediaQueryInfo(); + // 多媒体查询信息处理 Uri + public static final MediaQueryInfoUri MEDIA_QUERY_INFO_URI = new MediaQueryInfoUri(); + + /** + * detail: 多媒体查询抽象类 + * @author Ttt + */ + public static abstract class MediaQuery { + + /** + * 获取查询结果 + * @param uri Uri + * @param filePath 文件路径 + * @param cursor Cursor + * @return 查询结果 + */ + public abstract String[] getResult( + Uri uri, + String filePath, + Cursor cursor + ); + + // ========== + // = 查询条件 = + // ========== + + /** + * 获取查询的字段 + * @param uri Uri + * @param filePath 文件路径 + * @return 查询的字段 + */ + public abstract String[] getProjection( + Uri uri, + String filePath + ); + + /** + * 获取查询条件 + * @param uri Uri + * @param filePath 文件路径 + * @return 查询条件 + */ + public abstract String getSelection( + Uri uri, + String filePath + ); + + /** + * 获取查询条件的参数 + * @param uri Uri + * @param filePath 文件路径 + * @return 查询条件的参数 + */ + public abstract String[] getSelectionArgs( + Uri uri, + String filePath + ); + + /** + * 获取排序方式 + * @param uri Uri + * @param filePath 文件路径 + * @return 排序方式 + */ + public String getSortOrder( + Uri uri, + String filePath + ) { + return null; + } + } + + /** + * detail: 多媒体查询获取 Uri 处理 + * @author Ttt + */ + private static class MediaQueryUri + extends MediaQuery { + @Override + public String[] getResult( + Uri uri, + String filePath, + Cursor cursor + ) { + String[] result = new String[2]; + long rowId = cursor.getLong( + cursor.getColumnIndex(MediaStore.Files.FileColumns._ID) + ); + String volumeName = VOLUME_EXTERNAL; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + volumeName = cursor.getString( + cursor.getColumnIndex(MediaStore.Files.FileColumns.VOLUME_NAME) + ); + } + result[0] = String.valueOf(rowId); + result[1] = volumeName; + return result; + } + + @Override + public String[] getProjection( + Uri uri, + String filePath + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return new String[]{ + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.VOLUME_NAME + }; + } else { + return new String[]{MediaStore.Files.FileColumns._ID}; + } + } + + @Override + public String getSelection( + Uri uri, + String filePath + ) { + return MediaStore.Files.FileColumns.DATA + "=?"; + } + + @Override + public String[] getSelectionArgs( + Uri uri, + String filePath + ) { + return new String[]{filePath}; + } + } + + /** + * detail: 多媒体查询信息处理 + * @author Ttt + */ + public static class MediaQueryInfo + extends MediaQuery { + @Override + public String[] getResult( + Uri uri, + String filePath, + Cursor cursor + ) { + String[] result = new String[8]; + long rowId = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns._ID)); + int width = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns.WIDTH)); + int height = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns.HEIGHT)); + String mimeType = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.MIME_TYPE)); + long mediaType = cursor.getInt(cursor.getColumnIndex(MediaStore.Files.FileColumns.MEDIA_TYPE)); + String dateAdded = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATE_ADDED)); + String dateModified = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATE_MODIFIED)); + long duration = 0; + if (mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO + || mediaType == MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO) { + duration = cursor.getLong(cursor.getColumnIndex(MediaStore.Files.FileColumns.DURATION)); + } + + result[0] = String.valueOf(rowId); + result[1] = String.valueOf(width); + result[2] = String.valueOf(height); + result[3] = mimeType; + result[4] = String.valueOf(mediaType); + result[5] = dateAdded; + result[6] = dateModified; + result[7] = String.valueOf(duration); + return result; + } + + @Override + public String[] getProjection( + Uri uri, + String filePath + ) { + return new String[]{ + MediaStore.Files.FileColumns._ID, // ID + MediaStore.Files.FileColumns.WIDTH, // 宽度 + MediaStore.Files.FileColumns.HEIGHT, // 高度 + MediaStore.Files.FileColumns.MIME_TYPE, // 文件类型 + MediaStore.Files.FileColumns.MEDIA_TYPE, // 资源类型 + MediaStore.Files.FileColumns.DATE_ADDED, // 添加时间 + MediaStore.Files.FileColumns.DATE_MODIFIED, // 修改时间 + MediaStore.Files.FileColumns.DURATION, // 多媒体时长 + }; + } + + @Override + public String getSelection( + Uri uri, + String filePath + ) { + return MediaStore.Files.FileColumns.DATA + "=?"; + } + + @Override + public String[] getSelectionArgs( + Uri uri, + String filePath + ) { + return new String[]{filePath}; + } + } + + /** + * detail: 多媒体查询信息处理 Uri + * @author Ttt + */ + public static class MediaQueryInfoUri + extends MediaQueryInfo { + + @Override + public String getSelection( + Uri uri, + String filePath + ) { +// return MediaStore.Files.FileColumns._ID + "=?"; + return null; + } + + @Override + public String[] getSelectionArgs( + Uri uri, + String filePath + ) { +// return new String[]{String.valueOf(ContentUris.parseId(uri))}; + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/CrashUtils.java b/lib/DevApp/src/main/java/dev/utils/app/CrashUtils.java new file mode 100644 index 0000000000..917f3d76d9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/CrashUtils.java @@ -0,0 +1,119 @@ +package dev.utils.app; + +import android.content.Context; + +import java.lang.Thread.UncaughtExceptionHandler; + +/** + * detail: UncaughtException 处理工具类 + * @author Ttt + *
+ *     当程序发生 Uncaught 异常的时候, 由该类来接管程序, 并记录发送错误报告
+ * 
+ */ +public final class CrashUtils + implements UncaughtExceptionHandler { + + private CrashUtils() { + } + + // Context + private Context mContext; + // 系统默认的 UncaughtException 处理器 + private UncaughtExceptionHandler mDefaultHandler; + // 捕获异常事件处理 + private CrashCatchListener mCrashCatchListener; + // CrashUtils 实例 + private static volatile CrashUtils sInstance; + + /** + * 获取 CrashUtils 实例 + * @return {@link CrashUtils} + */ + public static CrashUtils getInstance() { + if (sInstance == null) { + synchronized (CrashUtils.class) { + if (sInstance == null) { + sInstance = new CrashUtils(); + } + } + } + return sInstance; + } + + /** + * 初始化方法 + * @param context {@link Context} + * @param crashCatchListener {@link CrashCatchListener} + */ + public void initialize( + final Context context, + final CrashCatchListener crashCatchListener + ) { + this.mContext = context; + this.mCrashCatchListener = crashCatchListener; + // 获取系统默认的 UncaughtException 处理器 + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + // 设置该 CrashUtils 为程序的默认处理器 + Thread.setDefaultUncaughtExceptionHandler(this); + } + + /** + * 当 UncaughtException 发生时会转入该函数来处理 + * @param thread {@link Thread} + * @param ex {@link Throwable} + */ + @Override + public void uncaughtException( + Thread thread, + Throwable ex + ) { + if (!handleException(ex) && mDefaultHandler != null) { + // 如果用户没有处理则让系统默认的异常处理器来处理 + mDefaultHandler.uncaughtException(thread, ex); + } else { + if (mCrashCatchListener != null) { + mCrashCatchListener.uncaughtException(mContext, thread, ex); + } + } + } + + /** + * 自定义错误处理 ( 收集错误信息、发送错误报告等操作均在此完成 ) + * @param ex {@link Throwable} + * @return {@code true} 处理该异常信息, {@code false} 未处理该异常信息 + */ + private boolean handleException(final Throwable ex) { + if (ex == null) return false; + // 触发回调 + if (mCrashCatchListener != null) { + mCrashCatchListener.handleException(ex); + } + return true; + } + + /** + * detail: 异常捕获处理 + * @author Ttt + */ + public interface CrashCatchListener { + + /** + * 处理异常 + * @param ex {@link Throwable} + */ + void handleException(Throwable ex); + + /** + * 处理未捕获的异常 + * @param context {@link Context} + * @param thread {@link Thread} + * @param ex {@link Throwable} + */ + void uncaughtException( + Context context, + Thread thread, + Throwable ex + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/DBUtils.java b/lib/DevApp/src/main/java/dev/utils/app/DBUtils.java new file mode 100644 index 0000000000..17c1e2512f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/DBUtils.java @@ -0,0 +1,152 @@ +package dev.utils.app; + +import java.io.InputStream; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 数据库工具类 ( 导入导出等 ) + * @author Ttt + */ +public final class DBUtils { + + private DBUtils() { + } + + // 日志 TAG + private static final String TAG = DBUtils.class.getSimpleName(); + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases ) + * @return /data/data/package/databases + */ + public static String getAppDbsPath() { + return PathUtils.getInternal().getAppDbsPath(); + } + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) + * @param name 数据库名 + * @return /data/data/package/databases/name + */ + public static String getAppDbPath(final String name) { + return PathUtils.getInternal().getAppDbPath(name); + } + + // ============ + // = 删除数据库 = + // ============ + + /** + * 根据名称清除数据库 + * @param dbName 数据库名 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteDatabase(final String dbName) { + try { + return DevUtils.getContext().deleteDatabase(dbName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "deleteDatabase"); + } + return false; + } + + // ============ + // = 导出数据库 = + // ============ + + /** + * 导出数据库 + * @param targetFile 目标文件 + * @param dbName 数据库名 + * @return {@code true} success, {@code false} fail + */ + public static boolean startExportDatabase( + final String targetFile, + final String dbName + ) { + return startExportDatabase(targetFile, dbName, true); + } + + /** + * 导出数据库 + * @param targetFile 目标文件 + * @param dbName 数据库名 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean startExportDatabase( + final String targetFile, + final String dbName, + final boolean overlay + ) { + if (!PathUtils.getSDCard().isSDCardEnable()) return false; + return FileUtils.copyFile(getAppDbPath(dbName), targetFile, overlay); + } + + // ============ + // = 导入数据库 = + // ============ + + /** + * 导入数据库 + * @param srcFilePath 待复制的文件地址 + * @param destFilePath 目标文件地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean startImportDatabase( + final String srcFilePath, + final String destFilePath + ) { + return startImportDatabase(srcFilePath, destFilePath, true); + } + + /** + * 导入数据库 + * @param srcFilePath 待复制的文件地址 + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean startImportDatabase( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + if (!PathUtils.getSDCard().isSDCardEnable()) return false; + return FileUtils.copyFile(srcFilePath, destFilePath, overlay); + } + + // = + + /** + * 导入数据库 + * @param inputStream 文件流 ( 被复制 ) + * @param destFilePath 目标文件地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean startImportDatabase( + final InputStream inputStream, + final String destFilePath + ) { + return startImportDatabase(inputStream, destFilePath, true); + } + + /** + * 导入数据库 + * @param inputStream 文件流 ( 被复制 ) + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean startImportDatabase( + final InputStream inputStream, + final String destFilePath, + final boolean overlay + ) { + if (!PathUtils.getSDCard().isSDCardEnable()) return false; + return FileUtils.copyFile(inputStream, destFilePath, overlay); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/DevicePolicyUtils.java b/lib/DevApp/src/main/java/dev/utils/app/DevicePolicyUtils.java new file mode 100644 index 0000000000..4d008b0f15 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/DevicePolicyUtils.java @@ -0,0 +1,416 @@ +package dev.utils.app; + +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Intent; +import android.text.TextUtils; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 设备管理工具类 + * @author Ttt + */ +public final class DevicePolicyUtils { + + private DevicePolicyUtils() { + } + + // 日志 TAG + private static final String TAG = DevicePolicyUtils.class.getSimpleName(); + + // DevicePolicyUtils 实例 + private static volatile DevicePolicyUtils sInstance; + + /** + * 获取 DevicePolicyUtils 实例 + * @return {@link DevicePolicyUtils} + */ + public static DevicePolicyUtils getInstance() { + if (sInstance == null) { + synchronized (DevicePolicyUtils.class) { + if (sInstance == null) { + sInstance = new DevicePolicyUtils(); + } + } + } + return sInstance; + } + + // 设备策略管理器 + private DevicePolicyManager mDevicePolicyManager; + + /** + * 获取 DevicePolicyManager + * @return {@link DevicePolicyManager} + */ + public DevicePolicyManager getDevicePolicyManager() { + if (mDevicePolicyManager == null) { + mDevicePolicyManager = AppUtils.getDevicePolicyManager(); + } + return mDevicePolicyManager; + } + + // ================= + // = ComponentName = + // ================= + + /** + * 判断给定的组件是否启动 ( 活跃 ) 中 + * @param admin {@link ComponentName} + * @return {@code true} yes, {@code false} no + */ + public boolean isAdminActive(final ComponentName admin) { + if (admin == null) return false; + try { + return getDevicePolicyManager().isAdminActive(admin); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAdminActive"); + } + return false; + } + + /** + * 获取激活跳转 Intent + * @param admin {@link ComponentName} + * @param tips 提示文案 + * @return {@link Intent} + */ + public Intent getActiveIntent( + final ComponentName admin, + final String tips + ) { + if (admin == null || TextUtils.isEmpty(tips)) return null; + Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN); + intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin); + intent.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, tips); + return intent; + } + + /** + * 激活给定的组件 + * @param admin {@link ComponentName} + * @param tips 提示文案 + * @return {@code true} success, {@code false} fail + */ + public boolean activeAdmin( + final ComponentName admin, + final String tips + ) { + if (admin == null) return false; + if (TextUtils.isEmpty(tips)) return false; + if (isAdminActive(admin)) return true; + return AppUtils.startActivity(getActiveIntent(admin, tips)); + } + + /** + * 移除激活组件 + * @param admin {@link ComponentName} + * @return {@code true} success, {@code false} fail + */ + public boolean removeActiveAdmin(final ComponentName admin) { + try { + getDevicePolicyManager().removeActiveAdmin(admin); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeActiveAdmin"); + } + return false; + } + + /** + * 设置锁屏密码 ( 不需要激活就可以运行 ) + * @return {@code true} success, {@code false} fail + */ + public boolean startLockPassword() { + return AppUtils.startActivity( + new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD) + ); + } + + /** + * 设置锁屏密码 + *
+     *     用户输入的密码必须要有字母 ( 或者其他字符 )
+     *     {@link DevicePolicyManager#PASSWORD_QUALITY_ALPHABETIC}
+     *     用户输入的密码必须要有字母和数字
+     *     {@link DevicePolicyManager#PASSWORD_QUALITY_ALPHANUMERIC}
+     *     用户输入的密码必须要有数字
+     *     {@link DevicePolicyManager#PASSWORD_QUALITY_NUMERIC}
+     *     由设计人员决定的
+     *     {@link DevicePolicyManager#PASSWORD_QUALITY_SOMETHING}
+     *     对密码没有要求
+     *     {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
+     *     ....
+     * 
+ * @param admin {@link ComponentName} + * @param passwordType 密码类型 + * @return {@code true} success, {@code false} fail + */ + public boolean setLockPassword( + final ComponentName admin, + final int passwordType + ) { + if (!isAdminActive(admin)) return false; + try { + Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD); + getDevicePolicyManager().setPasswordQuality(admin, passwordType); + return AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLockPassword"); + } + return false; + } + + /** + * 立刻锁屏 + * @param admin {@link ComponentName} + * @return {@code true} success, {@code false} fail + */ + public boolean lockNow(final ComponentName admin) { + if (!isAdminActive(admin)) return false; + try { + getDevicePolicyManager().lockNow(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "lockNow"); + } + return false; + } + + /** + * 设置多长时间后锁屏 + * @param admin {@link ComponentName} + * @param delayMillis 延时执行时间 + * @return {@code true} success, {@code false} fail + */ + public boolean lockByTime( + final ComponentName admin, + final long delayMillis + ) { + if (!isAdminActive(admin)) return false; + try { + getDevicePolicyManager().setMaximumTimeToLock(admin, delayMillis); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "lockByTime"); + } + return false; + } + + /** + * 清除所有数据 ( 恢复出厂设置 ) + * @param admin {@link ComponentName} + * @return {@code true} success, {@code false} fail + */ + public boolean wipeData(final ComponentName admin) { + if (!isAdminActive(admin)) return false; + try { + getDevicePolicyManager().wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "wipeData"); + } + return false; + } + + /** + * 设置新解锁密码 + * @param admin {@link ComponentName} + * @param password 密码 + * @return {@code true} success, {@code false} fail + */ + public boolean resetPassword( + final ComponentName admin, + final String password + ) { + if (!isAdminActive(admin)) return false; + try { + return getDevicePolicyManager().resetPassword( + password, + DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "resetPassword"); + } + return false; + } + + /** + * 设置存储设备加密 + * @param admin {@link ComponentName} + * @param encrypt 是否加密 + * @return {@code true} success, {@code false} fail + */ + public boolean setStorageEncryption( + final ComponentName admin, + final boolean encrypt + ) { + if (!isAdminActive(admin)) return false; + try { + getDevicePolicyManager().setStorageEncryption(admin, encrypt); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStorageEncryption"); + } + return false; + } + + /** + * 设置停用相机 + * @param admin {@link ComponentName} + * @param disabled 是否禁用相机 + * @return {@code true} success, {@code false} fail + */ + public boolean setCameraDisabled( + final ComponentName admin, + final boolean disabled + ) { + if (!isAdminActive(admin)) return false; + try { + getDevicePolicyManager().setCameraDisabled(admin, disabled); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setCameraDisabled"); + } + return false; + } + + // ========================= + // = Default ComponentName = + // ========================= + + // ComponentName + private ComponentName mComponentName; + + /** + * 获取 ComponentName + * @return {@link ComponentName} + */ + public ComponentName getComponentName() { + return mComponentName; + } + + /** + * 设置 ComponentName + * @param admin {@link ComponentName} + * @return {@link DevicePolicyUtils} + */ + public DevicePolicyUtils setComponentName(final ComponentName admin) { + this.mComponentName = admin; + return this; + } + + /** + * 设置 ComponentName + * @param clazz 继承 {@link DeviceAdminReceiver} + * @return {@link DevicePolicyUtils} + */ + public DevicePolicyUtils setComponentName(final Class clazz) { + try { + setComponentName(new ComponentName(DevUtils.getContext(), clazz)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setComponentName"); + } + return this; + } + + // = + + /** + * 判断给定的组件是否启动 ( 活跃 ) 中 + * @return {@code true} yes, {@code false} no + */ + public boolean isAdminActive() { + return isAdminActive(mComponentName); + } + + /** + * 获取激活跳转 Intent + * @param tips 提示文案 + * @return {@link Intent} + */ + public Intent getActiveIntent(final String tips) { + return getActiveIntent(mComponentName, tips); + } + + /** + * 激活给定的组件 + * @param tips 提示文案 + * @return {@code true} success, {@code false} fail + */ + public boolean activeAdmin(final String tips) { + return activeAdmin(mComponentName, tips); + } + + /** + * 移除激活组件 + * @return {@code true} success, {@code false} fail + */ + public boolean removeActiveAdmin() { + return removeActiveAdmin(mComponentName); + } + + /** + * 设置锁屏密码 + * @param passwordType 密码类型 + * @return {@code true} success, {@code false} fail + */ + public boolean setLockPassword(final int passwordType) { + return setLockPassword(mComponentName, passwordType); + } + + /** + * 立刻锁屏 + * @return {@code true} success, {@code false} fail + */ + public boolean lockNow() { + return lockNow(mComponentName); + } + + /** + * 设置多长时间后锁屏 + * @param delayMillis 延时执行时间 + * @return {@code true} success, {@code false} fail + */ + public boolean lockByTime(final long delayMillis) { + return lockByTime(mComponentName, delayMillis); + } + + /** + * 清除所有数据 ( 恢复出厂设置 ) + * @return {@code true} success, {@code false} fail + */ + public boolean wipeData() { + return wipeData(mComponentName); + } + + /** + * 设置新解锁密码 + * @param password 密码 + * @return {@code true} success, {@code false} fail + */ + public boolean resetPassword(final String password) { + return resetPassword(mComponentName, password); + } + + /** + * 设置存储设备加密 + * @param encrypt 是否加密 + * @return {@code true} success, {@code false} fail + */ + public boolean setStorageEncryption(final boolean encrypt) { + return setStorageEncryption(mComponentName, encrypt); + } + + /** + * 设置停用相机 + * @param disabled 是否禁用相机 + * @return {@code true} success, {@code false} fail + */ + public boolean setCameraDisabled(final boolean disabled) { + return setCameraDisabled(mComponentName, disabled); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/DeviceUtils.java b/lib/DevApp/src/main/java/dev/utils/app/DeviceUtils.java new file mode 100644 index 0000000000..ec79e9f345 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/DeviceUtils.java @@ -0,0 +1,818 @@ +package dev.utils.app; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.res.Configuration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresPermission; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; + +/** + * detail: 设备相关工具类 + * @author Ttt + *
+ *     android.os.Build.BOARD: 获取设备基板名称
+ *     android.os.Build.BOOTLOADER: 获取设备引导程序版本号
+ *     android.os.Build.BRAND: 获取设备品牌
+ *     android.os.Build.CPU_ABI: 获取设备指令集名称 (CPU 的类型 )
+ *     android.os.Build.CPU_ABI2: 获取第二个指令集名称
+ *     android.os.Build.DEVICE: 获取设备驱动名称
+ *     android.os.Build.DISPLAY: 获取设备显示的版本包 ( 在系统设置中显示为版本号 ) 和 ID 一样
+ *     android.os.Build.FINGERPRINT: 设备的唯一标识, 由设备的多个信息拼接合成
+ *     android.os.Build.HARDWARE: 设备硬件名称, 一般和基板名称一样 ( BOARD )
+ *     android.os.Build.HOST: 设备主机地址
+ *     android.os.Build.ID: 设备版本号
+ *     android.os.Build.MODEL : 获取手机的型号 设备名称
+ *     android.os.Build.MANUFACTURER: 获取设备制造商
+ *     android:os.Build.PRODUCT: 整个产品的名称
+ *     android:os.Build.RADIO: 无线电固件版本号, 通常是不可用的 显示 unknown
+ *     android.os.Build.TAGS: 设备标签, 如 release-keys 或测试的 test-keys
+ *     android.os.Build.TIME: 时间
+ *     android.os.Build.TYPE: 设备版本类型 主要为 "user" 或 "eng".
+ *     android.os.Build.USER: 设备用户名 基本上都为 android-build
+ *     android.os.Build.VERSION.RELEASE: 获取系统版本字符串, 如 4.1.2 或 2.2 或 2.3 等
+ *     android.os.Build.VERSION.CODENAME: 设备当前的系统开发代号, 一般使用 REL 代替
+ *     android.os.Build.VERSION.INCREMENTAL: 系统源代码控制值, 一个数字或者 git hash 值
+ *     android.os.Build.VERSION.SDK: 系统的 API 级别 一般使用下面大的 SDK_INT 来查看
+ *     android.os.Build.VERSION.SDK_INT: 系统的 API 级别 数字表示
+ *     

+ * 所需权限 + * + * + * + *

+ * Android Settings 系统属性, 共分三种: + * {@link Settings.Global}: 所有的偏好设置对系统的所有用户公开, 第三方 APP 有读没有写的权限 + * {@link Settings.System}: 包含各种各样的用户偏好系统设置 + * {@link Settings.Secure}: 安全性的用户偏好系统设置, 第三方 APP 有读没有写的权限 + *
+ */ +public final class DeviceUtils { + + private DeviceUtils() { + } + + // 日志 TAG + private static final String TAG = DeviceUtils.class.getSimpleName(); + + // 应用、设备信息 + private static String APP_DEVICE_INFO = null; + + /** + * 获取应用、设备信息 + * @return 应用、设备信息 + */ + public static String getAppDeviceInfo() { + if (TextUtils.isEmpty(APP_DEVICE_INFO)) { + refreshAppDeviceInfo(); + } + return APP_DEVICE_INFO; + } + + /** + * 刷新应用、设备信息 + * @return 应用、设备信息 + */ + public static String refreshAppDeviceInfo() { + try { + StringBuilder builder = new StringBuilder(); + // 获取 APP 版本信息 + String[] versions = ManifestUtils.getAppVersion(); + String versionName = versions[0]; + String versionCode = versions[1]; + String packageName = AppUtils.getPackageName(); + String deviceInfo = DeviceUtils.handlerDeviceInfo( + DeviceUtils.getDeviceInfo(), null + ); + if (TextUtils.isEmpty(versionName) || TextUtils.isEmpty(versionCode) || + TextUtils.isEmpty(packageName) || TextUtils.isEmpty(deviceInfo)) { + return null; + } + // 保存 APP 版本信息 + builder.append("versionName: ").append(versionName); + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append("versionCode: ").append(versionCode); + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append("package: ").append(packageName); + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append(deviceInfo); + // 设置应用、设备信息 + APP_DEVICE_INFO = builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "refreshAppDeviceInfo"); + } + return APP_DEVICE_INFO; + } + + // = + + /** + * 获取设备唯一 UUID + * @return 设备唯一 UUID + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getUUID() { + return PhoneUtils.getUUID(); + } + + /** + * 获取设备唯一 UUID ( 使用硬件信息拼凑出来的 ) + * @return 设备唯一 UUID + *
+     *     https://developer.android.com/training/articles/user-data-ids
+     *     注意事项:
+     *     相同机型、配置手机获取字符串是相同的, 需搭配 {@link #getAndroidId()} 使用
+     *     或者通过接入 OAID SDK 获取唯一码
+     *     http://www.msa-alliance.cn/col.jsp?id=120
+     * 
+ */ + @Deprecated + public static String getUUIDDevice() { + String serial = "serial"; + String m_szDevIDShort = "35" + + Build.BOARD.length() % 10 + Build.BRAND.length() % 10 + + Build.CPU_ABI.length() % 10 + Build.DEVICE.length() % 10 + + Build.DISPLAY.length() % 10 + Build.HOST.length() % 10 + + Build.ID.length() % 10 + Build.MANUFACTURER.length() % 10 + + Build.MODEL.length() % 10 + Build.PRODUCT.length() % 10 + + Build.TAGS.length() % 10 + Build.TYPE.length() % 10 + + Build.USER.length() % 10; // 13 位 + // 使用硬件信息拼凑出来的 15 位号码 + return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString(); + } + + // = + + /** + * 获取设备信息 + * @return 设备信息 + */ + public static Map getDeviceInfo() { + return getDeviceInfo(new HashMap<>()); + } + + /** + * 获取设备信息 + * @param deviceInfoMap 设备信息 Map + * @return 设备信息 + */ + public static Map getDeviceInfo(final Map deviceInfoMap) { + // 获取设备信息类的所有申明的字段, 即包括 public、private 和 protected, 但是不包括父类的申明字段 + Field[] fields = Build.class.getDeclaredFields(); + // 遍历字段 + for (Field field : fields) { + try { + // 取消 Java 的权限控制检查 + field.setAccessible(true); + // 转换当前设备支持的 ABI ( CPU 指令集 ) + if (field.getName().toLowerCase().startsWith("SUPPORTED".toLowerCase())) { + try { + Object object = field.get(null); + // 判断是否数组 + if (object instanceof String[]) { + // 获取类型对应字段的数据, 并保存支持的指令集 [arm64-v8a, armeabi-v7a, armeabi] + deviceInfoMap.put(field.getName(), Arrays.toString((String[]) object)); + continue; + } + } catch (Exception ignored) { + } + } + Object value = field.get(null); + if (value != null) { + // 获取类型对应字段的数据, 并保存 + deviceInfoMap.put(field.getName(), value.toString()); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDeviceInfo"); + } + } + return deviceInfoMap; + } + + /** + * 处理设备信息 + * @param deviceInfoMap 设备信息 Map + * @param errorInfo 错误提示信息, 如获取设备信息失败 + * @return 拼接后的设备信息字符串 + */ + public static String handlerDeviceInfo( + final Map deviceInfoMap, + final String errorInfo + ) { + try { + // 初始化 Builder, 拼接字符串 + StringBuilder builder = new StringBuilder(); + // 遍历设备信息 + for (Map.Entry rnEntry : deviceInfoMap.entrySet()) { + // 获取对应的 key - value + String rnKey = rnEntry.getKey(); + String rnValue = rnEntry.getValue(); + // 保存设备信息 + builder.append(rnKey); + builder.append(" = "); + builder.append(rnValue); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "handlerDeviceInfo"); + } + return errorInfo; + } + + // = + + /** + * 获取设备基板名称 + * @return 设备基板名称 + */ + public static String getBoard() { + return Build.BOARD; + } + + /** + * 获取设备引导程序版本号 + * @return 设备引导程序版本号 + */ + public static String getBootloader() { + return Build.BOOTLOADER; + } + + /** + * 获取设备品牌 + * @return 设备品牌 + */ + public static String getBrand() { + return Build.BRAND; + } + + /** + * 获取支持的第一个指令集 + * @return 支持的第一个指令集 + */ + public static String getCPU_ABI() { + return Build.CPU_ABI; + } + + /** + * 获取支持的第二个指令集 + * @return 支持的第二个指令集 + */ + public static String getCPU_ABI2() { + return Build.CPU_ABI2; + } + + /** + * 获取支持的指令集 如: [arm64-v8a, armeabi-v7a, armeabi] + * @return 支持的指令集 + */ + public static String[] getABIs() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return Build.SUPPORTED_ABIS; + } else { + if (!TextUtils.isEmpty(Build.CPU_ABI2)) { + return new String[]{Build.CPU_ABI, Build.CPU_ABI2}; + } + return new String[]{Build.CPU_ABI}; + } + } + + /** + * 获取支持的 32 位指令集 + * @return 支持的 32 位指令集 + */ + public static String[] getSUPPORTED_32_BIT_ABIS() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return Build.SUPPORTED_32_BIT_ABIS; + } + return null; + } + + /** + * 获取支持的 64 位指令集 + * @return 支持的 64 位指令集 + */ + public static String[] getSUPPORTED_64_BIT_ABIS() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return Build.SUPPORTED_64_BIT_ABIS; + } + return null; + } + + /** + * 获取设备驱动名称 + * @return 设备驱动名称 + */ + public static String getDevice() { + return Build.DEVICE; + } + + /** + * 获取设备显示的版本包 ( 在系统设置中显示为版本号 ) 和 ID 一样 + * @return 设备显示的版本包 + */ + public static String getDisplay() { + return Build.DISPLAY; + } + + /** + * 获取设备的唯一标识, 由设备的多个信息拼接合成 + * @return 设备的唯一标识, 由设备的多个信息拼接合成 + */ + public static String getFingerprint() { + return Build.FINGERPRINT; + } + + /** + * 获取设备硬件名称, 一般和基板名称一样 ( BOARD ) + * @return 设备硬件名称, 一般和基板名称一样 ( BOARD ) + */ + public static String getHardware() { + return Build.HARDWARE; + } + + /** + * 获取设备主机地址 + * @return 设备主机地址 + */ + public static String getHost() { + return Build.HOST; + } + + /** + * 获取设备版本号 + * @return 设备版本号 + */ + public static String getID() { + return Build.ID; + } + + /** + * 获取设备型号 如 RedmiNote4X + * @return 设备型号 + */ + public static String getModel() { + String model = Build.MODEL; + if (model != null) { + model = model.trim().replaceAll("\\s*", ""); + } else { + model = ""; + } + return model; + } + + /** + * 获取设备厂商 如 Xiaomi + * @return 设备厂商 + */ + public static String getManufacturer() { + return Build.MANUFACTURER; + } + + /** + * 获取整个产品的名称 + * @return 整个产品的名称 + */ + public static String getProduct() { + return Build.PRODUCT; + } + + /** + * 获取无线电固件版本号, 通常是不可用的 显示 unknown + * @return 无线电固件版本号 + */ + public static String getRadio() { + return Build.RADIO; + } + + /** + * 获取设备标签, 如 release-keys 或测试的 test-keys + * @return 设备标签 + */ + public static String getTags() { + return Build.TAGS; + } + + /** + * 获取设备时间 + * @return 设备时间 + */ + public static long getTime() { + return Build.TIME; + } + + /** + * 获取设备版本类型 主要为 "user" 或 "eng". + * @return 设备版本类型 + */ + public static String getType() { + return Build.TYPE; + } + + /** + * 获取设备用户名 基本上都为 android-build + * @return 设备用户名 + */ + public static String getUser() { + return Build.USER; + } + + // = + + /** + * 获取 SDK 版本号 + * @return SDK 版本号 + */ + public static int getSDKVersion() { + return Build.VERSION.SDK_INT; + } + + /** + * 获取系统版本号, 如 4.1.2 或 2.2 或 2.3 等 + * @return 系统版本号 + */ + public static String getRelease() { + return Build.VERSION.RELEASE; + } + + /** + * 获取设备当前的系统开发代号, 一般使用 REL 代替 + * @return 设备当前的系统开发代号 + */ + public static String getCodename() { + return Build.VERSION.CODENAME; + } + + /** + * 获取系统源代码控制值, 一个数字或者 git hash 值 + * @return 系统源代码控制值 + */ + public static String getIncremental() { + return Build.VERSION.INCREMENTAL; + } + + /** + * 获取 Android id + *
+     *     在设备首次启动时, 系统会随机生成一个 64 位的数字, 并把这个数字以十六进制字符串的形式保存下来,
+     *     这个十六进制的字符串就是 ANDROID_ID, 当设备被 wipe 后该值会被重置
+     * 
+ * @return Android id + */ + @SuppressLint("HardwareIds") + public static String getAndroidId() { + String androidId = null; + try { + androidId = Settings.Secure.getString( + ResourceUtils.getContentResolver(), Settings.Secure.ANDROID_ID + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAndroidId"); + } + return androidId; + } + + /** + * 获取基带版本 BASEBAND-VER + * @return 基带版本 BASEBAND-VER + */ + public static String getBaseband_Ver() { + String basebandVersion = ""; + try { + @SuppressLint("PrivateApi") + Class clazz = Class.forName("android.os.SystemProperties"); + Object invoker = clazz.newInstance(); + Method method = clazz.getMethod("get", String.class, String.class); + Object result = method.invoke(invoker, "gsm.version.baseband", "no message"); + basebandVersion = (String) result; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBaseband_Ver"); + } + return basebandVersion; + } + + /** + * 获取内核版本 CORE-VER + * @return 内核版本 CORE-VER + */ + public static String getLinuxCore_Ver() { + String kernelVersion = ""; + try { + Process process = Runtime.getRuntime().exec("cat /proc/version"); + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr, 8 * 1024); + + String line; + StringBuilder builder = new StringBuilder(); + while ((line = br.readLine()) != null) { + builder.append(line); + } + String result = builder.toString(); + if (!"".equals(result)) { + String keyword = "version "; + int index = result.indexOf(keyword); + line = result.substring(index + keyword.length()); + index = line.indexOf(' '); + kernelVersion = line.substring(0, index); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLinuxCore_Ver"); + } + return kernelVersion; + } + + // = + + /** + * 判断设备是否 root + * @return {@code true} yes, {@code false} no + */ + public static boolean isDeviceRooted() { + String su = "su"; + String[] locations = { + "/system/bin/", "/system/xbin/", "/sbin/", + "/system/sd/xbin/", "/system/bin/failsafe/", + "/data/local/xbin/", "/data/local/bin/", "/data/local/" + }; + for (String location : locations) { + if (new File(location + su).exists()) { + return true; + } + } + return false; + } + + /** + * 获取是否启用 ADB + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isAdbEnabled() { + try { + return Settings.Secure.getInt( + ResourceUtils.getContentResolver(), + Settings.Global.ADB_ENABLED, 0 + ) > 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAdbEnabled"); + } + return false; + } + + /** + * 是否打开开发者选项 + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean isDevelopmentSettingsEnabled() { + try { + return Settings.Global.getInt( + ResourceUtils.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0 + ) > 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isDevelopmentSettingsEnabled"); + } + return false; + } + + // = + + // Default MAC address reported to a client that does not have the android.permission.LOCAL_MAC_ADDRESS permission. + private static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00"; + + /** + * 获取设备 MAC 地址 + *
+     *     没有打开 Wifi, 则获取 WLAN MAC 地址失败
+     * 
+ * @return 设备 MAC 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getMacAddress() { + String macAddress = getMacAddressByWifiInfo(); + if (!DEFAULT_MAC_ADDRESS.equals(macAddress)) { + return macAddress; + } + macAddress = getMacAddressByNetworkInterface(); + if (!DEFAULT_MAC_ADDRESS.equals(macAddress)) { + return macAddress; + } + macAddress = getMacAddressByInetAddress(); + if (!DEFAULT_MAC_ADDRESS.equals(macAddress)) { + return macAddress; + } + macAddress = getMacAddressByFile(); + if (!DEFAULT_MAC_ADDRESS.equals(macAddress)) { + return macAddress; + } + return null; + } + + /** + * 获取 MAC 地址 + * @return MAC 地址 + */ + @SuppressLint("HardwareIds") + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + private static String getMacAddressByWifiInfo() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + if (wifiManager != null) { + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + if (wifiInfo != null) return wifiInfo.getMacAddress(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMacAddressByWifiInfo"); + } + return DEFAULT_MAC_ADDRESS; + } + + /** + * 获取 MAC 地址 + * @return MAC 地址 + */ + private static String getMacAddressByNetworkInterface() { + try { + Enumeration nis = NetworkInterface.getNetworkInterfaces(); + while (nis.hasMoreElements()) { + NetworkInterface ni = nis.nextElement(); + if (ni == null || !ni.getName().equalsIgnoreCase("wlan0")) continue; + byte[] macBytes = ni.getHardwareAddress(); + if (macBytes != null && macBytes.length > 0) { + StringBuilder builder = new StringBuilder(); + for (byte value : macBytes) { + builder.append(String.format("%02x:", value)); + } + return builder.substring(0, builder.length() - 1); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMacAddressByNetworkInterface"); + } + return DEFAULT_MAC_ADDRESS; + } + + /** + * 通过 InetAddress 获取 Mac 地址 + * @return Mac 地址 + */ + private static String getMacAddressByInetAddress() { + try { + InetAddress inetAddress = getInetAddress(); + if (inetAddress != null) { + NetworkInterface ni = NetworkInterface.getByInetAddress(inetAddress); + if (ni != null) { + byte[] macBytes = ni.getHardwareAddress(); + if (macBytes != null && macBytes.length > 0) { + StringBuilder builder = new StringBuilder(); + for (byte value : macBytes) { + builder.append(String.format("%02x:", value)); + } + return builder.substring(0, builder.length() - 1); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMacAddressByInetAddress"); + } + return DEFAULT_MAC_ADDRESS; + } + + /** + * 获取 InetAddress + * @return {@link InetAddress} + */ + private static InetAddress getInetAddress() { + try { + Enumeration nis = NetworkInterface.getNetworkInterfaces(); + while (nis.hasMoreElements()) { + NetworkInterface ni = nis.nextElement(); + // To prevent phone of xiaomi return "10.0.2.15" + if (!ni.isUp()) continue; + Enumeration addresses = ni.getInetAddresses(); + while (addresses.hasMoreElements()) { + InetAddress inetAddress = addresses.nextElement(); + if (!inetAddress.isLoopbackAddress()) { + String hostAddress = inetAddress.getHostAddress(); + if (hostAddress.indexOf(':') < 0) return inetAddress; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getInetAddress"); + } + return null; + } + + /** + * 获取 MAC 地址 + * @return MAC 地址 + */ + private static String getMacAddressByFile() { + ShellUtils.CommandResult result = ShellUtils.execCmd( + "getprop wifi.interface", false + ); + if (result.isSuccess()) { + String name = result.successMsg; + if (name != null) { + result = ShellUtils.execCmd( + "cat /sys/class/net/" + name + "/address", false + ); + if (result.result == 0) { + String address = result.successMsg; + if (address != null && address.length() > 0) { + return address; + } + } + } + } + return DEFAULT_MAC_ADDRESS; + } + + // = + + /** + * 关机 ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean shutdown() { + return ADBUtils.shutdown(); + } + + /** + * 重启设备 ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean reboot() { + return ADBUtils.reboot(); + } + + /** + * 重启设备 ( 需要 root 权限 ) + * @param reason 传递给内核来请求特殊的引导模式, 如 "recovery" + * 重启到 Fastboot 模式 bootloader + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.REBOOT) + public static boolean reboot(final String reason) { + return ADBUtils.reboot(reason); + } + + /** + * 重启引导到 recovery ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean rebootToRecovery() { + return ADBUtils.rebootToRecovery(); + } + + /** + * 重启引导到 bootloader ( 需要 root 权限 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean rebootToBootloader() { + return ADBUtils.rebootToBootloader(); + } + + // = + + /** + * 判断是否是平板 + * @return {@code true} yes, {@code false} no + */ + public static boolean isTablet() { + try { + return (ResourceUtils.getConfiguration().screenLayout + & Configuration.SCREENLAYOUT_SIZE_MASK + ) >= Configuration.SCREENLAYOUT_SIZE_LARGE; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isTablet"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/DialogUtils.java b/lib/DevApp/src/main/java/dev/utils/app/DialogUtils.java new file mode 100644 index 0000000000..324106d1ed --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/DialogUtils.java @@ -0,0 +1,1825 @@ +package dev.utils.app; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.widget.PopupWindow; + +import androidx.annotation.ArrayRes; +import androidx.annotation.ColorInt; +import androidx.annotation.RequiresApi; +import androidx.annotation.StyleRes; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; + +import dev.utils.LogPrintUtils; + +/** + * detail: Dialog 操作相关工具类 + * @author Ttt + */ +public final class DialogUtils { + + private DialogUtils() { + } + + // 日志 TAG + private static final String TAG = DialogUtils.class.getSimpleName(); + + // ============== + // = Dialog 相关 = + // ============== + + /** + * 设置 Dialog 状态栏颜色 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return {@link Window} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static Window setStatusBarColor( + final Dialog dialog, + @ColorInt final int color + ) { + return setStatusBarColor(WindowUtils.getWindow(dialog), color); + } + + /** + * 设置 Dialog 状态栏颜色 + * @param window Dialog Window + * @param color Dialog StatusBar Color + * @return {@link Window} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static Window setStatusBarColor( + final Window window, + @ColorInt final int color + ) { + WindowUtils.get().setStatusBarColor(window, color); + return window; + } + + /** + * 设置 Dialog 高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return {@link Window} + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static Window setSemiTransparentStatusBarColor( + final Dialog dialog, + @ColorInt final int color + ) { + return setSemiTransparentStatusBarColor( + WindowUtils.getWindow(dialog), color + ); + } + + /** + * 设置 Dialog 高版本状态栏蒙层 + * @param window Dialog Window + * @param color Dialog StatusBar Color + * @return {@link Window} + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static Window setSemiTransparentStatusBarColor( + final Window window, + @ColorInt final int color + ) { + WindowUtils.get().setSemiTransparentStatusBarColor(window, color); + return window; + } + + /** + * 设置 Dialog 状态栏颜色、高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return {@link Window} + */ + public static Window setStatusBarColorAndFlag( + final Dialog dialog, + @ColorInt final int color, + final boolean addFlags + ) { + return setStatusBarColorAndFlag( + WindowUtils.getWindow(dialog), color, addFlags + ); + } + + /** + * 设置 Dialog 状态栏颜色、高版本状态栏蒙层 + * @param window Dialog Window + * @param color Dialog StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return {@link Window} + */ + public static Window setStatusBarColorAndFlag( + final Window window, + @ColorInt final int color, + final boolean addFlags + ) { + WindowUtils.get().setStatusBarColorAndFlag(window, color, addFlags); + return window; + } + + /** + * 获取 Dialog Window LayoutParams + * @param dialog {@link Dialog} + * @return {@link WindowManager.LayoutParams} + */ + public static WindowManager.LayoutParams getAttributes(final Dialog dialog) { + return WindowUtils.get(dialog).getAttributes(); + } + + /** + * 设置 Dialog Window LayoutParams + * @param dialog {@link Dialog} + * @param params {@link WindowManager.LayoutParams} + * @param 泛型 + * @return {@link Dialog} + */ + public static T setAttributes( + final T dialog, + final WindowManager.LayoutParams params + ) { + WindowUtils.get(dialog).setAttributes(params); + return dialog; + } + + // = + + /** + * 设置 Dialog 宽度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setWidth( + final T dialog, + final int width + ) { + WindowUtils.get(dialog).setWidthByParams(width); + return dialog; + } + + /** + * 设置 Dialog 高度 + * @param dialog {@link Dialog} + * @param height 高度 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setHeight( + final T dialog, + final int height + ) { + WindowUtils.get(dialog).setHeightByParams(height); + return dialog; + } + + /** + * 设置 Dialog 宽度、高度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @param height 高度 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setWidthHeight( + final T dialog, + final int width, + final int height + ) { + WindowUtils.get(dialog).setWidthHeightByParams(width, height); + return dialog; + } + + /** + * 设置 Dialog X 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setX( + final T dialog, + final int x + ) { + WindowUtils.get(dialog).setXByParams(x); + return dialog; + } + + /** + * 设置 Dialog Y 轴坐标 + * @param dialog {@link Dialog} + * @param y Y 轴坐标 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setY( + final T dialog, + final int y + ) { + WindowUtils.get(dialog).setYByParams(y); + return dialog; + } + + /** + * 设置 Dialog X、Y 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setXY( + final T dialog, + final int x, + final int y + ) { + WindowUtils.get(dialog).setXYByParams(x, y); + return dialog; + } + + /** + * 设置 Dialog Gravity + * @param dialog {@link Dialog} + * @param gravity 重心 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setGravity( + final T dialog, + final int gravity + ) { + WindowUtils.get(dialog).setGravityByParams(gravity); + return dialog; + } + + /** + * 设置 Dialog 透明度 + * @param dialog {@link Dialog} + * @param dimAmount 透明度 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setDimAmount( + final T dialog, + final float dimAmount + ) { + WindowUtils.get(dialog).setDimAmountByParams(dimAmount); + return dialog; + } + + // = + + /** + * 设置是否允许返回键关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setCancelable( + final T dialog, + final boolean cancel + ) { + if (dialog != null) { + // 返回键关闭 + dialog.setCancelable(cancel); + } + return dialog; + } + + /** + * 设置是否允许点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setCanceledOnTouchOutside( + final T dialog, + final boolean cancel + ) { + if (dialog != null) { + // 点击其他地方自动关闭 + dialog.setCanceledOnTouchOutside(cancel); + } + return dialog; + } + + /** + * 设置是否允许 返回键关闭、点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @param 泛型 + * @return {@link Dialog} + */ + public static T setCancelableAndTouchOutside( + final T dialog, + final boolean cancel + ) { + if (dialog != null) { + // 返回键关闭 + dialog.setCancelable(cancel); + // 点击其他地方自动关闭 + dialog.setCanceledOnTouchOutside(cancel); + } + return dialog; + } + + // = + + /** + * 获取 Dialog 是否显示 + * @param dialog {@link Dialog} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShowing(final Dialog dialog) { + return (dialog != null && dialog.isShowing()); + } + + /** + * 获取 Dialog 是否显示 + * @param dialogFragment {@link DialogFragment} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShowing(final DialogFragment dialogFragment) { + return (dialogFragment != null && dialogFragment.getDialog() != null + && dialogFragment.getDialog().isShowing()); + } + + /** + * 获取 PopupWindow 是否显示 + * @param popupWindow {@link PopupWindow} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShowing(final PopupWindow popupWindow) { + return (popupWindow != null && popupWindow.isShowing()); + } + + // ============== + // = Dialog 操作 = + // ============== + + /** + * 显示 Dialog + * @param dialog {@link Dialog} + * @param 泛型 + * @return {@link Dialog} + */ + public static T showDialog(final T dialog) { + if (dialog != null && !dialog.isShowing()) { + try { + dialog.show(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "showDialog"); + } + } + return dialog; + } + + /** + * 关闭 Dialog + * @param dialog {@link Dialog} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeDialog(final Dialog dialog) { + if (dialog != null && dialog.isShowing()) { + try { + dialog.dismiss(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeDialog"); + } + } + return false; + } + + /** + * 关闭多个 Dialog + * @param dialogs {@link Dialog} 数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean closeDialogs(final Dialog... dialogs) { + if (dialogs != null && dialogs.length != 0) { + for (Dialog dialog : dialogs) { + closeDialog(dialog); + } + return true; + } + return false; + } + + // = + + /** + * 关闭 DialogFragment + * @param dialog {@link DialogFragment} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeDialog(final DialogFragment dialog) { + if (dialog != null) { + try { + dialog.dismiss(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeDialog"); + } + } + return false; + } + + /** + * 关闭多个 DialogFragment + * @param dialogs {@link DialogFragment} 数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean closeDialogs(final DialogFragment... dialogs) { + if (dialogs != null && dialogs.length != 0) { + for (DialogFragment dialog : dialogs) { + closeDialog(dialog); + } + return true; + } + return false; + } + + // = + + /** + * 关闭 PopupWindow + * @param popupWindow {@link PopupWindow} + * @return {@code true} success, {@code false} fail + */ + public static boolean closePopupWindow(final PopupWindow popupWindow) { + if (popupWindow != null && popupWindow.isShowing()) { + try { + popupWindow.dismiss(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closePopupWindow"); + } + } + return false; + } + + /** + * 关闭多个 PopupWindow + * @param popupWindows {@link PopupWindow} 数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean closePopupWindows(final PopupWindow... popupWindows) { + if (popupWindows != null && popupWindows.length != 0) { + for (PopupWindow popupWindow : popupWindows) { + closePopupWindow(popupWindow); + } + return true; + } + return false; + } + + // = + + /** + * 显示 Dialog 并关闭其他 Dialog + * @param dialog {@link Dialog} + * @param dialogs 待关闭 Dialog 数组 + * @param 泛型 + * @return {@link Dialog} + */ + public static T showDialogAndCloses( + final T dialog, + final Dialog... dialogs + ) { + closeDialogs(dialogs); + return showDialog(dialog); + } + + /** + * 显示 Dialog 并关闭其他 Dialog + * @param dialog {@link Dialog} + * @param dialogs 待关闭 DialogFragment 数组 + * @param 泛型 + * @return {@link Dialog} + */ + public static T showDialogAndCloses( + final T dialog, + final DialogFragment... dialogs + ) { + closeDialogs(dialogs); + return showDialog(dialog); + } + + // = + + /** + * detail: Dialog 事件 + * @author Ttt + */ + public static abstract class DialogListener { + + /** + * 最左边按钮点击事件 + * @param dialog {@link DialogInterface} + */ + public void onLeftButton(DialogInterface dialog) { + } + + /** + * 最右边按钮点击事件 + * @param dialog {@link DialogInterface} + */ + public abstract void onRightButton(DialogInterface dialog); + + /** + * 关闭通知 + * @param dialog {@link DialogInterface} + */ + public void onDismiss(DialogInterface dialog) { + } + } + + /** + * 创建提示 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param rightBtn 右边按钮文案 + * @return {@link AlertDialog} + */ + public static AlertDialog createAlertDialog( + final Context context, + final String title, + final String content, + final String rightBtn + ) { + return createAlertDialog(context, title, content, null, rightBtn, null); + } + + /** + * 创建提示 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param rightBtn 右边按钮文案 + * @param dialogListener 事件通知 + * @return {@link AlertDialog} + */ + public static AlertDialog createAlertDialog( + final Context context, + final String title, + final String content, + final String rightBtn, + final DialogListener dialogListener + ) { + return createAlertDialog(context, title, content, null, rightBtn, dialogListener); + } + + /** + * 创建提示 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param leftBtn 左边按钮文案 + * @param rightBtn 右边按钮文案 + * @return {@link AlertDialog} + */ + public static AlertDialog createAlertDialog( + final Context context, + final String title, + final String content, + final String leftBtn, + final String rightBtn + ) { + return createAlertDialog(context, title, content, leftBtn, rightBtn, null); + } + + /** + * 创建提示 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param leftBtn 左边按钮文案 + * @param rightBtn 右边按钮文案 + * @param dialogListener 事件通知 + * @return {@link AlertDialog} + */ + public static AlertDialog createAlertDialog( + final Context context, + final String title, + final String content, + final String leftBtn, + final String rightBtn, + final DialogListener dialogListener + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(title); + builder.setMessage(content); + + if (leftBtn != null) { + if (dialogListener == null) { + builder.setNegativeButton(leftBtn, null); + } else { + builder.setNegativeButton( + leftBtn, (dialog, which) -> dialogListener.onLeftButton(dialog) + ); + } + } + + if (rightBtn != null) { + if (dialogListener == null) { + builder.setPositiveButton(rightBtn, null); + } else { + builder.setPositiveButton( + rightBtn, (dialog, which) -> dialogListener.onRightButton(dialog) + ); + } + } + + if (dialogListener != null) { + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> dialogListener.onDismiss(dialog)); + } + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createAlertDialog"); + } + return null; + } + + // = + + /** + * 创建加载中 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @return {@link ProgressDialog} + */ + public static ProgressDialog createProgressDialog( + final Context context, + final String title, + final String content + ) { + return createProgressDialog(context, title, content, false, null); + } + + /** + * 创建加载中 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param isCancel 是否可以返回键关闭 + * @return {@link ProgressDialog} + */ + public static ProgressDialog createProgressDialog( + final Context context, + final String title, + final String content, + final boolean isCancel + ) { + return createProgressDialog(context, title, content, isCancel, null); + } + + /** + * 创建加载中 Dialog ( 原生样式 ) + * @param context {@link Context} + * @param title dialog 标题 + * @param content dialog 内容 + * @param isCancel 是否可以返回键关闭 + * @param cancelListener 取消事件 + * @return {@link ProgressDialog} + */ + public static ProgressDialog createProgressDialog( + final Context context, + final String title, + final String content, + final boolean isCancel, + final DialogInterface.OnCancelListener cancelListener + ) { + try { + ProgressDialog dialog = new ProgressDialog(context); + dialog.setTitle(title); + dialog.setMessage(content); + dialog.setIndeterminate(false); + dialog.setCancelable(isCancel); + dialog.setOnCancelListener(cancelListener); + return dialog; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createProgressDialog"); + } + return null; + } + + // = + + /** + * 自动关闭 dialog + * @param dialog {@link Dialog} + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param 泛型 + * @return {@link Dialog} + */ + public static T autoCloseDialog( + final T dialog, + final long delayMillis, + final Handler handler + ) { + if (dialog != null && dialog.isShowing()) { + if (handler != null) { + handler.postDelayed(() -> closeDialog(dialog), delayMillis); + } + } + return dialog; + } + + /** + * 自动关闭 DialogFragment + * @param dialog {@link DialogFragment} + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param 泛型 + * @return {@link Dialog} + */ + public static T autoCloseDialog( + final T dialog, + final long delayMillis, + final Handler handler + ) { + if (dialog != null) { + if (handler != null) { + handler.postDelayed(() -> closeDialog(dialog), delayMillis); + } + } + return dialog; + } + + /** + * 自动关闭 PopupWindow + * @param popupWindow {@link PopupWindow} + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param 泛型 + * @return {@link PopupWindow} + */ + public static T autoClosePopupWindow( + final T popupWindow, + final long delayMillis, + final Handler handler + ) { + if (popupWindow != null && popupWindow.isShowing()) { + if (handler != null) { + handler.postDelayed(() -> closePopupWindow(popupWindow), delayMillis); + } + } + return popupWindow; + } + + // ================= + // = 单选列表 Dialog = + // ================= + + /** + * detail: Dialog 单选事件 + * @author Ttt + */ + public static abstract class SingleChoiceListener { + + /** + * 单选选中触发 + * @param dialog {@link DialogInterface} + * @param which 选中索引 + */ + public void onSingleChoiceItems( + DialogInterface dialog, + int which + ) { + } + + /** + * 确认选择触发 + * @param dialog {@link DialogInterface} + */ + public void onPositiveButton(DialogInterface dialog) { + } + + /** + * 取消选择触发 + * @param dialog {@link DialogInterface} + */ + public void onCancel(DialogInterface dialog) { + } + + /** + * 关闭通知 + * @param dialog {@link DialogInterface} + */ + public void onDismiss(DialogInterface dialog) { + } + } + + // = + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + @ArrayRes final int itemsId, + final String title, + final Drawable icon, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceListDialog( + context, itemsId, title, icon, null, + positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + @ArrayRes final int itemsId, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceListDialog( + context, itemsId, title, icon, negativeBtnText, + positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + @ArrayRes final int itemsId, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setItems(itemsId, (dialog, which) -> { + if (singleChoiceListener != null) { + singleChoiceListener.onSingleChoiceItems(dialog, which); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onPositiveButton(dialog); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (singleChoiceListener != null) { + singleChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createSingleChoiceListDialog"); + } + return null; + } + + // = + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + final CharSequence[] items, + final String title, + final Drawable icon, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceListDialog( + context, items, title, icon, null, + positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + final CharSequence[] items, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceListDialog( + context, items, title, icon, negativeBtnText, + positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选列表样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceListDialog( + final Context context, + final CharSequence[] items, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setItems(items, (dialog, which) -> { + if (singleChoiceListener != null) { + singleChoiceListener.onSingleChoiceItems(dialog, which); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onPositiveButton(dialog); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (singleChoiceListener != null) { + singleChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createSingleChoiceListDialog"); + } + return null; + } + + // ============== + // = 单选 Dialog = + // ============== + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final int checkedItem, + final String title, + final Drawable icon, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceDialog( + context, itemsId, checkedItem, title, icon, + null, positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final int checkedItem, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceDialog( + context, itemsId, checkedItem, title, icon, negativeBtnText, + positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final int checkedItem, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setSingleChoiceItems(itemsId, checkedItem, (dialog, which) -> { + if (singleChoiceListener != null) { + singleChoiceListener.onSingleChoiceItems(dialog, which); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onPositiveButton(dialog); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (singleChoiceListener != null) { + singleChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createSingleChoiceDialog"); + } + return null; + } + + // = + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + final CharSequence[] items, + final int checkedItem, + final String title, + final Drawable icon, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceDialog( + context, items, checkedItem, title, icon, + null, positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + final CharSequence[] items, + final int checkedItem, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener + ) { + return createSingleChoiceDialog( + context, items, checkedItem, title, icon, + negativeBtnText, positiveBtnText, singleChoiceListener, 0 + ); + } + + /** + * 创建单选样式 Dialog + * @param context {@link Context} + * @param items 单选文案数组 + * @param checkedItem 默认选中索引 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param singleChoiceListener 单选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createSingleChoiceDialog( + final Context context, + final CharSequence[] items, + final int checkedItem, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final SingleChoiceListener singleChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setSingleChoiceItems(items, checkedItem, (dialog, which) -> { + if (singleChoiceListener != null) { + singleChoiceListener.onSingleChoiceItems(dialog, which); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onPositiveButton(dialog); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (singleChoiceListener != null) { + singleChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (singleChoiceListener != null) { + singleChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createSingleChoiceDialog"); + } + return null; + } + + // ============== + // = 多选 Dialog = + // ============== + + /** + * detail: Dialog 多选事件 + * @author Ttt + */ + public static abstract class MultiChoiceListener { + + /** + * 多选选中触发 + * @param dialog {@link DialogInterface} + * @param which 操作索引 + * @param isChecked 是否选中 + */ + public void onMultiChoiceItems( + DialogInterface dialog, + int which, + boolean isChecked + ) { + } + + /** + * 确认选择触发 + * @param dialog {@link DialogInterface} + * @param checkedItems 选中的数据 + */ + public void onPositiveButton( + DialogInterface dialog, + boolean[] checkedItems + ) { + } + + /** + * 取消选择触发 + * @param dialog {@link DialogInterface} + */ + public void onCancel(DialogInterface dialog) { + } + + /** + * 关闭通知 + * @param dialog {@link DialogInterface} + */ + public void onDismiss(DialogInterface dialog) { + } + } + + // = + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener + ) { + return createMultiChoiceDialog( + context, itemsId, checkedItems, title, icon, + null, positiveBtnText, multiChoiceListener, 0 + ); + } + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener + ) { + return createMultiChoiceDialog( + context, itemsId, checkedItems, title, icon, + negativeBtnText, positiveBtnText, multiChoiceListener, 0 + ); + } + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param itemsId R.arrays 数据源 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + @ArrayRes final int itemsId, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setMultiChoiceItems(itemsId, checkedItems, (dialog, which, isChecked) -> { + if (multiChoiceListener != null) { + multiChoiceListener.onMultiChoiceItems(dialog, which, isChecked); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (multiChoiceListener != null) { + multiChoiceListener.onPositiveButton(dialog, checkedItems); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (multiChoiceListener != null) { + multiChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (multiChoiceListener != null) { + multiChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createMultiChoiceDialog"); + } + return null; + } + + // = + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param items 多选文案数组 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + final CharSequence[] items, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener + ) { + return createMultiChoiceDialog( + context, items, checkedItems, title, icon, + null, positiveBtnText, multiChoiceListener, 0 + ); + } + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param items 多选文案数组 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + final CharSequence[] items, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener + ) { + return createMultiChoiceDialog( + context, items, checkedItems, title, icon, + negativeBtnText, positiveBtnText, multiChoiceListener, 0 + ); + } + + /** + * 创建多选样式 Dialog + * @param context {@link Context} + * @param items 多选文案数组 + * @param checkedItems 选中状态 + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param multiChoiceListener 多选事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createMultiChoiceDialog( + final Context context, + final CharSequence[] items, + final boolean[] checkedItems, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final MultiChoiceListener multiChoiceListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + builder.setMultiChoiceItems(items, checkedItems, (dialog, which, isChecked) -> { + if (multiChoiceListener != null) { + multiChoiceListener.onMultiChoiceItems(dialog, which, isChecked); + } + }); + + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (multiChoiceListener != null) { + multiChoiceListener.onPositiveButton(dialog, checkedItems); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (multiChoiceListener != null) { + multiChoiceListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (multiChoiceListener != null) { + multiChoiceListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createMultiChoiceDialog"); + } + return null; + } + + // =================== + // = 设置 View Dialog = + // =================== + + /** + * detail: 自定义 View Dialog 事件 + * @author Ttt + */ + public static abstract class ViewDialogListener { + + /** + * 确认触发 + * @param dialog {@link DialogInterface} + */ + public void onPositiveButton(DialogInterface dialog) { + } + + /** + * 取消触发 + * @param dialog {@link DialogInterface} + */ + public void onCancel(DialogInterface dialog) { + } + + /** + * 关闭通知 + * @param dialog {@link DialogInterface} + */ + public void onDismiss(DialogInterface dialog) { + } + } + + // = + + /** + * 创建自定义 View 样式 Dialog + * @param context {@link Context} + * @param view {@link View} + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param viewDialogListener 自定义 View Dialog 事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createViewDialog( + final Context context, + final View view, + final String title, + final Drawable icon, + final String positiveBtnText, + final ViewDialogListener viewDialogListener + ) { + return createViewDialog( + context, view, title, icon, null, + positiveBtnText, viewDialogListener, 0 + ); + } + + /** + * 创建自定义 View 样式 Dialog + * @param context {@link Context} + * @param view {@link View} + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param viewDialogListener 自定义 View Dialog 事件 + * @return {@link AlertDialog} + */ + public static AlertDialog createViewDialog( + final Context context, + final View view, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final ViewDialogListener viewDialogListener + ) { + return createViewDialog( + context, view, title, icon, negativeBtnText, + positiveBtnText, viewDialogListener, 0 + ); + } + + /** + * 创建自定义 View 样式 Dialog + * @param context {@link Context} + * @param view {@link View} + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param positiveBtnText 确认按钮文案 + * @param viewDialogListener 自定义 View Dialog 事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createViewDialog( + final Context context, + final View view, + final String title, + final Drawable icon, + final String positiveBtnText, + final ViewDialogListener viewDialogListener, + @StyleRes final int themeResId + ) { + return createViewDialog( + context, view, title, icon, null, + positiveBtnText, viewDialogListener, themeResId + ); + } + + /** + * 创建自定义 View 样式 Dialog + * @param context {@link Context} + * @param view {@link View} + * @param title 标题 + * @param icon 图标 ( 标题左侧 ) + * @param negativeBtnText 取消按钮文案 + * @param positiveBtnText 确认按钮文案 + * @param viewDialogListener 自定义 View Dialog 事件 + * @param themeResId 样式 + * @return {@link AlertDialog} + */ + public static AlertDialog createViewDialog( + final Context context, + final View view, + final String title, + final Drawable icon, + final String negativeBtnText, + final String positiveBtnText, + final ViewDialogListener viewDialogListener, + @StyleRes final int themeResId + ) { + try { + AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId); + if (!TextUtils.isEmpty(title)) { + builder.setTitle(title); + } + if (icon != null) { + builder.setIcon(icon); + } + if (view != null) { + builder.setView(view); + } + // 判断是否存在确认按钮文案 + if (!TextUtils.isEmpty(positiveBtnText)) { + builder.setPositiveButton(positiveBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (viewDialogListener != null) { + viewDialogListener.onPositiveButton(dialog); + } + }); + } + + // 判断是否存在取消按钮文案 + if (!TextUtils.isEmpty(negativeBtnText)) { + builder.setNegativeButton(negativeBtnText, (dialog, which) -> { + dialog.dismiss(); + // 触发回调 + if (viewDialogListener != null) { + viewDialogListener.onCancel(dialog); + } + }); + } + + // 设置 Dialog 关闭监听 + builder.setOnDismissListener(dialog -> { + if (viewDialogListener != null) { + viewDialogListener.onDismiss(dialog); + } + }); + return builder.create(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createViewDialog"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/EditTextUtils.java b/lib/DevApp/src/main/java/dev/utils/app/EditTextUtils.java new file mode 100644 index 0000000000..feab839aab --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/EditTextUtils.java @@ -0,0 +1,894 @@ +package dev.utils.app; + +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.Editable; +import android.text.InputFilter; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.DigitsKeyListener; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.KeyListener; +import android.text.method.PasswordTransformationMethod; +import android.text.method.TransformationMethod; +import android.view.View; +import android.widget.EditText; + +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; + +import java.util.UUID; + +import dev.utils.LogPrintUtils; + +/** + * detail: EditText 工具类 + * @author Ttt + *
+ *     EditText 属性大全 ( 不局限于仅仅是 EditText )
+ *     @see 
+ *     

+ * EditText 多行显示及所有属性 + * @see
+ *

+ * EditText 设置不自动获取焦点, 点击后才获取, 并弹出软键盘 + * @see
+ *

+ * EditText 点击无反应解决办法 + * @see
+ *

+ * EditText 限制输入的 4 种方法 + * @see
+ *

+ * 自定义 EditText 光标和下划线颜色 + * @see
+ * @see + *
+ */ +public final class EditTextUtils { + + private EditTextUtils() { + } + + // 日志 TAG + private static final String TAG = EditTextUtils.class.getSimpleName(); + + // ================ + // = 获取 EditText = + // ================ + + /** + * 获取 EditText + * @param view {@link View} + * @param 泛型 + * @return {@link EditText} + */ + public static T getEditText(final View view) { + if (view instanceof EditText) { + try { + return (T) view; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getEditText"); + } + } + return null; + } + + // = + + /** + * 获取输入的内容 + * @param editText {@link EditText} + * @param 泛型 + * @return {@link EditText} + */ + public static String getText(final T editText) { + if (editText != null) { + return editText.getText().toString(); + } + return ""; + } + + /** + * 获取输入的内容长度 + * @param editText {@link EditText} + * @param 泛型 + * @return {@link EditText} + */ + public static int getTextLength(final T editText) { + return getText(editText).length(); + } + + // = + + /** + * 设置内容 + * @param editText {@link EditText} + * @param content 文本内容 + * @param 泛型 + * @return {@link EditText} + */ + public static T setText( + final T editText, + final CharSequence content + ) { + return setText(editText, content, true); + } + + /** + * 设置内容 + * @param editText {@link EditText} + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param 泛型 + * @return {@link EditText} + */ + public static T setText( + final T editText, + final CharSequence content, + final boolean isSelect + ) { + if (editText != null && content != null) { + editText.setText(content); + // 设置光标 + if (isSelect) { + editText.setSelection(editText.getText().toString().length()); + } + } + return editText; + } + + /** + * 设置多个 EditText 文本 + * @param content 文本内容 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setTexts( + final CharSequence content, + final View... views + ) { + if (views != null) { + for (View view : views) { + setText(getEditText(view), content); + } + return true; + } + return false; + } + + /** + * 设置多个 EditText 文本 + * @param content 文本内容 + * @param views EditText[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTexts( + final CharSequence content, + final T... views + ) { + if (views != null) { + for (T view : views) { + setText(view, content); + } + return true; + } + return false; + } + + // = + + /** + * 追加内容 ( 当前光标位置追加 ) + * @param editText {@link EditText} + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param 泛型 + * @return {@link EditText} + */ + public static T insert( + final T editText, + final CharSequence content, + final boolean isSelect + ) { + return insert(editText, content, editText.getSelectionStart(), isSelect); + } + + /** + * 追加内容 + * @param editText {@link EditText} + * @param content 文本内容 + * @param start 开始添加的位置 + * @param isSelect 是否设置光标 + * @param 泛型 + * @return {@link EditText} + */ + public static T insert( + final T editText, + final CharSequence content, + final int start, + final boolean isSelect + ) { + if (editText != null && !TextUtils.isEmpty(content)) { + try { + Editable editable = editText.getText(); + // 在指定位置 追加内容 + editable.insert(start, content); + // 设置光标 + if (isSelect) { + editText.setSelection(editText.getText().toString().length()); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "insert"); + } + } + return editText; + } + + // = + + /** + * 设置长度限制 + * @param editText {@link EditText} + * @param maxLength 长度限制 + * @param 泛型 + * @return {@link EditText} + */ + public static T setMaxLength( + final T editText, + final int maxLength + ) { + if (editText != null && maxLength > 0) { + // 设置最大长度限制 + InputFilter[] filters = {new InputFilter.LengthFilter(maxLength)}; + editText.setFilters(filters); + } + return editText; + } + + /** + * 设置长度限制, 并且设置内容 + * @param editText {@link EditText} + * @param content 文本内容 + * @param maxLength 长度限制 + * @param 泛型 + * @return {@link EditText} + */ + public static T setMaxLengthAndText( + final T editText, + final CharSequence content, + final int maxLength + ) { + return setText(setMaxLength(editText, maxLength), content); + } + + // ======= + // = 光标 = + // ======= + + /** + * 是否显示光标 + * @param editText {@link EditText} + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean isCursorVisible(final T editText) { + if (editText != null) { + return editText.isCursorVisible(); + } + return false; + } + + /** + * 设置是否显示光标 + * @param editText {@link EditText} + * @param visible 是否显示光标 + * @param 泛型 + * @return {@link EditText} + */ + public static T setCursorVisible( + final T editText, + final boolean visible + ) { + if (editText != null) { + editText.setCursorVisible(visible); + } + return editText; + } + + /** + * 设置光标 + * @param editText {@link EditText} + * @param textCursorDrawable 光标 + * @param 泛型 + * @return {@link EditText} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static T setTextCursorDrawable( + final T editText, + @DrawableRes final int textCursorDrawable + ) { + if (editText != null) { + editText.setTextCursorDrawable(textCursorDrawable); + } + return editText; + } + + /** + * 设置光标 + * @param editText {@link EditText} + * @param textCursorDrawable 光标 + * @param 泛型 + * @return {@link EditText} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static T setTextCursorDrawable( + final T editText, + final Drawable textCursorDrawable + ) { + if (editText != null) { + editText.setTextCursorDrawable(textCursorDrawable); + } + return editText; + } + + // = + + /** + * 获取光标位置 + * @param editText {@link EditText} + * @param 泛型 + * @return 光标位置 + */ + public static int getSelectionStart(final T editText) { + if (editText != null) { + return editText.getSelectionStart(); + } + return 0; + } + + /** + * 设置光标在第一位 + * @param editText {@link EditText} + * @param 泛型 + * @return {@link EditText} + */ + public static T setSelectionToTop(final T editText) { + return setSelection(editText, 0); + } + + /** + * 设置光标在最后一位 + * @param editText {@link EditText} + * @param 泛型 + * @return {@link EditText} + */ + public static T setSelectionToBottom(final T editText) { + return setSelection(editText, getTextLength(editText)); + } + + /** + * 设置光标位置 + * @param editText {@link EditText} + * @param index 光标位置 + * @param 泛型 + * @return {@link EditText} + */ + public static T setSelection( + final T editText, + final int index + ) { + if (editText != null && index >= 0) { + // 获取数据长度 + int length = editText.getText().toString().length(); + // 设置光标 + editText.setSelection(Math.min(index, length)); + } + return editText; + } + + // = + + /** + * 设置输入类型 + * @param editText {@link EditText} + * @param 泛型 + * @return 输入类型 + */ + public static int getInputType(final T editText) { + if (editText != null) { + return editText.getInputType(); + } + return 0; + } + + /** + * 设置输入类型 + * @param editText {@link EditText} + * @param type 类型 + * @param 泛型 + * @return {@link EditText} + */ + public static T setInputType( + final T editText, + final int type + ) { + if (editText != null) { + editText.setInputType(type); + } + return editText; + } + + // = + + /** + * 设置软键盘右下角按钮类型 + * @param editText {@link EditText} + * @param 泛型 + * @return 软键盘右下角按钮类型 + */ + public static int getImeOptions(final T editText) { + if (editText != null) { + return editText.getImeOptions(); + } + return 0; + } + + /** + * 设置软键盘右下角按钮类型 + * @param editText {@link EditText} + * @param imeOptions 软键盘按钮类型 + * @param 泛型 + * @return {@link EditText} + */ + public static T setImeOptions( + final T editText, + final int imeOptions + ) { + if (editText != null) { + editText.setImeOptions(imeOptions); + } + return editText; + } + + // = + + /** + * 获取文本视图显示转换 + * @param editText {@link EditText} + * @param 泛型 + * @return {@link TransformationMethod} + */ + public static TransformationMethod getTransformationMethod(final T editText) { + if (editText != null) { + return editText.getTransformationMethod(); + } + return null; + } + + /** + * 设置文本视图显示转换 + * @param editText {@link EditText} + * @param method {@link TransformationMethod} + * @param 泛型 + * @return {@link EditText} + */ + public static T setTransformationMethod( + final T editText, + final TransformationMethod method + ) { + if (editText != null) { + editText.setTransformationMethod(method); + } + return editText; + } + + // = + + /** + * 设置密码文本视图显示转换 + * @param editText {@link EditText} + * @param isDisplayPassword 是否显示密码 + * @param 泛型 + * @return {@link EditText} + */ + public static T setTransformationMethod( + final T editText, + final boolean isDisplayPassword + ) { + return setTransformationMethod(editText, isDisplayPassword, true); + } + + /** + * 设置密码文本视图显示转换 + * @param editText {@link EditText} + * @param isDisplayPassword 是否显示密码 + * @param isSelectBottom 是否设置光标到最后 + * @param 泛型 + * @return {@link EditText} + */ + public static T setTransformationMethod( + final T editText, + final boolean isDisplayPassword, + final boolean isSelectBottom + ) { + if (editText != null) { + // 获取光标位置 + int curSelect = 0; + if (!isSelectBottom) { + curSelect = getSelectionStart(editText); + } + editText.setTransformationMethod( + isDisplayPassword ? HideReturnsTransformationMethod.getInstance() + : PasswordTransformationMethod.getInstance() + ); + if (isSelectBottom) { // 设置光标到最后 + setSelectionToBottom(editText); + } else { // 设置光标到之前的位置 + setSelection(editText, curSelect); + } + } + return editText; + } + + // = + + /** + * 添加输入监听事件 + * @param editText {@link EditText} + * @param watcher 输入监听 + * @param 泛型 + * @return {@link EditText} + */ + public static T addTextChangedListener( + final T editText, + final TextWatcher watcher + ) { + if (editText != null && watcher != null) { + editText.addTextChangedListener(watcher); + } + return editText; + } + + /** + * 移除输入监听事件 + * @param editText {@link EditText} + * @param watcher 输入监听 + * @param 泛型 + * @return {@link EditText} + */ + public static T removeTextChangedListener( + final T editText, + final TextWatcher watcher + ) { + if (editText != null && watcher != null) { + editText.removeTextChangedListener(watcher); + } + return editText; + } + + // ======================= + // = Key Listener 快捷处理 = + // ======================= + + // 0123456789 + private static final char[] NUMBERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 + }; + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90 + }; + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] NUMBERS_AND_LETTERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + /** + * 设置 KeyListener + * @param editText {@link EditText} + * @param listener {@link KeyListener} + * @param 泛型 + * @return {@link EditText} + */ + public static T setKeyListener( + final T editText, + final KeyListener listener + ) { + if (editText != null) { + editText.setKeyListener(listener); + } + return editText; + } + + /** + * 设置 KeyListener + * @param editText {@link EditText} + * @param accepted 允许输入的内容, 如: 0123456789 + * @param 泛型 + * @return {@link EditText} + */ + public static T setKeyListener( + final T editText, + final String accepted + ) { + if (editText != null) { + // editText.setKeyListener(DigitsKeyListener.getInstance(accepted)); + editText.setKeyListener(createDigitsKeyListener(-1, accepted)); + } + return editText; + } + + /** + * 设置 KeyListener + * @param editText {@link EditText} + * @param accepted 允许输入的内容 + * @param 泛型 + * @return {@link EditText} + */ + public static T setKeyListener( + final T editText, + final char[] accepted + ) { + if (editText != null) { + editText.setKeyListener(createDigitsKeyListener(-1, accepted)); + } + return editText; + } + + // = + + /** + * 获取 DigitsKeyListener ( 限制只能输入字母, 默认弹出英文软键盘 ) + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener getLettersKeyListener() { + return createDigitsKeyListener( + InputType.TYPE_TEXT_VARIATION_PASSWORD, LETTERS + ); + } + + /** + * 获取 DigitsKeyListener ( 限制只能输入字母和数字, 默认弹出英文软键盘 ) + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener getNumberAndLettersKeyListener() { + return createDigitsKeyListener( + InputType.TYPE_TEXT_VARIATION_PASSWORD, NUMBERS_AND_LETTERS + ); + } + + /** + * 获取 DigitsKeyListener ( 限制只能输入数字, 默认弹出数字列表 ) + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener getNumberKeyListener() { + return createDigitsKeyListener( + (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_NORMAL), NUMBERS + ); + } + + // = + + /** + * 创建 DigitsKeyListener + * @param accepted 允许输入的内容 ( 可以传入 "", 这样无法输入内容 ) + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener createDigitsKeyListener(final String accepted) { + return createDigitsKeyListener( + -1, (accepted == null) ? null : accepted.toCharArray() + ); + } + + /** + * 创建 DigitsKeyListener + * @param inputType 输入类型 + * @param accepted 允许输入的内容 ( 可以传入 "", 这样无法输入内容 ) + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener createDigitsKeyListener( + final int inputType, + final String accepted + ) { + return createDigitsKeyListener( + inputType, (accepted == null) ? null : accepted.toCharArray() + ); + } + + // = + + /** + * 创建 DigitsKeyListener + * @param accepted 允许输入的内容 + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener createDigitsKeyListener(final char[] accepted) { + return createDigitsKeyListener(-1, accepted); + } + + /** + * 创建 DigitsKeyListener + * @param inputType 输入类型 + * @param accepted 允许输入的内容 + * @return {@link DigitsKeyListener} + */ + public static DigitsKeyListener createDigitsKeyListener( + final int inputType, + final char[] accepted + ) { + return new DigitsKeyListener() { + @Override + protected char[] getAcceptedChars() { + if (accepted != null) { + return accepted; + } + return super.getAcceptedChars(); + } + + @Override + public int getInputType() { + if (inputType != -1) { + return inputType; + } + return super.getInputType(); + } + }; + } + + // =============== + // = TextWatcher = + // =============== + + /** + * detail: 开发输入监听抽象类 + * @author Ttt + *
+     *     @see 
+     *     editText.addTextChangedListener(DevTextWatcher);
+     * 
+ */ + public static abstract class DevTextWatcher + implements TextWatcher { + + // uuid ( 一定程度上唯一 ) + private final int markId = UUID.randomUUID().hashCode(); + // 判断是否操作中 + private boolean operate = false; + // 标记状态, 特殊需求处理 + private int operateState = -1; + // 类型 + private int type = -1; + + /** + * 构造函数 + */ + public DevTextWatcher() { + } + + /** + * 构造函数 + * @param type 类型 + */ + public DevTextWatcher(int type) { + this.type = type; + } + + /** + * 获取标记 id + * @return 标记 id + */ + public final int getMarkId() { + return markId; + } + + /** + * 判断是否操作中 + * @return {@code true} yes, {@code false} no + */ + public final boolean isOperate() { + return operate; + } + + /** + * 设置是否操作中 + * @param operate {@code true} yes, {@code false} no + * @return {@link DevTextWatcher} + */ + public final DevTextWatcher setOperate(boolean operate) { + this.operate = operate; + return this; + } + + /** + * 获取操作状态 + * @return 操作状态 + */ + public final int getOperateState() { + return operateState; + } + + /** + * 设置操作状态 + * @param operateState 操作状态 + * @return {@link DevTextWatcher} + */ + public final DevTextWatcher setOperateState(int operateState) { + this.operateState = operateState; + return this; + } + + /** + * 获取类型 + * @return 类型 + */ + public int getType() { + return type; + } + + /** + * 设置类型 + * @param type 类型 + * @return {@link DevTextWatcher} + */ + public DevTextWatcher setType(int type) { + this.type = type; + return this; + } + + // ========== + // = 回调接口 = + // ========== + + /** + * 在文本变化前调用 + * @param text 修改之前的文字 + * @param start 字符串中即将发生修改的位置 + * @param count 字符串中即将被修改的文字的长度, 如果是新增的话则为 0 + * @param after 被修改的文字修改之后的长度, 如果是删除的话则为 0 + */ + @Override + public void beforeTextChanged( + CharSequence text, + int start, + int count, + int after + ) { + } + + /** + * 在文本变化后调用 + * @param text 改变后的字符串 + * @param start 有变动的字符串的位置 + * @param before 被改变的字符串长度, 如果是新增则为 0 + * @param count 添加的字符串长度, 如果是删除则为 0 + */ + @Override + public void onTextChanged( + CharSequence text, + int start, + int before, + int count + ) { + } + + /** + * 在文本变化后调用 + * @param s 修改后的文字 + */ + @Override + public void afterTextChanged(Editable s) { + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/HandlerUtils.java b/lib/DevApp/src/main/java/dev/utils/app/HandlerUtils.java new file mode 100644 index 0000000000..146b442ae1 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/HandlerUtils.java @@ -0,0 +1,258 @@ +package dev.utils.app; + +import android.os.Handler; +import android.os.Looper; + +import java.util.HashMap; +import java.util.Map; + +import dev.utils.LogPrintUtils; + +/** + * detail: Handler 工具类 + * @author Ttt + */ +public final class HandlerUtils { + + private HandlerUtils() { + } + + // 日志 TAG + private static final String TAG = HandlerUtils.class.getSimpleName(); + + // 主线程 Handler + private static Handler sMainHandler; + + /** + * 获取主线程 Handler + * @return 主线程 Handler + */ + public static Handler getMainHandler() { + if (sMainHandler == null) { + sMainHandler = new Handler(Looper.getMainLooper()); + } + return sMainHandler; + } + + /** + * 当前线程是否主线程 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMainThread() { + return Looper.getMainLooper().getThread() == Thread.currentThread(); + } + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + */ + public static void postRunnable(final Runnable runnable) { + if (runnable != null) { + getMainHandler().post(runnable); + } + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + */ + public static void postRunnable( + final Runnable runnable, + final long delayMillis + ) { + if (runnable != null) { + getMainHandler().postDelayed(runnable, delayMillis); + } + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + */ + public static void postRunnable( + final Runnable runnable, + final long delayMillis, + final int number, + final long interval + ) { + postRunnable(runnable, delayMillis, number, interval, null); + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param onEndListener 结束通知 + */ + public static void postRunnable( + final Runnable runnable, + final long delayMillis, + final int number, + final long interval, + final OnEndListener onEndListener + ) { + if (runnable != null) { + Runnable loop = new Runnable() { + private int mNumber; + + @Override + public void run() { + if (mNumber < number) { + mNumber++; + try { + runnable.run(); + } catch (Exception ignored) { + } + // 判断是否超过次数 + if (mNumber < number) { + getMainHandler().postDelayed(this, interval); + } + } + + // 判断是否超过次数 + if (mNumber >= number) { + if (onEndListener != null) { + onEndListener.onEnd(delayMillis, number, interval); + } + } + } + }; + getMainHandler().postDelayed(loop, delayMillis); + } + } + + /** + * 在主线程 Handler 中清除任务 + *
+     *     也可使用 {@link Handler#removeCallbacksAndMessages(Object)} 实现
+     *     注意: 会将所有的 Callbacks、Messages 全部清除掉
+     * 
+ * @param runnable 需要清除的任务 + */ + public static void removeRunnable(final Runnable runnable) { + if (runnable != null) { + getMainHandler().removeCallbacks(runnable); + } + } + + // = + + /** + * detail: 结束回调事件 + * @author Ttt + */ + public interface OnEndListener { + + /** + * 结束通知 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + */ + void onEnd( + long delayMillis, + int number, + long interval + ); + } + + // ================ + // = Runnable Map = + // ================ + + // 通过 Key 快捷控制 Runnable, 进行 postDelayed、removeCallbacks + private static final Map sRunnableMaps = new HashMap<>(); + + /** + * 获取 Key Runnable Map + * @return Key Runnable Map + */ + public static Map getRunnableMaps() { + return new HashMap<>(sRunnableMaps); + } + + /** + * 清空 Key Runnable Map + */ + public static void clearRunnableMaps() { + sRunnableMaps.clear(); + } + + /** + * 判断 Map 是否存储 key Runnable + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean containsKey(final String key) { + return sRunnableMaps.containsKey(key); + } + + /** + * 通过 Key 存储 Runnable + * @param key key + * @param runnable 线程任务 + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final String key, + final Runnable runnable + ) { + if (key != null && runnable != null) { + try { + sRunnableMaps.put(key, runnable); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "put"); + } + } + return false; + } + + /** + * 通过 Key 移除 Runnable + * @param key key + * @return {@code true} success, {@code false} fail + */ + public static boolean remove(final String key) { + if (key != null) { + try { + sRunnableMaps.remove(key); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 执行对应 Key Runnable + * @param key key + * @param delayMillis 延迟时间 + */ + public static void postRunnable( + final String key, + final long delayMillis + ) { + Runnable runnable = sRunnableMaps.get(key); + if (runnable != null) { + removeRunnable(runnable); + postRunnable(runnable, delayMillis); + } + } + + /** + * 清除对应 Key Runnable + * @param key key + */ + public static void removeRunnable(final String key) { + Runnable runnable = sRunnableMaps.get(key); + if (runnable != null) removeRunnable(runnable); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ImageViewUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ImageViewUtils.java new file mode 100644 index 0000000000..30592f6745 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ImageViewUtils.java @@ -0,0 +1,874 @@ +package dev.utils.app; + +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; + +import java.lang.reflect.Field; + +import dev.utils.LogPrintUtils; + +/** + * detail: ImageView 工具类 + * @author Ttt + */ +public final class ImageViewUtils { + + private ImageViewUtils() { + } + + // 日志 TAG + private static final String TAG = ImageViewUtils.class.getSimpleName(); + + // ================= + // = 获取 ImageView = + // ================= + + /** + * 获取 ImageView + * @param view {@link View} + * @param 泛型 + * @return {@link ImageView} + */ + public static T getImageView(final View view) { + if (view instanceof ImageView) { + try { + return (T) view; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageView"); + } + } + return null; + } + + // ======= + // = 宽高 = + // ======= + + /** + * 获取 ImageView 是否保持宽高比 + * @param imageView ImageView + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean getAdjustViewBounds(final ImageView imageView) { + if (imageView != null) { + return imageView.getAdjustViewBounds(); + } + return false; + } + + /** + * 设置 ImageView 是否保持宽高比 + * @param imageView ImageView + * @param adjustViewBounds 是否调整此视图的边界以保持可绘制的原始纵横比 + * @return {@link ImageView} + */ + public static ImageView setAdjustViewBounds( + final ImageView imageView, + final boolean adjustViewBounds + ) { + if (imageView != null) { + imageView.setAdjustViewBounds(adjustViewBounds); + } + return imageView; + } + + /** + * 获取 ImageView 最大高度 + * @param imageView ImageView + * @return view 最大高度 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMaxHeight(final ImageView imageView) { + if (imageView != null) { + return imageView.getMaxHeight(); + } + return -1; + } + + /** + * 设置 ImageView 最大高度 + * @param imageView ImageView + * @param maxHeight 最大高度 + * @return {@link ImageView} + */ + public static ImageView setMaxHeight( + final ImageView imageView, + final int maxHeight + ) { + if (imageView != null) { + imageView.setMaxHeight(maxHeight); + } + return imageView; + } + + /** + * 获取 ImageView 最大宽度 + * @param imageView ImageView + * @return view 最大宽度 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMaxWidth(final ImageView imageView) { + if (imageView != null) { + return imageView.getMaxWidth(); + } + return -1; + } + + /** + * 设置 ImageView 最大宽度 + * @param imageView ImageView + * @param maxWidth 最大宽度 + * @return {@link ImageView} + */ + public static ImageView setMaxWidth( + final ImageView imageView, + final int maxWidth + ) { + if (imageView != null) { + imageView.setMaxWidth(maxWidth); + } + return imageView; + } + + // ============= + // = ImageView = + // ============= + + /** + * 设置 ImageView Level + * @param view {@link View} + * @param level level Image + * @return {@link View} + */ + public static View setImageLevel( + final View view, + final int level + ) { + setImageLevel(getImageView(view), level); + return view; + } + + /** + * 设置 ImageView Level + *
+     *     配合 {@link android.graphics.drawable.LevelListDrawable} 使用
+     * 
+ * @param imageView {@link ImageView} + * @param level level Image + * @param 泛型 + * @return {@link ImageView} + */ + public static T setImageLevel( + final T imageView, + final int level + ) { + if (imageView != null) { + try { + imageView.setImageLevel(level); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageLevel"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView Bitmap + * @param view {@link View} + * @param bitmap {@link Bitmap} + * @return {@link View} + */ + public static View setImageBitmap( + final View view, + final Bitmap bitmap + ) { + setImageBitmap(getImageView(view), bitmap); + return view; + } + + /** + * 设置 ImageView Bitmap + * @param imageView {@link ImageView} + * @param bitmap {@link Bitmap} + * @param 泛型 + * @return {@link ImageView} + */ + public static T setImageBitmap( + final T imageView, + final Bitmap bitmap + ) { + if (imageView != null) { + try { + imageView.setImageBitmap(bitmap); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageBitmap"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView Drawable + * @param view {@link View} + * @param drawable {@link Bitmap} + * @return {@link View} + */ + public static View setImageDrawable( + final View view, + final Drawable drawable + ) { + setImageDrawable(getImageView(view), drawable); + return view; + } + + /** + * 设置 ImageView Drawable + * @param imageView {@link ImageView} + * @param drawable {@link Drawable} + * @param 泛型 + * @return {@link ImageView} + */ + public static T setImageDrawable( + final T imageView, + final Drawable drawable + ) { + if (imageView != null) { + try { + imageView.setImageDrawable(drawable); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageDrawable"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView 资源 + * @param view {@link View} + * @param resId resource identifier + * @return {@link View} + */ + public static View setImageResource( + final View view, + @DrawableRes final int resId + ) { + setImageResource(getImageView(view), resId); + return view; + } + + /** + * 设置 ImageView 资源 + * @param imageView {@link ImageView} + * @param resId resource identifier + * @param 泛型 + * @return {@link ImageView} + */ + public static T setImageResource( + final T imageView, + @DrawableRes final int resId + ) { + if (imageView != null) { + try { + imageView.setImageResource(resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageResource"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView Matrix + * @param view {@link View} + * @param matrix {@link Matrix} + * @return {@link View} + */ + public static View setImageMatrix( + final View view, + final Matrix matrix + ) { + setImageMatrix(getImageView(view), matrix); + return view; + } + + /** + * 设置 ImageView Matrix + * @param imageView {@link ImageView} + * @param matrix {@link Matrix} + * @param 泛型 + * @return {@link ImageView} + */ + public static T setImageMatrix( + final T imageView, + final Matrix matrix + ) { + if (imageView != null) { + try { + imageView.setImageMatrix(matrix); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageMatrix"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView 着色颜色 + * @param view {@link View} + * @param tint 着色颜色 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static View setImageTintList( + final View view, + final ColorStateList tint + ) { + setImageTintList(getImageView(view), tint); + return view; + } + + /** + * 设置 ImageView 着色颜色 + * @param imageView {@link ImageView} + * @param tint 着色颜色 + * @param 泛型 + * @return {@link ImageView} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static T setImageTintList( + final T imageView, + final ColorStateList tint + ) { + if (imageView != null) { + try { + imageView.setImageTintList(tint); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageTintList"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView 着色模式 + * @param view {@link View} + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static View setImageTintMode( + final View view, + final PorterDuff.Mode tintMode + ) { + setImageTintMode(getImageView(view), tintMode); + return view; + } + + /** + * 设置 ImageView 着色模式 + * @param imageView {@link ImageView} + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param 泛型 + * @return {@link ImageView} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static T setImageTintMode( + final T imageView, + final PorterDuff.Mode tintMode + ) { + if (imageView != null) { + try { + imageView.setImageTintMode(tintMode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageTintMode"); + } + } + return imageView; + } + + // = + + /** + * 设置 ImageView 缩放类型 + * @param view {@link View} + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @return {@link View} + */ + public static View setScaleType( + final View view, + final ImageView.ScaleType scaleType + ) { + setScaleType(getImageView(view), scaleType); + return view; + } + + /** + * 设置 ImageView 缩放类型 + * @param imageView {@link ImageView} + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @param 泛型 + * @return {@link ImageView} + */ + public static T setScaleType( + final T imageView, + final ImageView.ScaleType scaleType + ) { + if (imageView != null) { + try { + imageView.setScaleType(scaleType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setScaleType"); + } + } + return imageView; + } + + // ======= + // = 获取 = + // ======= + + /** + * 获取 ImageView Matrix + * @param view {@link View} + * @return {@link Matrix} + */ + public static Matrix getImageMatrix(final View view) { + return getImageMatrix(getImageView(view)); + } + + /** + * 获取 ImageView Matrix + * @param imageView {@link ImageView} + * @param 泛型 + * @return {@link Matrix} + */ + public static Matrix getImageMatrix(final T imageView) { + if (imageView != null) return imageView.getImageMatrix(); + return null; + } + + // = + + /** + * 获取 ImageView 着色颜色 + * @param view {@link View} + * @return {@link ColorStateList} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static ColorStateList getImageTintList(final View view) { + return getImageTintList(getImageView(view)); + } + + /** + * 获取 ImageView 着色颜色 + * @param imageView {@link ImageView} + * @param 泛型 + * @return {@link ColorStateList} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static ColorStateList getImageTintList(final T imageView) { + if (imageView != null) return imageView.getImageTintList(); + return null; + } + + // = + + /** + * 获取 ImageView 着色模式 + * @param view {@link View} + * @return {@link PorterDuff.Mode} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static PorterDuff.Mode getImageTintMode(final View view) { + return getImageTintMode(getImageView(view)); + } + + /** + * 获取 ImageView 着色模式 + * @param imageView {@link ImageView} + * @param 泛型 + * @return {@link PorterDuff.Mode} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static PorterDuff.Mode getImageTintMode(final T imageView) { + if (imageView != null) return imageView.getImageTintMode(); + return null; + } + + // = + + /** + * 获取 ImageView 缩放模式 + * @param view {@link View} + * @return {@link ImageView.ScaleType} + */ + public static ImageView.ScaleType getScaleType(final View view) { + return getScaleType(getImageView(view)); + } + + /** + * 获取 ImageView 缩放模式 + * @param imageView {@link ImageView} + * @param 泛型 + * @return {@link ImageView.ScaleType} + */ + public static ImageView.ScaleType getScaleType(final T imageView) { + if (imageView != null) return imageView.getScaleType(); + return null; + } + + // = + + /** + * 获取 ImageView Drawable + * @param view {@link View} + * @return {@link Drawable} + */ + public static Drawable getDrawable(final View view) { + return getDrawable(getImageView(view)); + } + + /** + * 获取 ImageView Drawable + * @param imageView {@link ImageView} + * @param 泛型 + * @return {@link Drawable} + */ + public static Drawable getDrawable(final T imageView) { + if (imageView != null) return imageView.getDrawable(); + return null; + } + + // ========== + // = 多个操作 = + // ========== + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setBackgroundResources( + @DrawableRes final int resId, + final View... views + ) { + return setBackgroundResources(resId, View.VISIBLE, views); + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setBackgroundResources( + @DrawableRes final int resId, + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + try { + // 设置显示状态 + view.setVisibility(isVisibility); + // 设置图片资源 + view.setBackgroundResource(resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackgroundResources"); + } + } + } + return true; + } + return false; + } + + // = + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageResources( + @DrawableRes final int resId, + final View... views + ) { + return setImageResources(resId, View.VISIBLE, views); + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageResources( + @DrawableRes final int resId, + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + ImageView imageView = getImageView(view); + if (imageView != null) { + try { + // 设置显示状态 + imageView.setVisibility(isVisibility); + // 设置图片资源 + imageView.setImageResource(resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageResources"); + } + } + } + return true; + } + return false; + } + + // = + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageBitmaps( + final Bitmap bitmap, + final View... views + ) { + return setImageBitmaps(bitmap, View.VISIBLE, views); + } + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageBitmaps( + final Bitmap bitmap, + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + ImageView imageView = getImageView(view); + if (imageView != null) { + try { + // 设置显示状态 + imageView.setVisibility(isVisibility); + // 设置 Bitmap + imageView.setImageBitmap(bitmap); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageBitmaps"); + } + } + } + return true; + } + return false; + } + + // = + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageDrawables( + final Drawable drawable, + final View... views + ) { + return setImageDrawables(drawable, View.VISIBLE, views); + } + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setImageDrawables( + final Drawable drawable, + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + ImageView imageView = getImageView(view); + if (imageView != null) { + try { + // 设置显示状态 + imageView.setVisibility(isVisibility); + // 设置 Drawable + imageView.setImageDrawable(drawable); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setImageDrawables"); + } + } + } + return true; + } + return false; + } + + // = + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setScaleTypes( + final ImageView.ScaleType scaleType, + final View... views + ) { + return setScaleTypes(scaleType, View.VISIBLE, views); + } + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setScaleTypes( + final ImageView.ScaleType scaleType, + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + ImageView imageView = getImageView(view); + if (imageView != null) { + try { + // 设置显示状态 + imageView.setVisibility(isVisibility); + // 设置缩放模式 + imageView.setScaleType(scaleType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setScaleTypes"); + } + } + } + return true; + } + return false; + } + + // ========== + // = 信息获取 = + // ========== + + /** + * 根据 ImageView 获适当的宽高 + * @param imageView {@link ImageView} + * @return 宽高, 0 = 宽, 1 = 高 + */ + public static int[] getImageViewSize(final ImageView imageView) { + int[] imageSize = new int[]{0, 0}; + try { + if (imageView == null) return imageSize; + + DisplayMetrics displayMetrics = ResourceUtils.getDisplayMetrics( + imageView.getContext() + ); + ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams(); + + // 获取 ImageView 的实际宽度 + int width = imageView.getWidth(); + if (width <= 0) { + width = layoutParams.width; // 获取 imageView 在 layout 中声明的宽度 + } + if (width <= 0) { +// width = imageView.getMaxWidth(); // 检查最大值 + width = getImageViewFieldValue(imageView, "mMaxWidth"); + } + if (width <= 0) { + width = displayMetrics.widthPixels; + } + + // 获取 ImageView 的实际高度 + int height = imageView.getHeight(); + if (height <= 0) { + height = layoutParams.height; // 获取 imageView 在 layout 中声明的高度 + } + if (height <= 0) { +// height = imageView.getMaxHeight(); // 检查最大值 + height = getImageViewFieldValue(imageView, "mMaxHeight"); // 检查最大值 + } + if (height <= 0) { + height = displayMetrics.heightPixels; + } + + // 填充宽高信息 + imageSize[0] = width; + imageSize[1] = height; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageViewSize"); + } + return imageSize; + } + + /** + * 通过反射获取 ImageView 的属性值 + * @param object 对象 + * @param fieldName 属性名 + * @return 指定属性的值 + */ + private static int getImageViewFieldValue( + final Object object, + final String fieldName + ) { + int value = 0; + try { + Field field = ImageView.class.getDeclaredField(fieldName); + field.setAccessible(true); + int fieldValue = field.getInt(object); + if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) { + value = fieldValue; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageViewFieldValue"); + } + return value; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/IntentUtils.java b/lib/DevApp/src/main/java/dev/utils/app/IntentUtils.java new file mode 100644 index 0000000000..695dbdd9e8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/IntentUtils.java @@ -0,0 +1,1089 @@ +package dev.utils.app; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.MediaStore; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.FileUtils; + +/** + * detail: Intent 相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ *     
+ *     
+ *     
+ * 
+ */ +public final class IntentUtils { + + private IntentUtils() { + } + + // 日志 TAG + private static final String TAG = IntentUtils.class.getSimpleName(); + + /** + * 获取 Intent + * @param intent {@link Intent} + * @param isNewTask 是否开启新的任务栈 (Context 非 Activity 则需要设置 FLAG_ACTIVITY_NEW_TASK) + * @return {@link Intent} + */ + public static Intent getIntent( + final Intent intent, + final boolean isNewTask + ) { + if (intent != null) { + return isNewTask ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) : intent; + } + return null; + } + + /** + * 判断 Intent 是否可用 + * @param intent {@link Intent} + * @return {@code true} yes, {@code false} no + */ + public static boolean isIntentAvailable(final Intent intent) { + if (intent != null) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return false; + try { + return packageManager.queryIntentActivities( + intent, PackageManager.MATCH_DEFAULT_ONLY + ).size() > 0; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isIntentAvailable"); + } + } + return false; + } + + /** + * 获取 CATEGORY_LAUNCHER Intent + * @param className class.getCanonicalName() + * @return {@link Intent} + */ + public static Intent getCategoryLauncherIntent(final String className) { + return getCategoryLauncherIntent( + AppUtils.getPackageName(), className, true + ); + } + + /** + * 获取 CATEGORY_LAUNCHER Intent + * @param className class.getCanonicalName() + * @param isNewTask 是否开启新的任务栈 + * @return {@link Intent} + */ + public static Intent getCategoryLauncherIntent( + final String className, + final boolean isNewTask + ) { + return getCategoryLauncherIntent( + AppUtils.getPackageName(), className, isNewTask + ); + } + + /** + * 获取 CATEGORY_LAUNCHER Intent + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @return {@link Intent} + */ + public static Intent getCategoryLauncherIntent( + final String packageName, + final String className + ) { + return getCategoryLauncherIntent(packageName, className, true); + } + + /** + * 获取 CATEGORY_LAUNCHER Intent + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @param isNewTask 是否开启新的任务栈 + * @return {@link Intent} + */ + public static Intent getCategoryLauncherIntent( + final String packageName, + final String className, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.setComponent(new ComponentName(packageName, className)); + if (isNewTask) { + intent.setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + ); + } + return intent; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCategoryLauncherIntent"); + } + return null; + } + + /** + * 获取安装 APP ( 支持 8.0 ) 的意图 + * @param filePath 文件路径 + * @return 安装 APP ( 支持 8.0 ) 的意图 + */ + public static Intent getInstallAppIntent(final String filePath) { + return getInstallAppIntent(FileUtils.getFileByPath(filePath)); + } + + /** + * 获取安装 APP ( 支持 8.0 ) 的意图 + * @param file 文件 + * @return 安装 APP ( 支持 8.0 ) 的意图 + */ + public static Intent getInstallAppIntent(final File file) { + return getInstallAppIntent(file, false); + } + + /** + * 获取安装 APP ( 支持 8.0 ) 的意图 + * @param file 文件 + * @param isNewTask 是否开启新的任务栈 + * @return 安装 APP ( 支持 8.0 ) 的意图 + */ + public static Intent getInstallAppIntent( + final File file, + final boolean isNewTask + ) { + if (!FileUtils.isFileExists(file)) return null; + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + Uri data = UriUtils.getUriForFile(file); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(data, "application/vnd.android.package-archive"); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getInstallAppIntent"); + } + return null; + } + + /** + * 获取卸载 APP 的意图 + * @param packageName 应用包名 + * @return 卸载 APP 的意图 + */ + public static Intent getUninstallAppIntent(final String packageName) { + return getUninstallAppIntent(packageName, false); + } + + /** + * 获取卸载 APP 的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return 卸载 APP 的意图 + */ + public static Intent getUninstallAppIntent( + final String packageName, + final boolean isNewTask + ) { + if (TextUtils.isEmpty(packageName)) return null; + try { + Intent intent = new Intent(Intent.ACTION_DELETE); + intent.setData(Uri.parse("package:" + packageName)); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUninstallAppIntent"); + } + return null; + } + + /** + * 获取打开 APP 的意图 + * @param packageName 应用包名 + * @return 打开 APP 的意图 + */ + public static Intent getLaunchAppIntent(final String packageName) { + return getLaunchAppIntent(packageName, false); + } + + /** + * 获取打开 APP 的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return 打开 APP 的意图 + */ + public static Intent getLaunchAppIntent( + final String packageName, + final boolean isNewTask + ) { + if (TextUtils.isEmpty(packageName)) return null; + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + Intent intent = packageManager.getLaunchIntentForPackage(packageName); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLaunchAppIntent"); + } + return null; + } + + /** + * 获取跳转到系统设置的意图 + * @param isNewTask 是否开启新的任务栈 + * @return 跳转到系统设置的意图 + */ + public static Intent getSystemSettingIntent(final boolean isNewTask) { + try { + Intent intent = new Intent(Settings.ACTION_SETTINGS); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSystemSettingIntent"); + } + return null; + } + + /** + * 获取 APP 安装权限设置的意图 + * @return APP 安装权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppInstallPermissionSettingsIntent() { + return getLaunchAppInstallPermissionSettingsIntent( + AppUtils.getPackageName(), false + ); + } + + /** + * 获取 APP 安装权限设置的意图 + * @param packageName 应用包名 + * @return APP 安装权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppInstallPermissionSettingsIntent(final String packageName) { + return getLaunchAppInstallPermissionSettingsIntent( + packageName, false + ); + } + + /** + * 获取 APP 安装权限设置的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return APP 安装权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppInstallPermissionSettingsIntent( + final String packageName, + final boolean isNewTask + ) { + try { + Uri uri = Uri.parse("package:" + packageName); + Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, uri); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLaunchAppInstallPermissionSettingsIntent"); + } + return null; + } + + /** + * 获取 APP 通知权限设置的意图 + * @return APP 通知权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppNotificationSettingsIntent() { + return getLaunchAppNotificationSettingsIntent( + AppUtils.getPackageName(), false + ); + } + + /** + * 获取 APP 通知权限设置的意图 + * @param packageName 应用包名 + * @return APP 通知权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppNotificationSettingsIntent(final String packageName) { + return getLaunchAppNotificationSettingsIntent( + packageName, false + ); + } + + /** + * 获取 APP 通知权限设置的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return APP 通知权限设置的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static Intent getLaunchAppNotificationSettingsIntent( + final String packageName, + final boolean isNewTask + ) { + PackageInfo packageInfo = AppUtils.getPackageInfo(packageName, 0); + if (packageInfo == null) return null; + try { + ApplicationInfo applicationInfo = packageInfo.applicationInfo; + Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + // 这种方案适用于 API 26 即 8.0 ( 含 8.0) 以上可以用 + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); + intent.putExtra(Settings.EXTRA_CHANNEL_ID, applicationInfo.uid); + // 这种方案适用于 API 21 - 25 即 5.0 - 7.1 之间的版本可以使用 + intent.putExtra("app_package", packageName); + intent.putExtra("app_uid", applicationInfo.uid); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLaunchAppNotificationSettingsIntent"); + } + return null; + } + + /** + * 获取 APP 通知使用权页面 + * @return APP 通知使用权页面 + */ + public static Intent getLaunchAppNotificationListenSettingsIntent() { + return getLaunchAppNotificationListenSettingsIntent(false); + } + + /** + * 获取 APP 通知使用权页面 + * @param isNewTask 是否开启新的任务栈 + * @return APP 通知使用权页面 + */ + public static Intent getLaunchAppNotificationListenSettingsIntent(final boolean isNewTask) { + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS); + } else { + intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"); + } + return getIntent(intent, isNewTask); + } + + /** + * 获取悬浮窗口权限列表的意图 + * @return 悬浮窗口权限列表的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static Intent getManageOverlayPermissionIntent() { + return getManageOverlayPermissionIntent(false); + } + + /** + * 获取悬浮窗口权限列表的意图 + * @param isNewTask 是否开启新的任务栈 + * @return 悬浮窗口权限列表的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static Intent getManageOverlayPermissionIntent(final boolean isNewTask) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getManageOverlayPermissionIntent"); + } + return null; + } + + /** + * 获取 APP 授予所有文件管理权限的意图 + * @return APP 授予所有文件管理权限的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.R) + public static Intent getManageAppAllFilesAccessPermissionIntent() { + return getManageAppAllFilesAccessPermissionIntent( + AppUtils.getPackageName(), false + ); + } + + /** + * 获取 APP 授予所有文件管理权限的意图 + * @param packageName 应用包名 + * @return APP 授予所有文件管理权限的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.R) + public static Intent getManageAppAllFilesAccessPermissionIntent(final String packageName) { + return getManageAppAllFilesAccessPermissionIntent( + packageName, false + ); + } + + /** + * 获取 APP 授予所有文件管理权限的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return APP 授予所有文件管理权限的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.R) + public static Intent getManageAppAllFilesAccessPermissionIntent( + final String packageName, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); + intent.setData(Uri.parse("package:" + packageName)); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getManageAppAllFilesAccessPermissionIntent"); + } + return null; + } + + /** + * 获取授予所有文件管理权限列表的意图 + * @return 授予所有文件管理权限列表的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.R) + public static Intent getManageAllFilesAccessPermissionIntent() { + return getManageAllFilesAccessPermissionIntent(false); + } + + /** + * 获取授予所有文件管理权限列表的意图 + * @param isNewTask 是否开启新的任务栈 + * @return 授予所有文件管理权限列表的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.R) + public static Intent getManageAllFilesAccessPermissionIntent(final boolean isNewTask) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getManageAllFilesAccessPermissionIntent"); + } + return null; + } + + /** + * 获取 APP 具体设置的意图 + * @return APP 具体设置的意图 + */ + public static Intent getLaunchAppDetailsSettingsIntent() { + return getLaunchAppDetailsSettingsIntent( + AppUtils.getPackageName(), false + ); + } + + /** + * 获取 APP 具体设置的意图 + * @param packageName 应用包名 + * @return APP 具体设置的意图 + */ + public static Intent getLaunchAppDetailsSettingsIntent(final String packageName) { + return getLaunchAppDetailsSettingsIntent( + packageName, false + ); + } + + /** + * 获取 APP 具体设置的意图 + * @param packageName 应用包名 + * @param isNewTask 是否开启新的任务栈 + * @return APP 具体设置的意图 + */ + public static Intent getLaunchAppDetailsSettingsIntent( + final String packageName, + final boolean isNewTask + ) { + if (TextUtils.isEmpty(packageName)) return null; + try { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + packageName)); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLaunchAppDetailsSettingsIntent"); + } + return null; + } + + /** + * 获取到应用商店 APP 详情界面的意图 + * @param packageName 应用包名 + * @param marketPkg 应用商店包名, 如果为 "" 则由系统弹出应用商店列表供用户选择, 否则调转到目标市场的应用详情界面, 某些应用商店可能会失败 + * @return 到应用商店 APP 详情界面的意图 + */ + public static Intent getLaunchAppDetailIntent( + final String packageName, + final String marketPkg + ) { + return getLaunchAppDetailIntent(packageName, marketPkg, false); + } + + /** + * 获取到应用商店 APP 详情界面的意图 + * @param packageName 应用包名 + * @param marketPkg 应用商店包名, 如果为 "" 则由系统弹出应用商店列表供用户选择, 否则调转到目标市场的应用详情界面, 某些应用商店可能会失败 + * @param isNewTask 是否开启新的任务栈 + * @return 到应用商店 APP 详情界面的意图 + */ + public static Intent getLaunchAppDetailIntent( + final String packageName, + final String marketPkg, + final boolean isNewTask + ) { + if (TextUtils.isEmpty(packageName)) return null; + try { + Uri uri = Uri.parse("market://details?id=" + packageName); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if (!TextUtils.isEmpty(marketPkg)) { + intent.setPackage(marketPkg); + } + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLaunchAppDetailIntent"); + } + return null; + } + + // = + + /** + * 获取分享文本的意图 + * @param content 分享文本 + * @return 分享文本的意图 + */ + public static Intent getShareTextIntent(final String content) { + return getShareTextIntent(content, false); + } + + /** + * 获取分享文本的意图 + * @param content 分享文本 + * @param isNewTask 是否开启新的任务栈 + * @return 分享文本的意图 + */ + public static Intent getShareTextIntent( + final String content, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, content); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShareTextIntent"); + } + return null; + } + + /** + * 获取分享图片的意图 + * @param content 文本 + * @param imagePath 图片文件路径 + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final String imagePath + ) { + return getShareImageIntent(content, imagePath, false); + } + + /** + * 获取分享图片的意图 + * @param content 文本 + * @param imagePath 图片文件路径 + * @param isNewTask 是否开启新的任务栈 + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final String imagePath, + final boolean isNewTask + ) { + try { + return getShareImageIntent( + content, FileUtils.getFileByPath(imagePath), isNewTask + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShareImageIntent"); + } + return null; + } + + /** + * 获取分享图片的意图 + * @param content 文本 + * @param image 图片文件 + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final File image + ) { + return getShareImageIntent(content, image, false); + } + + /** + * 获取分享图片的意图 + * @param content 文本 + * @param image 图片文件 + * @param isNewTask 是否开启新的任务栈 + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final File image, + final boolean isNewTask + ) { + try { + return getShareImageIntent( + content, DevUtils.getUriForFile(image), isNewTask + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShareImageIntent"); + } + return null; + } + + /** + * 获取分享图片的意图 + * @param content 分享文本 + * @param uri 图片 uri + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final Uri uri + ) { + return getShareImageIntent(content, uri, false); + } + + /** + * 获取分享图片的意图 + * @param content 分享文本 + * @param uri 图片 uri + * @param isNewTask 是否开启新的任务栈 + * @return 分享图片的意图 + */ + public static Intent getShareImageIntent( + final String content, + final Uri uri, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, content); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.setType("image/*"); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShareImageIntent"); + } + return null; + } + + /** + * 获取其他应用组件的意图 + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @return 其他应用组件的意图 + */ + public static Intent getComponentIntent( + final String packageName, + final String className + ) { + return getComponentIntent(packageName, className, null, false); + } + + /** + * 获取其他应用组件的意图 + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @param isNewTask 是否开启新的任务栈 + * @return 其他应用组件的意图 + */ + public static Intent getComponentIntent( + final String packageName, + final String className, + final boolean isNewTask + ) { + return getComponentIntent(packageName, className, null, isNewTask); + } + + /** + * 获取其他应用组件的意图 + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @param bundle {@link Bundle} + * @return 其他应用组件的意图 + */ + public static Intent getComponentIntent( + final String packageName, + final String className, + final Bundle bundle + ) { + return getComponentIntent(packageName, className, bundle, false); + } + + /** + * 获取其他应用组件的意图 + * @param packageName 应用包名 + * @param className class.getCanonicalName() + * @param bundle {@link Bundle} + * @param isNewTask 是否开启新的任务栈 + * @return 其他应用组件的意图 + */ + public static Intent getComponentIntent( + final String packageName, + final String className, + final Bundle bundle, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + if (bundle != null) intent.putExtras(bundle); + ComponentName componentName = new ComponentName(packageName, className); + intent.setComponent(componentName); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getComponentIntent"); + } + return null; + } + + /** + * 获取关机的意图 + * @return 关机的意图 + */ + public static Intent getShutdownIntent() { + return getShutdownIntent(false); + } + + /** + * 获取关机的意图 + * @param isNewTask 是否开启新的任务栈 + * @return 关机的意图 + */ + public static Intent getShutdownIntent(final boolean isNewTask) { + try { + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShutdownIntent"); + } + return null; + } + + /** + * 获取跳至拨号界面意图 + * @param phoneNumber 电话号码 + * @return 跳至拨号界面意图 + */ + public static Intent getDialIntent(final String phoneNumber) { + return getDialIntent(phoneNumber, false); + } + + /** + * 获取跳至拨号界面意图 + * @param phoneNumber 电话号码 + * @param isNewTask 是否开启新的任务栈 + * @return 跳至拨号界面意图 + */ + public static Intent getDialIntent( + final String phoneNumber, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDialIntent"); + } + return null; + } + + /** + * 获取拨打电话意图 + * @param phoneNumber 电话号码 + * @return 拨打电话意图 + */ + public static Intent getCallIntent(final String phoneNumber) { + return getCallIntent(phoneNumber, false); + } + + /** + * 获取拨打电话意图 + * @param phoneNumber 电话号码 + * @param isNewTask 是否开启新的任务栈 + * @return 拨打电话意图 + */ + public static Intent getCallIntent( + final String phoneNumber, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber)); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCallIntent"); + } + return null; + } + + /** + * 获取发送短信界面的意图 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return 发送短信界面的意图 + */ + public static Intent getSendSmsIntent( + final String phoneNumber, + final String content + ) { + return getSendSmsIntent(phoneNumber, content, false); + } + + /** + * 获取跳至发送短信界面的意图 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @param isNewTask 是否开启新的任务栈 + * @return 发送短信界面的意图 + */ + public static Intent getSendSmsIntent( + final String phoneNumber, + final String content, + final boolean isNewTask + ) { + try { + Uri uri = Uri.parse("smsto:" + phoneNumber); + Intent intent = new Intent(Intent.ACTION_SENDTO, uri); + intent.putExtra("sms_body", content); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSendSmsIntent"); + } + return null; + } + + /** + * 获取图片拍摄的意图 + * @param outUri 输出的 uri ( 存储地址 ) + * @return 图片拍摄的意图 + */ + public static Intent getImageCaptureIntent(final Uri outUri) { + return getImageCaptureIntent(outUri, false); + } + + /** + * 获取图片拍摄的意图 + * @param outUri 输出的 uri ( 存储地址 ) + * @param isNewTask 是否开启新的任务栈 + * @return 图片拍摄的意图 + */ + public static Intent getImageCaptureIntent( + final Uri outUri, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageCaptureIntent"); + } + return null; + } + + /** + * 获取视频拍摄的意图 + * @param outUri 输出的 uri ( 存储地址 ) + * @return 视频拍摄的意图 + */ + public static Intent getVideoCaptureIntent(final Uri outUri) { + return getVideoCaptureIntent(outUri, false); + } + + /** + * 获取视频拍摄的意图 + * @param outUri 输出的 uri ( 存储地址 ) + * @param isNewTask 是否开启新的任务栈 + * @return 视频拍摄的意图 + */ + public static Intent getVideoCaptureIntent( + final Uri outUri, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, outUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoCaptureIntent"); + } + return null; + } + + /** + * 获取存储访问框架的意图 + * @return 存储访问框架的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static Intent getOpenDocumentIntent() { + return getOpenDocumentIntent("*/*"); + } + + /** + * 获取存储访问框架的意图 + *
+     *     SAF 存储访问框架 Storage Access Framework
+     * 
+ * @param type 跳转类型 + * @return 存储访问框架的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static Intent getOpenDocumentIntent(final String type) { + try { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(type); + return intent; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getOpenDocumentIntent"); + } + return null; + } + + /** + * 获取创建文件的意图 + *
+     *     getCreateDocumentIntent("text/plain", "foobar.txt");
+     *     getCreateDocumentIntent("image/png", "picture.png");
+     *     

+ * 创建后在 onActivityResult 中获取到 Uri, 对 Uri 进行读写 + *
+ * @param mimeType 资源类型 + * @param fileName 文件名 + * @return 创建文件的意图 + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static Intent getCreateDocumentIntent( + final String mimeType, + final String fileName + ) { + try { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + return intent; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCreateDocumentIntent"); + } + return null; + } + + /** + * 获取打开浏览器的意图 + *
+     *     Uri uri = Uri.parse("https://www.baidu.com")
+     *     如果手机本身安装了多个浏览器而又没有设置默认浏览器的话, 系统将让用户选择使用哪个浏览器来打开链接
+     * 
+ * @param uri 链接地址 + * @param isNewTask 是否开启新的任务栈 + * @return 打开浏览器的意图 + */ + public static Intent getOpenBrowserIntent( + final Uri uri, + final boolean isNewTask + ) { + return getOpenBrowserIntent(uri, null, null, isNewTask); + } + + /** + * 获取打开 Android 浏览器的意图 + * @param uri 链接地址 + * @param isNewTask 是否开启新的任务栈 + * @return 打开 Android 浏览器的意图 + */ + public static Intent getOpenAndroidBrowserIntent( + final Uri uri, + final boolean isNewTask + ) { + return getOpenBrowserIntent( + uri, "com.android.browser", + "com.android.browser.BrowserActivity", isNewTask + ); + } + + /** + * 获取打开指定浏览器的意图 + *
+     *     打开指定浏览器, 如:
+     *     intent.setClassName("com.UCMobile", "com.uc.browser.InnerUCMobile"); // 打开 UC 浏览器
+     *     intent.setClassName("com.tencent.mtt", "com.tencent.mtt.MainActivity"); // 打开 QQ 浏览器
+     *     intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity"); // 系统指定浏览器
+     * 
+ * @param uri 链接地址 + * @param packageName 应用包名 + * @param className 完整类名 ( 可不传 ) + * @param isNewTask 是否开启新的任务栈 + * @return 打开指定浏览器的意图 + */ + public static Intent getOpenBrowserIntent( + final Uri uri, + final String packageName, + final String className, + final boolean isNewTask + ) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + intent.addCategory(Intent.CATEGORY_BROWSABLE); + if (!TextUtils.isEmpty(packageName)) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + List lists = packageManager.queryIntentActivities( + intent, PackageManager.MATCH_DEFAULT_ONLY + ); + Map browsers = new HashMap<>(); + for (ResolveInfo resolveInfo : lists) { + ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo != null) { // 包名, Activity Name + browsers.put(activityInfo.packageName, activityInfo.targetActivity); + } + } + if (browsers.containsKey(packageName)) { + if (TextUtils.isEmpty(className)) { + intent.setComponent( + new ComponentName(packageName, browsers.get(packageName)) + ); + } else { + intent.setComponent(new ComponentName(packageName, className)); + } + } + } + return getIntent(intent, isNewTask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getOpenBrowserIntent"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/JSONObjectUtils.java b/lib/DevApp/src/main/java/dev/utils/app/JSONObjectUtils.java new file mode 100644 index 0000000000..c7976b7ca0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/JSONObjectUtils.java @@ -0,0 +1,632 @@ +package dev.utils.app; + +import android.os.Build; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONTokener; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.LogPrintUtils; +import dev.utils.common.ConvertUtils; + +/** + * detail: Android 原生 JSONObject 工具类 + * @author Ttt + */ +public final class JSONObjectUtils { + + private JSONObjectUtils() { + } + + // 日志 TAG + private static final String TAG = JSONObjectUtils.class.getSimpleName(); + + // ================== + // = 转换 JSON 字符串 = + // ================== + + /** + * 转换为 JSON 格式字符串 + *
+     *     TODO 不支持 实体类 转 JSON 字符串
+     * 
+ * @param object Object + * @return JSON String + */ + public static String toJson(final Object object) { + return toJson(object, -1); + } + + /** + * 转换为 JSON 格式字符串 + *
+     *     TODO 不支持 实体类 转 JSON 字符串
+     * 
+ * @param object Object + * @param jsonIndent JSON 缩进间隔 + * @return JSON String + */ + public static String toJson( + final Object object, + final int jsonIndent + ) { + if (object == null) return null; + // 判断是否格式化 + boolean format = jsonIndent >= 1; + try { + if (object instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) object; + return format ? jsonObject.toString(jsonIndent) : jsonObject.toString(); + } else if (object instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) object; + return format ? jsonArray.toString(jsonIndent) : jsonArray.toString(); + } else if (object instanceof Map) { + JSONObject jsonObject = new JSONObject((Map) object); + return format ? jsonObject.toString(jsonIndent) : jsonObject.toString(); + } else if (object instanceof Collection) { + JSONArray jsonArray = new JSONArray((Collection) object); + return format ? jsonArray.toString(jsonIndent) : jsonArray.toString(); + } else if (object.getClass().isArray()) { + JSONArray jsonArray; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + jsonArray = new JSONArray(object); + } else { + jsonArray = new JSONArray(); + int length = Array.getLength(object); + for (int i = 0; i < length; ++i) { + jsonArray.put(wrap(Array.get(object, i))); + } + } + return format ? jsonArray.toString(jsonIndent) : jsonArray.toString(); + } else if (object instanceof JSONTokener) { + JSONTokener jsonTokener = (JSONTokener) object; + // 获取 value 对象 + Object tokenerObj = jsonTokener.nextValue(); + // 判断是什么格式 + if (tokenerObj instanceof JSONObject) { + JSONObject jsonObject = (JSONObject) tokenerObj; + return format ? jsonObject.toString(jsonIndent) : jsonObject.toString(); + } else if (tokenerObj instanceof JSONArray) { + JSONArray jsonArray = (JSONArray) tokenerObj; + return format ? jsonArray.toString(jsonIndent) : jsonArray.toString(); + } + } else { + String json = ConvertUtils.newStringNotArrayDecode(object); + if (json != null) { + if (json.startsWith("{")) { + JSONObject jsonObject = new JSONObject(json); + return format ? jsonObject.toString(jsonIndent) : jsonObject.toString(); + } else if (json.startsWith("[")) { + JSONArray jsonArray = new JSONArray(json); + return format ? jsonArray.toString(jsonIndent) : jsonArray.toString(); + } + } + } + // 抛出不支持的类型 + throw new Exception("Value " + object + " of className" + object.getClass().getName()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "toJson"); + } + return null; + } + + // ================ + // = 转换 JSON 对象 = + // ================ + + /** + * Object 转换 JSON 对象 + *
+     *     fromJson(xx, JSONObject.class);
+     *     fromJson(xx, JSONArray.class);
+     *     fromJson(xx, JSONTokener.class);
+     * 
+ * @param object Object + * @param type JSONObject.class || JSONArray.class || JSONTokener.class + * @param 泛型 + * @return 指定 type JSON 对象 + */ + public static T fromJson( + final Object object, + final Class type + ) { + if (object == null || type == null) return null; + try { + if (type == JSONObject.class) { + if (object instanceof JSONObject) { + return (T) object; + } else if (object instanceof JSONTokener) { + return (T) new JSONObject((JSONTokener) object); + } else if (object instanceof Map) { + return (T) new JSONObject((Map) object); + } else { + String json = ConvertUtils.newStringNotArrayDecode(object); + if (json != null) { + return (T) new JSONObject(json); + } + } + } else if (type == JSONArray.class) { + if (object instanceof JSONArray) { + return (T) object; + } else if (object instanceof JSONTokener) { + return (T) new JSONArray((JSONTokener) object); + } else if (object instanceof Collection) { + return (T) new JSONArray((Collection) object); + } else if (object.getClass().isArray()) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + return (T) new JSONArray(object); + } else { + JSONArray jsonArray = new JSONArray(); + int length = Array.getLength(object); + for (int i = 0; i < length; ++i) { + jsonArray.put(wrap(Array.get(object, i))); + } + return (T) jsonArray; + } + } else { + String json = ConvertUtils.newStringNotArrayDecode(object); + if (json != null) { + return (T) new JSONArray(json); + } + } + } else if (type == JSONTokener.class) { + String json = ConvertUtils.newStringNotArrayDecode(object); + if (json != null) { + return (T) new JSONTokener(json); + } + } + // 抛出不支持的类型 + throw new Exception( + "Value " + object + " of className" + object.getClass().getName() + + " converted Type " + type.getCanonicalName() + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "fromJson"); + } + return null; + } + + // ========== + // = 其他处理 = + // ========== + + /** + * 包装转换 Object + *
+     *     {@link JSONObject#wrap(Object)}
+     * 
+ * @param object Object + * @return 转换后的 Object + */ + public static Object wrap(final Object object) { + if (object == null) return null; + if (object instanceof JSONArray || object instanceof JSONObject) { + return object; + } + try { + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } else if (object.getClass().isArray()) { + // 版本兼容 + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { + return new JSONArray(object); + } else { + JSONArray jsonArray = new JSONArray(); + int length = Array.getLength(object); + for (int i = 0; i < length; ++i) { + jsonArray.put(wrap(Array.get(object, i))); + } + return jsonArray; + } + } + if (object instanceof Map) { + return new JSONObject((Map) object); + } + if (object instanceof Boolean || + object instanceof Byte || + object instanceof Character || + object instanceof Double || + object instanceof Float || + object instanceof Integer || + object instanceof Long || + object instanceof Short || + object instanceof String) { + return object; + } + if (object.getClass().getPackage().getName().startsWith("java.")) { + return object.toString(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "wrap"); + } + return null; + } + + /** + * 字符串 JSON 转义处理 + * @param str 字符串 + * @return 转义后的 JSON 字符串 + */ + public static String stringJSONEscape(final String str) { + if (str == null) return ""; + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = str.length(); i < len; i++) { + char ch = str.charAt(i); + switch (ch) { + case '"': + builder.append("\\\""); + break; + case '\\': + builder.append("\\\\"); + break; + case '\b': + builder.append("\\b"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '\t': + builder.append("\\t"); + break; + case '/': + builder.append("\\/"); + break; + default: + if (ch <= '\u001F') { + String ss = Integer.toHexString(ch); + builder.append("\\u"); + for (int k = 0; k < 4 - ss.length(); k++) { + builder.append('0'); + } + builder.append(ss.toUpperCase()); + } else { + builder.append(ch); + } + } + } + return builder.toString(); + } + + /** + * 判断字符串是否 JSON 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + public static boolean isJSON(final String json) { + if (!TextUtils.isEmpty(json)) { + if (json.startsWith("[") && json.endsWith("]")) { + try { + new JSONArray(json); + return true; + } catch (Exception ignored) { + } + } else if (json.startsWith("{") && json.endsWith("}")) { + try { + new JSONObject(json); + return true; + } catch (Exception ignored) { + } + } + } + return false; + } + + /** + * 判断字符串是否 JSON Object 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + public static boolean isJSONObject(final String json) { + if (!TextUtils.isEmpty(json)) { + if (json.startsWith("{") && json.endsWith("}")) { + try { + new JSONObject(json); + return true; + } catch (Exception ignored) { + } + } + } + return false; + } + + /** + * 判断字符串是否 JSON Array 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + public static boolean isJSONArray(final String json) { + if (!TextUtils.isEmpty(json)) { + if (json.startsWith("[") && json.endsWith("]")) { + try { + new JSONArray(json); + return true; + } catch (Exception ignored) { + } + } + } + return false; + } + + // = + + /** + * 将 JSON 格式字符串转化为 Map + * @param json JSON String + * @return {@link Map} + */ + public static Map jsonToMap(final String json) { + return isJSONObject(json) ? jsonToMap(fromJson(json, JSONObject.class)) : null; + } + + /** + * 将 JSON 对象转化为 Map + * @param jsonObject {@link JSONObject} + * @return {@link Map} + */ + public static Map jsonToMap(final JSONObject jsonObject) { + if (JSONObject.NULL.equals(jsonObject)) return null; + try { + Map map = new LinkedHashMap<>(); + Iterator iterator = jsonObject.keys(); + while (iterator.hasNext()) { + String key = iterator.next(); + Object value = jsonObject.get(key); + if (value instanceof JSONArray) { + value = jsonToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonToMap((JSONObject) value); + } + map.put(key, value); + } + return map; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "jsonToMap"); + } + return null; + } + + // = + + /** + * 将 JSON 格式字符串转化为 List + * @param json JSON String + * @return {@link List} + */ + public static List jsonToList(final String json) { + return isJSONArray(json) ? jsonToList(fromJson(json, JSONArray.class)) : null; + } + + /** + * 将 JSON 对象转化为 List + * @param jsonArray {@link JSONArray} + * @return {@link List} + */ + public static List jsonToList(final JSONArray jsonArray) { + if (JSONObject.NULL.equals(jsonArray)) return null; + try { + List list = new ArrayList<>(); + for (int i = 0, len = jsonArray.length(); i < len; i++) { + Object value = jsonArray.get(i); + if (value instanceof JSONArray) { + value = jsonToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonToMap((JSONObject) value); + } + list.add(value); + } + return list; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "jsonToList"); + } + return null; + } + + // = + + /** + * 获取 JSONObject + * @param json JSON String + * @return {@link JSONObject} + */ + public static JSONObject getJSONObject(final String json) { + try { + return new JSONObject(json); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONObject"); + } + return null; + } + + /** + * 获取 JSONArray + * @param json JSON String + * @return {@link JSONArray} + */ + public static JSONArray getJSONArray(final String json) { + try { + return new JSONArray(json); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONArray"); + } + return null; + } + + // = + + /** + * 获取 JSONObject + * @param jsonObject {@link JSONObject} + * @param key Key + * @return {@link JSONObject} + */ + public static JSONObject getJSONObject( + final JSONObject jsonObject, + final String key + ) { + try { + return jsonObject.getJSONObject(key); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONObject"); + } + return null; + } + + /** + * 获取 JSONArray + * @param jsonObject {@link JSONObject} + * @param key Key + * @return {@link JSONArray} + */ + public static JSONArray getJSONArray( + final JSONObject jsonObject, + final String key + ) { + try { + return jsonObject.getJSONArray(key); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONArray"); + } + return null; + } + + // = + + /** + * 获取 JSONObject + * @param jsonArray {@link JSONArray} + * @param index 索引 + * @return {@link JSONObject} + */ + public static JSONObject getJSONObject( + final JSONArray jsonArray, + final int index + ) { + try { + return jsonArray.getJSONObject(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONObject"); + } + return null; + } + + /** + * 获取 JSONArray + * @param jsonArray {@link JSONArray} + * @param index 索引 + * @return {@link JSONArray} + */ + public static JSONArray getJSONArray( + final JSONArray jsonArray, + final int index + ) { + try { + return jsonArray.getJSONArray(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getJSONArray"); + } + return null; + } + + // = + + /** + * 获取指定 key 数据 + * @param jsonObject {@link JSONObject} + * @param key Key + * @param 泛型 + * @return 指定 key 数据 + */ + public static T get( + final JSONObject jsonObject, + final String key + ) { + if (jsonObject != null && key != null) { + try { + return (T) jsonObject.get(key); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get - JSONObject"); + } + } + return null; + } + + /** + * 获取指定 key 数据 + * @param jsonObject {@link JSONObject} + * @param key Key + * @param 泛型 + * @return 指定 key 数据 + */ + public static T opt( + final JSONObject jsonObject, + final String key + ) { + if (jsonObject != null && key != null) { + try { + return (T) jsonObject.opt(key); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "opt - JSONObject"); + } + } + return null; + } + + // = + + /** + * 获取指定索引数据 + * @param jsonArray {@link JSONArray} + * @param index 索引 + * @param 泛型 + * @return 指定 key 数据 + */ + public static T get( + final JSONArray jsonArray, + final int index + ) { + if (jsonArray != null && index >= 0) { + try { + return (T) jsonArray.get(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get - JSONArray"); + } + } + return null; + } + + /** + * 获取指定索引数据 + * @param jsonArray {@link JSONArray} + * @param index 索引 + * @param 泛型 + * @return 指定 key 数据 + */ + public static T opt( + final JSONArray jsonArray, + final int index + ) { + if (jsonArray != null && index >= 0) { + try { + return (T) jsonArray.opt(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "opt - JSONArray"); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/KeyBoardUtils.java b/lib/DevApp/src/main/java/dev/utils/app/KeyBoardUtils.java new file mode 100644 index 0000000000..8d0c086280 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/KeyBoardUtils.java @@ -0,0 +1,675 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +import java.lang.reflect.Field; + +import dev.utils.LogPrintUtils; + +/** + * detail: 软键盘相关工具类 + * @author Ttt + *
+ *     避免软键盘面板遮挡 manifest.xml 中 activity 中设置
+ *     android:windowSoftInputMode="adjustPan"
+ *     android:windowSoftInputMode="adjustUnspecified|stateHidden"
+ * 
+ */ +public final class KeyBoardUtils { + + private KeyBoardUtils() { + } + + // 日志 TAG + private static final String TAG = KeyBoardUtils.class.getSimpleName(); + + // 主线程 Handler + private static final Handler sMainHandler = new Handler(Looper.getMainLooper()); + // 默认延迟时间 ( 毫秒 ) + private static long DELAY_MILLIS = 300; + // 键盘显示 + public static final int KEYBOARD_DISPLAY = 930; + // 键盘隐藏 + public static final int KEYBOARD_HIDE = 931; + + /** + * 设置延迟时间 + * @param delayMillis 延迟时间 ( 毫秒 ) + */ + public static void setDelayMillis(final long delayMillis) { + DELAY_MILLIS = delayMillis; + } + + // = + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean setSoftInputMode( + final Activity activity, + final boolean inputVisible + ) { + return setSoftInputMode( + activity != null ? activity.getWindow() : null, + inputVisible, true + ); + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean setSoftInputMode( + final Window window, + final boolean inputVisible + ) { + return setSoftInputMode(window, inputVisible, true); + } + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return {@code true} success, {@code false} fail + */ + public static boolean setSoftInputMode( + final Activity activity, + final boolean inputVisible, + final boolean clearFlag + ) { + return setSoftInputMode( + activity != null ? activity.getWindow() : null, + inputVisible, clearFlag + ); + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return {@code true} success, {@code false} fail + */ + public static boolean setSoftInputMode( + final Window window, + final boolean inputVisible, + final boolean clearFlag + ) { + return WindowUtils.get().setKeyBoardSoftInputMode( + window, inputVisible, clearFlag + ); + } + + // ============================ + // = 点击非 EditText 则隐藏软键盘 = + // ============================ + + /** + * 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 + * @param view {@link View} + * @param activity {@link Activity} + */ + @SuppressLint("ClickableViewAccessibility") + public static void judgeView( + final View view, + final Activity activity + ) { + if (view == null || activity == null) return; + if (!(view instanceof EditText)) { + view.setOnTouchListener((v, event) -> { + closeKeyboard(activity); + return false; + }); + } + // = + if (view instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) view; + for (int i = 0, len = viewGroup.getChildCount(); i < len; i++) { + View innerView = viewGroup.getChildAt(i); + judgeView(innerView, activity); + } + } + } + + // =============== + // = 软键盘隐藏显示 = + // =============== + + /** + * 判断软键盘是否可见 + * @param activity {@link Activity} + * @return {@code true} 可见, {@code false} 不可见 + */ + public static boolean isSoftInputVisible(final Activity activity) { + return isSoftInputVisible(activity, 200); + } + + /** + * 判断软键盘是否可见 + * @param activity {@link Activity} + * @param minHeightOfSoftInput 软键盘最小高度 + * @return {@code true} 可见, {@code false} 不可见 + */ + public static boolean isSoftInputVisible( + final Activity activity, + final int minHeightOfSoftInput + ) { + return getContentViewInvisibleHeight(activity) >= minHeightOfSoftInput; + } + + /** + * 计算 Activity content View 高度 + * @param activity {@link Activity} + * @return View 的高度 + */ + private static int getContentViewInvisibleHeight(final Activity activity) { + try { + final View contentView = activity.findViewById(android.R.id.content); + Rect rect = new Rect(); + contentView.getWindowVisibleDisplayFrame(rect); + return contentView.getRootView().getHeight() - rect.height(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getContentViewInvisibleHeight"); + return 0; + } + } + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link OnSoftInputChangedListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean registerSoftInputChangedListener( + final Activity activity, + final OnSoftInputChangedListener listener + ) { + try { + // 获取根 View + final View contentView = activity.findViewById(android.R.id.content); + // 添加事件 + contentView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if (listener != null) { + // 获取高度 + int height = getContentViewInvisibleHeight(activity); + // 判断是否相同 + listener.onSoftInputChanged(height >= 200, height); + } + }); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "registerSoftInputChangedListener"); + } + return false; + } + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link OnSoftInputChangedListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean registerSoftInputChangedListener2( + final Activity activity, + final OnSoftInputChangedListener listener + ) { + try { + final View decorView = activity.getWindow().getDecorView(); + decorView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { + if (listener != null) { + try { + Rect rect = new Rect(); + decorView.getWindowVisibleDisplayFrame(rect); + // 计算出可见屏幕的高度 + int displayHeight = rect.bottom - rect.top; + // 获取屏幕整体的高度 + int height = decorView.getHeight(); + // 获取键盘高度 + int keyboardHeight = height - displayHeight; + // 计算一定比例 + boolean visible = ((double) displayHeight / (double) height) < 0.8D; + // 判断是否显示 + listener.onSoftInputChanged(visible, keyboardHeight); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "registerSoftInputChangedListener2"); + } + } + }); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "registerSoftInputChangedListener2"); + } + return false; + } + + /** + * detail: 软键盘弹出、隐藏改变事件 + * @author Ttt + */ + public interface OnSoftInputChangedListener { + + /** + * 软键盘弹出、隐藏改变通知 + * @param visible 是否显示了软键盘 + * @param height 软键盘高度 + */ + void onSoftInputChanged( + boolean visible, + int height + ); + } + + // = + + /** + * 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 + * @param context {@link Context} + */ + public static void fixSoftInputLeaks(final Context context) { + if (context == null) return; + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + String[] array = new String[]{ + "mCurRootView", "mServedView", + "mNextServedView", "mLastSrvView" + }; + for (String value : array) { + try { + Field declaredField = imm.getClass().getDeclaredField(value); + if (!declaredField.isAccessible()) { + declaredField.setAccessible(true); + } + Object object = declaredField.get(imm); + if (!(object instanceof View)) continue; + View view = (View) object; + if (view.getContext() == context) { + declaredField.set(imm, null); + } else { + return; + } + } catch (Throwable ignore) { + } + } + } catch (Exception ignored) { + } + } + + /** + * 自动切换键盘状态, 如果键盘显示则隐藏反之显示 + *
+     *     // 无法获取键盘是否打开 ( 不准确 )
+     *     InputMethodManager.isActive()
+     *     // 获取状态有些版本可以, 不适用
+     *     Activity.getWindow().getAttributes().softInputMode
+     *     

+ * 可以配合 {@link #isSoftInputVisible(Activity)} 判断是否显示输入法 + *
+ * @return {@code true} success, {@code false} fail + */ + public static boolean toggleKeyboard() { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "toggleKeyboard"); + } + return false; + } + + // =========== + // = 打开软键盘 = + // =========== + + /** + * 打开软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboard() { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openKeyboard"); + } + return false; + } + + /** + * 延时打开软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboardDelay() { + return openKeyboardDelay(DELAY_MILLIS); + } + + /** + * 延时打开软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboardDelay(final long delayMillis) { + sMainHandler.postDelayed(() -> openKeyboard(), delayMillis); + return true; + } + + // = + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboard(final EditText editText) { + if (editText != null) { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.showSoftInput(editText, InputMethodManager.SHOW_FORCED); + imm.toggleSoftInput( + InputMethodManager.SHOW_FORCED, + InputMethodManager.HIDE_IMPLICIT_ONLY + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openKeyboard"); + } + } + return false; + } + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboardDelay(final EditText editText) { + return openKeyboardDelay(editText, DELAY_MILLIS); + } + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboardDelay( + final EditText editText, + final long delayMillis + ) { + if (editText != null) { + sMainHandler.postDelayed(() -> { + try { + editText.requestFocus(); + editText.setSelection(editText.getText().toString().length()); + } catch (Exception ignored) { + } + openKeyboard(editText); + }, delayMillis); + return true; + } + return false; + } + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return {@code true} success, {@code false} fail + */ + public static boolean openKeyboardByFocus(final EditText editText) { + if (editText != null) { + editText.setFocusable(true); + editText.setFocusableInTouchMode(true); + editText.requestFocus(); + editText.performClick(); + return true; + } + return false; + } + + // =========== + // = 关闭软键盘 = + // =========== + + /** + * 关闭软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboard() { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeKeyboard"); + } + return false; + } + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboard(final EditText editText) { + if (editText != null) { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.hideSoftInputFromWindow(editText.getWindowToken(), 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeKeyboard"); + } + } + return false; + } + + /** + * 关闭软键盘 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboard(final Activity activity) { + if (activity != null) { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.hideSoftInputFromWindow( + activity.getWindow().peekDecorView().getWindowToken(), 0 + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeKeyboard"); + } + } + return false; + } + + /** + * 关闭 dialog 中打开的键盘 + * @param dialog {@link Dialog} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboard(final Dialog dialog) { + if (dialog != null) { + try { + InputMethodManager imm = AppUtils.getInputMethodManager(); + imm.hideSoftInputFromWindow( + dialog.getWindow().peekDecorView().getWindowToken(), 0 + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeKeyboard"); + } + } + return false; + } + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyBoardSpecial( + final EditText editText, + final Dialog dialog + ) { + try { + closeKeyboard(); + closeKeyboard(editText); + closeKeyboard(dialog); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeKeyBoardSpecial"); + } + return false; + } + + // ========== + // = 延时关闭 = + // ========== + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyBoardSpecialDelay( + final EditText editText, + final Dialog dialog + ) { + return closeKeyBoardSpecialDelay(editText, dialog, DELAY_MILLIS); + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyBoardSpecialDelay( + final EditText editText, + final Dialog dialog, + final long delayMillis + ) { + sMainHandler.postDelayed(() -> closeKeyBoardSpecial(editText, dialog), delayMillis); + return true; + } + + // = + + /** + * 延时关闭软键盘 + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay() { + return closeKeyboardDelay(DELAY_MILLIS); + } + + /** + * 延时关闭软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay(final long delayMillis) { + sMainHandler.postDelayed(() -> closeKeyboard(), delayMillis); + return true; + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay(final EditText editText) { + return closeKeyboardDelay(editText, DELAY_MILLIS); + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay( + final EditText editText, + final long delayMillis + ) { + if (editText != null) { + sMainHandler.postDelayed(() -> closeKeyboard(editText), delayMillis); + return true; + } + return false; + } + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay(final Activity activity) { + return closeKeyboardDelay(activity, DELAY_MILLIS); + } + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay( + final Activity activity, + final long delayMillis + ) { + if (activity != null) { + sMainHandler.postDelayed(() -> closeKeyboard(activity), delayMillis); + return true; + } + return false; + } + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay(final Dialog dialog) { + return closeKeyboardDelay(dialog, DELAY_MILLIS); + } + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean closeKeyboardDelay( + final Dialog dialog, + final long delayMillis + ) { + if (dialog != null) { + sMainHandler.postDelayed(() -> closeKeyboard(dialog), delayMillis); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/KeyguardUtils.java b/lib/DevApp/src/main/java/dev/utils/app/KeyguardUtils.java new file mode 100644 index 0000000000..e672b93b60 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/KeyguardUtils.java @@ -0,0 +1,184 @@ +package dev.utils.app; + +import android.Manifest; +import android.app.KeyguardManager; +import android.os.Build; + +import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresPermission; + +import dev.utils.LogPrintUtils; + +/** + * detail: 锁屏管理工具类 ( 锁屏、禁用锁屏, 判断是否锁屏 ) + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) +public final class KeyguardUtils { + + // 日志 TAG + private static final String TAG = KeyguardUtils.class.getSimpleName(); + + // KeyguardUtils 实例 + private static volatile KeyguardUtils sInstance; + + /** + * 获取 KeyguardUtils 实例 + * @return {@link KeyguardUtils} + */ + public static KeyguardUtils getInstance() { + if (sInstance == null) { + synchronized (KeyguardUtils.class) { + if (sInstance == null) { + sInstance = new KeyguardUtils(); + } + } + } + return sInstance; + } + + // 锁屏管理类 + private KeyguardManager mKeyguardManager; + // android 26 开始过时 + private KeyguardManager.KeyguardLock mKeyguardLock; + + /** + * 构造函数 + */ + private KeyguardUtils() { + try { + mKeyguardManager = AppUtils.getKeyguardManager(); + // 初始化锁 + mKeyguardLock = mKeyguardManager.newKeyguardLock(TAG); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "KeyguardUtils"); + } + } + + /** + * 是否锁屏 ( android 4.1 以上支持 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isKeyguardLocked() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (mKeyguardManager != null) return mKeyguardManager.isKeyguardLocked(); + } + return false; + } + + /** + * 是否有锁屏密码 ( android 4.1 以上支持 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isKeyguardSecure() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (mKeyguardManager != null) return mKeyguardManager.isKeyguardSecure(); + } + return false; + } + + /** + * 是否锁屏 + * @return {@code true} yes, {@code false} no + */ + public boolean inKeyguardRestrictedInputMode() { + if (mKeyguardManager != null) return mKeyguardManager.inKeyguardRestrictedInputMode(); + return false; + } + + /** + * 获取 KeyguardManager + * @return {@link KeyguardManager} + */ + public KeyguardManager getKeyguardManager() { + return mKeyguardManager; + } + + /** + * 设置 KeyguardManager + * @param keyguardManager {@link KeyguardManager} + * @return {@link KeyguardUtils} + */ + public KeyguardUtils setKeyguardManager(final KeyguardManager keyguardManager) { + this.mKeyguardManager = keyguardManager; + return this; + } + + // = + + /** + * 屏蔽系统的屏保 + * 利用 disableKeyguard 解锁, 解锁并不是真正的解锁, 只是把锁屏的界面隐藏掉而已 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) + public boolean disableKeyguard() { + if (mKeyguardLock != null) { + mKeyguardLock.disableKeyguard(); + return true; + } + return false; + } + + /** + * 使能显示锁屏界面, 如果你之前调用了 disableKeyguard() 方法取消锁屏界面, 那么会马上显示锁屏界面 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) + public boolean reenableKeyguard() { + if (mKeyguardLock != null) { + mKeyguardLock.reenableKeyguard(); + return true; + } + return false; + } + + /** + * 释放资源 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.DISABLE_KEYGUARD) + public boolean release() { + if (mKeyguardLock != null) { + mKeyguardLock.reenableKeyguard(); + return true; + } + return false; + } + + // = + + /** + * 获取 KeyguardManager.KeyguardLock + * @return {@link KeyguardManager.KeyguardLock} + */ + public KeyguardManager.KeyguardLock getKeyguardLock() { + return mKeyguardLock; + } + + /** + * 设置 KeyguardManager.KeyguardLock + * @param keyguardLock {@link KeyguardManager.KeyguardLock} + * @return {@link KeyguardUtils} + */ + public KeyguardUtils setKeyguardLock(final KeyguardManager.KeyguardLock keyguardLock) { + this.mKeyguardLock = keyguardLock; + return this; + } + + /** + * 设置 KeyguardManager.KeyguardLock ( 通过 TAG 生成 ) + * @param tag TAG + * @return {@link KeyguardUtils} + */ + public KeyguardUtils setKeyguardLock(final String tag) { + if (mKeyguardManager != null && tag != null) { + mKeyguardLock = mKeyguardManager.newKeyguardLock(tag); + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/LanguageUtils.java b/lib/DevApp/src/main/java/dev/utils/app/LanguageUtils.java new file mode 100644 index 0000000000..1acd5632ec --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/LanguageUtils.java @@ -0,0 +1,307 @@ +package dev.utils.app; + +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import dev.utils.LogPrintUtils; + +/** + * detail: 语言工具类 + * @author Ttt + */ +public final class LanguageUtils { + + private LanguageUtils() { + } + + // 日志 TAG + private static final String TAG = LanguageUtils.class.getSimpleName(); + + /** + * 获取系统语言 + * @return Locale Language + */ + public static String getSystemLanguage() { + Locale locale = getSystemPreferredLanguage(); + return (locale != null) ? locale.getLanguage() : ""; + } + + /** + * 获取系统语言区域 + * @return Locale Language + */ + public static String getSystemCountry() { + Locale locale = getSystemPreferredLanguage(); + return (locale != null) ? locale.getCountry() : ""; + } + + /** + * 获取系统首选语言 + * @return {@link Locale} + */ + public static Locale getSystemPreferredLanguage() { + Locale locale = null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + //locale = LocaleList.getDefault().get(0); + locale = ResourceUtils.getConfiguration().getLocales().get(0); + } else { + //locale = Locale.getDefault(); + locale = ResourceUtils.getConfiguration().locale; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSystemPreferredLanguage"); + } + return locale; + } + + /** + * 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} - Activity + * @param locale {@link Locale} + * @return {@code true} success, {@code false} fail + */ + public static boolean applyLanguage( + final Context context, + final Locale locale + ) { + if (context != null && locale != null) { + try { + // 获取 res 资源对象 + Resources resources = context.getResources(); + // 获取设置对象 + Configuration config = resources.getConfiguration(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // apply locale + config.setLocale(locale); + context.createConfigurationContext(config); + } else { + // updateConfiguration + // 获取屏幕参数: 主要是分辨率, 像素等 + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + config.locale = locale; + // 更新语言 + resources.updateConfiguration(config, displayMetrics); + } + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "applyLanguage"); + } + } + return false; + } + + /** + * 修改系统语言 (APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} + * @param language 语言 + * @return {@code true} success, {@code false} fail + */ + public static boolean applyLanguage( + final Context context, + final String language + ) { + Locale locale = getSupportLanguage(language); + if (locale != null) { + return applyLanguage(context, locale); + } else { // 如果为 null, 则使用系统默认语言 + return applyLanguage(context, getSystemPreferredLanguage()); + } + } + + // = + + // 英语 + public static final String ENGLISH = "en"; + // 英语 ( 英式 ) + public static final String UK = "enGB"; + // 英语 ( 美式 ) + public static final String US = "enUS"; + // 法语 + public static final String FRENCH = "fr"; + // 德语 + public static final String GERMAN = "de"; + // 日文 + public static final String JAPAN = "jp"; + // 韩文 + public static final String KOREA = "kr"; + // 中文 + public static final String CHINESE = "zh"; + // 简体中文 + public static final String SIMPLIFIED_CHINESE = "zhCN"; + // 繁体中文 ( 默认台湾 ) + public static final String TRADITIONAL_CHINESE = "zhTW"; + // 台湾 + public static final String TAIWAN_CHINESE = TRADITIONAL_CHINESE; + // 支持的语言字典 + private static final Map sSupportLanguageMaps = new HashMap<>(20); + + static { + // 英语 + sSupportLanguageMaps.put(ENGLISH, Locale.ENGLISH); + // 英语 ( 英式 ) + sSupportLanguageMaps.put(UK, Locale.UK); + // 英语 ( 美式 ) + sSupportLanguageMaps.put(US, Locale.US); + // 法语 + sSupportLanguageMaps.put(FRENCH, Locale.FRENCH); + // 德语 + sSupportLanguageMaps.put(GERMAN, Locale.GERMAN); + // 日文 + sSupportLanguageMaps.put(JAPAN, Locale.JAPAN); + // 韩文 + sSupportLanguageMaps.put(KOREA, Locale.KOREA); + // 中文 + sSupportLanguageMaps.put(CHINESE, Locale.CHINESE); + // 简体中文 + sSupportLanguageMaps.put(SIMPLIFIED_CHINESE, Locale.SIMPLIFIED_CHINESE); + // 繁体中文 ( 默认台湾 ) + sSupportLanguageMaps.put(TRADITIONAL_CHINESE, Locale.TRADITIONAL_CHINESE); + } + + /** + * 获取支持的语言 + * @return {@link Map} 支持的语言 + */ + public static Map getSupportLanguages() { + return new HashMap<>(sSupportLanguageMaps); + } + + /** + * 添加支持的语言 + * @param language 语言 + * @param locale {@link Locale} + */ + public static void putSupportLanguage( + final String language, + final Locale locale + ) { + sSupportLanguageMaps.put(language, locale); + } + + /** + * 移除支持的语言 + * @param language 语言 + */ + public static void removeSupportLanguage(final String language) { + sSupportLanguageMaps.remove(language); + } + + /** + * 是否支持此语言 + * @param language 语言 + * @return {@code true} 支持, {@code false} 不支持 + */ + public static boolean isSupportLanguage(final String language) { + return sSupportLanguageMaps.containsKey(language); + } + + /** + * 获取支持语言 + * @param language 语言 + * @return {@link Locale} + */ + public static Locale getSupportLanguage(final String language) { + if (isSupportLanguage(language)) { + return sSupportLanguageMaps.get(language); + } + return null; + } + + // ========== + // = 语言判断 = + // ========== + + /** + * 判断是否为英文语言环境 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEn() { + return isLanguage("en"); + } + + /** + * 判断是否为中文语言环境 + * @return {@code true} yes, {@code false} no + */ + public static boolean isZh() { + return isLanguage("zh"); + } + + /** + * 判断是否为中文简体语言环境 + * @return {@code true} yes, {@code false} no + */ + public static boolean isZhCN() { + return isRegion("CN", "zh"); + } + + /** + * 判断是否为中文繁体语言环境 + * @return {@code true} yes, {@code false} no + */ + public static boolean isZhTW() { + return isRegion("TW", "zh"); + } + + // = + + /** + * 判断是否为指定语言环境 + * @param language 语言 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLanguage(final String language) { + if (TextUtils.isEmpty(language)) return false; + Locale locale = getSystemPreferredLanguage(); + if (locale != null) { + String lang = locale.getLanguage(); + return language.equalsIgnoreCase(lang); + } + return false; + } + + /** + * 判断是否为指定区域语言环境 + * @param region 区域 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRegion(final String region) { + if (TextUtils.isEmpty(region)) return false; + Locale locale = getSystemPreferredLanguage(); + if (locale != null) { + String country = locale.getCountry(); + return region.equalsIgnoreCase(country); + } + return false; + } + + /** + * 判断是否为指定区域语言环境 + * @param region 区域 + * @param language 语言 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRegion( + final String region, + final String language + ) { + if (TextUtils.isEmpty(region)) return false; + if (TextUtils.isEmpty(language)) return false; + Locale locale = getSystemPreferredLanguage(); + if (locale != null) { + String country = locale.getCountry(); + String lang = locale.getLanguage(); + return region.equalsIgnoreCase(country) && language.equalsIgnoreCase(lang); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ListViewUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ListViewUtils.java new file mode 100644 index 0000000000..0d67477aaa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ListViewUtils.java @@ -0,0 +1,648 @@ +package dev.utils.app; + +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; +import android.widget.GridView; +import android.widget.HorizontalScrollView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.ScrollView; + +import androidx.core.widget.NestedScrollView; +import androidx.recyclerview.widget.RecyclerView; + +import dev.utils.LogPrintUtils; +import dev.utils.common.NumberUtils; + +/** + * detail: List View ( 列表 View ) 相关工具类 + * @author Ttt + *
+ *     Fading Edge 让你的 View 更有层次感
+ *     @see 
+ *     

+ * android:descendantFocusability="blocksDescendants" + * android:overScrollMode="never" + * android:scrollbars="none" + *

+ * 如果滑动到指定位置不准确可使用, 进行偏移滑动 + * LayoutManager.scrollToPositionWithOffset(position, 0); + *
+ */ +public final class ListViewUtils { + + private ListViewUtils() { + } + + // 日志 TAG + private static final String TAG = ListViewUtils.class.getSimpleName(); + + // ============= + // = List View = + // ============= + + /** + * 获取 Adapter Item 总数 + * @param view {@link View} + * @return Adapter Item 总数 + */ + public static int getItemCount(final View view) { + if (view != null) { + if (view instanceof RecyclerView) { + RecyclerView recyclerView = (RecyclerView) view; + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + return (adapter != null) ? adapter.getItemCount() : 0; + } else if (view instanceof ListView) { + ListView listView = (ListView) view; + ListAdapter listAdapter = listView.getAdapter(); + return (listAdapter != null) ? listAdapter.getCount() : 0; + } else if (view instanceof GridView) { + GridView gridView = (GridView) view; + ListAdapter listAdapter = gridView.getAdapter(); + return (listAdapter != null) ? listAdapter.getCount() : 0; + } + } + return 0; + } + + /** + * 获取指定索引 Item View + * @param view {@link View} + * @param position 索引 + * @return {@link View} + */ + public static View getItemView( + final View view, + final int position + ) { + if (view != null && position >= 0) { + if (view instanceof RecyclerView) { + RecyclerView recyclerView = (RecyclerView) view; + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter != null && adapter.getItemCount() > 0 + && position < adapter.getItemCount()) { + try { + RecyclerView.ViewHolder holder = adapter.createViewHolder( + recyclerView, adapter.getItemViewType(position) + ); + adapter.onBindViewHolder(holder, position); + return holder.itemView; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemView - RecyclerView"); + } + } + } else if (view instanceof ListView) { + ListView listView = (ListView) view; + ListAdapter listAdapter = listView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0 + && position < listAdapter.getCount()) { + try { + return listAdapter.getView(position, null, listView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemView - ListView"); + } + } + } else if (view instanceof GridView) { + GridView gridView = (GridView) view; + ListAdapter listAdapter = gridView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0 + && position < listAdapter.getCount()) { + try { + return listAdapter.getView(position, null, gridView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemView - GridView"); + } + } + } + } + return null; + } + + // ======= + // = 滑动 = + // ======= + + /** + * 滑动到指定索引 ( 有滚动过程 ) + * @param view {@link View} + * @param position 索引 + * @param 泛型 + * @return {@link View} + */ + public static T smoothScrollToPosition( + final T view, + final int position + ) { + if (view != null && position >= 0) { + if (view instanceof RecyclerView) { + ((RecyclerView) view).smoothScrollToPosition(position); + } else if (view instanceof ListView) { + ((ListView) view).smoothScrollToPosition(position); + } else if (view instanceof GridView) { + ((GridView) view).smoothScrollToPosition(position); + } + } + return view; + } + + /** + * 滑动到指定索引 ( 无滚动过程 ) + * @param view {@link View} + * @param position 索引 + * @param 泛型 + * @return {@link View} + */ + public static T scrollToPosition( + final T view, + final int position + ) { + if (view != null && position >= 0) { + if (view instanceof RecyclerView) { + ((RecyclerView) view).scrollToPosition(position); + } else if (view instanceof ListView) { + ((ListView) view).setSelection(position); + } else if (view instanceof GridView) { + ((GridView) view).setSelection(position); + } + } + return view; + } + + // ============ + // = 滑动到顶部 = + // ============ + + /** + * 滑动到顶部 ( 有滚动过程 ) + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T smoothScrollToTop(final T view) { + if (view != null) { + if (view instanceof RecyclerView || view instanceof ListView || view instanceof GridView) { + smoothScrollToPosition(view, 0); + } else { // 其他 View + smoothScrollTo(view, 0, 0); + } + } + return view; + } + + /** + * 滑动到顶部 ( 无滚动过程 ) + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T scrollToTop(final T view) { + if (view != null) { + if (view instanceof RecyclerView || view instanceof ListView || view instanceof GridView) { + scrollToPosition(view, 0); + } else { // 其他 View + scrollTo(view, 0, 0); + } + } + return view; + } + + // ============ + // = 滑动到底部 = + // ============ + + /** + * 滑动到底部 ( 有滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 smoothScrollBy 搭配到底部 )
+     *     smoothScrollToBottom(view)
+     *     smoothScrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T smoothScrollToBottom(final T view) { + if (view != null) { + if (view instanceof RecyclerView) { + RecyclerView recyclerView = (RecyclerView) view; + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter != null && adapter.getItemCount() > 0) { + recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); + } + } else if (view instanceof ListView) { + ListView listView = (ListView) view; + ListAdapter listAdapter = listView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0) { + listView.smoothScrollToPosition(listAdapter.getCount() - 1); + } + } else if (view instanceof GridView) { + GridView gridView = (GridView) view; + ListAdapter listAdapter = gridView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0) { + gridView.smoothScrollToPosition(listAdapter.getCount() - 1); + } + } else { // 其他 View + fullScroll(view, View.FOCUS_DOWN); + } + } + return view; + } + + /** + * 滑动到底部 ( 无滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 scrollBy 搭配到底部 )
+     *     scrollToBottom(view)
+     *     scrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T scrollToBottom(final T view) { + if (view != null) { + if (view instanceof RecyclerView) { + RecyclerView recyclerView = (RecyclerView) view; + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter != null && adapter.getItemCount() > 0) { + recyclerView.scrollToPosition(adapter.getItemCount() - 1); + } + } else if (view instanceof ListView) { + ListView listView = (ListView) view; + ListAdapter listAdapter = listView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0) { + listView.setSelection(listAdapter.getCount() - 1); + } + } else if (view instanceof GridView) { + GridView gridView = (GridView) view; + ListAdapter listAdapter = gridView.getAdapter(); + if (listAdapter != null && listAdapter.getCount() > 0) { + gridView.setSelection(listAdapter.getCount() - 1); + } + } else { // 其他 View + fullScroll(view, View.FOCUS_DOWN); + } + } + return view; + } + + // ============== + // = ScrollView = + // ============== + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) + * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param 泛型 + * @return {@link View} + */ + public static T smoothScrollTo( + final T view, + final int x, + final int y + ) { + if (view != null) { + if (view instanceof NestedScrollView) { + ((NestedScrollView) view).smoothScrollTo(x, y); + } else if (view instanceof ScrollView) { + ((ScrollView) view).smoothScrollTo(x, y); + } else if (view instanceof HorizontalScrollView) { + ((HorizontalScrollView) view).smoothScrollTo(x, y); + } + } + return view; + } + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) + * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param 泛型 + * @return {@link View} + */ + public static T smoothScrollBy( + final T view, + final int x, + final int y + ) { + if (view != null) { + if (view instanceof NestedScrollView) { + ((NestedScrollView) view).smoothScrollBy(x, y); + } else if (view instanceof ScrollView) { + ((ScrollView) view).smoothScrollBy(x, y); + } else if (view instanceof HorizontalScrollView) { + ((HorizontalScrollView) view).smoothScrollBy(x, y); + } else if (view instanceof RecyclerView) { + ((RecyclerView) view).smoothScrollBy(x, y); + } else if (view instanceof ListView) { + ((ListView) view).smoothScrollBy(y, 200); // PositionScroller.SCROLL_DURATION + } else if (view instanceof GridView) { + ((GridView) view).smoothScrollBy(y, 200); + } + } + return view; + } + + /** + * 滚动方向 ( 有滚动过程 ) + * @param view {@link View} + * @param direction 滚动方向 如: View.FOCUS_UP、View.FOCUS_DOWN + * @param 泛型 + * @return {@link View} + */ + public static T fullScroll( + final T view, + final int direction + ) { + if (view != null) { + if (view instanceof NestedScrollView) { + ((NestedScrollView) view).fullScroll(direction); + } else if (view instanceof ScrollView) { + ((ScrollView) view).fullScroll(direction); + } else if (view instanceof HorizontalScrollView) { + ((HorizontalScrollView) view).fullScroll(direction); + } + } + return view; + } + + // ============= + // = ViewUtils = + // ============= + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return {@link View} + */ + public static View scrollTo( + final View view, + final int x, + final int y + ) { + return ViewUtils.scrollTo(view, x, y); + } + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return {@link View} + */ + public static View scrollBy( + final View view, + final int x, + final int y + ) { + return ViewUtils.scrollBy(view, x, y); + } + + /** + * 设置 View 滑动的 X 轴坐标 + * @param view {@link View} + * @param value X 轴坐标 + * @return {@link View} + */ + public static View setScrollX( + final View view, + final int value + ) { + return ViewUtils.setScrollX(view, value); + } + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param view {@link View} + * @param value Y 轴坐标 + * @return {@link View} + */ + public static View setScrollY( + final View view, + final int value + ) { + return ViewUtils.setScrollY(view, value); + } + + /** + * 获取 View 滑动的 X 轴坐标 + * @param view {@link View} + * @return 滑动的 X 轴坐标 + */ + public static int getScrollX(final View view) { + return ViewUtils.getScrollX(view); + } + + /** + * 获取 View 滑动的 Y 轴坐标 + * @param view {@link View} + * @return 滑动的 Y 轴坐标 + */ + public static int getScrollY(final View view) { + return ViewUtils.getScrollY(view); + } + + // = + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param view {@link ViewGroup} + * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @param 泛型 + * @return {@link ViewGroup} + */ + public static T setDescendantFocusability( + final T view, + final int focusability + ) { + return ViewUtils.setDescendantFocusability(view, focusability); + } + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param view {@link View} + * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @param 泛型 + * @return {@link View} + */ + public static T setOverScrollMode( + final T view, + final int overScrollMode + ) { + return ViewUtils.setOverScrollMode(view, overScrollMode); + } + + // ========== + // = 计算高度 = + // ========== + + // ============ + // = ListView = + // ============ + + /** + * 计算 ListView 高度 + * @param listView {@link ListView} + * @return ListView 高度 + */ + public static int calcListViewHeight(final ListView listView) { + return calcListViewHeight(listView, false); + } + + /** + * 计算 ListView 高度 + * @param listView {@link ListView} + * @param setParams 是否 setLayoutParams + * @return ListView 高度 + */ + public static int calcListViewHeight( + final ListView listView, + final boolean setParams + ) { + if (listView == null) return 0; + try { + // Adapter + ListAdapter listAdapter = listView.getAdapter(); + // Item 总条数 + int itemCount = listAdapter.getCount(); + // 没数据则直接跳过 + if (itemCount == 0) return 0; + // 高度 + int height = 0; + // 获取子项间分隔符占用的高度 + int dividerHeight = listView.getDividerHeight(); + + // 循环绘制每个 Item 并保存 Bitmap + for (int i = 0; i < itemCount; i++) { + View childView = listAdapter.getView(i, null, listView); + WidgetUtils.measureView(childView, listView.getWidth()); + height += childView.getMeasuredHeight(); + } + // 追加子项间分隔符占用的高度 + height += (dividerHeight * (itemCount - 1)); + + // 是否需要设置高度 + if (setParams) { + ViewGroup.LayoutParams layoutParams = listView.getLayoutParams(); + layoutParams.height = height; + listView.setLayoutParams(layoutParams); + } + return height; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "calcListViewHeight"); + } + return 0; + } + + // ============ + // = GridView = + // ============ + + /** + * 计算 GridView 高度 + * @param gridView {@link GridView} + * @return GridView 高度 + */ + public static int calcGridViewHeight(final GridView gridView) { + return calcGridViewHeight(gridView, false); + } + + /** + * 计算 GridView 高度 + * @param gridView {@link GridView} + * @param setParams 是否 setLayoutParams + * @return GridView 高度 + */ + public static int calcGridViewHeight( + final GridView gridView, + final boolean setParams + ) { + if (gridView == null) return 0; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) return 0; + try { + // Adapter + ListAdapter listAdapter = gridView.getAdapter(); + // Item 总条数 + int itemCount = listAdapter.getCount(); + // 没数据则直接跳过 + if (itemCount == 0) return 0; + // 高度 + int height = 0; + // 获取一共多少列 + int numColumns = gridView.getNumColumns(); + // 每列之间的间隔 | + int horizontalSpacing = gridView.getHorizontalSpacing(); + // 每行之间的间隔 - + int verticalSpacing = gridView.getVerticalSpacing(); + // 获取倍数 ( 行数 ) + int lineNumber = NumberUtils.multiple(itemCount, numColumns); + // 计算总共的宽度 (GridView 宽度 - 列分割间距 ) / numColumns + int childWidth = (gridView.getWidth() - (numColumns - 1) * horizontalSpacing) / numColumns; + +// // 记录每行最大高度 +// int[] rowHeightArrays = new int[lineNumber]; + // 临时高度 ( 保存行中最高的列高度 ) + int tempHeight; + // 循环每一行绘制每个 Item 并保存 Bitmap + for (int i = 0; i < lineNumber; i++) { + // 清空高度 + tempHeight = 0; + // 循环列数 + for (int j = 0; j < numColumns; j++) { + // 获取对应的索引 + int position = i * numColumns + j; + // 如果大于总数据则跳过 + if (position < itemCount) { + View childView = listAdapter.getView(position, null, gridView); + WidgetUtils.measureView(childView, childWidth); + + int itemHeight = childView.getMeasuredHeight(); + // 保留最大高度 + tempHeight = Math.max(itemHeight, tempHeight); + } + + // 记录高度并累加 + if (j == numColumns - 1) { + height += tempHeight; +// rowHeightArrays[i] = tempHeight; + } + } + } + // 追加子项间分隔符占用的高度 + height += (verticalSpacing * (lineNumber - 1)); + + // 是否需要设置高度 + if (setParams) { + ViewGroup.LayoutParams layoutParams = gridView.getLayoutParams(); + layoutParams.height = height; + gridView.setLayoutParams(layoutParams); + } + return height; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "calcGridViewHeight"); + } + return 0; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ListenerUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ListenerUtils.java new file mode 100644 index 0000000000..ac73cef53a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ListenerUtils.java @@ -0,0 +1,316 @@ +package dev.utils.app; + +import android.app.Activity; +import android.view.View; + +import androidx.annotation.IdRes; + +import java.lang.reflect.Field; + +import dev.utils.LogPrintUtils; + +/** + * detail: 事件工具类 + * @author Ttt + */ +public final class ListenerUtils { + + private ListenerUtils() { + } + + // 日志 TAG + private static final String TAG = ListenerUtils.class.getSimpleName(); + + // ======== + // = Hook = + // ======== + + /** + * 获取 View 设置的 OnTouchListener 事件对象 + * @param view {@link View} + * @return {@link View.OnTouchListener} + */ + public static View.OnTouchListener getTouchListener(final View view) { + return (View.OnTouchListener) getListenerInfoListener( + view, "mOnTouchListener" + ); + } + + /** + * 获取 View 设置的 OnClickListener 事件对象 + * @param view {@link View} + * @return {@link View.OnClickListener} + */ + public static View.OnClickListener getClickListener(final View view) { + return (View.OnClickListener) getListenerInfoListener( + view, "mOnClickListener" + ); + } + + // = + + /** + * 获取 View ListenerInfo 对象 ( 内部类 ) + * @param view {@link View} + * @return ListenerInfo + */ + public static Object getListenerInfo(final View view) { + try { + // 获取 ListenerInfo 对象 + Field field = View.class.getDeclaredField("mListenerInfo"); + field.setAccessible(true); + return field.get(view); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getListenerInfo"); + } + return null; + } + + /** + * 获取 View ListenerInfo 对象内部事件对象 + * @param view {@link View} + * @param listener 事件名 + * @return 指定事件名事件对象 + */ + public static Object getListenerInfoListener( + final View view, + final String listener + ) { + try { + // 获取 ListenerInfo 对象 + Object listenerInfo = getListenerInfo(view); + // 获取 ListenerInfo 对象中的 mOnTouchListener 属性 + Class clazz = Class.forName("android.view.View$ListenerInfo"); + Field field = clazz.getDeclaredField(listener); + field.setAccessible(true); + // 进行获取返回 + return field.get(listenerInfo); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getListenerInfoListener"); + } + return null; + } + + // ========== + // = 常见事件 = + // ========== + + // ======= + // = 点击 = + // ======= + + /** + * 设置点击事件 + * @param view {@link View} + * @param listener {@link View.OnClickListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnClicks( + final View view, + final View.OnClickListener listener, + @IdRes final int... viewIds + ) { + if (view != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(view, viewId); + if (findView != null) { + findView.setOnClickListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置点击事件 + * @param activity {@link Activity} + * @param listener {@link View.OnClickListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnClicks( + final Activity activity, + final View.OnClickListener listener, + @IdRes final int... viewIds + ) { + if (activity != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(activity, viewId); + if (findView != null) { + findView.setOnClickListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnClicks( + final View.OnClickListener listener, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setOnClickListener(listener); + } + } + return true; + } + return false; + } + + // ======= + // = 长按 = + // ======= + + /** + * 设置长按事件 + * @param view {@link View} + * @param listener {@link View.OnLongClickListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnLongClicks( + final View view, + final View.OnLongClickListener listener, + @IdRes final int... viewIds + ) { + if (view != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(view, viewId); + if (findView != null) { + findView.setOnLongClickListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置长按事件 + * @param activity {@link Activity} + * @param listener {@link View.OnLongClickListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnLongClicks( + final Activity activity, + final View.OnLongClickListener listener, + @IdRes final int... viewIds + ) { + if (activity != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(activity, viewId); + if (findView != null) { + findView.setOnLongClickListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnLongClicks( + final View.OnLongClickListener listener, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setOnLongClickListener(listener); + } + } + return true; + } + return false; + } + + // ======= + // = 触摸 = + // ======= + + /** + * 设置触摸事件 + * @param view {@link View} + * @param listener {@link View.OnTouchListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnTouchs( + final View view, + final View.OnTouchListener listener, + @IdRes final int... viewIds + ) { + if (view != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(view, viewId); + if (findView != null) { + findView.setOnTouchListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置触摸事件 + * @param activity {@link Activity} + * @param listener {@link View.OnTouchListener} + * @param viewIds ViewId[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnTouchs( + final Activity activity, + final View.OnTouchListener listener, + @IdRes final int... viewIds + ) { + if (activity != null && viewIds != null) { + for (int viewId : viewIds) { + View findView = ViewUtils.findViewById(activity, viewId); + if (findView != null) { + findView.setOnTouchListener(listener); + } + } + return true; + } + return false; + } + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnTouchs( + final View.OnTouchListener listener, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setOnTouchListener(listener); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/LocationUtils.java b/lib/DevApp/src/main/java/dev/utils/app/LocationUtils.java new file mode 100644 index 0000000000..6c3c09ebe4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/LocationUtils.java @@ -0,0 +1,467 @@ +package dev.utils.app; + +import android.Manifest; +import android.content.Intent; +import android.location.Address; +import android.location.Criteria; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.os.Bundle; +import android.provider.Settings; + +import androidx.annotation.RequiresPermission; + +import java.util.List; +import java.util.Locale; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 定位相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ *     
+ * 
+ */ +public final class LocationUtils { + + private LocationUtils() { + } + + // 日志 TAG + private static final String TAG = LocationUtils.class.getSimpleName(); + + // 时间常量 ( 2 分钟 ) + private static final int MINUTES_TWO = 1000 * 60 * 2; + // 定位改变通知事件 + private static OnLocationChangeListener sListener; + // 自定义定位事件 + private static CustomLocationListener sCustomLocationListener; + // 定位管理对象 + private static LocationManager sLocationManager; + + /** + * 判断 GPS 是否可用 + * @return {@code true} yes, {@code false} no + */ + public static boolean isGpsEnabled() { + try { + LocationManager locationManager = AppUtils.getLocationManager(); + return locationManager != null && locationManager.isProviderEnabled( + LocationManager.GPS_PROVIDER + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isGpsEnabled"); + } + return false; + } + + /** + * 判断定位是否可用 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLocationEnabled() { + try { + LocationManager locationManager = AppUtils.getLocationManager(); + return locationManager != null && (locationManager.isProviderEnabled( + LocationManager.NETWORK_PROVIDER) + || locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isLocationEnabled"); + } + return false; + } + + /** + * 打开 GPS 设置界面 + * @return {@code true} success, {@code false} fail + */ + public static boolean openGpsSettings() { + try { + Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + return AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openGpsSettings"); + } + return false; + } + + /** + * 注册 + *
+     *     使用完记得调用 {@link #unregister()}
+     *     如果 minDistance 为 0, 则通过 minTime 来定时更新, minDistance 不为 0, 则以 minDistance 为准, 两者都为 0, 则随时刷新
+     * 
+ * @param minTime 位置信息更新周期 ( 单位: 毫秒 ) + * @param minDistance 位置变化最小距离: 当位置距离变化超过此值时, 将更新位置信息 ( 单位: 米 ) + * @param listener 位置刷新的回调接口 + * @return {@code true} 初始化成功, {@code false} 初始化失败 + */ + @RequiresPermission(anyOf = { + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public static boolean register( + final long minTime, + final long minDistance, + final OnLocationChangeListener listener + ) { + if (listener == null) return false; + try { + sLocationManager = AppUtils.getLocationManager(); + if (!isLocationEnabled()) return false; + sListener = listener; + String provider = sLocationManager.getBestProvider(getCriteria(), true); + Location location = sLocationManager.getLastKnownLocation(provider); + if (location != null) listener.getLastKnownLocation(location); + if (sCustomLocationListener == null) { + sCustomLocationListener = new CustomLocationListener(); + } + sLocationManager.requestLocationUpdates( + provider, minTime, minDistance, sCustomLocationListener + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "register"); + } + return false; + } + + /** + * 注销监听 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(anyOf = { + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public static boolean unregister() { + try { + if (sLocationManager != null) { + if (sCustomLocationListener != null) { + sLocationManager.removeUpdates(sCustomLocationListener); + sCustomLocationListener = null; + } + sLocationManager = null; + } + if (sListener != null) { + sListener = null; + } + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregister"); + } + return false; + } + + /** + * 获取位置 ( 需要先判断是否开启了定位 ) + * @param listener {@link LocationListener} + * @param millis 间隔时间 + * @param distance 间隔距离 + * @return {@link Location} + */ + @RequiresPermission(anyOf = { + Manifest.permission.ACCESS_COARSE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public static Location getLocation( + final LocationListener listener, + final long millis, + final float distance + ) { + Location location = null; + try { + sLocationManager = AppUtils.getLocationManager(); + if (isLocationEnabled()) { + sLocationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, millis, distance, listener + ); + if (sLocationManager != null) { + location = sLocationManager.getLastKnownLocation( + LocationManager.NETWORK_PROVIDER + ); + if (location != null) { + sLocationManager.removeUpdates(listener); + return location; + } + } + } + if (isGpsEnabled()) { + sLocationManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, millis, distance, listener + ); + if (sLocationManager != null) { + location = sLocationManager.getLastKnownLocation( + LocationManager.GPS_PROVIDER + ); + if (location != null) { + sLocationManager.removeUpdates(listener); + return location; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLocation"); + } + return location; + } + + /** + * 获取定位参数对象 + * @return {@link Criteria} + */ + private static Criteria getCriteria() { + Criteria criteria = new Criteria(); + // 设置定位精确度 Criteria.ACCURACY_COARSE 比较粗略, Criteria.ACCURACY_FINE 则比较精细 + criteria.setAccuracy(Criteria.ACCURACY_FINE); + // 设置是否要求速度 + criteria.setSpeedRequired(false); + // 设置是否允许运营商收费 + criteria.setCostAllowed(false); + // 设置是否需要方位信息 + criteria.setBearingRequired(false); + // 设置是否需要海拔信息 + criteria.setAltitudeRequired(false); + // 设置对电源的需求 + criteria.setPowerRequirement(Criteria.POWER_LOW); + return criteria; + } + + /** + * 根据经纬度获取地理位置 + * @param latitude 纬度 + * @param longitude 经度 + * @return {@link Address} + */ + public static Address getAddress( + final double latitude, + final double longitude + ) { + try { + Geocoder geocoder = new Geocoder( + DevUtils.getContext(), Locale.getDefault() + ); + List
addresses = geocoder.getFromLocation( + latitude, longitude, 1 + ); + if (addresses.size() > 0) return addresses.get(0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAddress"); + } + return null; + } + + /** + * 根据经纬度获取所在国家 + * @param latitude 纬度 + * @param longitude 经度 + * @return 所在国家 + */ + public static String getCountryName( + final double latitude, + final double longitude + ) { + Address address = getAddress(latitude, longitude); + return address == null ? "unknown" : address.getCountryName(); + } + + /** + * 根据经纬度获取所在地 + * @param latitude 纬度 + * @param longitude 经度 + * @return 所在地 + */ + public static String getLocality( + final double latitude, + final double longitude + ) { + Address address = getAddress(latitude, longitude); + return address == null ? "unknown" : address.getLocality(); + } + + /** + * 根据经纬度获取所在街道 + * @param latitude 纬度 + * @param longitude 经度 + * @return 所在街道 + */ + public static String getStreet( + final double latitude, + final double longitude + ) { + Address address = getAddress(latitude, longitude); + try { + return address == null ? "unknown" : address.getAddressLine(0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStreet"); + } + return "unknown"; + } + + /** + * 判断是否更好的位置 + * @param newLocation 新的位置 + * @param currentBestLocation 当前最佳位置 + * @return {@code true} yes, {@code false} no + */ + public static boolean isBetterLocation( + final Location newLocation, + final Location currentBestLocation + ) { + if (newLocation == null || currentBestLocation == null) return true; + // 检查位置信息的时间间隔 + long timeDelta = newLocation.getTime() - currentBestLocation.getTime(); + boolean isSignificantlyNewer = timeDelta > MINUTES_TWO; + boolean isSignificantlyOlder = timeDelta < -MINUTES_TWO; + boolean isNewer = timeDelta > 0; + + // 如果时间超过 2 分钟, 则使用新的位置 + if (isSignificantlyNewer) { + return true; + } else if (isSignificantlyOlder) { // 时间超过两分钟 + return false; + } + + // 检查新的位置时间 + int accuracyDelta = (int) (newLocation.getAccuracy() - currentBestLocation.getAccuracy()); + boolean isLessAccurate = accuracyDelta > 0; + boolean isMoreAccurate = accuracyDelta < 0; + boolean isSignificantlyLessAccurate = accuracyDelta > 200; + + // 检查旧位置和新位置是否来自同一提供者 + boolean isFromSameProvider = isSameProvider( + newLocation.getProvider(), currentBestLocation.getProvider() + ); + + // 判断最新的位置 + if (isMoreAccurate) { + return true; + } else if (isNewer && !isLessAccurate) { + return true; + } else { + return isNewer && !isSignificantlyLessAccurate && isFromSameProvider; + } + } + + /** + * 是否相同的提供者 + * @param provider0 提供者 1 + * @param provider1 提供者 2 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSameProvider( + final String provider0, + final String provider1 + ) { + if (provider0 == null) { + return provider1 == null; + } + return provider0.equals(provider1); + } + + /** + * detail: 自定义定位监听事件 + * @author Ttt + */ + private static class CustomLocationListener + implements LocationListener { + + /** + * 当坐标改变时触发此函数, 如果 Provider 传进相同的坐标, 它就不会被触发 + * @param location 坐标 + */ + @Override + public void onLocationChanged(Location location) { + if (sListener != null) { + sListener.onLocationChanged(location); + } + } + + /** + * provider 的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 + * @param provider 提供者 + * @param status 状态 + * @param extras provider 可选包 + */ + @Override + public void onStatusChanged( + String provider, + int status, + Bundle extras + ) { + if (sListener != null) { + sListener.onStatusChanged(provider, status, extras); + } + switch (status) { + case LocationProvider.AVAILABLE: + LogPrintUtils.dTag(TAG, "当前 GPS 状态为可见状态"); + break; + case LocationProvider.OUT_OF_SERVICE: + LogPrintUtils.dTag(TAG, "当前 GPS 状态为服务区外状态"); + break; + case LocationProvider.TEMPORARILY_UNAVAILABLE: + LogPrintUtils.dTag(TAG, "当前 GPS 状态为暂停服务状态"); + break; + } + } + + /** + * provider 被 enable 时触发此函数, 比如 GPS 被打开 + * @param provider 提供者 + */ + @Override + public void onProviderEnabled(String provider) { + } + + /** + * provider 被 disable 时触发此函数, 比如 GPS 被关闭 + * @param provider 提供者 + */ + @Override + public void onProviderDisabled(String provider) { + } + } + + /** + * detail: 定位改变事件 + * @author Ttt + */ + public interface OnLocationChangeListener { + + /** + * 获取最后一次保留的坐标 + * @param location 坐标 + */ + void getLastKnownLocation(Location location); + + /** + * 当坐标改变时触发此函数, 如果 Provider 传进相同的坐标, 它就不会被触发 + * @param location 坐标 + */ + void onLocationChanged(Location location); + + /** + * provider 的在可用、暂时不可用和无服务三个状态直接切换时触发此函数 + *
+         *     位置状态发生改变
+         * 
+ * @param provider 提供者 + * @param status 状态 + * @param extras provider 可选包 + */ + void onStatusChanged( + String provider, + int status, + Bundle extras + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ManifestUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ManifestUtils.java new file mode 100644 index 0000000000..61b020a2ac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ManifestUtils.java @@ -0,0 +1,352 @@ +package dev.utils.app; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.os.Build; + +import dev.utils.LogPrintUtils; + +/** + * detail: Android Manifest 工具类 + * @author Ttt + */ +public final class ManifestUtils { + + private ManifestUtils() { + } + + // 日志 TAG + private static final String TAG = ManifestUtils.class.getSimpleName(); + + /** + * 获取 Application meta Data + * @param metaKey meta Key + * @return Application meta Data + */ + public static String getMetaData(final String metaKey) { + return getMetaData(AppUtils.getPackageName(), metaKey); + } + + /** + * 获取 Application meta Data + * @param packageName 应用包名 + * @param metaKey meta Key + * @return Application meta Data + */ + public static String getMetaData( + final String packageName, + final String metaKey + ) { + try { + ApplicationInfo appInfo = AppUtils.getApplicationInfo( + packageName, PackageManager.GET_META_DATA + ); + if (appInfo == null) return null; + return appInfo.metaData.getString(metaKey); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMetaData"); + } + return null; + } + + // = + + /** + * 获取 Activity meta Data + * @param clazz Activity.class + * @param metaKey meta Key + * @return Activity meta Data + */ + public static String getMetaDataInActivity( + final Class clazz, + final String metaKey + ) { + return (clazz != null) ? getMetaDataInActivity( + AppUtils.getPackageName(), clazz.getCanonicalName(), metaKey + ) : null; + } + + /** + * 获取 Activity meta Data + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Activity meta Data + */ + public static String getMetaDataInActivity( + final String name, + final String metaKey + ) { + return getMetaDataInActivity(AppUtils.getPackageName(), name, metaKey); + } + + /** + * 获取 Activity meta Data + * @param packageName 应用包名 + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Activity meta Data + */ + public static String getMetaDataInActivity( + final String packageName, + final String name, + final String metaKey + ) { + try { + ComponentName componentName = new ComponentName(packageName, name); + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + ActivityInfo activityInfo = packageManager.getActivityInfo( + componentName, PackageManager.GET_META_DATA + ); + return activityInfo.metaData.getString(metaKey); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMetaDataInActivity"); + } + return null; + } + + // = + + /** + * 获取 Service meta Data + * @param clazz Service.class + * @param metaKey meta Key + * @return Service meta Data + */ + public static String getMetaDataInService( + final Class clazz, + final String metaKey + ) { + return (clazz != null) ? getMetaDataInService( + AppUtils.getPackageName(), clazz.getCanonicalName(), metaKey + ) : null; + } + + /** + * 获取 Service meta Data + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Service meta Data + */ + public static String getMetaDataInService( + final String name, + final String metaKey + ) { + return getMetaDataInService(AppUtils.getPackageName(), name, metaKey); + } + + /** + * 获取 Service meta Data + * @param packageName 应用包名 + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Service meta Data + */ + public static String getMetaDataInService( + final String packageName, + final String name, + final String metaKey + ) { + try { + ComponentName componentName = new ComponentName(packageName, name); + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + ServiceInfo serviceInfo = packageManager.getServiceInfo( + componentName, PackageManager.GET_META_DATA + ); + return serviceInfo.metaData.getString(metaKey); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMetaDataInService"); + } + return null; + } + + // = + + /** + * 获取 Receiver meta Data + * @param clazz Receiver.class + * @param metaKey meta Key + * @return Receiver meta Data + */ + public static String getMetaDataInReceiver( + final Class clazz, + final String metaKey + ) { + return (clazz != null) ? getMetaDataInReceiver( + AppUtils.getPackageName(), clazz.getCanonicalName(), metaKey + ) : null; + } + + /** + * 获取 Receiver meta Data + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Receiver meta Data + */ + public static String getMetaDataInReceiver( + final String name, + final String metaKey + ) { + return getMetaDataInReceiver(AppUtils.getPackageName(), name, metaKey); + } + + /** + * 获取 Receiver meta Data + * @param packageName 应用包名 + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return Receiver meta Data + */ + public static String getMetaDataInReceiver( + final String packageName, + final String name, + final String metaKey + ) { + try { + ComponentName componentName = new ComponentName(packageName, name); + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + ActivityInfo receiverInfo = packageManager.getReceiverInfo( + componentName, PackageManager.GET_META_DATA + ); + return receiverInfo.metaData.getString(metaKey); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMetaDataInReceiver"); + } + return null; + } + + // = + + /** + * 获取 ContentProvider meta Data + * @param clazz ContentProvider.class + * @param metaKey meta Key + * @return ContentProvider meta Data + */ + public static String getMetaDataInProvider( + final Class clazz, + final String metaKey + ) { + return (clazz != null) ? getMetaDataInProvider( + AppUtils.getPackageName(), clazz.getCanonicalName(), metaKey + ) : null; + } + + /** + * 获取 ContentProvider meta Data + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return ContentProvider meta Data + */ + public static String getMetaDataInProvider( + final String name, + final String metaKey + ) { + return getMetaDataInProvider(AppUtils.getPackageName(), name, metaKey); + } + + /** + * 获取 ContentProvider meta Data + * @param packageName 应用包名 + * @param name class.getCanonicalName() + * @param metaKey meta Key + * @return ContentProvider meta Data + */ + public static String getMetaDataInProvider( + final String packageName, + final String name, + final String metaKey + ) { + try { + ComponentName componentName = new ComponentName(packageName, name); + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + ProviderInfo providerInfo = packageManager.getProviderInfo( + componentName, PackageManager.GET_META_DATA + ); + return providerInfo.metaData.getString(metaKey); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMetaDataInProvider"); + } + return null; + } + + // = + + /** + * 获取 APP 版本信息 + * @return 0 = versionName, 1 = versionCode + */ + public static String[] getAppVersion() { + return getAppVersion(AppUtils.getPackageName()); + } + + /** + * 获取 APP 版本信息 + * @param packageName 应用包名 + * @return 0 = versionName, 1 = versionCode + */ + public static String[] getAppVersion(final String packageName) { + try { + PackageInfo packageInfo = AppUtils.getPackageInfo( + packageName, PackageManager.GET_SIGNATURES + ); + if (packageInfo != null) { + String versionName = String.valueOf(packageInfo.versionName); + String versionCode; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + versionCode = String.valueOf(packageInfo.getLongVersionCode()); + } else { + versionCode = String.valueOf(packageInfo.versionCode); + } + return new String[]{versionName, versionCode}; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppVersion"); + } + return null; + } + + /** + * 获取 APP versionCode + * @return APP versionCode + */ + public static long getAppVersionCode() { + return AppUtils.getAppVersionCode(); + } + + /** + * 获取 APP versionName + * @return APP versionName + */ + public static String getAppVersionName() { + return AppUtils.getAppVersionName(); + } + + // = + + /** + * 获取 APP versionCode + * @param packageName 应用包名 + * @return APP versionCode + */ + public static long getAppVersionCode(final String packageName) { + return AppUtils.getAppVersionCode(packageName); + } + + /** + * 获取 APP versionName + * @param packageName 应用包名 + * @return APP versionName + */ + public static String getAppVersionName(final String packageName) { + return AppUtils.getAppVersionName(packageName); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/MediaStoreUtils.java b/lib/DevApp/src/main/java/dev/utils/app/MediaStoreUtils.java new file mode 100644 index 0000000000..a8edf52135 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/MediaStoreUtils.java @@ -0,0 +1,1297 @@ +package dev.utils.app; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ContentValues; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadataRetriever; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.webkit.MimeTypeMap; + +import androidx.annotation.IntRange; +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.image.BitmapUtils; +import dev.utils.app.image.ImageUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileIOUtils; +import dev.utils.common.FileUtils; + +/** + * detail: MediaStore 工具类 + * @author Ttt + *
+ *     通过 FileProvider Uri 是无法进行读取 ( MediaStore Cursor 无法扫描 内部存储、外部存储 ( 私有目录 ) )
+ *     需通过 {@link ResourceUtils#openInputStream(Uri)} 获取并保存到 {@link PathUtils#getAppExternal()} 外部存储 ( 私有目录 ) 中
+ *     调用此类方法传入 filePath 获取所需信息 ( 私有目录不需要兼容 Android 10 ( Q ) )
+ *     

+ * MimeType + * @see
+ * 存储后缀根据 MIME_TYPE 决定, 值类型 libcore.net.MimeUtils + * @see + * ContentProvider 获取 MimeType + * @see + *

+ * 访问下载内容 ( 文档和电子书籍 ) 加载系统的文件选择器 + * {@link IntentUtils#getOpenDocumentIntent()} + * 使用存储访问框架打开文件 {@link ResourceUtils#openInputStream(Uri)} + * @see
+ *

+ * 图片、视频、音频、下载文件 对应存储位置 + * @see
+ *
+ */ +public final class MediaStoreUtils { + + private MediaStoreUtils() { + } + + // 日志 TAG + private static final String TAG = MediaStoreUtils.class.getSimpleName(); + + // ========== + // = 通知相册 = + // ========== + + /** + * 通知刷新本地资源 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + public static boolean notifyMediaStore(final String filePath) { + return notifyMediaStore(FileUtils.getFile(filePath)); + } + + /** + * 通知刷新本地资源 + *
+     *     注意事项: 部分手机 ( 如小米 ) 通知文件地址层级过深, 将会并入相册文件夹中
+     *     尽量放在 SDCrad/XXX/xx.jpg 层级中, 推荐直接存储到 {@link PathUtils#getSDCard().getDCIMPath()}
+     *     该方法只能通知刷新 外部存储 ( 公开目录 ) SDCard 文件地址
+     *     MediaStore Cursor 无法扫描 内部存储、外部存储 ( 私有目录 )
+     *     

+ * 正确的操作应该是: 不论版本统一存储到 外部存储 ( 私有目录 ) 再通过 MediaStore 插入数据 + *
+ * @param file 文件 + * @return {@code true} success, {@code false} fail + * @deprecated Android 10 ( Q ) 以后的版本需要通过 MediaStore 插入数据 + */ + @Deprecated + public static boolean notifyMediaStore(final File file) { + if (file != null) { + try { + return notifyMediaStore(Uri.fromFile(file)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyMediaStore"); + } + } + return false; + } + + /** + * 通知刷新本地资源 + * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyMediaStore(final Uri uri) { + if (uri != null) { + try { + // 通知图库扫描更新 + return AppUtils.sendBroadcast( + new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri) + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyMediaStore"); + } + } + return false; + } + + // =========== + // = 创建 Uri = + // =========== + + // PNG + public static final String MIME_TYPE_IMAGE_PNG = "image/png"; + // JPEG + public static final String MIME_TYPE_IMAGE_JPG = "image/jpeg"; + // 图片类型 + public static final String MIME_TYPE_IMAGE = "image/*"; + // MP4 + public static final String MIME_TYPE_VIDEO_MP4 = "video/mp4"; + // MOV + public static final String MIME_TYPE_VIDEO_MOV = "video/quicktime"; + // 视频类型 + public static final String MIME_TYPE_VIDEO = "video/*"; + // MP3 + public static final String MIME_TYPE_AUDIO_MP3 = "audio/mpeg"; + // AAC + public static final String MIME_TYPE_AUDIO_AAC = "audio/aac"; + // WAV + public static final String MIME_TYPE_AUDIO_WAV = "audio/x-wav"; + // 音频类型 + public static final String MIME_TYPE_AUDIO = "audio/*"; + // PDF + public static final String MIME_TYPE_APPLICATION_PDF = "application/pdf"; + // ZIP + public static final String MIME_TYPE_APPLICATION_ZIP = "application/zip"; + // 应用文件类型 + public static final String MIME_TYPE_APPLICATION = "application/*"; + // 图片文件夹 + public static final String RELATIVE_IMAGE_PATH = Environment.DIRECTORY_PICTURES; + // 视频文件夹 + public static final String RELATIVE_VIDEO_PATH = Environment.DIRECTORY_DCIM + "/Video"; + // 音频文件夹 + public static final String RELATIVE_AUDIO_PATH = Environment.DIRECTORY_MUSIC; + // 下载文件夹 + public static final String RELATIVE_DOWNLOAD_PATH = Environment.DIRECTORY_DOWNLOADS; + + /** + * 获取待显示名 + * @param prefix 前缀 + * @return DISPLAY_NAME + */ + public static String getDisplayName(final String prefix) { + return prefix + "_" + DateUtils.getDateNow(DevFinal.TIME.yyyyMMddHHmmss); + } + + /** + * 获取 Image 显示名 + * @return Image DISPLAY_NAME + */ + public static String getImageDisplayName() { + return getDisplayName("IMG"); + } + + /** + * 获取 Video 显示名 + * @return Video DISPLAY_NAME + */ + public static String getVideoDisplayName() { + return getDisplayName("VID"); + } + + /** + * 获取 Audio 显示名 + * @return Audio DISPLAY_NAME + */ + public static String getAudioDisplayName() { + return getDisplayName("AUD"); + } + + // ======= + // = 图片 = + // ======= + + /** + * 创建图片 Uri + * @return 图片 Uri + */ + public static Uri createImageUri() { + return createImageUri( + getImageDisplayName(), MIME_TYPE_IMAGE_PNG, + RELATIVE_IMAGE_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @return 图片 Uri + */ + public static Uri createImageUri(final String mimeType) { + return createImageUri( + getImageDisplayName(), mimeType, + RELATIVE_IMAGE_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + public static Uri createImageUri( + final String mimeType, + final String relativePath + ) { + return createImageUri( + getImageDisplayName(), mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + public static Uri createImageUri( + final String displayName, + final String mimeType, + final String relativePath + ) { + return createImageUri( + displayName, mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 图片 Uri + */ + public static Uri createImageUri( + final String displayName, + final String mimeType, + final String relativePath, + final long createTime + ) { + return createMediaUri( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + displayName, mimeType, relativePath, createTime + ); + } + + // ======= + // = 视频 = + // ======= + + /** + * 创建视频 Uri + * @return 视频 Uri + */ + public static Uri createVideoUri() { + return createVideoUri( + getVideoDisplayName(), MIME_TYPE_VIDEO_MP4, + RELATIVE_VIDEO_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @return 视频 Uri + */ + public static Uri createVideoUri(final String mimeType) { + return createVideoUri( + getVideoDisplayName(), mimeType, + RELATIVE_VIDEO_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + public static Uri createVideoUri( + final String mimeType, + final String relativePath + ) { + return createVideoUri( + getVideoDisplayName(), mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + public static Uri createVideoUri( + final String displayName, + final String mimeType, + final String relativePath + ) { + return createVideoUri( + displayName, mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 视频 Uri + */ + public static Uri createVideoUri( + final String displayName, + final String mimeType, + final String relativePath, + final long createTime + ) { + return createMediaUri( + MediaStore.Video.Media.EXTERNAL_CONTENT_URI, + displayName, mimeType, relativePath, createTime + ); + } + + // ======= + // = 音频 = + // ======= + + /** + * 创建音频 Uri + * @return 音频 Uri + */ + public static Uri createAudioUri() { + return createAudioUri( + getAudioDisplayName(), MIME_TYPE_AUDIO_MP3, + RELATIVE_AUDIO_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @return 音频 Uri + */ + public static Uri createAudioUri(final String mimeType) { + return createAudioUri( + getAudioDisplayName(), mimeType, + RELATIVE_AUDIO_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + public static Uri createAudioUri( + final String mimeType, + final String relativePath + ) { + return createAudioUri( + getAudioDisplayName(), mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + public static Uri createAudioUri( + final String displayName, + final String mimeType, + final String relativePath + ) { + return createAudioUri( + displayName, mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 音频 Uri + */ + public static Uri createAudioUri( + final String displayName, + final String mimeType, + final String relativePath, + final long createTime + ) { + return createMediaUri( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + displayName, mimeType, relativePath, createTime + ); + } + + // ============ + // = Download = + // ============ + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 需后缀 ) + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static Uri createDownloadUri(final String displayName) { + return createDownloadUri( + displayName, MIME_TYPE_APPLICATION, + RELATIVE_DOWNLOAD_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static Uri createDownloadUri( + final String displayName, + final String mimeType + ) { + return createDownloadUri( + displayName, mimeType, + RELATIVE_DOWNLOAD_PATH, System.currentTimeMillis() + ); + } + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static Uri createDownloadUri( + final String displayName, + final String mimeType, + final String relativePath + ) { + return createDownloadUri( + displayName, mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建 Download Uri + *
+     *     Android 10 ( Q ) 以下直接通过 File 写入到 {@link Environment#DIRECTORY_DOWNLOADS}
+     * 
+ * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public static Uri createDownloadUri( + final String displayName, + final String mimeType, + final String relativePath, + final long createTime + ) { + return createMediaUri( + MediaStore.Downloads.EXTERNAL_CONTENT_URI, + displayName, mimeType, relativePath, createTime + ); + } + + // ========== + // = 通用创建 = + // ========== + + /** + * 创建预存储 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Media Uri + */ + public static Uri createMediaUri( + final Uri uri, + final String displayName, + final String mimeType, + final String relativePath + ) { + return createMediaUri( + uri, displayName, mimeType, + relativePath, System.currentTimeMillis() + ); + } + + /** + * 创建预存储 Media Uri + *
+     *     也可通过 {@link IntentUtils#getCreateDocumentIntent(String, String)} 创建
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Media Uri + */ + public static Uri createMediaUri( + final Uri uri, + final String displayName, + final String mimeType, + final String relativePath, + final long createTime + ) { + boolean isAndroidQ = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q); + ContentValues values = new ContentValues(isAndroidQ ? 4 : 3); + // 文件名 + values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, displayName); + // 资源类型 + values.put(MediaStore.Files.FileColumns.MIME_TYPE, mimeType); + // MediaStore 会根据 Uri 自动存储到对应分类目录下, 并在目录下创建 relativePath 文件夹 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, relativePath); + } + // 创建时间 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + values.put(MediaStore.Files.FileColumns.DATE_TAKEN, createTime); + } else { + values.put(MediaStore.Files.FileColumns.DATE_ADDED, createTime); + } + try { + return ResourceUtils.getContentResolver().insert(uri, values); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createMediaUri"); + } + return null; + } + + // ======== + // = File = + // ======== + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @return File Uri + */ + public static Uri createUriByPath(final String fileName) { + return createUriByPath( + fileName, PathUtils.getSDCard().getSDCardPath(RELATIVE_DOWNLOAD_PATH) + ); + } + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @param filePath 文件路径 + * @return File Uri + */ + public static Uri createUriByPath( + final String fileName, + final String filePath + ) { + return UriUtils.fromFile( + FileUtils.getFile(filePath, fileName) + ); + } + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return File Uri + */ + public static Uri createUriByFile(final String filePath) { + return UriUtils.fromFile(filePath); + } + + /** + * 通过 File 创建 Uri + *
+     *     主要用于如低版本写入 Download 文件夹, 创建 Uri
+     *     统一使用 {@link #insertMedia(Uri, Uri)} 写入文件夹
+     *     高版本使用 {@link #createDownloadUri(String)}
+     *     并不局限 Download 文件夹操作
+     * 
+ * @param file 文件 + * @return File Uri + */ + public static Uri createUriByFile(final File file) { + return UriUtils.fromFile(file); + } + + // ========== + // = 插入数据 = + // ========== + + /** + * 插入一张图片 + *
+     *     Android 10 ( Q ) 已抛弃仍可用, 推荐使用传入 Uri 方式 {@link #createImageUri}
+     * 
+ * @param filePath 文件路径 + * @param name 存储文件名 + * @param notify 是否通知相册 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + public static Uri insertImage( + final String filePath, + final String name, + final boolean notify + ) { + if (filePath != null && name != null) { + try { + String uriString = MediaStore.Images.Media.insertImage( + ResourceUtils.getContentResolver(), + filePath, name, null + ); + Uri uri = Uri.parse(uriString); + if (notify) notifyMediaStore(UriUtils.getFilePathByUri(uri)); + return uri; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "insertImage"); + } + } + return null; + } + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertImage( + final Uri uri, + final Uri inputUri, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + if (uri == null || inputUri == null || format == null) return false; + OutputStream os = null; + ParcelFileDescriptor fileDescriptor = null; + try { + os = ResourceUtils.openOutputStream(uri); + fileDescriptor = ResourceUtils.openFileDescriptor(inputUri, "r"); + Bitmap bitmap = ImageUtils.decodeFileDescriptor(fileDescriptor.getFileDescriptor()); + return ImageUtils.saveBitmapToStream(bitmap, os, format, quality); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "insertImage"); + } finally { + CloseUtils.closeIOQuietly(os, fileDescriptor); + } + return false; + } + + // = + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + public static boolean insertImage( + final Uri uri, + final Uri inputUri + ) { + return insertMedia(uri, inputUri); + } + + /** + * 插入一条视频 + * @param uri {@link #createVideoUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + public static boolean insertVideo( + final Uri uri, + final Uri inputUri + ) { + return insertMedia(uri, inputUri); + } + + /** + * 插入一条音频 + * @param uri {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + public static boolean insertAudio( + final Uri uri, + final Uri inputUri + ) { + return insertMedia(uri, inputUri); + } + + /** + * 插入一条文件资源 + * @param uri {@link #createDownloadUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + public static boolean insertDownload( + final Uri uri, + final Uri inputUri + ) { + return insertMedia(uri, inputUri); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Uri inputUri + ) { + if (uri == null || inputUri == null) return false; + return insertMedia( + ResourceUtils.openOutputStream(uri), + ResourceUtils.openInputStream(inputUri) + ); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final InputStream inputStream + ) { + if (uri == null || inputStream == null) return false; + return insertMedia( + ResourceUtils.openOutputStream(uri), inputStream + ); + } + + /** + * 插入一条多媒体资源 + * @param outputStream {@link OutputStream} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final OutputStream outputStream, + final InputStream inputStream + ) { + return FileIOUtils.copyLarge(inputStream, outputStream) != -1; + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param filePath 待存储文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final String filePath + ) { + return insertMedia(uri, FileUtils.getFile(filePath)); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param file 待存储文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final File file + ) { + return insertMedia(uri, UriUtils.getUriForFile(file)); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Drawable drawable + ) { + return insertMedia(uri, drawable, Bitmap.CompressFormat.PNG); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Drawable drawable, + final Bitmap.CompressFormat format + ) { + return insertMedia(uri, drawable, format, 100); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Drawable drawable, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + return insertMedia(uri, ImageUtils.drawableToBitmap(drawable), format, quality); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Bitmap bitmap + ) { + return insertMedia(uri, bitmap, Bitmap.CompressFormat.PNG); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Bitmap bitmap, + final Bitmap.CompressFormat format + ) { + return insertMedia(uri, bitmap, format, 100); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean insertMedia( + final Uri uri, + final Bitmap bitmap, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + return ImageUtils.saveBitmapToStream( + bitmap, ResourceUtils.openOutputStream(uri), format, quality + ); + } + + // ========== + // = 资源信息 = + // ========== + + /** + * 获取本地视频时长 + * @param filePath 文件路径 + * @return 本地视频时长 + */ + public static long getVideoDuration(final String filePath) { + return getVideoDuration(filePath, true); + } + + /** + * 获取本地视频时长 + * @param filePath 文件路径 + * @param isAndroidQ 是否兼容 Android 10 ( Q ) ( 私有目录传入 false ) + * @return 本地视频时长 + */ + public static long getVideoDuration( + final String filePath, + final boolean isAndroidQ + ) { + if (TextUtils.isEmpty(filePath)) return 0L; + try { + if (isAndroidQ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return getVideoDuration(ContentResolverUtils.getMediaUri(filePath)); + } + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(filePath); + return Long.parseLong(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION + )); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoDuration"); + return 0L; + } + } + + /** + * 获取本地视频时长 + * @param uri Video Uri content:// + * @return 本地视频时长 + */ + public static long getVideoDuration(final Uri uri) { + if (uri == null) return 0L; + try { + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(DevUtils.getContext(), uri); + return Long.parseLong(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_DURATION + )); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoDuration"); + return 0L; + } + } + + // = + + /** + * 获取本地视频宽高 + * @param filePath 文件路径 + * @return 本地视频宽高 0 = 宽, 1 = 高 + */ + public static int[] getVideoSize(final String filePath) { + return getVideoSize(filePath, true); + } + + /** + * 获取本地视频宽高 + * @param filePath 文件路径 + * @param isAndroidQ 是否兼容 Android 10 ( Q ) ( 私有目录传入 false ) + * @return 本地视频宽高 0 = 宽, 1 = 高 + */ + public static int[] getVideoSize( + final String filePath, + final boolean isAndroidQ + ) { + int[] size = new int[2]; + try { + if (isAndroidQ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return getVideoSize(ContentResolverUtils.getMediaUri(filePath)); + } + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(filePath); + size[0] = ConvertUtils.toInt(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH + )); + size[1] = ConvertUtils.toInt(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT + )); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoSize"); + } + return size; + } + + /** + * 获取本地视频宽高 + * @param uri Video Uri + * @return 本地视频宽高 0 = 宽, 1 = 高 + */ + public static int[] getVideoSize(final Uri uri) { + int[] size = new int[2]; + try { + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + mmr.setDataSource(DevUtils.getContext(), uri); + size[0] = ConvertUtils.toInt(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH + )); + size[1] = ConvertUtils.toInt(mmr.extractMetadata( + MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT + )); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoSize"); + } + return size; + } + + // = + + /** + * 获取本地图片宽高 + * @param filePath 文件路径 + * @return 本地图片宽高 0 = 宽, 1 = 高 + */ + public static int[] getImageWidthHeight(final String filePath) { + return getImageWidthHeight(filePath, true); + } + + /** + * 获取本地图片宽高 + * @param filePath 文件路径 + * @param isAndroidQ 是否兼容 Android 10 ( Q ) ( 私有目录传入 false ) + * @return 本地图片宽高 0 = 宽, 1 = 高 + */ + public static int[] getImageWidthHeight( + final String filePath, + final boolean isAndroidQ + ) { + try { + if (isAndroidQ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return getImageWidthHeight(ContentResolverUtils.getMediaUri(filePath)); + } + return BitmapUtils.getBitmapWidthHeight(filePath); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageWidthHeight"); + } + return new int[]{0, 0}; + } + + /** + * 获取本地图片宽高 + * @param uri Image Uri content:// + * @return 本地图片宽高 0 = 宽, 1 = 高 + */ + public static int[] getImageWidthHeight(final Uri uri) { + try { + InputStream is = ResourceUtils.openInputStream(uri); + return BitmapUtils.getBitmapWidthHeight(is); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageWidthHeight"); + } + return new int[]{0, 0}; + } + + // = + + /** + * 获取多媒体资源信息 + *
+     *     FileProvider Uri 无法进行获取
+     *     根据类型调用 {@link #getVideoSize}、{@link #getVideoDuration}、{@link #getImageWidthHeight}
+     *     其余信息可通过 File 直接获取
+     * 
+ * @param filePath 文件路径 + * @return 多媒体资源信息 + */ + public static String[] getMediaInfo(final String filePath) { + return ContentResolverUtils.mediaQuery( + filePath, ContentResolverUtils.MEDIA_QUERY_INFO + ); + } + + /** + * 获取多媒体资源信息 + *
+     *     FileProvider Uri 无法进行获取
+     *     根据类型调用 {@link #getVideoSize}、{@link #getVideoDuration}、{@link #getImageWidthHeight}
+     *     其余信息可通过 File 直接获取
+     * 
+ * @param uri Uri content:// + * @return 多媒体资源信息 + */ + public static String[] getMediaInfo(final Uri uri) { + return ContentResolverUtils.mediaQuery( + uri, TAG, ContentResolverUtils.MEDIA_QUERY_INFO_URI + ); + } + + // ============= + // = 执行批量操作 = + // ============= + + /** + * 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 + *
+     *     以下四个方法搭配 startIntentSenderForResult() 使用
+     *     {@link AppUtils#startIntentSenderForResult(Activity, PendingIntent, int)}
+     *     

+ * startIntentSenderForResult(pendingIntent.getIntentSender(), EDIT_REQUEST_CODE, null, 0, 0, 0) + *
+ * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + public static PendingIntent createWriteRequest(final Collection uris) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + return MediaStore.createWriteRequest( + ResourceUtils.getContentResolver(), uris + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createWriteRequest"); + } + } + return null; + } + + /** + * 获取用户将设备上指定的媒体文件标记为收藏的请求 + *
+     *     对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为收藏
+     * 
+ * @param uris 待请求 Uri 集 + * @param favorite 是否喜欢 + * @return {@link PendingIntent} + */ + public static PendingIntent createFavoriteRequest( + final Collection uris, + final boolean favorite + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + return MediaStore.createFavoriteRequest( + ResourceUtils.getContentResolver(), uris, favorite + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createFavoriteRequest"); + } + } + return null; + } + + /** + * 获取用户将指定的媒体文件放入设备垃圾箱的请求 + *
+     *     垃圾箱中的内容会在系统定义的时间段后被永久删除
+     * 
+ * @param uris 待请求 Uri 集 + * @param trashed 是否遗弃 + * @return {@link PendingIntent} + */ + public static PendingIntent createTrashRequest( + final Collection uris, + final boolean trashed + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + return MediaStore.createTrashRequest( + ResourceUtils.getContentResolver(), uris, trashed + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createTrashRequest"); + } + } + return null; + } + + /** + * 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 + * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + public static PendingIntent createDeleteRequest(final Collection uris) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + try { + return MediaStore.createDeleteRequest( + ResourceUtils.getContentResolver(), uris + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createDeleteRequest"); + } + } + return null; + } + + // ============ + // = MimeType = + // ============ + + /** + * 通过后缀获取 MimeType + * @param extension 文件后缀 ( 无 "." 单独后缀 ) + * @return MimeType + */ + public static String getMimeTypeFromExtension(final String extension) { + if (TextUtils.isEmpty(extension)) return null; + return MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(extension); + } + + /** + * 通过 MimeType 获取后缀 ( 不含 . ) + * @param mimeType 例: text/plain + * @return 对应 Type 后缀 + */ + public static String getExtensionFromMimeType(final String mimeType) { + if (TextUtils.isEmpty(mimeType)) return null; + return MimeTypeMap.getSingleton() + .getExtensionFromMimeType(mimeType); + } + + /** + * 通过 Url 获取文件后缀 + * @param url 文件链接 ( 可传入文件路径 ) + * @return 文件后缀 + */ + public static String getFileExtensionFromUrl(final String url) { + if (TextUtils.isEmpty(url)) return null; + String extension = MimeTypeMap.getFileExtensionFromUrl(url); + if (TextUtils.isEmpty(extension)) return null; + return extension; + } + + /** + * 判断 MimeMap 是否存在指定的 MimeType + * @param mimeType 例: text/plain + * @return {@code true} yes, {@code false} no + */ + public static boolean hasMimeType(final String mimeType) { + if (TextUtils.isEmpty(mimeType)) return false; + return MimeTypeMap.getSingleton().hasMimeType(mimeType); + } + + /** + * 判断是否支持的 MimeType 后缀 + * @param extension 文件后缀 ( 无 "." 单独后缀 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean hasExtension(final String extension) { + if (TextUtils.isEmpty(extension)) return false; + return MimeTypeMap.getSingleton().hasExtension(extension); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/MemoryUtils.java b/lib/DevApp/src/main/java/dev/utils/app/MemoryUtils.java new file mode 100644 index 0000000000..fc0243ea17 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/MemoryUtils.java @@ -0,0 +1,190 @@ +package dev.utils.app; + +import android.app.ActivityManager; +import android.os.Build; +import android.text.format.Formatter; + +import androidx.annotation.RequiresApi; + +import java.io.BufferedReader; +import java.io.FileReader; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: 内存信息工具类 + * @author Ttt + */ +public final class MemoryUtils { + + private MemoryUtils() { + } + + // 日志 TAG + private static final String TAG = MemoryUtils.class.getSimpleName(); + + // 内存信息文件地址 + private static final String MEM_INFO_PATH = "/proc/meminfo"; + // 获取内存总大小 + private static final String MEMTOTAL = "MemTotal"; + // 获取可用内存 + private static final String MEMAVAILABLE = "MemAvailable"; + + /** + * 获取内存信息 + * @return 内存信息 + */ + public static String printMemoryInfo() { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(MEM_INFO_PATH), 4 * 1024); + StringBuilder builder = new StringBuilder(); + String str; + while ((str = br.readLine()) != null) { + builder.append(str); + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "printMemoryInfo"); + } finally { + CloseUtils.closeIOQuietly(br); + } + return null; + } + + /** + * 获取内存信息 + * @return 内存信息 + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static String printMemoryInfo2() { + try { + ActivityManager.MemoryInfo memoryInfo = getMemoryInfo(); + if (memoryInfo == null) return null; + StringBuilder builder = new StringBuilder(); + builder.append("Memory: "); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + builder.append("\ntotalMem: ").append(memoryInfo.totalMem); + } + builder.append("\navailMem: ").append(memoryInfo.availMem); + builder.append("\nlowMemory: ").append(memoryInfo.lowMemory); + builder.append("\nthreshold: ").append(memoryInfo.threshold); + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "printMemoryInfo2"); + } + return null; + } + + /** + * 获取内存信息 + * @return 内存信息 + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static ActivityManager.MemoryInfo getMemoryInfo() { + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); + activityManager.getMemoryInfo(memoryInfo); + return memoryInfo; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMemoryInfo"); + } + return null; + } + + // = + + /** + * 获取可用内存信息 + * @return 可用内存信息 + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static long getAvailMemory() { + try { + // 获取 android 当前可用内存大小 + ActivityManager activityManager = AppUtils.getActivityManager(); + ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); + activityManager.getMemoryInfo(memoryInfo); + // 当前系统的可用内存 + return memoryInfo.availMem; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAvailMemory"); + } + return 0L; + } + + /** + * 获取可用内存信息 ( 格式化 ) + * @return 可用内存信息 + */ + @RequiresApi(api = Build.VERSION_CODES.CUPCAKE) + public static String getAvailMemoryFormat() { + return Formatter.formatFileSize(DevUtils.getContext(), getAvailMemory()); + } + + // = + + /** + * 获取总内存大小 + * @return 总内存大小 + */ + public static long getTotalMemory() { + return getMemInfoType(MEMTOTAL); + } + + /** + * 获取总内存大小 ( 格式化 ) + * @return 总内存大小 + */ + public static String getTotalMemoryFormat() { + return Formatter.formatFileSize(DevUtils.getContext(), getTotalMemory()); + } + + // = + + /** + * 获取可用内存大小 + * @return 可用内存大小 + */ + public static long getMemoryAvailable() { + return getMemInfoType(MEMAVAILABLE); + } + + /** + * 获取可用内存大小 ( 格式化 ) + * @return 可用内存大小 + */ + public static String getMemoryAvailableFormat() { + return Formatter.formatFileSize(DevUtils.getContext(), getMemoryAvailable()); + } + + /** + * 通过不同 type 获取对应的内存信息 + * @param type 内存类型 + * @return 对应 type 内存信息 + */ + public static long getMemInfoType(final String type) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader(MEM_INFO_PATH), 4 * 1024); + String str; + while ((str = br.readLine()) != null) { + if (str.contains(type)) { + break; + } + } + // 拆分空格、回车、换行等空白符 + String[] array = str.split("\\s+"); + // 获取系统总内存, 单位是 KB, 乘以 1024 转换为 Byte + return Long.parseLong(array[1]) * 1024; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMemInfoType %s", type); + } finally { + CloseUtils.closeIOQuietly(br); + } + return 0L; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/NetWorkUtils.java b/lib/DevApp/src/main/java/dev/utils/app/NetWorkUtils.java new file mode 100644 index 0000000000..94e695454f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/NetWorkUtils.java @@ -0,0 +1,671 @@ +package dev.utils.app; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.telephony.TelephonyManager; +import android.text.format.Formatter; + +import androidx.annotation.RequiresPermission; + +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import dev.utils.LogPrintUtils; + +/** + * detail: 网络管理工具类 + * @author Ttt + *
+ *     @see 
+ *     所需权限
+ *     
+ *     
+ *     
+ *     
+ * 
+ */ +public final class NetWorkUtils { + + private NetWorkUtils() { + } + + // 日志 TAG + private static final String TAG = NetWorkUtils.class.getSimpleName(); + + /** + * 获取移动网络打开状态 ( 默认属于未打开 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean getMobileDataEnabled() { + try { + // 移动网络开关状态 + boolean state; + // 属于 5.0 以下的使用 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // 获取网络连接状态 + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 反射获取方法 + Method method = manager.getClass().getMethod("getMobileDataEnabled"); + // 调用方法, 获取状态 + state = (Boolean) method.invoke(manager); + } else { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + // 反射获取方法 + Method method = telephonyManager.getClass().getDeclaredMethod("getDataEnabled"); + // 调用方法, 获取状态 + state = (Boolean) method.invoke(telephonyManager); + } + return state; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMobileDataEnabled"); + } + return false; + } + + /** + * 设置移动网络开关 ( 无判断是否已开启移动网络 ) + *
+     *     实际无效果, 非系统应用无权限需手动开启
+     * 
+ * @param isOpen 是否打开移动网络 + * @return {@code true} success, {@code false} fail + */ + public static boolean setMobileDataEnabled(final boolean isOpen) { + try { + // 属于 5.0 以下的使用 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // 获取网络连接状态 + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 通过反射设置移动网络 + Method method = manager.getClass().getDeclaredMethod( + "setMobileDataEnabled", Boolean.TYPE + ); + // 设置移动网络 + method.invoke(manager, isOpen); + } else { // 需要 Manifest.permission.MODIFY_PHONE_STATE 权限, 普通 APP 无法获取 + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + // 通过反射设置移动网络 + Method method = telephonyManager.getClass().getDeclaredMethod( + "setDataEnabled", boolean.class + ); + // 设置移动网络 + method.invoke(telephonyManager, isOpen); + } + return true; + } catch (Exception e) { // 开启移动网络失败 + LogPrintUtils.eTag(TAG, e, "setMobileDataEnabled"); + } + return false; + } + + /** + * 判断是否连接了网络 + * @return {@code true} yes, {@code false} no + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static boolean isConnect() { + try { + // 获取手机所有连接管理对象 ( 包括对 wi-fi,net 等连接的管理 ) + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 版本兼容处理 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + // 获取网络连接管理的对象 + NetworkInfo info = manager.getActiveNetworkInfo(); + // 判断是否为 null + if (info != null) { + // 判断当前网络是否已经连接 + if (info.getState() == State.CONNECTED) { + return true; + } + } + } else { + // 获取当前活跃的网络 ( 连接的网络信息 ) + Network network = manager.getActiveNetwork(); + // 判断是否为 null + if (network != null) { + return true; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isConnect"); + } + return false; + } + + /** + * 获取连接的网络类型 + * @return -1 = 等于未知, 1 = Wifi, 2 = 移动网络 + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static int getConnectType() { + try { + // 获取手机所有连接管理对象 ( 包括对 wi-fi,net 等连接的管理 ) + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 版本兼容处理 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + // 判断连接的是否 Wifi + State wifiState = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState(); + // 判断是否连接上 + if (wifiState == State.CONNECTED || wifiState == State.CONNECTING) { + return 1; + } else { + // 判断连接的是否移动网络 + State mobileState = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState(); + // 判断移动网络是否连接上 + if (mobileState == State.CONNECTED || mobileState == State.CONNECTING) { + return 2; + } + } + } else { + // 获取当前活跃的网络 ( 连接的网络信息 ) + Network network = manager.getActiveNetwork(); + if (network != null) { + NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(network); + // 判断连接的是否 Wifi + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + return 1; + } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + // 判断连接的是否移动网络 + return 2; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getConnectType"); + } + return -1; + } + + /** + * 判断是否连接 Wifi ( 连接上、连接中 ) + * @return {@code true} yes, {@code false} no + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static boolean isConnWifi() { + return (getConnectType() == 1); + } + + /** + * 判断是否连接移动网络 ( 连接上、连接中 ) + * @return {@code true} yes, {@code false} no + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static boolean isConnMobileData() { + return (getConnectType() == 2); + } + + // = + + /** + * detail: 网络连接类型 + * @author Ttt + */ + public enum NetworkType { + NETWORK_WIFI, + NETWORK_5G, + NETWORK_4G, + NETWORK_3G, + NETWORK_2G, + NETWORK_UNKNOWN, + NETWORK_NO + } + + /** + * 判断网络是否可用 + * @return {@code true} 可用, {@code false} 不可用 + */ + @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static boolean isAvailable() { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + NetworkInfo info = getActiveNetworkInfo(); + return info != null && info.isAvailable(); + } else { + return isAvailableByPing(); + } + } + + /** + * 使用 ping ip 方式判断网络是否可用 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAvailableByPing() { + return isAvailableByPing(null); + } + + /** + * 使用 ping ip 方式判断网络是否可用 + * @param ip IP 地址 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAvailableByPing(String ip) { + if (ip == null || ip.length() <= 0) { + ip = "223.5.5.5"; // 默认阿里巴巴 DNS + } + // cmd ping ip + ShellUtils.CommandResult result = ShellUtils.execCmd( + String.format("ping -c 1 %s", ip), false + ); + // 打印信息 + if (result.errorMsg != null) { + LogPrintUtils.dTag( + TAG, "isAvailableByPing - errorMsg: %s", + result.errorMsg + ); + } + if (result.successMsg != null) { + LogPrintUtils.dTag( + TAG, "isAvailableByPing - successMsg: %s", + result.successMsg + ); + } + // 判断结果, 返回数据不为 null + return result.isSuccess3(); + } + + /** + * 获取活动网络信息 + * @return {@link NetworkInfo} + */ + @Deprecated + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static NetworkInfo getActiveNetworkInfo() { + try { + return AppUtils.getConnectivityManager().getActiveNetworkInfo(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActiveNetworkInfo"); + } + return null; + } + + /** + * 获取活动网络 + * @return {@link Network} + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static Network getActiveNetwork() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + try { + return AppUtils.getConnectivityManager().getActiveNetwork(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getActiveNetwork"); + } + } + return null; + } + + // = + + /** + * 判断是否 4G 网络 + * @return {@code true} yes, {@code false} no + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.READ_PHONE_STATE + }) + public static boolean is4G() { + return getNetworkType() == NetworkType.NETWORK_4G; + } + + /** + * 判断 Wifi 是否打开 + * @return {@code true} yes, {@code false} no + */ + public static boolean getWifiEnabled() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + return wifiManager.isWifiEnabled(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiEnabled"); + } + return false; + } + + /** + * 判断 Wifi 数据是否可用 + * @return {@code true} yes, {@code false} no + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + public static boolean isWifiAvailable() { + return getWifiEnabled() && isAvailable(); + } + + /** + * 获取网络运营商名称 ( 中国移动、如中国联通、中国电信 ) + * @return 运营商名称 + */ + public static String getNetworkOperatorName() { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + return telephonyManager != null ? telephonyManager.getNetworkOperatorName() : null; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNetworkOperatorName"); + } + return null; + } + + // = + + /** + * 获取当前网络类型 + * @return {@link NetworkType} + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.READ_PHONE_STATE + }) + public static NetworkType getNetworkType() { + // 默认网络类型 + NetworkType netType = NetworkType.NETWORK_NO; + // 版本兼容处理 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + // 获取网络信息 + NetworkInfo networkInfo = getActiveNetworkInfo(); + // 判断是否可用 + if (networkInfo != null && networkInfo.isAvailable()) { // 同 getNetworkClass 方法 + // 属于可用则修改为未知 + netType = NetworkType.NETWORK_UNKNOWN; + // 获取类型 + switch (networkInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: // 属于 Wifi + netType = NetworkType.NETWORK_WIFI; + break; + case ConnectivityManager.TYPE_MOBILE: // 属于手机网络 + switch (networkInfo.getSubtype()) { + // = 2G 网络 = + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + netType = NetworkType.NETWORK_2G; + break; + // = 3G 网络 = + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + netType = NetworkType.NETWORK_3G; + break; + // = 4G 网络 = + case TelephonyManager.NETWORK_TYPE_LTE: + case TelephonyManager.NETWORK_TYPE_IWLAN: + case 19: // TelephonyManager.NETWORK_TYPE_LTE_CA + netType = NetworkType.NETWORK_4G; + break; + // = 5G 网络 = + case TelephonyManager.NETWORK_TYPE_NR: + netType = NetworkType.NETWORK_5G; + break; + // = 其他判断 = + default: + try { + // 判断子类名字 + String subtypeName = networkInfo.getSubtypeName(); + // = 3G 网络 = + if ("TD-SCDMA".equalsIgnoreCase(subtypeName) + || "WCDMA".equalsIgnoreCase(subtypeName) + || "CDMA2000".equalsIgnoreCase(subtypeName)) { + netType = NetworkType.NETWORK_3G; + } + } catch (Exception ignored) { + } + break; + } + break; + } + } + } else { + try { + // 获取网络连接状态 + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 获取当前活跃的网络 ( 连接的网络信息 ) + Network network = manager.getActiveNetwork(); + // 防止为 null + if (network != null) { + // 属于可用则修改为未知 + netType = NetworkType.NETWORK_UNKNOWN; + // 获取网络连接信息 + NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(network); + // 判断是否连接 Wifi + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + netType = NetworkType.NETWORK_WIFI; + } else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + // 判断连接的是否移动网络 + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + // 获取网络类型 + int networkType = telephonyManager.getNetworkType(); + // 获取移动网络类型 + switch (getNetworkClass(networkType)) { + case 1: // 2G + netType = NetworkType.NETWORK_2G; + break; + case 2: // 3G + netType = NetworkType.NETWORK_3G; + break; + case 3: // 4G + netType = NetworkType.NETWORK_4G; + break; + case 4: // 5G + netType = NetworkType.NETWORK_5G; + break; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNetworkType"); + } + } + return netType; + } + + /** + * 获取移动网络连接类型 + * @param networkType {@link TelephonyManager#getNetworkType} + * @return 0 = 未知, 1 = 2G, 2 = 3G, 3 = 4G, 4 = 5G + */ + public static int getNetworkClass(final int networkType) { + switch (networkType) { + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_GSM: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_IDEN: + return 1; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_TD_SCDMA: + return 2; + case TelephonyManager.NETWORK_TYPE_LTE: + case TelephonyManager.NETWORK_TYPE_IWLAN: + case 19: // TelephonyManager.NETWORK_TYPE_LTE_CA + return 3; + case TelephonyManager.NETWORK_TYPE_NR: + return 4; + default: + return 0; + } + } + + /** + * 获取广播 IP 地址 + * @return 广播 IP 地址 + */ + public static String getBroadcastIpAddress() { + try { + Enumeration nis = NetworkInterface.getNetworkInterfaces(); + while (nis.hasMoreElements()) { + NetworkInterface ni = nis.nextElement(); + if (!ni.isUp() || ni.isLoopback()) continue; + List ias = ni.getInterfaceAddresses(); + for (int i = 0, len = ias.size(); i < len; i++) { + InterfaceAddress ia = ias.get(i); + InetAddress broadcast = ia.getBroadcast(); + if (broadcast != null) { + return broadcast.getHostAddress(); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBroadcastIpAddress"); + } + return null; + } + + /** + * 获取域名 IP 地址 + * @param domain 域名 www.baidu.com 不需要加上 http + * @return 域名 IP 地址 + */ + public static String getDomainAddress(final String domain) { + try { + ExecutorService exec = Executors.newCachedThreadPool(); + Future fs = exec.submit(() -> { + InetAddress inetAddress; + try { + inetAddress = InetAddress.getByName(domain); + return inetAddress.getHostAddress(); + } catch (UnknownHostException e) { + LogPrintUtils.eTag(TAG, e, "getDomainAddress"); + } + return null; + }); + return fs.get(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDomainAddress"); + } + return null; + } + + /** + * 获取 IP 地址 + * @param useIPv4 是否用 IPv4 + * @return IP 地址 + */ + public static String getIPAddress(final boolean useIPv4) { + try { + Enumeration nis = NetworkInterface.getNetworkInterfaces(); + while (nis.hasMoreElements()) { + NetworkInterface ni = nis.nextElement(); + // 防止小米手机返回 10.0.2.15 + if (!ni.isUp()) continue; + for (Enumeration addresses = ni.getInetAddresses(); addresses.hasMoreElements(); ) { + InetAddress inetAddress = addresses.nextElement(); + if (!inetAddress.isLoopbackAddress()) { + String hostAddress = inetAddress.getHostAddress(); + boolean isIPv4 = hostAddress.indexOf(':') < 0; + if (useIPv4) { + if (isIPv4) return hostAddress; + } else { + if (!isIPv4) { + int index = hostAddress.indexOf('%'); + return index < 0 ? hostAddress.toUpperCase() + : hostAddress.substring(0, index).toUpperCase(); + } + } + } + } + } + } catch (SocketException e) { + LogPrintUtils.eTag(TAG, e, "getIPAddress"); + } + return null; + } + + /** + * 根据 Wifi 获取网络 IP 地址 + * @return 网络 IP 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getIpAddressByWifi() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + return Formatter.formatIpAddress(wifiManager.getDhcpInfo().ipAddress); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIpAddressByWifi"); + } + return null; + } + + /** + * 根据 Wifi 获取网关 IP 地址 + * @return 网关 IP 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getGatewayByWifi() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + return Formatter.formatIpAddress(wifiManager.getDhcpInfo().gateway); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getGatewayByWifi"); + } + return null; + } + + /** + * 根据 Wifi 获取子网掩码 IP 地址 + * @return 子网掩码 IP 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getNetMaskByWifi() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + return Formatter.formatIpAddress(wifiManager.getDhcpInfo().netmask); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNetMaskByWifi"); + } + return null; + } + + /** + * 根据 Wifi 获取服务端 IP 地址 + * @return 服务端 IP 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getServerAddressByWifi() { + try { + @SuppressLint("WifiManagerLeak") + WifiManager wifiManager = AppUtils.getWifiManager(); + return Formatter.formatIpAddress(wifiManager.getDhcpInfo().serverAddress); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getServerAddressByWifi"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/NotificationUtils.java b/lib/DevApp/src/main/java/dev/utils/app/NotificationUtils.java new file mode 100644 index 0000000000..648073c505 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/NotificationUtils.java @@ -0,0 +1,623 @@ +package dev.utils.app; + +import android.app.AppOpsManager; +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.graphics.Bitmap; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.DrawableRes; +import androidx.core.app.NotificationManagerCompat; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Set; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.assist.ResourceAssist; + +/** + * detail: 通知栏管理工具类 + * @author Ttt + *
+ *     @see 
+ * 
+ */ +public final class NotificationUtils { + + private NotificationUtils() { + } + + // 日志 TAG + private static final String TAG = NotificationUtils.class.getSimpleName(); + + // 通知栏管理类 + private static NotificationManager sNotificationManager = null; + + /** + * 获取通知栏管理对象 + * @return {@link NotificationManager} + */ + public static NotificationManager getNotificationManager() { + if (sNotificationManager == null) { + try { + sNotificationManager = AppUtils.getNotificationManager(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNotificationManager"); + } + } + return sNotificationManager; + } + + /** + * 检查通知栏权限是否开启 + * 参考 SupportCompat 包中的: NotificationManagerCompat.from(context).areNotificationsEnabled(); + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotificationEnabled() { + Context context = DevUtils.getContext(); + if (context != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return AppUtils.getNotificationManager().areNotificationsEnabled(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + try { + AppOpsManager appOps = AppUtils.getAppOpsManager(); + ApplicationInfo appInfo = context.getApplicationInfo(); + String pkg = context.getApplicationContext().getPackageName(); + int uid = appInfo.uid; + + Class appOpsClass = Class.forName(AppOpsManager.class.getName()); + Method checkOpNoThrowMethod = appOpsClass.getMethod("checkOpNoThrow", Integer.TYPE, Integer.TYPE, String.class); + Field opPostNotificationValue = appOpsClass.getDeclaredField("OP_POST_NOTIFICATION"); + int value = (Integer) opPostNotificationValue.get(Integer.class); + return (Integer) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg) == 0; + } catch (Throwable ignore) { + return true; + } + } else { + return true; + } + } + return false; + } + + /** + * 检查是否有获取通知栏信息权限并跳转设置页面 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkAndIntentSetting() { + if (!isNotificationListenerEnabled()) { + startNotificationListenSettings(); + return false; + } + return true; + } + + /** + * 判断是否有获取通知栏信息权限 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotificationListenerEnabled() { + return isNotificationListenerEnabled(AppUtils.getPackageName()); + } + + /** + * 判断是否有获取通知栏信息权限 + * @param packageName 应用包名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotificationListenerEnabled(final String packageName) { + Set packageNames = NotificationManagerCompat.getEnabledListenerPackages(DevUtils.getContext()); + return packageNames.contains(packageName); + } + + /** + * 跳转到设置页面, 开启获取通知栏信息权限 + * @return {@code true} success, {@code false} fail + */ + public static boolean startNotificationListenSettings() { + return AppUtils.startActivity(IntentUtils.getLaunchAppNotificationListenSettingsIntent()); + } + + // = + + /** + * 移除通知 ( 移除所有通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @return {@code true} success, {@code false} fail + */ + public static boolean cancelAll() { + if (getNotificationManager() != null) { + try { + sNotificationManager.cancelAll(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cancelAll"); + } + } + return false; + } + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param args 消息 id 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean cancel(final int... args) { + if (getNotificationManager() != null && args != null) { + for (int id : args) { + try { + sNotificationManager.cancel(id); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cancel - id: %s", id); + } + } + } + return false; + } + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param tag 标记 TAG + * @param id 消息 id + * @return {@code true} success, {@code false} fail + */ + public static boolean cancel( + final String tag, + final int id + ) { + if (getNotificationManager() != null && tag != null) { + try { + sNotificationManager.cancel(tag, id); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cancel - id: %s, tag: %s", id, tag); + } + } + return false; + } + + /** + * 进行通知 + * @param id 消息 id + * @param notification {@link Notification} + * @return {@code true} success, {@code false} fail + */ + public static boolean notify( + final int id, + final Notification notification + ) { + if (getNotificationManager() != null && notification != null) { + try { + sNotificationManager.notify(id, notification); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notify - id: %s", id); + } + } + return false; + } + + /** + * 进行通知 + * @param tag 标记 TAG + * @param id 消息 id + * @param notification {@link Notification} + * @return {@code true} success, {@code false} fail + */ + public static boolean notify( + final String tag, + final int id, + final Notification notification + ) { + if (getNotificationManager() != null && tag != null && notification != null) { + try { + sNotificationManager.notify(tag, id, notification); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notify - id: %s, tag: %s", id, tag); + } + } + return false; + } + + // ============= + // = 封装外部调用 = + // ============= + +// // 使用自定义 View +// RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.xxx); +// // 设置 View +// Notification.builder.setContent(remoteViews); + + /** + * 获取 NotificationChannel + * @return {@link NotificationChannel} + */ + public static NotificationChannel getNotificationChannel() { + return getNotificationChannel(true); + } + + /** + * 获取 NotificationChannel + * @param create 是否创建 Channel + * @return {@link NotificationChannel} + */ + public static NotificationChannel getNotificationChannel(final boolean create) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + DevUtils.TAG + "." + AppUtils.getPackageName(), + DevUtils.TAG + "_" + TAG, + NotificationManager.IMPORTANCE_HIGH + ); + if (create) createNotificationChannel(channel); + return channel; + } + return null; + } + + /** + * 创建 NotificationChannel + * @param channel {@link NotificationChannel} + * @return {@link NotificationChannel} + */ + public static NotificationChannel createNotificationChannel(final NotificationChannel channel) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (channel != null) { + getNotificationManager().createNotificationChannel(channel); + } + return channel; + } + return null; + } + + // = + + /** + * 获取 PendingIntent + * @param intent {@link Intent} + * @param requestCode 请求 code + * @return {@link PendingIntent} + */ + public static PendingIntent createPendingIntent( + final Intent intent, + final int requestCode + ) { + try { + return PendingIntent.getActivity(DevUtils.getContext(), requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createPendingIntent"); + } + return null; + } + + /** + * 创建通知栏对象 + * @param params Notification 参数 + * @return {@link Notification} + */ + public static Notification createNotification(final Params params) { + return createNotification(DevUtils.getContext(), params, null); + } + + /** + * 创建通知栏对象 + * @param context {@link Context} + * @param params Notification 参数 + * @param callback {@link Callback} + * @return {@link Notification} + */ + public static Notification createNotification( + final Context context, + final Params params, + final Callback callback + ) { + Notification.Builder builder = createNotificationBuilder(context, params); + if (builder == null) return null; + // 额外操作触发 + if (callback != null) { + callback.callback(params, builder); + } + // 初始化 Notification 对象 + Notification baseNF; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + baseNF = builder.getNotification(); + } else { + baseNF = builder.build(); + } + return baseNF; + } + + /** + * 创建通知栏 Builder 对象 + * @param context {@link Context} + * @param params Notification 参数 + * @return {@link Notification.Builder} + */ + public static Notification.Builder createNotificationBuilder( + final Context context, + final Params params + ) { + if (params == null) return null; + Notification.Builder builder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String channelId = null; + if (params.getChannel() != null) { + channelId = params.getChannel().getId(); + createNotificationChannel(params.getChannel()); + } + if (TextUtils.isEmpty(channelId)) { + channelId = getNotificationChannel().getId(); + } + builder = new Notification.Builder(DevUtils.getContext(context), channelId); + } else { + builder = new Notification.Builder(DevUtils.getContext(context)); + } + // 点击通知执行 intent + builder.setContentIntent(params.getPendingIntent()); + // 设置图标 + builder.setSmallIcon(params.getIcon()); + // 设置图标 + Bitmap iconBitmap = ResourceUtils.getBitmap(ResourceAssist.get(context), params.getIcon()); + builder.setLargeIcon(iconBitmap); + // 指定通知的 ticker 内容, 通知被创建的时候, 在状态栏一闪而过, 属于瞬时提示信息 + builder.setTicker(params.getTicker()); + // 设置标题 + builder.setContentTitle(params.getTitle()); + // 设置内容 + builder.setContentText(params.getContent()); + // 设置消息提醒, 震动 | 声音 + builder.setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND); + // 将 AutoCancel 设为 true 后, 当你点击通知栏的 notification 后, 它会自动被取消消失 + builder.setAutoCancel(params.isAutoCancel()); + // 设置时间 + builder.setWhen(System.currentTimeMillis()); + // 设置震动参数 + VibratePattern vibratePattern = params.getVibratePattern(); + if (vibratePattern != null && !vibratePattern.isEmpty()) { + builder.setVibrate(vibratePattern.vibrates); + } + // 设置 led 灯参数 + LightPattern lightPattern = params.getLightPattern(); + if (lightPattern != null) { + builder.setLights(lightPattern.argb, lightPattern.durationMS, lightPattern.startOffMS); + } + return builder; + } + + /** + * detail: 设置通知栏 Led 灯参数实体类 + * @author Ttt + *
+     *     手机处于锁屏状态时, LED 灯就会不停地闪烁, 提醒用户去查看手机, 下面是绿色的灯光一闪一闪的效果
+     * 
+ */ + public static class LightPattern { + + // 控制 LED 灯的颜色, 一般有红绿蓝三种颜色可选 + private final int argb; + // 指定 LED 灯暗去的时长, 以毫秒为单位 + private final int startOffMS; + // 指定 LED 灯亮起的时长, 以毫秒为单位 + private final int durationMS; + + /** + * 构造函数 + * @param argb 颜色值 + * @param startOffMS 开始时间 + * @param durationMS 持续时间 + */ + private LightPattern( + int argb, + int startOffMS, + int durationMS + ) { + this.argb = argb; + this.startOffMS = startOffMS; + this.durationMS = durationMS; + } + + /** + * 获取 Led 配置参数 + * @param argb 颜色值 + * @param startOffMS 开始时间 + * @param durationMS 持续时间 + * @return {@link LightPattern} + */ + public static LightPattern get( + final int argb, + final int startOffMS, + final int durationMS + ) { + return new LightPattern(argb, startOffMS, durationMS); + } + } + + /** + * detail: 设置通知栏震动参数实体类 + * @author Ttt + *
+     *     vibrate 属性是一个长整型的数组, 用于设置手机静止和震动的时长, 以毫秒为单位
+     *     参数中下标为 0 的值表示手机静止的时长, 下标为 1 的值表示手机震动的时长, 下标为 2 的值又表示手机静止的时长, 以此类推
+     * 
+ */ + public static class VibratePattern { + + // long[] vibrates = { 0, 1000, 1000, 1000 }; + private final long[] vibrates; + + /** + * 构造函数 + * @param vibrates 震动时间数组 + */ + private VibratePattern(long[] vibrates) { + this.vibrates = vibrates; + } + + /** + * 判断是否为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isEmpty() { + return (vibrates == null || vibrates.length == 0); + } + + /** + * 获取 震动时间 配置参数 + * @param times 震动时间数组 + * @return {@link VibratePattern} + */ + public static VibratePattern get(final long... times) { + return new VibratePattern(times); + } + } + + /** + * detail: Notification 参数 + * @author Ttt + */ + public static class Params { + + // 点击跳转 Intent + private PendingIntent mPendingIntent; + // 图标 + private int mIcon; + // 状态栏通知文案 + private String mTicker; + // 通知栏标题 + private String mTitle; + // 通知栏内容 + private String mContent; + // 点击是否自动关闭 + private boolean mAutoCancel; + // 震动频率 + private VibratePattern mVibratePattern; + // Led 闪灯参数 + private LightPattern mLightPattern; + // NotificationChannel channel + private NotificationChannel mChannel; + + public Params() { + } + + public Params( + @DrawableRes int icon, + String title, + String content + ) { + this.mIcon = icon; + this.mTicker = title; + this.mTitle = title; + this.mContent = content; + } + + public PendingIntent getPendingIntent() { + return mPendingIntent; + } + + public Params setPendingIntent(final PendingIntent pendingIntent) { + this.mPendingIntent = pendingIntent; + return this; + } + + public int getIcon() { + return mIcon; + } + + public Params setIcon(@DrawableRes final int icon) { + this.mIcon = icon; + return this; + } + + public String getTicker() { + return mTicker; + } + + public Params setTicker(final String ticker) { + this.mTicker = ticker; + return this; + } + + public String getTitle() { + return mTitle; + } + + public Params setTitle(final String title) { + this.mTitle = title; + return this; + } + + public String getContent() { + return mContent; + } + + public Params setContent(final String content) { + this.mContent = content; + return this; + } + + public boolean isAutoCancel() { + return mAutoCancel; + } + + public Params setAutoCancel(final boolean autoCancel) { + mAutoCancel = autoCancel; + return this; + } + + public VibratePattern getVibratePattern() { + return mVibratePattern; + } + + public Params setVibratePattern(final VibratePattern vibratePattern) { + this.mVibratePattern = vibratePattern; + return this; + } + + public LightPattern getLightPattern() { + return mLightPattern; + } + + public Params setLightPattern(final LightPattern lightPattern) { + this.mLightPattern = lightPattern; + return this; + } + + public NotificationChannel getChannel() { + return mChannel; + } + + public Params setChannel(final NotificationChannel channel) { + this.mChannel = channel; + return this; + } + } + + /** + * detail: 额外操作回调 + * @author Ttt + */ + public interface Callback { + + void callback( + Params params, + Notification.Builder builder + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/PathUtils.java b/lib/DevApp/src/main/java/dev/utils/app/PathUtils.java new file mode 100644 index 0000000000..920c318ec4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/PathUtils.java @@ -0,0 +1,1387 @@ +package dev.utils.app; + +import android.os.Build; +import android.os.Environment; + +import java.io.File; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 路径相关工具类 + * @author Ttt + *
+ *     处理外部存储中的媒体文件
+ *     @see 
+ *     管理分区外部存储访问
+ *     @see 
+ *     

+ * 内部存储 : /data/data/package/ 目录 + * 外部存储 ( 私有目录 ) : /storage/emulated/0/Android/data/package/ 目录 + * 外部存储 ( 公开目录 ) : /storage/emulated/0/ 目录 + *

+ * 推荐使用 {@link PathUtils#getAppExternal()}、 {@link PathUtils#getInternal()} ( 外部存储 ( 私有目录 ) 、内部存储 ) + * Android 11 ( R ) 对外部存储 ( 公开目录 ) 进行限制 Scoped Storage, 或使用 MediaStore 对部分公开目录进行操作 + *

+ * 关于路径建议及兼容: + * 推荐隐私数据存储到内部存储中 {@link PathUtils#getInternal()} + * 应用数据全部存储到 外部存储 ( 私有目录 ) 中 {@link PathUtils#getAppExternal()} + * SDCard 目录推荐使用 {@link MediaStoreUtils} 存储常用 Image、Video、Document 等, 尽量减少需适配情况 + * 并且能够完善项目存储目录结构, 减少外部存储 ( 公开目录 ) 目录混乱等 + *
+ */ +public final class PathUtils { + + private PathUtils() { + } + + // 日志 TAG + private static final String TAG = PathUtils.class.getSimpleName(); + + // 内部存储路径类 + private static final InternalPath sInternalPath = new InternalPath(); + // 应用外部存储路径类 + private static final AppExternalPath sAppExternalPath = new AppExternalPath(); + // SDCard 外部存储路径类 + private static final SDCardPath sSDCardPath = new SDCardPath(); + // Internal + public static final String INTERNAL = "internal"; + // External + public static final String EXTERNAL = "external"; + + /** + * 获取内部存储路径类 + * @return {@link InternalPath} + */ + public static InternalPath getInternal() { + return sInternalPath; + } + + /** + * 获取应用外部存储路径类 + * @return {@link AppExternalPath} + */ + public static AppExternalPath getAppExternal() { + return sAppExternalPath; + } + + /** + * 获取 SDCard 外部存储路径类 + * @return {@link SDCardPath} + */ + public static SDCardPath getSDCard() { + return sSDCardPath; + } + + /** + * 是否获得 MANAGE_EXTERNAL_STORAGE 权限 + * @return {@code true} yes, {@code false} no + */ + public static boolean isExternalStorageManager() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Environment.isExternalStorageManager(); + } + return false; + } + + /** + * 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 + *
+     *     MANAGE_EXTERNAL_STORAGE
+     *     @see 
+     * 
+ * @return {@code true} yes, {@code false} no + */ + public static boolean checkExternalStorageAndIntentSetting() { + if (!isExternalStorageManager()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + AppUtils.startActivity(IntentUtils.getManageAppAllFilesAccessPermissionIntent()); + } + return false; + } + return true; + } + + // ====================================== + // = SDCard 外部存储 /storage/emulated/0/ = + // ====================================== + + /** + * detail: SDCard 外部存储路径类 + * @author Ttt + *
+     *     外部存储, 属于 SDCard 公开目录
+     *     路径: /storage/emulated/0/ 目录
+     *     需读写权限
+     * 
+ */ + public static final class SDCardPath { + + private SDCardPath() { + } + + /** + * 判断 SDCard 是否正常挂载 + * @return {@code true} yes, {@code false} no + */ + public boolean isSDCardEnable() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @return /storage/emulated/0/ + * @deprecated 推荐使用 {@link PathUtils#getAppExternal()}、 {@link PathUtils#getInternal()} ( 外部存储 ( 私有目录 ) 、内部存储 ) + * Android 11 ( R ) 对外部存储 ( 公开目录 ) 进行限制 Scoped Storage, 或使用 MediaStore 对部分公开目录进行操作 + */ + @Deprecated + public File getSDCardFile() { + return Environment.getExternalStorageDirectory(); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @return /storage/emulated/0/ + */ + @Deprecated + public String getSDCardPath() { + return FileUtils.getAbsolutePath(getSDCardFile()); + } + + // = + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @param fileName 文件名 + * @return /storage/emulated/0/ + */ + @Deprecated + public File getSDCardFile(final String fileName) { + return FileUtils.getFile(getSDCardPath(), fileName); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @param fileName 文件名 + * @return /storage/emulated/0/ + */ + @Deprecated + public String getSDCardPath(final String fileName) { + return FileUtils.getAbsolutePath(getSDCardFile(fileName)); + } + + // = + + /** + * 获取 SDCard 外部存储文件路径 ( path /storage/emulated/0/ ) + * @param type 文件类型 + * @return /storage/emulated/0/ + */ + @Deprecated + public String getExternalStoragePublicPath(final String type) { + return FileUtils.getAbsolutePath(getExternalStoragePublicDir(type)); + } + + /** + * 获取 SDCard 外部存储文件路径 ( path /storage/emulated/0/ ) + *
+         *     Environment.STANDARD_DIRECTORIES
+         * 
+ * @param type 文件类型 + * @return /storage/emulated/0/ + * @deprecated 推荐使用 {@link PathUtils#getAppExternal()}、 {@link PathUtils#getInternal()} ( 外部存储 ( 私有目录 ) 、内部存储 ) + * Android 11 ( R ) 对外部存储 ( 公开目录 ) 进行限制 Scoped Storage, 或使用 MediaStore 对部分公开目录进行操作 + */ + @Deprecated + public File getExternalStoragePublicDir(final String type) { + if (!getSDCard().isSDCardEnable()) return null; + try { + return Environment.getExternalStoragePublicDirectory(type); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getExternalStoragePublicDir"); + } + return null; + } + + // = + + /** + * 获取 SDCard 外部存储音乐路径 ( path /storage/emulated/0/Music ) + * @return /storage/emulated/0/Music + */ + @Deprecated + public String getMusicPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_MUSIC); + } + + /** + * 获取 SDCard 外部存储音乐路径 ( path /storage/emulated/0/Music ) + * @return /storage/emulated/0/Music + */ + @Deprecated + public File getMusicDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_MUSIC); + } + + // = + + /** + * 获取 SDCard 外部存储播客路径 ( path /storage/emulated/0/Podcasts ) + * @return /storage/emulated/0/Podcasts + */ + @Deprecated + public String getPodcastsPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_PODCASTS); + } + + /** + * 获取 SDCard 外部存储播客路径 ( path /storage/emulated/0/Podcasts ) + * @return /storage/emulated/0/Podcasts + */ + @Deprecated + public File getPodcastsDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_PODCASTS); + } + + // = + + /** + * 获取 SDCard 外部存储铃声路径 ( path /storage/emulated/0/Ringtones ) + * @return /storage/emulated/0/Ringtones + */ + @Deprecated + public String getRingtonesPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_RINGTONES); + } + + /** + * 获取 SDCard 外部存储铃声路径 ( path /storage/emulated/0/Ringtones ) + * @return /storage/emulated/0/Ringtones + */ + @Deprecated + public File getRingtonesDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_RINGTONES); + } + + // = + + /** + * 获取 SDCard 外部存储闹铃路径 ( path /storage/emulated/0/Alarms ) + * @return /storage/emulated/0/Alarms + */ + @Deprecated + public String getAlarmsPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_ALARMS); + } + + /** + * 获取 SDCard 外部存储闹铃路径 ( path /storage/emulated/0/Alarms ) + * @return /storage/emulated/0/Alarms + */ + @Deprecated + public File getAlarmsDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_ALARMS); + } + + // = + + /** + * 获取 SDCard 外部存储通知路径 ( path /storage/emulated/0/Notifications ) + * @return /storage/emulated/0/Notifications + */ + @Deprecated + public String getNotificationsPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_NOTIFICATIONS); + } + + /** + * 获取 SDCard 外部存储通知路径 ( path /storage/emulated/0/Notifications ) + * @return /storage/emulated/0/Notifications + */ + @Deprecated + public File getNotificationsDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_NOTIFICATIONS); + } + + // = + + /** + * 获取 SDCard 外部存储图片路径 ( path /storage/emulated/0/Pictures ) + * @return /storage/emulated/0/Pictures + */ + @Deprecated + public String getPicturesPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_PICTURES); + } + + /** + * 获取 SDCard 外部存储图片路径 ( path /storage/emulated/0/Pictures ) + * @return /storage/emulated/0/Pictures + */ + @Deprecated + public File getPicturesDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_PICTURES); + } + + // = + + /** + * 获取 SDCard 外部存储影片路径 ( path /storage/emulated/0/Movies ) + * @return /storage/emulated/0/Movies + */ + @Deprecated + public String getMoviesPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_MOVIES); + } + + /** + * 获取 SDCard 外部存储影片路径 ( path /storage/emulated/0/Movies ) + * @return /storage/emulated/0/Movies + */ + @Deprecated + public File getMoviesDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_MOVIES); + } + + // = + + /** + * 获取 SDCard 外部存储下载路径 ( path /storage/emulated/0/Download ) + * @return /storage/emulated/0/Download + */ + @Deprecated + public String getDownloadPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_DOWNLOADS); + } + + /** + * 获取 SDCard 外部存储下载路径 ( path /storage/emulated/0/Download ) + * @return /storage/emulated/0/Download + */ + @Deprecated + public File getDownloadDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_DOWNLOADS); + } + + // = + + /** + * 获取 SDCard 外部存储数码相机图片路径 ( path /storage/emulated/0/DCIM ) + * @return /storage/emulated/0/DCIM + */ + @Deprecated + public String getDCIMPath() { + return getExternalStoragePublicPath(Environment.DIRECTORY_DCIM); + } + + /** + * 获取 SDCard 外部存储数码相机图片路径 ( path /storage/emulated/0/DCIM ) + * @return /storage/emulated/0/DCIM + */ + @Deprecated + public File getDCIMDir() { + return getExternalStoragePublicDir(Environment.DIRECTORY_DCIM); + } + + // = + + /** + * 获取 SDCard 外部存储文档路径 ( path /storage/emulated/0/Documents ) + * @return /storage/emulated/0/Documents + */ + @Deprecated + public String getDocumentsPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getExternalStoragePublicPath("Documents"); + } + return getExternalStoragePublicPath(Environment.DIRECTORY_DOCUMENTS); + } + + /** + * 获取 SDCard 外部存储文档路径 ( path /storage/emulated/0/Documents ) + * @return /storage/emulated/0/Documents + */ + @Deprecated + public File getDocumentsDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getExternalStoragePublicDir("Documents"); + } + return getExternalStoragePublicDir(Environment.DIRECTORY_DOCUMENTS); + } + + // = + + /** + * 获取 SDCard 外部存储有声读物路径 ( path /storage/emulated/0/Audiobooks ) + * @return /storage/emulated/0/Audiobooks + */ + @Deprecated + public String getAudiobooksPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getExternalStoragePublicPath("Audiobooks"); + } + return getExternalStoragePublicPath(Environment.DIRECTORY_AUDIOBOOKS); + } + + /** + * 获取 SDCard 外部存储有声读物路径 ( path /storage/emulated/0/Audiobooks ) + * @return /storage/emulated/0/Audiobooks + */ + @Deprecated + public File getAudiobooksDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getExternalStoragePublicDir("Audiobooks"); + } + return getExternalStoragePublicDir(Environment.DIRECTORY_AUDIOBOOKS); + } + } + + // ======================================================= + // = 应用外部存储 /storage/emulated/0/Android/data/package/ = + // ======================================================= + + /** + * detail: 应用外部存储路径类 + * @author Ttt + *
+     *     外部存储 ( 内部存储之外的路径都是外部存储 ), 属于 SDCard 路径中为 APP 创建的私有目录
+     *     Android 7.0 以后其他应用需通过 FileProvider 方式访问其私有目录
+     *     路径: /storage/emulated/0/Android/data/package/ 目录
+     *     App 卸载该目录文件会全部清除
+     *     无需读写权限
+     *     推荐常用私有目录
+     * 
+ */ + public static final class AppExternalPath { + + private AppExternalPath() { + } + + /** + * 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) + * @return /storage/emulated/0/Android/data/package + */ + public String getAppDataPath() { + return FileUtils.getAbsolutePath(getAppDataDir()); + } + + /** + * 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) + * @return /storage/emulated/0/Android/data/package + */ + public File getAppDataDir() { + if (!getSDCard().isSDCardEnable()) return null; + try { + return DevUtils.getContext().getExternalCacheDir().getParentFile(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppDataDir"); + } + return null; + } + + /** + * 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package + */ + public String getAppDataPath(final String fileName) { + return FileUtils.getAbsolutePath(FileUtils.getFile(getAppDataPath(), fileName)); + } + + /** + * 获取应用外部存储数据路径 ( path /storage/emulated/0/Android/data/package ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package + */ + public File getAppDataDir(final String fileName) { + return FileUtils.getFile(getAppDataPath(), fileName); + } + + // = + + /** + * 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) + * @return /storage/emulated/0/Android/data/package/cache + */ + public String getAppCachePath() { + return FileUtils.getAbsolutePath(getAppCacheDir()); + } + + /** + * 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) + * @return /storage/emulated/0/Android/data/package/cache + */ + public File getAppCacheDir() { + if (!getSDCard().isSDCardEnable()) return null; + try { + return DevUtils.getContext().getExternalCacheDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppCacheDir"); + } + return null; + } + + /** + * 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package/cache + */ + public String getAppCachePath(final String fileName) { + return FileUtils.getAbsolutePath(FileUtils.getFile(getAppCachePath(), fileName)); + } + + /** + * 获取应用外部存储缓存路径 ( path /storage/emulated/0/Android/data/package/cache ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package/cache + */ + public File getAppCacheDir(final String fileName) { + return FileUtils.getFile(getAppCachePath(), fileName); + } + + // = + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + * @param type 文件类型 + * @return /storage/emulated/0/Android/data/package/files + */ + public String getExternalFilesPath(final String type) { + return FileUtils.getAbsolutePath(getExternalFilesDir(type)); + } + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + *
+         *     Environment.STANDARD_DIRECTORIES
+         * 
+ * @param type 文件类型 + * @return /storage/emulated/0/Android/data/package/files + */ + public File getExternalFilesDir(final String type) { + if (!getSDCard().isSDCardEnable()) return null; + try { + return DevUtils.getContext().getExternalFilesDir(type); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getExternalFilesDir"); + } + return null; + } + + // = + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + * @return /storage/emulated/0/Android/data/package/files + */ + public String getAppFilesPath() { + return getExternalFilesPath(null); + } + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + * @return /storage/emulated/0/Android/data/package/files + */ + public File getAppFilesDir() { + return getExternalFilesDir(null); + } + + // = + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package/files + */ + public String getAppFilesPath(final String fileName) { + return FileUtils.getAbsolutePath(getAppFilesDir(fileName)); + } + + /** + * 获取应用外部存储文件路径 ( path /storage/emulated/0/Android/data/package/files ) + * @param fileName 文件名 + * @return /storage/emulated/0/Android/data/package/files + */ + public File getAppFilesDir(final String fileName) { + return FileUtils.getFile(getAppFilesPath(), fileName); + } + + // = + + /** + * 获取应用外部存储音乐路径 ( path /storage/emulated/0/Android/data/package/files/Music ) + * @return /storage/emulated/0/Android/data/package/files/Music + */ + public String getAppMusicPath() { + return getExternalFilesPath(Environment.DIRECTORY_MUSIC); + } + + /** + * 获取应用外部存储音乐路径 ( path /storage/emulated/0/Android/data/package/files/Music ) + * @return /storage/emulated/0/Android/data/package/files/Music + */ + public File getAppMusicDir() { + return getExternalFilesDir(Environment.DIRECTORY_MUSIC); + } + + // = + + /** + * 获取应用外部存储播客路径 ( path /storage/emulated/0/Android/data/package/files/Podcasts ) + * @return /storage/emulated/0/Android/data/package/files/Podcasts + */ + public String getAppPodcastsPath() { + return getExternalFilesPath(Environment.DIRECTORY_PODCASTS); + } + + /** + * 获取应用外部存储播客路径 ( path /storage/emulated/0/Android/data/package/files/Podcasts ) + * @return /storage/emulated/0/Android/data/package/files/Podcasts + */ + public File getAppPodcastsDir() { + return getExternalFilesDir(Environment.DIRECTORY_PODCASTS); + } + + // = + + /** + * 获取应用外部存储铃声路径 ( path /storage/emulated/0/Android/data/package/files/Ringtones ) + * @return /storage/emulated/0/Android/data/package/files/Ringtones + */ + public String getAppRingtonesPath() { + return getExternalFilesPath(Environment.DIRECTORY_RINGTONES); + } + + /** + * 获取应用外部存储铃声路径 ( path /storage/emulated/0/Android/data/package/files/Ringtones ) + * @return /storage/emulated/0/Android/data/package/files/Ringtones + */ + public File getAppRingtonesDir() { + return getExternalFilesDir(Environment.DIRECTORY_RINGTONES); + } + + // = + + /** + * 获取应用外部存储闹铃路径 ( path /storage/emulated/0/Android/data/package/files/Alarms ) + * @return /storage/emulated/0/Android/data/package/files/Alarms + */ + public String getAppAlarmsPath() { + return getExternalFilesPath(Environment.DIRECTORY_ALARMS); + } + + /** + * 获取应用外部存储闹铃路径 ( path /storage/emulated/0/Android/data/package/files/Alarms ) + * @return /storage/emulated/0/Android/data/package/files/Alarms + */ + public File getAppAlarmsDir() { + return getExternalFilesDir(Environment.DIRECTORY_ALARMS); + } + + // = + + /** + * 获取应用外部存储通知路径 ( path /storage/emulated/0/Android/data/package/files/Notifications ) + * @return /storage/emulated/0/Android/data/package/files/Notifications + */ + public String getAppNotificationsPath() { + return getExternalFilesPath(Environment.DIRECTORY_NOTIFICATIONS); + } + + /** + * 获取应用外部存储通知路径 ( path /storage/emulated/0/Android/data/package/files/Notifications ) + * @return /storage/emulated/0/Android/data/package/files/Notifications + */ + public File getAppNotificationsDir() { + return getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS); + } + + // = + + /** + * 获取应用外部存储图片路径 ( path /storage/emulated/0/Android/data/package/files/Pictures ) + * @return /storage/emulated/0/Android/data/package/files/Pictures + */ + public String getAppPicturesPath() { + return getExternalFilesPath(Environment.DIRECTORY_PICTURES); + } + + /** + * 获取应用外部存储图片路径 ( path /storage/emulated/0/Android/data/package/files/Pictures ) + * @return /storage/emulated/0/Android/data/package/files/Pictures + */ + public File getAppPicturesDir() { + return getExternalFilesDir(Environment.DIRECTORY_PICTURES); + } + + // = + + /** + * 获取应用外部存储影片路径 ( path /storage/emulated/0/Android/data/package/files/Movies ) + * @return /storage/emulated/0/Android/data/package/files/Movies + */ + public String getAppMoviesPath() { + return getExternalFilesPath(Environment.DIRECTORY_MOVIES); + } + + /** + * 获取应用外部存储影片路径 ( path /storage/emulated/0/Android/data/package/files/Movies ) + * @return /storage/emulated/0/Android/data/package/files/Movies + */ + public File getAppMoviesDir() { + return getExternalFilesDir(Environment.DIRECTORY_MOVIES); + } + + // = + + /** + * 获取应用外部存储下载路径 ( path /storage/emulated/0/Android/data/package/files/Download ) + * @return /storage/emulated/0/Android/data/package/files/Download + */ + public String getAppDownloadPath() { + return getExternalFilesPath(Environment.DIRECTORY_DOWNLOADS); + } + + /** + * 获取应用外部存储下载路径 ( path /storage/emulated/0/Android/data/package/files/Download ) + * @return /storage/emulated/0/Android/data/package/files/Download + */ + public File getAppDownloadDir() { + return getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); + } + + // = + + /** + * 获取应用外部存储数码相机图片路径 ( path /storage/emulated/0/Android/data/package/files/DCIM ) + * @return /storage/emulated/0/Android/data/package/files/DCIM + */ + public String getAppDCIMPath() { + return getExternalFilesPath(Environment.DIRECTORY_DCIM); + } + + /** + * 获取应用外部存储数码相机图片路径 ( path /storage/emulated/0/Android/data/package/files/DCIM ) + * @return /storage/emulated/0/Android/data/package/files/DCIM + */ + public File getAppDCIMDir() { + return getExternalFilesDir(Environment.DIRECTORY_DCIM); + } + + // = + + /** + * 获取应用外部存储文档路径 ( path /storage/emulated/0/Android/data/package/files/Documents ) + * @return /storage/emulated/0/Android/data/package/files/Documents + */ + public String getAppDocumentsPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getExternalFilesPath("Documents"); + } + return getExternalFilesPath(Environment.DIRECTORY_DOCUMENTS); + } + + /** + * 获取应用外部存储文档路径 ( path /storage/emulated/0/Android/data/package/files/Documents ) + * @return /storage/emulated/0/Android/data/package/files/Documents + */ + public File getAppDocumentsDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getExternalFilesDir("Documents"); + } + return getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS); + } + + // = + + /** + * 获取应用外部存储有声读物路径 ( path /storage/emulated/0/Android/data/package/files/Audiobooks ) + * @return /storage/emulated/0/Android/data/package/files/Audiobooks + */ + public String getAppAudiobooksPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getExternalFilesPath("Audiobooks"); + } + return getExternalFilesPath(Environment.DIRECTORY_AUDIOBOOKS); + } + + /** + * 获取应用外部存储有声读物路径 ( path /storage/emulated/0/Android/data/package/files/Audiobooks ) + * @return /storage/emulated/0/Android/data/package/files/Audiobooks + */ + public File getAppAudiobooksDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getExternalFilesDir("Audiobooks"); + } + return getExternalFilesDir(Environment.DIRECTORY_AUDIOBOOKS); + } + + // = + + /** + * 获取应用外部存储 OBB 路径 ( path /storage/emulated/0/Android/obb/package ) + * @return /storage/emulated/0/Android/obb/package + */ + public String getAppObbPath() { + return FileUtils.getAbsolutePath(getAppObbDir()); + } + + /** + * 获取应用外部存储 OBB 路径 ( path /storage/emulated/0/Android/obb/package ) + * @return /storage/emulated/0/Android/obb/package + */ + public File getAppObbDir() { + if (!getSDCard().isSDCardEnable()) return null; + try { + return DevUtils.getContext().getObbDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppObbDir"); + } + return null; + } + } + + // ============================= + // = 内部存储 /data/data/package = + // ============================= + + /** + * detail: 内部存储路径类 + * @author Ttt + *
+     *     内部存储, 只有本 App 才可以访问, 其他应用无法访问
+     *     路径: /data/data/package/ 目录
+     *     App 卸载该目录文件会全部清除
+     *     无需读写权限
+     * 
+ */ + public static final class InternalPath { + + private InternalPath() { + } + + /** + * 获取 Android 系统根目录 ( path /system ) + * @return /system + */ + public String getRootPath() { + return FileUtils.getAbsolutePath(getRootDirectory()); + } + + /** + * 获取 Android 系统根目录 ( path /system ) + * @return /system + */ + public File getRootDirectory() { + return Environment.getRootDirectory(); + } + + // = + + /** + * 获取 data 目录 ( path /data ) + * @return /data + */ + public String getDataPath() { + return FileUtils.getAbsolutePath(getDataDirectory()); + } + + /** + * 获取 data 目录 ( path /data ) + * @return /system + */ + public File getDataDirectory() { + return Environment.getDataDirectory(); + } + + // = + + /** + * 获取下载缓存目录 ( path data/cache ) + * @return data/cache + */ + public String getDownloadCachePath() { + return FileUtils.getAbsolutePath(getDownloadCacheDirectory()); + } + + /** + * 获取下载缓存目录 ( path data/cache ) + * @return data/cache + */ + public File getDownloadCacheDirectory() { + return Environment.getDownloadCacheDirectory(); + } + + // = + + /** + * 获取应用内部存储数据路径 ( path /data/data/package ) + * @return /data/data/package + */ + public String getAppDataPath() { + return FileUtils.getAbsolutePath(getAppDataDir()); + } + + /** + * 获取应用内部存储数据路径 ( path /data/data/package ) + * @return /data/data/package + */ + public File getAppDataDir() { + try { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return FileUtils.getFile(DevUtils.getContext().getApplicationInfo().dataDir); + } + return DevUtils.getContext().getDataDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppDataDir"); + } + return null; + } + + /** + * 获取应用内部存储数据路径 ( path /data/data/package ) + * @param fileName 文件名 + * @return /data/data/package + */ + public String getAppDataPath(final String fileName) { + return FileUtils.getAbsolutePath(FileUtils.getFile(getAppDataPath(), fileName)); + } + + /** + * 获取应用内部存储数据路径 ( path /data/data/package ) + * @param fileName 文件名 + * @return /data/data/package + */ + public File getAppDataDir(final String fileName) { + return FileUtils.getFile(getAppDataPath(), fileName); + } + + // = + + /** + * 获取应用内部存储缓存路径 ( path /data/data/package/cache ) + * @return /data/data/package/cache + */ + public String getAppCachePath() { + return FileUtils.getAbsolutePath(getAppCacheDir()); + } + + /** + * 获取应用内部存储缓存路径 ( path /data/data/package/cache ) + * @return /data/data/package/cache + */ + public File getAppCacheDir() { + try { + return DevUtils.getContext().getCacheDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppCacheDir"); + } + return null; + } + + /** + * 获取应用内部存储缓存路径 ( path /data/data/package/cache ) + * @param fileName 文件名 + * @return /data/data/package/cache + */ + public String getAppCachePath(final String fileName) { + return FileUtils.getAbsolutePath(FileUtils.getFile(getAppCachePath(), fileName)); + } + + /** + * 获取应用内部存储缓存路径 ( path /data/data/package/cache ) + * @param fileName 文件名 + * @return /data/data/package/cache + */ + public File getAppCacheDir(final String fileName) { + return FileUtils.getFile(getAppCachePath(), fileName); + } + + // = + + /** + * 获取应用内部存储代码缓存路径 ( path /data/data/package/code_cache ) + * @return /data/data/package/code_cache + */ + public String getAppCodeCachePath() { + return FileUtils.getAbsolutePath(getAppCodeCacheDir()); + } + + /** + * 获取应用内部存储代码缓存路径 ( path /data/data/package/code_cache ) + * @return /data/data/package/code_cache + */ + public File getAppCodeCacheDir() { + try { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return FileUtils.getFile(getAppDataPath(), "code_cache"); + } + return DevUtils.getContext().getCodeCacheDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppCodeCacheDir"); + } + return null; + } + + // = + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases ) + * @return /data/data/package/databases + */ + public String getAppDbsPath() { + return FileUtils.getAbsolutePath(getAppDbsDir()); + } + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases ) + * @return /data/data/package/databases + */ + public File getAppDbsDir() { + return FileUtils.getFile(getAppDataPath(), "databases"); + } + + // = + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) + * @param name 数据库名 + * @return /data/data/package/databases/name + */ + public String getAppDbPath(final String name) { + return FileUtils.getAbsolutePath(getAppDbFile(name)); + } + + /** + * 获取应用内部存储数据库路径 ( path /data/data/package/databases/name ) + * @param name 数据库名 + * @return /data/data/package/databases/name + */ + public File getAppDbFile(final String name) { + try { + return DevUtils.getContext().getDatabasePath(name); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppDbFile %s", name); + } + return null; + } + + // = + + /** + * 获取应用内部存储文件路径 ( path /data/data/package/files ) + * @return /data/data/package/files + */ + public String getAppFilesPath() { + return FileUtils.getAbsolutePath(getAppFilesDir()); + } + + /** + * 获取应用内部存储文件路径 ( path /data/data/package/files ) + * @return /data/data/package/files + */ + public File getAppFilesDir() { + try { + return DevUtils.getContext().getFilesDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppFilesDir"); + } + return null; + } + + /** + * 获取应用内部存储文件路径 ( path /data/data/package/files ) + * @param type 文件类型 + * @return /data/data/package/files + */ + public String getAppFilesPath(final String type) { + return FileUtils.getAbsolutePath(getAppFilesDir(type)); + } + + /** + * 获取应用内部存储文件路径 ( path /data/data/package/files ) + *
+         *     Environment.STANDARD_DIRECTORIES
+         * 
+ * @param type 文件类型 + * @return /data/data/package/files + */ + public File getAppFilesDir(final String type) { + return FileUtils.getFile(getAppFilesPath(), type); + } + + // = + + /** + * 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) + * @return /data/data/package/shared_prefs + */ + public String getAppSpPath() { + return FileUtils.getAbsolutePath(getAppSpDir()); + } + + /** + * 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) + * @return /data/data/package/shared_prefs + */ + public File getAppSpDir() { + return FileUtils.getFile(getAppDataPath(), "shared_prefs"); + } + + /** + * 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) + * @param spName SP 文件名 + * @return /data/data/package/shared_prefs + */ + public String getAppSpPath(final String spName) { + return FileUtils.getAbsolutePath(getAppSpFile(spName)); + } + + /** + * 获取应用内部存储 SP 路径 ( path /data/data/package/shared_prefs ) + * @param spName SP 文件名 + * @return /data/data/package/shared_prefs + */ + public File getAppSpFile(final String spName) { + return FileUtils.getFile(getAppSpPath(), spName); + } + + // = + + /** + * 获取应用内部存储未备份文件路径 ( path /data/data/package/no_backup ) + * @return /data/data/package/no_backup + */ + public String getAppNoBackupFilesPath() { + return FileUtils.getAbsolutePath(getAppNoBackupFilesDir()); + } + + /** + * 获取应用内部存储未备份文件路径 ( path /data/data/package/no_backup ) + * @return /data/data/package/no_backup + */ + public File getAppNoBackupFilesDir() { + try { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return FileUtils.getFile(getAppDataPath(), "no_backup"); + } + return DevUtils.getContext().getNoBackupFilesDir(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppNoBackupFilesDir"); + } + return null; + } + + // = + + /** + * 获取应用内部存储音乐路径 ( path /data/data/package/files/Music ) + * @return /data/data/package/files/Music + */ + public String getAppMusicPath() { + return getAppFilesPath(Environment.DIRECTORY_MUSIC); + } + + /** + * 获取应用内部存储音乐路径 ( path /data/data/package/files/Music ) + * @return /data/data/package/files/Music + */ + public File getAppMusicDir() { + return getAppFilesDir(Environment.DIRECTORY_MUSIC); + } + + // = + + /** + * 获取应用内部存储播客路径 ( path /data/data/package/files/Podcasts ) + * @return /data/data/package/files/Podcasts + */ + public String getAppPodcastsPath() { + return getAppFilesPath(Environment.DIRECTORY_PODCASTS); + } + + /** + * 获取应用内部存储播客路径 ( path /data/data/package/files/Podcasts ) + * @return /data/data/package/files/Podcasts + */ + public File getAppPodcastsDir() { + return getAppFilesDir(Environment.DIRECTORY_PODCASTS); + } + + // = + + /** + * 获取应用内部存储铃声路径 ( path /data/data/package/files/Ringtones ) + * @return /data/data/package/files/Ringtones + */ + public String getAppRingtonesPath() { + return getAppFilesPath(Environment.DIRECTORY_RINGTONES); + } + + /** + * 获取应用内部存储铃声路径 ( path /data/data/package/files/Ringtones ) + * @return /data/data/package/files/Ringtones + */ + public File getAppRingtonesDir() { + return getAppFilesDir(Environment.DIRECTORY_RINGTONES); + } + + // = + + /** + * 获取应用内部存储闹铃路径 ( path /data/data/package/files/Alarms ) + * @return /data/data/package/files/Alarms + */ + public String getAppAlarmsPath() { + return getAppFilesPath(Environment.DIRECTORY_ALARMS); + } + + /** + * 获取应用内部存储闹铃路径 ( path /data/data/package/files/Alarms ) + * @return /data/data/package/files/Alarms + */ + public File getAppAlarmsDir() { + return getAppFilesDir(Environment.DIRECTORY_ALARMS); + } + + // = + + /** + * 获取应用内部存储通知路径 ( path /data/data/package/files/Notifications ) + * @return /data/data/package/files/Notifications + */ + public String getAppNotificationsPath() { + return getAppFilesPath(Environment.DIRECTORY_NOTIFICATIONS); + } + + /** + * 获取应用内部存储通知路径 ( path /data/data/package/files/Notifications ) + * @return /data/data/package/files/Notifications + */ + public File getAppNotificationsDir() { + return getAppFilesDir(Environment.DIRECTORY_NOTIFICATIONS); + } + + // = + + /** + * 获取应用内部存储图片路径 ( path /data/data/package/files/Pictures ) + * @return /data/data/package/files/Pictures + */ + public String getAppPicturesPath() { + return getAppFilesPath(Environment.DIRECTORY_PICTURES); + } + + /** + * 获取应用内部存储图片路径 ( path /data/data/package/files/Pictures ) + * @return /data/data/package/files/Pictures + */ + public File getAppPicturesDir() { + return getAppFilesDir(Environment.DIRECTORY_PICTURES); + } + + // = + + /** + * 获取应用内部存储影片路径 ( path /data/data/package/files/Movies ) + * @return /data/data/package/files/Movies + */ + public String getAppMoviesPath() { + return getAppFilesPath(Environment.DIRECTORY_MOVIES); + } + + /** + * 获取应用内部存储影片路径 ( path /data/data/package/files/Movies ) + * @return /data/data/package/files/Movies + */ + public File getAppMoviesDir() { + return getAppFilesDir(Environment.DIRECTORY_MOVIES); + } + + // = + + /** + * 获取应用内部存储下载路径 ( path /data/data/package/files/Download ) + * @return /data/data/package/files/Download + */ + public String getAppDownloadPath() { + return getAppFilesPath(Environment.DIRECTORY_DOWNLOADS); + } + + /** + * 获取应用内部存储下载路径 ( path /data/data/package/files/Download ) + * @return /data/data/package/files/Download + */ + public File getAppDownloadDir() { + return getAppFilesDir(Environment.DIRECTORY_DOWNLOADS); + } + + // = + + /** + * 获取应用内部存储数码相机图片路径 ( path /data/data/package/files/DCIM ) + * @return /data/data/package/files/DCIM + */ + public String getAppDCIMPath() { + return getAppFilesPath(Environment.DIRECTORY_DCIM); + } + + /** + * 获取应用内部存储数码相机图片路径 ( path /data/data/package/files/DCIM ) + * @return /data/data/package/files/DCIM + */ + public File getAppDCIMDir() { + return getAppFilesDir(Environment.DIRECTORY_DCIM); + } + + // = + + /** + * 获取应用内部存储文档路径 ( path /data/data/package/files/Documents ) + * @return /data/data/package/files/Documents + */ + public String getAppDocumentsPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getAppFilesPath("Documents"); + } + return getAppFilesPath(Environment.DIRECTORY_DOCUMENTS); + } + + /** + * 获取应用内部存储文档路径 ( path /data/data/package/files/Documents ) + * @return /data/data/package/files/Documents + */ + public File getAppDocumentsDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + return getAppFilesDir("Documents"); + } + return getAppFilesDir(Environment.DIRECTORY_DOCUMENTS); + } + + // = + + /** + * 获取应用内部存储有声读物路径 ( path /data/data/package/files/Audiobooks ) + * @return /data/data/package/files/Audiobooks + */ + public String getAppAudiobooksPath() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getAppFilesPath("Audiobooks"); + } + return getAppFilesPath(Environment.DIRECTORY_AUDIOBOOKS); + } + + /** + * 获取应用内部存储有声读物路径 ( path /data/data/package/files/Audiobooks ) + * @return /data/data/package/files/Audiobooks + */ + public File getAppAudiobooksDir() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return getAppFilesDir("Audiobooks"); + } + return getAppFilesDir(Environment.DIRECTORY_AUDIOBOOKS); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/PhoneUtils.java b/lib/DevApp/src/main/java/dev/utils/app/PhoneUtils.java new file mode 100644 index 0000000000..2a5e3c083a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/PhoneUtils.java @@ -0,0 +1,820 @@ +package dev.utils.app; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.provider.ContactsContract; +import android.provider.Settings; +import android.telephony.SmsManager; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Xml; + +import androidx.annotation.RequiresPermission; + +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 手机相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public final class PhoneUtils { + + private PhoneUtils() { + } + + // 日志 TAG + private static final String TAG = PhoneUtils.class.getSimpleName(); + + /** + * 判断设备是否是手机 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhone() { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + return telephonyManager != null && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isPhone"); + } + return false; + } + + /** + * 获取 SIM 卡状态 + * @return SIM 卡状态 + */ + public static int getSimState() { + return getSimState(-1); + } + + /** + * 获取 SIM 卡状态 + * @param slotIndex 卡槽索引 + * @return SIM 卡状态 + */ + public static int getSimState(final int slotIndex) { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) { + return telephonyManager.getSimState(); + } else { // 使用默认卡槽 + if (slotIndex == -1) { + return telephonyManager.getSimState(); + } + // 26 以上有公开 api + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return telephonyManager.getSimState(slotIndex); + } + // 反射调用方法 + Method method = telephonyManager.getClass().getDeclaredMethod("getSimState"); + method.setAccessible(true); + return (Integer) (method.invoke(telephonyManager, slotIndex)); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimState"); + } + return TelephonyManager.SIM_STATE_UNKNOWN; + } + + /** + * 判断是否装载 SIM 卡 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSimReady() { + return isSimReady(-1); + } + + /** + * 判断是否装载 SIM 卡 + * @param slotIndex 卡槽索引 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSimReady(final int slotIndex) { + return getSimState(slotIndex) == TelephonyManager.SIM_STATE_READY; + } + + /** + * 获取 SIM 卡运营商的国家代码 + * @return SIM 卡运营商的国家代码 + */ + public static String getSimCountryIso() { + try { + return AppUtils.getTelephonyManager().getSimCountryIso(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimCountryIso"); + } + return null; + } + + /** + * 获取 SIM 卡注册的网络运营商的国家代码 + * @return SIM 卡注册的网络运营商的国家代码 + */ + public static String getNetworkCountryIso() { + try { + return AppUtils.getTelephonyManager().getNetworkCountryIso(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNetworkCountryIso"); + } + return null; + } + + /** + * 获取 SIM 卡运营商的国家代码 + * @return SIM 卡运营商的国家代码 + */ + public static String getSimCountry() { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + // SIM 卡运营商的国家代码 + String simCountry = telephonyManager.getSimCountryIso(); + // 注册的网络运营商的国家代码 + String networkCountry = telephonyManager.getNetworkCountryIso(); + if (simCountry != null && simCountry.trim().length() != 0) { + return simCountry.trim(); + } else if (networkCountry != null && networkCountry.trim().length() != 0) { + return networkCountry.trim(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimCountry"); + } + return null; + } + + /** + * 判断 SIM 卡运营商是否国内 + * @return 状态码 1 属于国内 ( 中国 ), 2 属于国外, 3 属于无 SIM 卡 + */ + public static int checkSimCountry() { + try { + String countryCode = getSimCountry(); + // 不等于 null, 表示属于存在 SIM 卡 + if (countryCode != null) { + // zh_CN Locale.SIMPLIFIED_CHINESE + // 截取前面两位属于 zh 表示属于中国 + String country = countryCode.substring(0, 2); + // 如果属于 cn 表示属于国内 + if ("cn".equalsIgnoreCase(country)) { + return 1; + } else { + return 2; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "checkSimCountry"); + } + return 3; + } + + /** + * 获取 MEID 码 + * @return MEID 码 + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getMEID() { + return getMEID(-1); + } + + /** + * 获取 MEID 码 + * @param slotIndex 卡槽索引 + * @return MEID 码 + */ + @SuppressLint("MissingPermission") + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getMEID(final int slotIndex) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + if (slotIndex == -1) return AppUtils.getTelephonyManager().getMeid(); + return AppUtils.getTelephonyManager().getMeid(slotIndex); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMEID"); + } + } + return null; + } + + /** + * 获取 IMEI 码 + * @return IMEI 码 + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getIMEI() { + return getIMEI(-1); + } + + /** + * 获取 IMEI 码 + *
+     *     IMEI 是 International Mobile Equipment Identity ( 国际移动设备标识 ) 的简称
+     *     IMEI 由 15 位数字组成的「电子串号」它与每台手机一一对应, 而且该码是全世界唯一的
+     *     其组成为:
+     *     1. 前 6 位数 (TAC) 是「型号核准号码」一般代表机型
+     *     2. 接着的 2 位数 (FAC) 是「最后装配号」一般代表产地
+     *     3. 之后的 6 位数 (SNR) 是「串号」一般代表生产顺序号
+     *     4. 最后 1 位数 (SP) 通常是「0」为检验码, 目前暂备用
+     * 
+ * @param slotIndex 卡槽索引 + * @return IMEI 码 + */ + @SuppressLint("MissingPermission") + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getIMEI(final int slotIndex) { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (slotIndex == -1) return telephonyManager.getImei(); + return telephonyManager.getImei(slotIndex); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // 反射调用方法 + Class clazz = telephonyManager.getClass(); + Method method = clazz.getDeclaredMethod("getImei"); + method.setAccessible(true); + return (String) method.invoke(telephonyManager); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIMEI"); + } + return null; + } + + /** + * 获取 IMSI 码 + *
+     *     IMSI 是国际移动用户识别码的简称 (International Mobile Subscriber Identity)
+     *     IMSI 共有 15 位, 其结构如下:
+     *     MCC + MNC + MIN
+     *     MCC: Mobile Country Code, 移动国家码, 共 3 位, 中国为 460
+     *     MNC: Mobile NetworkCode, 移动网络码, 共 2 位
+     *     在中国, 移动的代码为 00 和 02, 联通的代码为 01, 电信的代码为 03
+     *     合起来就是 Android 手机中 APN 配置文件中的代码
+     *     中国移动: 46000 46002 46007
+     *     中国联通: 46001 46006
+     *     中国电信: 46003 46005 46011
+     *     举例, 一个典型的 IMSI 号码为 460030912121001
+     * 
+ * @return IMSI 码 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getIMSI() { + try { + return AppUtils.getTelephonyManager().getSubscriberId(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIMSI"); + } + return null; + } + + /** + * 获取 SIM 卡运营商名称 ( 如: 中国移动、如中国联通、中国电信 ) + * @return SIM 卡运营商名称 + */ + public static String getSimOperatorName() { + try { + return AppUtils.getTelephonyManager().getSimOperatorName(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimOperatorName"); + } + return null; + } + + /** + * 获取 SIM 卡运营商 MCC + MNC + * @return SIM 卡运营商 MCC + MNC + */ + public static String getSimOperator() { + try { + return AppUtils.getTelephonyManager().getSimOperator(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimOperator"); + } + return null; + } + + /** + * 通过 IMSI 获取中国运营商简称 + * @return 中国运营商简称 + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getChinaOperatorByIMSI() { + return getChinaOperatorByIMSI(getIMSI()); + } + + /** + * 通过 IMSI 获取中国运营商简称 + * @param imsi IMSI 码 + * @return 中国运营商简称 + */ + public static String getChinaOperatorByIMSI(final String imsi) { + if (imsi != null) { + if (imsi.startsWith("46000") || imsi.startsWith("46002") || imsi.startsWith("46007")) { + return "中国移动"; + } else if (imsi.startsWith("46001") || imsi.startsWith("46006")) { + return "中国联通"; + } else if (imsi.startsWith("46003") || imsi.startsWith("46005") || imsi.startsWith("46011")) { + return "中国电信"; + } + } + return null; + } + + /** + * 获取 SIM 卡中国运营商简称 + * @return SIM 卡中国运营商简称 + */ + public static String getChinaOperatorBySimOperator() { + return getChinaOperatorBySimOperator(getSimOperator()); + } + + /** + * 获取 SIM 卡中国运营商简称 + * @param simOperator SIM 卡运营商 MCC + MNC + * @return SIM 卡中国运营商简称 + */ + public static String getChinaOperatorBySimOperator(final String simOperator) { + if (simOperator != null) { + switch (simOperator) { + case "46000": + case "46002": + case "46007": + return "中国移动"; + case "46001": + case "46006": + return "中国联通"; + case "46003": + case "46005": + case "46011": + return "中国电信"; + } + } + return null; + } + + /** + * 获取手机类型 + *
+     *     {@link TelephonyManager#PHONE_TYPE_NONE} 0 手机制式未知
+     *     {@link TelephonyManager#PHONE_TYPE_GSM} 1 手机制式为 GSM, 移动和联通
+     *     {@link TelephonyManager#PHONE_TYPE_CDMA} 2 手机制式为 CDMA, 电信
+     *     {@link TelephonyManager#PHONE_TYPE_SIP} 3 手机制式为 SIP
+     * 
+ * @return 手机类型 + */ + public static int getPhoneType() { + try { + return AppUtils.getTelephonyManager().getPhoneType(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPhoneType"); + } + return TelephonyManager.PHONE_TYPE_NONE; + } + + /** + * 获取设备 id + * @return 设备 id + */ + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getDeviceId() { + return getDeviceId(-1); + } + + /** + * 获取设备 id + * @param slotIndex 卡槽索引 + * @return 设备 id + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getDeviceId(final int slotIndex) { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (slotIndex == -1) return telephonyManager.getDeviceId(); + return telephonyManager.getDeviceId(slotIndex); + } + return telephonyManager.getDeviceId(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDeviceId"); + } + return null; + } + + /** + * 获取 Android id + *
+     *     在设备首次启动时, 系统会随机生成一个 64 位的数字, 并把这个数字以十六进制字符串的形式保存下来
+     *     这个十六进制的字符串就是 ANDROID_ID, 当设备被 wipe 后该值会被重置
+     * 
+ * @return Android id + */ + @SuppressLint("HardwareIds") + public static String getAndroidId() { + try { + return Settings.Secure.getString( + ResourceUtils.getContentResolver(), Settings.Secure.ANDROID_ID + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAndroidId"); + } + return null; + } + + /** + * 获取设备序列号 + * @return 设备序列号 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getSerialNumber() { + try { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? Build.getSerial() : Build.SERIAL; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSerialNumber"); + } + return null; + } + + /** + * 获取 SIM 卡序列号 + * @return SIM 卡序列号 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getSimSerialNumber() { + try { + return AppUtils.getTelephonyManager().getSimSerialNumber(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSimSerialNumber"); + } + return null; + } + + /** + * 获取设备唯一 UUID + * @return 设备唯一 UUID + */ + @SuppressLint("MissingPermission") + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getUUID() { + String deviceId = StringUtils.getString(getDeviceId()); + String androidId = StringUtils.getString(getAndroidId()); + String serialNumber = StringUtils.getString(getSerialNumber()); + // 生成唯一关联 uuid + UUID deviceUUID = new UUID(androidId.hashCode(), ((long) deviceId.hashCode() << 32) | serialNumber.hashCode()); + return deviceUUID.toString(); + } + + /** + * 获取手机状态信息 + *
+     *     DeviceId(IMEI) = 99000311726612
+     *     DeviceSoftwareVersion = 00
+     *     Line1Number =
+     *     NetworkCountryIso = cn
+     *     NetworkOperator = 46003
+     *     NetworkOperatorName = 中国电信
+     *     NetworkType = 6
+     *     PhoneType = 2
+     *     SimCountryIso = cn
+     *     SimOperator = 46003
+     *     SimOperatorName = 中国电信
+     *     SimSerialNumber = 89860315045710604022
+     *     SimState = 5
+     *     SubscriberId(IMSI) = 460030419724900
+     *     VoiceMailNumber = *86
+     * 
+ * @return 手机状态信息 + */ + @SuppressLint({"MissingPermission", "HardwareIds"}) + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + public static String getPhoneStatus() { + try { + TelephonyManager telephonyManager = AppUtils.getTelephonyManager(); + if (telephonyManager == null) return ""; + return "DeviceId(IMEI) = " + telephonyManager.getDeviceId() + + "\nDeviceSoftwareVersion = " + telephonyManager.getDeviceSoftwareVersion() + + "\nLine1Number = " + telephonyManager.getLine1Number() + + "\nNetworkCountryIso = " + telephonyManager.getNetworkCountryIso() + + "\nNetworkOperator = " + telephonyManager.getNetworkOperator() + + "\nNetworkOperatorName = " + telephonyManager.getNetworkOperatorName() + + "\nNetworkType = " + telephonyManager.getNetworkType() + + "\nPhoneType = " + telephonyManager.getPhoneType() + + "\nSimCountryIso = " + telephonyManager.getSimCountryIso() + + "\nSimOperator = " + telephonyManager.getSimOperator() + + "\nSimOperatorName = " + telephonyManager.getSimOperatorName() + + "\nSimSerialNumber = " + telephonyManager.getSimSerialNumber() + + "\nSimState = " + telephonyManager.getSimState() + + "\nSubscriberId(IMSI) = " + telephonyManager.getSubscriberId() + + "(" + getChinaOperatorByIMSI() + ")" + + "\nVoiceMailNumber = " + telephonyManager.getVoiceMailNumber(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPhoneStatus"); + } + return ""; + } + + /** + * 跳至拨号界面 + * @param phoneNumber 电话号码 + * @return {@code true} success, {@code false} fail + */ + public static boolean dial(final String phoneNumber) { + try { + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)); + if (IntentUtils.isIntentAvailable(intent)) { + return AppUtils.startActivity(intent); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "dial"); + } + return false; + } + + /** + * 拨打电话 + * @param phoneNumber 电话号码 + * @return {@code true} success, {@code false} fail + */ + public static boolean call(final String phoneNumber) { + try { + Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber)); + if (IntentUtils.isIntentAvailable(intent)) { + return AppUtils.startActivity(intent); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "call"); + } + return false; + } + + /** + * 跳至发送短信界面 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean sendSms( + final String phoneNumber, + final String content + ) { + try { + Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:" + phoneNumber)); + if (IntentUtils.isIntentAvailable(intent)) { + intent.putExtra("sms_body", content); + return AppUtils.startActivity(intent); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendSms"); + } + return false; + } + + /** + * 发送短信 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean sendSmsSilent( + final String phoneNumber, + final String content + ) { + if (TextUtils.isEmpty(content)) return false; + try { + PendingIntent sentIntent; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + sentIntent = PendingIntent.getBroadcast( + DevUtils.getContext(), 0, + new Intent("send"), + PendingIntent.FLAG_IMMUTABLE + ); + } else { + sentIntent = PendingIntent.getBroadcast( + DevUtils.getContext(), 0, + new Intent("send"), 0 + ); + } + SmsManager smsManager = SmsManager.getDefault(); + if (content.length() >= 70) { + List lists = smsManager.divideMessage(content); + for (String value : lists) { + smsManager.sendTextMessage( + phoneNumber, null, value, + sentIntent, null + ); + } + } else { + smsManager.sendTextMessage( + phoneNumber, null, content, + sentIntent, null + ); + } + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sendSmsSilent"); + } + return false; + } + + /** + * 打开手机联系人界面点击联系人后便获取该号码 + *
+     *     protected void onActivityResult (int requestCode, int resultCode, Intent intent) {
+     *          super.onActivityResult(requestCode, resultCode, intent);
+     *          if (intent != null) {
+     *              Uri uri = intent.getData();
+     *              String num = null;
+     *              // 创建内容解析者
+     *              ContentResolver resolver = getContentResolver();
+     *              Cursor cursor = resolver.query(uri, null, null, null, null);
+     *              while (cursor.moveToNext()) {
+     *                  num = cursor.getString(cursor.getColumnIndex("data1"));
+     *              }
+     *              CloseUtils.closeIOQuietly(cursor);
+     *              num = num.replaceAll("-", "");
+     *          }
+     *      }
+     * 
+ * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean getContactNum(final Activity activity) { + if (activity != null && !activity.isFinishing()) { + try { + Intent intent = new Intent(); + intent.setAction("android.intent.action.PICK"); + intent.setType("vnd.android.cursor.dir/phone_v2"); + activity.startActivityForResult(intent, 0); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getContactNum"); + } + } + return false; + } + + /** + * 获取手机联系人信息 + * @return 手机联系人信息 + */ + public static List> getAllContactInfo() { + List> list = new ArrayList<>(); + // 游标 + Cursor cursor = null; + try { + ContentResolver resolver = ResourceUtils.getContentResolver(); + Uri raw_uri = Uri.parse("content://com.android.contacts/raw_contacts"); + Uri date_uri = Uri.parse("content://com.android.contacts/data"); + cursor = resolver.query( + raw_uri, new String[]{"contact_id"}, + null, null, null + ); + if (cursor != null) { + while (cursor.moveToNext()) { + String contact_id = cursor.getString(0); + if (!TextUtils.isEmpty(contact_id)) { + Cursor c = resolver.query( + date_uri, new String[]{"data1", "mimetype"}, + "raw_contact_id=?", new String[]{contact_id}, null + ); + Map map = new HashMap<>(); + if (c != null) { + while (c.moveToNext()) { + String data1 = c.getString(0); + String mimetype = c.getString(1); + if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) { + map.put("phone", data1); // 电话 + } else if ("vnd.android.cursor.item/name".equals(mimetype)) { + map.put("name", data1); // 姓名 + } + } + } + list.add(map); + CloseUtils.closeIOQuietly(c); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllContactInfo"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return list; + } + + /** + * 获取手机联系人信息 + * @return 手机联系人信息 + */ + public static List> getAllContactInfo2() { + List> list = new ArrayList<>(); + Cursor cursor = null; + try { + cursor = ResourceUtils.getContentResolver().query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + null, null, null, null + ); + while (cursor.moveToNext()) { + Map map = new HashMap<>(); + String phoneNumber = cursor.getString( + cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + ).trim().replaceAll(" ", ""); + String phoneName = cursor.getString( + cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME) + ).trim(); + map.put("phone", phoneNumber); + map.put("name", phoneName); + list.add(map); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllContactInfo2"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return list; + } + + /** + * 获取手机短信并保存到 xml 中 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean getAllSMS(final String filePath) { + Cursor cursor = null; + try { + ContentResolver resolver = ResourceUtils.getContentResolver(); + Uri uri = Uri.parse("content://sms"); + cursor = resolver.query( + uri, new String[]{"address", "date", "type", "body"}, + null, null, null + ); +// // 获取短信的个数 +// int count = cursor.getCount(); + XmlSerializer xmlSerializer = Xml.newSerializer(); + xmlSerializer.setOutput(new FileOutputStream(new File(filePath)), DevFinal.ENCODE.UTF_8); + xmlSerializer.startDocument(DevFinal.ENCODE.UTF_8, true); + xmlSerializer.startTag(null, "smss"); + while (cursor.moveToNext()) { + xmlSerializer.startTag(null, "sms"); + + xmlSerializer.startTag(null, "address"); + String address = cursor.getString(0); + xmlSerializer.text(address); + xmlSerializer.endTag(null, "address"); + + xmlSerializer.startTag(null, "date"); + String date = cursor.getString(1); + xmlSerializer.text(date); + xmlSerializer.endTag(null, "date"); + + xmlSerializer.startTag(null, "type"); + String type = cursor.getString(2); + xmlSerializer.text(type); + xmlSerializer.endTag(null, "type"); + + xmlSerializer.startTag(null, "body"); + String body = cursor.getString(3); + xmlSerializer.text(body); + xmlSerializer.endTag(null, "body"); + + xmlSerializer.endTag(null, "sms"); + } + xmlSerializer.endTag(null, "smss"); + xmlSerializer.endDocument(); + xmlSerializer.flush(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllSMS"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/PowerManagerUtils.java b/lib/DevApp/src/main/java/dev/utils/app/PowerManagerUtils.java new file mode 100644 index 0000000000..402bea69f0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/PowerManagerUtils.java @@ -0,0 +1,190 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.os.Build; +import android.os.PowerManager; +import android.view.Window; + +import dev.utils.LogPrintUtils; + +/** + * detail: 电源管理工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public final class PowerManagerUtils { + + // 日志 TAG + private static final String TAG = PowerManagerUtils.class.getSimpleName(); + + // PowerManagerUtils 实例 + private static volatile PowerManagerUtils sInstance; + + /** + * 获取 PowerManagerUtils 实例 + * @return {@link PowerManagerUtils} + */ + public static PowerManagerUtils getInstance() { + if (sInstance == null) { + synchronized (PowerManagerUtils.class) { + if (sInstance == null) { + sInstance = new PowerManagerUtils(); + } + } + } + return sInstance; + } + + // 电源管理类 + private PowerManager mPowerManager; + // 电源管理锁 + private PowerManager.WakeLock mWakeLock; + + /** + * 构造函数 + */ + private PowerManagerUtils() { + try { + // 获取系统服务 + mPowerManager = AppUtils.getPowerManager(); + // 电源管理锁 + mWakeLock = mPowerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.FULL_WAKE_LOCK, TAG); + } catch (Exception ignored) { + } + } + + /** + * 屏幕是否打开 ( 亮屏 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isScreenOn() { + if (mPowerManager == null) { + return false; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + return mPowerManager.isInteractive(); + } + return mPowerManager.isScreenOn(); + } + + /** + * 唤醒 / 点亮 屏幕 + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("WakelockTimeout") + public boolean turnScreenOn() { + if (mWakeLock != null && !mWakeLock.isHeld()) { + try { + mWakeLock.acquire(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "turnScreenOn"); + } + } + return false; + } + + /** + * 释放屏幕锁 ( 允许休眠时间自动黑屏 ) + * @return {@code true} success, {@code false} fail + */ + public boolean turnScreenOff() { + if (mWakeLock != null && mWakeLock.isHeld()) { + try { + mWakeLock.release(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "turnScreenOff"); + } + } + return false; + } + + /** + * 获取 PowerManager.WakeLock + * @return {@link PowerManager.WakeLock} + */ + public PowerManager.WakeLock getWakeLock() { + return mWakeLock; + } + + /** + * 设置 PowerManager.WakeLock + * @param wakeLock {@link PowerManager.WakeLock} + * @return {@link PowerManagerUtils} + */ + public PowerManagerUtils setWakeLock(final PowerManager.WakeLock wakeLock) { + this.mWakeLock = wakeLock; + return this; + } + + /** + * 获取 PowerManager + * @return {@link PowerManager} + */ + public PowerManager getPowerManager() { + return mPowerManager; + } + + /** + * 设置 PowerManager + * @param powerManager {@link PowerManager} + * @return {@link PowerManagerUtils} + */ + public PowerManagerUtils setPowerManager(final PowerManager powerManager) { + this.mPowerManager = powerManager; + return this; + } + + /** + * 设置屏幕常亮 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setBright(final Activity activity) { + return setBright(activity != null ? activity.getWindow() : null); + } + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public static boolean setBright(final Window window) { + return WindowUtils.get().setFlagKeepScreenOn(window); + } + + /** + * 设置 WakeLock 常亮 + *
+     *     Activity#onResume() 调用 setWakeLockToBright()
+     *     Activity#onPause() 调用 mWakeLock.release()
+     * 
+ * @return {@link PowerManager.WakeLock} + */ + @SuppressLint("WakelockTimeout") + public static PowerManager.WakeLock setWakeLockToBright() { + try { + // onResume() + PowerManager.WakeLock mWakeLock = PowerManagerUtils.getInstance().getPowerManager() + .newWakeLock( + PowerManager.SCREEN_BRIGHT_WAKE_LOCK + | PowerManager.ON_AFTER_RELEASE, TAG + ); + mWakeLock.acquire(); // 常量, 持有不黑屏 + +// // onPause() +// if (mWakeLock != null) { +// mWakeLock.release(); // 释放资源, 到休眠时间自动黑屏 +// } + return mWakeLock; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setWakeLockToBright"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ProcessUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ProcessUtils.java new file mode 100644 index 0000000000..4dfba68af9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ProcessUtils.java @@ -0,0 +1,319 @@ +package dev.utils.app; + +import android.Manifest; +import android.app.ActivityManager; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.provider.Settings; +import android.text.TextUtils; + +import androidx.annotation.RequiresPermission; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: 进程相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ * 
+ */ +public final class ProcessUtils { + + private ProcessUtils() { + } + + // 日志 TAG + private static final String TAG = ProcessUtils.class.getSimpleName(); + + /** + * 销毁自身进程 + */ + public static void kill() { + kill(android.os.Process.myPid()); + } + + /** + * 销毁进程 + * @param pid 进程 id + */ + public static void kill(final int pid) { + android.os.Process.killProcess(pid); + } + + /** + * 获取自身进程 id + * @return 自身进程 id + */ + public static int myPid() { + return android.os.Process.myPid(); + } + + /** + * 判断是否当前进程 + * @return {@code true} yes, {@code false} no + */ + public static boolean isCurProcess() { + String packageName = AppUtils.getPackageName(); + if (packageName == null) return false; + try { + return packageName.equals(getCurProcessName()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isCurProcess"); + } + return false; + } + + /** + * 获取当前进程名 + * @return 进程名 + */ + public static String getCurProcessName() { + try { + // 获取自身进程 id + int pid = android.os.Process.myPid(); + // 判断全部运行中的进程 + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (appProcess.pid == pid) { + return appProcess.processName; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCurProcessName"); + } + return null; + } + + /** + * 获取进程 id 对应的进程名 + * @param pid 进程 id + * @return 进程名 + */ + public static String getProcessName(final int pid) { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("/proc/" + pid + "/cmdline")); + String processName = br.readLine(); + if (!TextUtils.isEmpty(processName)) { + processName = processName.trim(); + } + return processName; + } catch (Throwable throwable) { + LogPrintUtils.eTag(TAG, throwable, "getProcessName"); + } finally { + CloseUtils.closeIOQuietly(br); + } + return null; + } + + /** + * 根据包名获取进程 id + * @param packageName 应用包名 + * @return 进程 id + */ + public static int getPid(final String packageName) { + if (packageName == null) return 0; + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (packageName.equals(appProcess.processName)) { + return appProcess.pid; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPid"); + } + return 0; + } + + /** + * 根据进程 id 获取进程信息 + * @param pid 进程 id + * @return 进程信息 + */ + public static ActivityManager.RunningAppProcessInfo getRunningAppProcessInfo(final int pid) { + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (appProcess.pid == pid) { + return appProcess; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRunningAppProcessInfo"); + } + return null; + } + + /** + * 根据包名获取进程信息 + * @param packageName 应用包名 + * @return 进程信息 + */ + public static ActivityManager.RunningAppProcessInfo getRunningAppProcessInfo(final String packageName) { + if (packageName == null) return null; + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (packageName.equals(appProcess.processName)) { + return appProcess; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRunningAppProcessInfo"); + } + return null; + } + + // = + + /** + * 获取前台线程包名 + * @return 前台应用包名 + */ + public static String getForegroundProcessName() { + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return appProcess.processName; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getForegroundProcessName"); + } + // SDK 大于 21 时 + if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + List lists = packageManager.queryIntentActivities( + intent, PackageManager.MATCH_DEFAULT_ONLY + ); + // 无权限 + if (lists.size() == 0) return null; + + UsageStatsManager usageStatsManager = AppUtils.getUsageStatsManager(); + List listUsageStats = null; + if (usageStatsManager != null) { + long endTime = System.currentTimeMillis(); + long beginTime = endTime - 86400000 * 7; + listUsageStats = usageStatsManager.queryUsageStats( + UsageStatsManager.INTERVAL_BEST, beginTime, endTime + ); + } + if (listUsageStats == null || listUsageStats.isEmpty()) return null; + UsageStats recentStats = null; + for (UsageStats usageStats : listUsageStats) { + if (recentStats == null || usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) { + recentStats = usageStats; + } + } + return recentStats == null ? null : recentStats.getPackageName(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getForegroundProcessName"); + } + } + return null; + } + + /** + * 获取后台服务进程 + * @return 后台服务进程 + */ + public static Set getAllBackgroundProcesses() { + try { + Set set = new HashSet<>(); + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + Collections.addAll(set, appProcess.pkgList); + } + return set; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllBackgroundProcesses"); + } + return Collections.emptySet(); + } + + /** + * 杀死所有的后台服务进程 + * @return 被暂时杀死的服务集合 + */ + @RequiresPermission(Manifest.permission.KILL_BACKGROUND_PROCESSES) + public static Set killAllBackgroundProcesses() { + try { + Set set = new HashSet<>(); + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + for (String packageName : appProcess.pkgList) { + activityManager.killBackgroundProcesses(packageName); + set.add(packageName); + } + } + lists = activityManager.getRunningAppProcesses(); + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + for (String packageName : appProcess.pkgList) { + set.remove(packageName); + } + } + return set; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "killAllBackgroundProcesses"); + } + return Collections.emptySet(); + } + + /** + * 杀死后台服务进程 + * @param packageName 应用包名 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.KILL_BACKGROUND_PROCESSES) + public static boolean killBackgroundProcesses(final String packageName) { + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + if (activityManager == null) return false; + List lists = activityManager.getRunningAppProcesses(); + if (lists == null || lists.size() == 0) return true; + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (Arrays.asList(appProcess.pkgList).contains(packageName)) { + activityManager.killBackgroundProcesses(packageName); + } + } + lists = activityManager.getRunningAppProcesses(); + if (lists == null || lists.size() == 0) return true; + for (ActivityManager.RunningAppProcessInfo appProcess : lists) { + if (Arrays.asList(appProcess.pkgList).contains(packageName)) { + return false; + } + } + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "killBackgroundProcesses"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ROMUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ROMUtils.java new file mode 100644 index 0000000000..a45e40fd59 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ROMUtils.java @@ -0,0 +1,520 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.os.Environment; +import android.text.TextUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.util.Properties; + +import dev.utils.common.CloseUtils; + +/** + * detail: ROM 相关工具类 + * @author Ttt + */ +public final class ROMUtils { + + private ROMUtils() { + } + + // ============== + // = ROM 标识信息 = + // ============== + + private static final String[] ROM_HUAWEI = {"huawei"}; + private static final String[] ROM_VIVO = {"vivo"}; + private static final String[] ROM_XIAOMI = {"xiaomi"}; + private static final String[] ROM_OPPO = {"oppo"}; + private static final String[] ROM_LEECO = {"leeco", "letv"}; + private static final String[] ROM_360 = {"360", "qiku"}; + private static final String[] ROM_ZTE = {"zte"}; + private static final String[] ROM_ONEPLUS = {"oneplus"}; + private static final String[] ROM_NUBIA = {"nubia"}; + private static final String[] ROM_COOLPAD = {"coolpad", "yulong"}; + private static final String[] ROM_LG = {"lg", "lge"}; + private static final String[] ROM_GOOGLE = {"google"}; + private static final String[] ROM_SAMSUNG = {"samsung"}; + private static final String[] ROM_MEIZU = {"meizu"}; + private static final String[] ROM_LENOVO = {"lenovo"}; + private static final String[] ROM_SMARTISAN = {"smartisan", "deltainno"}; + private static final String[] ROM_HTC = {"htc"}; + private static final String[] ROM_SONY = {"sony"}; + private static final String[] ROM_GIONEE = {"gionee", "amigo"}; + private static final String[] ROM_MOTOROLA = {"motorola"}; + + private static final String VERSION_PROPERTY_HUAWEI = "ro.build.version.emui"; + private static final String VERSION_PROPERTY_VIVO = "ro.vivo.os.build.display.id"; + private static final String VERSION_PROPERTY_XIAOMI = "ro.build.version.incremental"; + private static final String VERSION_PROPERTY_OPPO = "ro.build.version.opporom"; + private static final String VERSION_PROPERTY_LEECO = "ro.letv.release.version"; + private static final String VERSION_PROPERTY_360 = "ro.build.uiversion"; + private static final String VERSION_PROPERTY_ZTE = "ro.build.MiFavor_version"; + private static final String VERSION_PROPERTY_ONEPLUS = "ro.rom.version"; + private static final String VERSION_PROPERTY_NUBIA = "ro.build.rom.id"; + + /** + * 判断 ROM 是否 Huawei ( 华为 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isHuawei() { + return ROM_HUAWEI[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Vivo ( VIVO ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isVivo() { + return ROM_VIVO[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Xiaomi ( 小米 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isXiaomi() { + return ROM_XIAOMI[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Oppo ( OPPO ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isOppo() { + return ROM_OPPO[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Leeco ( 乐视 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isLeeco() { + return ROM_LEECO[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 360 ( 360 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean is360() { + return ROM_360[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Zte ( 中兴 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isZte() { + return ROM_ZTE[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Oneplus ( 一加 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isOneplus() { + return ROM_ONEPLUS[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Nubia ( 努比亚 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isNubia() { + return ROM_NUBIA[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Coolpad ( 酷派 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isCoolpad() { + return ROM_COOLPAD[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Lg ( LG ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isLg() { + return ROM_LG[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Google ( 谷歌 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isGoogle() { + return ROM_GOOGLE[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Samsung ( 三星 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isSamsung() { + return ROM_SAMSUNG[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Meizu ( 魅族 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isMeizu() { + return ROM_MEIZU[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Lenovo ( 联想 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isLenovo() { + return ROM_LENOVO[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Smartisan ( 锤子 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isSmartisan() { + return ROM_SMARTISAN[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Htc ( HTC ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isHtc() { + return ROM_HTC[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Sony ( 索尼 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isSony() { + return ROM_SONY[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Gionee ( 金立 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isGionee() { + return ROM_GIONEE[0].equals(getRomInfo().name); + } + + /** + * 判断 ROM 是否 Motorola ( 摩托罗拉 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isMotorola() { + return ROM_MOTOROLA[0].equals(getRomInfo().name); + } + + /** + * 获取 ROM 信息 + * @return {@link RomInfo} + */ + public static RomInfo getRomInfo() { + if (sBean != null) return sBean; + sBean = innerGetRomInfo(); + return sBean; + } + + // ========== + // = 内部方法 = + // ========== + + private static final String UNKNOWN = "unknown"; + + /** + * 是否匹配正确 ROM + * @param names 品牌名称集合 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRightRom( + final String... names + ) { + return isRightRom( + getBrand(), getManufacturer(), names + ); + } + + /** + * 是否匹配正确 ROM + * @param brand 产品 / 硬件品牌信息 + * @param manufacturer 产品 / 硬件制造商信息 + * @param names 品牌名称集合 + * @return {@code true} yes, {@code false} no + */ + private static boolean isRightRom( + final String brand, + final String manufacturer, + final String... names + ) { + for (String name : names) { + if (brand.contains(name) || manufacturer.contains(name)) { + return true; + } + } + return false; + } + + /** + * 获取产品 / 硬件制造商信息 + * @return 产品 / 硬件制造商信息 + */ + private static String getManufacturer() { + try { + String manufacturer = Build.MANUFACTURER; + if (!TextUtils.isEmpty(manufacturer)) { + return manufacturer.toLowerCase(); + } + } catch (Throwable ignore) { + } + return UNKNOWN; + } + + /** + * 获取产品 / 硬件品牌信息 + * @return 产品 / 硬件品牌信息 + */ + private static String getBrand() { + try { + String brand = Build.BRAND; + if (!TextUtils.isEmpty(brand)) { + return brand.toLowerCase(); + } + } catch (Throwable ignore) { + } + return UNKNOWN; + } + + /** + * 获取 ROM 版本信息 + * @param propertyName 属性名 + * @return ROM 版本信息 + */ + private static String getRomVersion(final String propertyName) { + String ret = ""; + if (!TextUtils.isEmpty(propertyName)) { + ret = getSystemProperty(propertyName); + } + if (TextUtils.isEmpty(ret) || ret.equals(UNKNOWN)) { + try { + String display = Build.DISPLAY; + if (!TextUtils.isEmpty(display)) { + ret = display.toLowerCase(); + } + } catch (Throwable ignore) { + } + } + if (TextUtils.isEmpty(ret)) { + return UNKNOWN; + } + return ret; + } + + /** + * 获取 system prop 文件指定属性信息 + * @param name 属性名 + * @return system prop 文件指定属性信息 + */ + private static String getSystemProperty(final String name) { + String prop = getSystemPropertyByShell(name); + if (!TextUtils.isEmpty(prop)) return prop; + prop = getSystemPropertyByStream(name); + if (!TextUtils.isEmpty(prop)) return prop; + if (Build.VERSION.SDK_INT < 28) { + return getSystemPropertyByReflect(name); + } + return prop; + } + + /** + * 通过 shell 方式获取 system prop 文件指定属性信息 + * @param propName 属性名 + * @return system prop 文件指定属性信息 + */ + private static String getSystemPropertyByShell(final String propName) { + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + propName); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + String ret = input.readLine(); + if (ret != null) { + return ret; + } + } catch (Throwable ignore) { + } finally { + CloseUtils.closeIOQuietly(input); + } + return ""; + } + + /** + * 获取 system prop 文件指定属性信息 + * @param key 属性 key + * @return system prop 文件指定属性信息 + */ + private static String getSystemPropertyByStream(final String key) { + try { + Properties prop = new Properties(); + FileInputStream is = new FileInputStream( + new File(Environment.getRootDirectory(), "build.prop") + ); + prop.load(is); + return prop.getProperty(key, ""); + } catch (Throwable ignore) { + } + return ""; + } + + /** + * 获取 system prop 文件指定属性信息 + * @param key 属性 key + * @return system prop 文件指定属性信息 + */ + private static String getSystemPropertyByReflect(final String key) { + try { + @SuppressLint("PrivateApi") + Class clz = Class.forName("android.os.SystemProperties"); + Method getMethod = clz.getMethod("get", String.class, String.class); + return (String) getMethod.invoke(clz, key, ""); + } catch (Throwable ignored) { + } + return ""; + } + + // ======== + // = 实体类 = + // ======== + + private static RomInfo sBean = null; + + /** + * detail: ROM 信息实体类 + * @author Ttt + */ + public static class RomInfo { + + private String name; + private String version; + + /** + * 获取 ROM 名称 + * @return ROM 名称 + */ + public String getName() { + return name; + } + + /** + * 获取 ROM 版本信息 + * @return ROM 版本信息 + */ + public String getVersion() { + return version; + } + + @Override + public String toString() { + return "RomInfo{name=" + name + ", version=" + version + "}"; + } + } + + /** + * 获取 ROM 信息 + * @return {@link RomInfo} + */ + private static RomInfo innerGetRomInfo() { + RomInfo bean = new RomInfo(); + final String brand = getBrand(); + final String manufacturer = getManufacturer(); + if (isRightRom(brand, manufacturer, ROM_HUAWEI)) { + bean.name = ROM_HUAWEI[0]; + String version = getRomVersion(VERSION_PROPERTY_HUAWEI); + String[] temp = version.split("_"); + if (temp.length > 1) { + bean.version = temp[1]; + } else { + bean.version = version; + } + return bean; + } + if (isRightRom(brand, manufacturer, ROM_VIVO)) { + bean.name = ROM_VIVO[0]; + bean.version = getRomVersion(VERSION_PROPERTY_VIVO); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_XIAOMI)) { + bean.name = ROM_XIAOMI[0]; + bean.version = getRomVersion(VERSION_PROPERTY_XIAOMI); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_OPPO)) { + bean.name = ROM_OPPO[0]; + bean.version = getRomVersion(VERSION_PROPERTY_OPPO); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_LEECO)) { + bean.name = ROM_LEECO[0]; + bean.version = getRomVersion(VERSION_PROPERTY_LEECO); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_360)) { + bean.name = ROM_360[0]; + bean.version = getRomVersion(VERSION_PROPERTY_360); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_ZTE)) { + bean.name = ROM_ZTE[0]; + bean.version = getRomVersion(VERSION_PROPERTY_ZTE); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_ONEPLUS)) { + bean.name = ROM_ONEPLUS[0]; + bean.version = getRomVersion(VERSION_PROPERTY_ONEPLUS); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_NUBIA)) { + bean.name = ROM_NUBIA[0]; + bean.version = getRomVersion(VERSION_PROPERTY_NUBIA); + return bean; + } + if (isRightRom(brand, manufacturer, ROM_COOLPAD)) { + bean.name = ROM_COOLPAD[0]; + } else if (isRightRom(brand, manufacturer, ROM_LG)) { + bean.name = ROM_LG[0]; + } else if (isRightRom(brand, manufacturer, ROM_GOOGLE)) { + bean.name = ROM_GOOGLE[0]; + } else if (isRightRom(brand, manufacturer, ROM_SAMSUNG)) { + bean.name = ROM_SAMSUNG[0]; + } else if (isRightRom(brand, manufacturer, ROM_MEIZU)) { + bean.name = ROM_MEIZU[0]; + } else if (isRightRom(brand, manufacturer, ROM_LENOVO)) { + bean.name = ROM_LENOVO[0]; + } else if (isRightRom(brand, manufacturer, ROM_SMARTISAN)) { + bean.name = ROM_SMARTISAN[0]; + } else if (isRightRom(brand, manufacturer, ROM_HTC)) { + bean.name = ROM_HTC[0]; + } else if (isRightRom(brand, manufacturer, ROM_SONY)) { + bean.name = ROM_SONY[0]; + } else if (isRightRom(brand, manufacturer, ROM_GIONEE)) { + bean.name = ROM_GIONEE[0]; + } else if (isRightRom(brand, manufacturer, ROM_MOTOROLA)) { + bean.name = ROM_MOTOROLA[0]; + } else { + bean.name = manufacturer; + } + bean.version = getRomVersion(""); + return bean; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/RecyclerViewUtils.java b/lib/DevApp/src/main/java/dev/utils/app/RecyclerViewUtils.java new file mode 100644 index 0000000000..9543652f7f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/RecyclerViewUtils.java @@ -0,0 +1,1757 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.LinearSnapHelper; +import androidx.recyclerview.widget.PagerSnapHelper; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; + +import dev.utils.LogPrintUtils; + +/** + * detail: RecyclerView 工具类 + * @author Ttt + *
+ *     RecyclerView 截图使用 {@link CapturePictureUtils}
+ *     RecyclerView 滑动使用 {@link ListViewUtils}
+ *     

+ * 判断当前 view 是否在屏幕可见 + * @see
+ * 判断 RecyclerView 中 View 的可见性 + * @see + * RecyclerView 嵌套 EditText, EditText 获取焦点时滑动异常问题解决记录 ( requestChildRectangleOnScreen ) + * @see + * 解决 ScrollView 嵌套 RecyclerView 的显示及滑动问题 + * @see + * RecyclerView 使用 GridLayoutManager 间距设置 + * @see + * Android 可伸缩布局 - FlexboxLayout ( 支持 RecyclerView 集成 ) + * @see + * Recycleview 中 item 没有填满屏幕的问题 + * @see + * android RecyclerView 悬浮吸顶效果 + * @see + * @see + *

+ * // 此方法常用作判断是否能下拉刷新, 来解决滑动冲突 + * int findFirstCompletelyVisibleItemPosition = linearManager.findFirstCompletelyVisibleItemPosition(); + * // 最后一个完整的可见的 item 位置 + * int findLastCompletelyVisibleItemPosition = linearManager.findLastCompletelyVisibleItemPosition(); + * // 最后一个可见的位置 + * int findLastVisibleItemPosition = linearManager.findLastVisibleItemPosition(); + * // 第一个可见的位置 + * int findFirstVisibleItemPosition = linearManager.findFirstVisibleItemPosition(); + *
+ */ +public final class RecyclerViewUtils { + + private RecyclerViewUtils() { + } + + // 日志 TAG + private static final String TAG = RecyclerViewUtils.class.getSimpleName(); + + // ==================== + // = 获取 RecyclerView = + // ==================== + + /** + * 获取 RecyclerView + * @param view {@link View} + * @param 泛型 + * @return {@link RecyclerView} + */ + public static T getRecyclerView(final View view) { + if (view instanceof RecyclerView) { + try { + return (T) view; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRecyclerView"); + } + } + return null; + } + + // ================ + // = LayoutParams = + // ================ + + /** + * 获取 RecyclerView Item View LayoutParams + * @param itemView Item View + * @return Item View LayoutParams + */ + public static RecyclerView.LayoutParams getLayoutParams(final View itemView) { + if (itemView != null) { + ViewGroup.LayoutParams layoutParams = itemView.getLayoutParams(); + if (layoutParams instanceof RecyclerView.LayoutParams) { + return (RecyclerView.LayoutParams) layoutParams; + } + } + return null; + } + + /** + * 获取 RecyclerView Item View LayoutParams + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return Item View LayoutParams + */ + public static RecyclerView.LayoutParams getLayoutParams( + final RecyclerView recyclerView, + final int position + ) { + return getLayoutParams( + findViewByPosition(recyclerView, position) + ); + } + + // ================= + // = LayoutManager = + // ================= + + /** + * 设置 RecyclerView LayoutManager + * @param view {@link View} + * @param layoutManager LayoutManager + * @return {@link View} + */ + public static View setLayoutManager( + final View view, + final RecyclerView.LayoutManager layoutManager + ) { + setLayoutManager(getRecyclerView(view), layoutManager); + return view; + } + + /** + * 设置 RecyclerView LayoutManager + * @param recyclerView {@link RecyclerView} + * @param layoutManager LayoutManager + * @param 泛型 + * @return {@link RecyclerView} + */ + public static T setLayoutManager( + final T recyclerView, + final RecyclerView.LayoutManager layoutManager + ) { + if (recyclerView != null && layoutManager != null) { + recyclerView.setLayoutManager(layoutManager); + } + return recyclerView; + } + + /** + * 获取 RecyclerView LayoutManager + * @param view {@link View} + * @return LayoutManager + */ + public static RecyclerView.LayoutManager getLayoutManager(final View view) { + return getLayoutManager(getRecyclerView(view)); + } + + /** + * 获取 RecyclerView LayoutManager + * @param recyclerView {@link RecyclerView} + * @return LayoutManager + */ + public static RecyclerView.LayoutManager getLayoutManager(final RecyclerView recyclerView) { + if (recyclerView != null) { + return recyclerView.getLayoutManager(); + } + return null; + } + + // = + + /** + * 获取 LinearLayoutManager + * @param view {@link View} + * @return {@link LinearLayoutManager} + */ + public static LinearLayoutManager getLinearLayoutManager(final View view) { + return getLinearLayoutManager(getRecyclerView(view)); + } + + /** + * 获取 LinearLayoutManager + * @param recyclerView {@link RecyclerView} + * @return {@link LinearLayoutManager} + */ + public static LinearLayoutManager getLinearLayoutManager(final RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = getLayoutManager(recyclerView); + if (layoutManager instanceof LinearLayoutManager) { + return (LinearLayoutManager) layoutManager; + } + return null; + } + + /** + * 获取 GridLayoutManager + * @param view {@link View} + * @return {@link GridLayoutManager} + */ + public static GridLayoutManager getGridLayoutManager(final View view) { + return getGridLayoutManager(getRecyclerView(view)); + } + + /** + * 获取 GridLayoutManager + * @param recyclerView {@link RecyclerView} + * @return {@link GridLayoutManager} + */ + public static GridLayoutManager getGridLayoutManager(final RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = getLayoutManager(recyclerView); + if (layoutManager instanceof GridLayoutManager) { + return (GridLayoutManager) layoutManager; + } + return null; + } + + /** + * 获取 StaggeredGridLayoutManager + * @param view {@link View} + * @return {@link StaggeredGridLayoutManager} + */ + public static StaggeredGridLayoutManager getStaggeredGridLayoutManager(final View view) { + return getStaggeredGridLayoutManager(getRecyclerView(view)); + } + + /** + * 获取 StaggeredGridLayoutManager + * @param recyclerView {@link RecyclerView} + * @return {@link StaggeredGridLayoutManager} + */ + public static StaggeredGridLayoutManager getStaggeredGridLayoutManager(final RecyclerView recyclerView) { + RecyclerView.LayoutManager layoutManager = getLayoutManager(recyclerView); + if (layoutManager instanceof StaggeredGridLayoutManager) { + return (StaggeredGridLayoutManager) layoutManager; + } + return null; + } + + // ============= + // = Grid Span = + // ============= + + /** + * 设置 GridLayoutManager SpanCount + * @param view {@link View} + * @param spanCount Span Count + * @return {@code true} success, {@code false} fail + */ + public static boolean setSpanCount( + final View view, + final int spanCount + ) { + return setSpanCount(getLayoutManager(view), spanCount); + } + + /** + * 设置 GridLayoutManager SpanCount + * @param recyclerView {@link RecyclerView} + * @param spanCount Span Count + * @return {@code true} success, {@code false} fail + */ + public static boolean setSpanCount( + final RecyclerView recyclerView, + final int spanCount + ) { + return setSpanCount(getLayoutManager(recyclerView), spanCount); + } + + /** + * 设置 GridLayoutManager SpanCount + * @param layoutManager LayoutManager + * @param spanCount Span Count + * @return {@code true} success, {@code false} fail + */ + public static boolean setSpanCount( + final RecyclerView.LayoutManager layoutManager, + final int spanCount + ) { + if (spanCount < 1) return false; + if (layoutManager instanceof GridLayoutManager) { + ((GridLayoutManager) layoutManager).setSpanCount(spanCount); + return true; + } else if (layoutManager instanceof StaggeredGridLayoutManager) { + ((StaggeredGridLayoutManager) layoutManager).setSpanCount(spanCount); + return true; + } + return false; + } + + // = + + /** + * 获取 GridLayoutManager SpanCount + * @param view {@link View} + * @return Span Count + */ + public static int getSpanCount(final View view) { + return getSpanCount(getLayoutManager(view)); + } + + /** + * 获取 GridLayoutManager SpanCount + * @param recyclerView {@link RecyclerView} + * @return Span Count + */ + public static int getSpanCount(final RecyclerView recyclerView) { + return getSpanCount(getLayoutManager(recyclerView)); + } + + /** + * 获取 GridLayoutManager SpanCount + * @param layoutManager LayoutManager + * @return Span Count + */ + public static int getSpanCount(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof GridLayoutManager) { + return ((GridLayoutManager) layoutManager).getSpanCount(); + } else if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).getSpanCount(); + } + return 0; + } + + // ============ + // = Position = + // ============ + + /** + * 获取 RecyclerView 对应 Item View 索引 + * @param view {@link View} + * @param itemView Item View + * @return 对应 Item View 索引 + */ + public static int getPosition( + final View view, + final View itemView + ) { + return getPosition(getLayoutManager(view), itemView); + } + + /** + * 获取 RecyclerView 对应 Item View 索引 + * @param recyclerView {@link RecyclerView} + * @param itemView Item View + * @return 对应 Item View 索引 + */ + public static int getPosition( + final RecyclerView recyclerView, + final View itemView + ) { + return getPosition(getLayoutManager(recyclerView), itemView); + } + + /** + * 获取 RecyclerView 对应 Item View 索引 + * @param layoutManager LayoutManager + * @param itemView Item View + * @return 对应 Item View 索引 + */ + public static int getPosition( + final RecyclerView.LayoutManager layoutManager, + final View itemView + ) { + if (layoutManager != null && itemView != null) { + try { + return layoutManager.getPosition(itemView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 对应索引 Item View + * @param view {@link View} + * @param position 索引 + * @return 对应索引 Item View + */ + public static View findViewByPosition( + final View view, + final int position + ) { + return findViewByPosition(getLayoutManager(view), position); + } + + /** + * 获取 RecyclerView 对应索引 Item View + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return 对应索引 Item View + */ + public static View findViewByPosition( + final RecyclerView recyclerView, + final int position + ) { + return findViewByPosition(getLayoutManager(recyclerView), position); + } + + /** + * 获取 RecyclerView 对应索引 Item View + * @param layoutManager LayoutManager + * @param position 索引 + * @return 对应索引 Item View + */ + public static View findViewByPosition( + final RecyclerView.LayoutManager layoutManager, + final int position + ) { + if (layoutManager != null && position >= 0) { + try { + return layoutManager.findViewByPosition(position); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findViewByPosition"); + } + } + return null; + } + + // = + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引 + * @param view {@link View} + * @return 第一条完全显示 Item 索引 + */ + public static int findFirstCompletelyVisibleItemPosition(final View view) { + return findFirstCompletelyVisibleItemPosition(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引 + * @param recyclerView {@link RecyclerView} + * @return 第一条完全显示 Item 索引 + */ + public static int findFirstCompletelyVisibleItemPosition(final RecyclerView recyclerView) { + return findFirstCompletelyVisibleItemPosition(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引 + * @param layoutManager LayoutManager + * @return 第一条完全显示 Item 索引 + */ + public static int findFirstCompletelyVisibleItemPosition(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).findFirstCompletelyVisibleItemPosition(); + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引数组 + * @param view {@link View} + * @return 第一条完全显示 Item 索引数组 + */ + public static int[] findFirstCompletelyVisibleItemPositions(final View view) { + return findFirstCompletelyVisibleItemPositions(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引数组 + * @param recyclerView {@link RecyclerView} + * @return 第一条完全显示 Item 索引数组 + */ + public static int[] findFirstCompletelyVisibleItemPositions(final RecyclerView recyclerView) { + return findFirstCompletelyVisibleItemPositions(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 第一条完全显示 Item 索引数组 + * @param layoutManager LayoutManager + * @return 第一条完全显示 Item 索引数组 + */ + public static int[] findFirstCompletelyVisibleItemPositions(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findFirstCompletelyVisibleItemPositions(null); + } + return null; + } + + // = + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引 + * @param view {@link View} + * @return 最后一条完全显示 Item 索引 + */ + public static int findLastCompletelyVisibleItemPosition(final View view) { + return findLastCompletelyVisibleItemPosition(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引 + * @param recyclerView {@link RecyclerView} + * @return 最后一条完全显示 Item 索引 + */ + public static int findLastCompletelyVisibleItemPosition(final RecyclerView recyclerView) { + return findLastCompletelyVisibleItemPosition(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引 + * @param layoutManager LayoutManager + * @return 最后一条完全显示 Item 索引 + */ + public static int findLastCompletelyVisibleItemPosition(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).findLastCompletelyVisibleItemPosition(); + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引数组 + * @param view {@link View} + * @return 最后一条完全显示 Item 索引数组 + */ + public static int[] findLastCompletelyVisibleItemPositions(final View view) { + return findLastCompletelyVisibleItemPositions(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引数组 + * @param recyclerView {@link RecyclerView} + * @return 最后一条完全显示 Item 索引数组 + */ + public static int[] findLastCompletelyVisibleItemPositions(final RecyclerView recyclerView) { + return findLastCompletelyVisibleItemPositions(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 最后一条完全显示 Item 索引数组 + * @param layoutManager LayoutManager + * @return 最后一条完全显示 Item 索引数组 + */ + public static int[] findLastCompletelyVisibleItemPositions(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(null); + } + return null; + } + + // = + + /** + * 获取 RecyclerView 第一条显示 Item 索引 + * @param view {@link View} + * @return 第一条显示 Item 索引 + */ + public static int findFirstVisibleItemPosition(final View view) { + return findFirstVisibleItemPosition(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 第一条显示 Item 索引 + * @param recyclerView {@link RecyclerView} + * @return 第一条显示 Item 索引 + */ + public static int findFirstVisibleItemPosition(final RecyclerView recyclerView) { + return findFirstVisibleItemPosition(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 第一条显示 Item 索引 + * @param layoutManager LayoutManager + * @return 第一条显示 Item 索引 + */ + public static int findFirstVisibleItemPosition(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition(); + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 第一条显示 Item 索引数组 + * @param view {@link View} + * @return 第一条显示 Item 索引数组 + */ + public static int[] findFirstVisibleItemPositions(final View view) { + return findFirstVisibleItemPositions(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 第一条显示 Item 索引数组 + * @param recyclerView {@link RecyclerView} + * @return 第一条显示 Item 索引数组 + */ + public static int[] findFirstVisibleItemPositions(final RecyclerView recyclerView) { + return findFirstVisibleItemPositions(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 第一条显示 Item 索引数组 + * @param layoutManager LayoutManager + * @return 第一条显示 Item 索引数组 + */ + public static int[] findFirstVisibleItemPositions(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null); + } + return null; + } + + // = + + /** + * 获取 RecyclerView 最后一条显示 Item 索引 + * @param view {@link View} + * @return 最后一条显示 Item 索引 + */ + public static int findLastVisibleItemPosition(final View view) { + return findLastVisibleItemPosition(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 最后一条显示 Item 索引 + * @param recyclerView {@link RecyclerView} + * @return 最后一条显示 Item 索引 + */ + public static int findLastVisibleItemPosition(final RecyclerView recyclerView) { + return findLastVisibleItemPosition(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 最后一条显示 Item 索引 + * @param layoutManager LayoutManager + * @return 最后一条显示 Item 索引 + */ + public static int findLastVisibleItemPosition(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 最后一条显示 Item 索引数组 + * @param view {@link View} + * @return 最后一条显示 Item 索引数组 + */ + public static int[] findLastVisibleItemPositions(final View view) { + return findLastVisibleItemPositions(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView 最后一条显示 Item 索引数组 + * @param recyclerView {@link RecyclerView} + * @return 最后一条显示 Item 索引数组 + */ + public static int[] findLastVisibleItemPositions(final RecyclerView recyclerView) { + return findLastVisibleItemPositions(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView 最后一条显示 Item 索引数组 + * @param layoutManager LayoutManager + * @return 最后一条显示 Item 索引数组 + */ + public static int[] findLastVisibleItemPositions(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null); + } + return null; + } + + // =============== + // = Orientation = + // =============== + + /** + * 设置 RecyclerView Orientation + * @param view {@link View} + * @param orientation 方向 + * @return {@link View} + */ + public static View setOrientation( + final View view, + @RecyclerView.Orientation final int orientation + ) { + setOrientation(getRecyclerView(view), orientation); + return view; + } + + /** + * 设置 RecyclerView Orientation + * @param recyclerView {@link RecyclerView} + * @param orientation 方向 + * @param 泛型 + * @return {@link RecyclerView} + */ + public static T setOrientation( + final T recyclerView, + @RecyclerView.Orientation final int orientation + ) { + RecyclerView.LayoutManager layoutManager = getLayoutManager(recyclerView); + if (layoutManager instanceof LinearLayoutManager) { + ((LinearLayoutManager) layoutManager).setOrientation(orientation); + } + if (layoutManager instanceof StaggeredGridLayoutManager) { + ((StaggeredGridLayoutManager) layoutManager).setOrientation(orientation); + } + return recyclerView; + } + + // = + + /** + * 获取 RecyclerView Orientation + * @param view {@link View} + * @return Orientation + */ + public static int getOrientation(final View view) { + return getOrientation(getLayoutManager(view)); + } + + /** + * 获取 RecyclerView Orientation + * @param recyclerView {@link RecyclerView} + * @return Orientation + */ + public static int getOrientation(final RecyclerView recyclerView) { + return getOrientation(getLayoutManager(recyclerView)); + } + + /** + * 获取 RecyclerView Orientation + * @param layoutManager LayoutManager + * @return Orientation + */ + public static int getOrientation(final RecyclerView.LayoutManager layoutManager) { + if (layoutManager instanceof LinearLayoutManager) { + return ((LinearLayoutManager) layoutManager).getOrientation(); + } + if (layoutManager instanceof StaggeredGridLayoutManager) { + return ((StaggeredGridLayoutManager) layoutManager).getOrientation(); + } + return -1; + } + + // = + + /** + * 校验 RecyclerView Orientation 是否为 VERTICAL + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollVertically(final View view) { + return canScrollVertically(getLayoutManager(view)); + } + + /** + * 校验 RecyclerView Orientation 是否为 VERTICAL + * @param recyclerView {@link RecyclerView} + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollVertically(final RecyclerView recyclerView) { + return canScrollVertically(getLayoutManager(recyclerView)); + } + + /** + * 校验 RecyclerView Orientation 是否为 VERTICAL + * @param layoutManager LayoutManager + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollVertically(final RecyclerView.LayoutManager layoutManager) { + return getOrientation(layoutManager) == RecyclerView.VERTICAL; + } + + // = + + /** + * 校验 RecyclerView Orientation 是否为 HORIZONTAL + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollHorizontally(final View view) { + return canScrollHorizontally(getLayoutManager(view)); + } + + /** + * 校验 RecyclerView Orientation 是否为 HORIZONTAL + * @param recyclerView {@link RecyclerView} + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollHorizontally(final RecyclerView recyclerView) { + return canScrollHorizontally(getLayoutManager(recyclerView)); + } + + /** + * 校验 RecyclerView Orientation 是否为 HORIZONTAL + * @param layoutManager LayoutManager + * @return {@code true} yes, {@code false} no + */ + public static boolean canScrollHorizontally(final RecyclerView.LayoutManager layoutManager) { + return getOrientation(layoutManager) == RecyclerView.HORIZONTAL; + } + + // =========== + // = Adapter = + // =========== + + /** + * 设置 RecyclerView Adapter + * @param view {@link View} + * @param adapter Adapter + * @return {@link View} + */ + public static View setAdapter( + final View view, + final RecyclerView.Adapter adapter + ) { + setAdapter(getRecyclerView(view), adapter); + return view; + } + + /** + * 设置 RecyclerView Adapter + * @param recyclerView {@link RecyclerView} + * @param adapter Adapter + * @param 泛型 + * @return {@link RecyclerView} + */ + public static T setAdapter( + final T recyclerView, + final RecyclerView.Adapter adapter + ) { + if (recyclerView != null && adapter != null) { + recyclerView.setAdapter(adapter); + } + return recyclerView; + } + + /** + * 获取 RecyclerView Adapter + * @param view {@link View} + * @param 泛型 + * @return LayoutManager + */ + public static > T getAdapter(final View view) { + return getAdapter(getRecyclerView(view)); + } + + /** + * 获取 RecyclerView Adapter + * @param recyclerView {@link RecyclerView} + * @param 泛型 + * @return LayoutManager + */ + public static > T getAdapter(final RecyclerView recyclerView) { + if (recyclerView != null) { + RecyclerView.Adapter adapter = recyclerView.getAdapter(); + if (adapter != null) { + try { + return (T) adapter; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAdapter"); + } + } + } + return null; + } + + // = + + /** + * 获取 Adapter ItemCount + * @param view {@link View} + * @return ItemCount + */ + public static int getItemCount(final View view) { + return getItemCount(getAdapter(view)); + } + + /** + * 获取 Adapter ItemCount + * @param recyclerView {@link RecyclerView} + * @return ItemCount + */ + public static int getItemCount(final RecyclerView recyclerView) { + return getItemCount(getAdapter(recyclerView)); + } + + /** + * 获取 Adapter ItemCount + * @param adapter RecyclerView.Adapter + * @return ItemCount + */ + public static int getItemCount(final RecyclerView.Adapter adapter) { + if (adapter != null) { + return adapter.getItemCount(); + } + return 0; + } + + // = + + /** + * 获取 Adapter 指定索引 Item Id + * @param view {@link View} + * @param position 索引 + * @return Item Id + */ + public static long getItemId( + final View view, + final int position + ) { + return getItemId(getAdapter(view), position); + } + + /** + * 获取 Adapter 指定索引 Item Id + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return Item Id + */ + public static long getItemId( + final RecyclerView recyclerView, + final int position + ) { + return getItemId(getAdapter(recyclerView), position); + } + + /** + * 获取 Adapter 指定索引 Item Id + * @param adapter RecyclerView.Adapter + * @param position 索引 + * @return Item Id + */ + public static long getItemId( + final RecyclerView.Adapter adapter, + final int position + ) { + if (adapter != null) { + try { + return adapter.getItemId(position); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemId"); + } + } + return RecyclerView.NO_ID; + } + + // = + + /** + * 获取 Adapter 指定索引 Item Type + * @param view {@link View} + * @param position 索引 + * @return Item Type + */ + public static int getItemViewType( + final View view, + final int position + ) { + return getItemViewType(getAdapter(view), position); + } + + /** + * 获取 Adapter 指定索引 Item Type + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return Item Type + */ + public static int getItemViewType( + final RecyclerView recyclerView, + final int position + ) { + return getItemViewType(getAdapter(recyclerView), position); + } + + /** + * 获取 Adapter 指定索引 Item Type + * @param adapter RecyclerView.Adapter + * @param position 索引 + * @return Item Type + */ + public static int getItemViewType( + final RecyclerView.Adapter adapter, + final int position + ) { + if (adapter != null) { + try { + return adapter.getItemViewType(position); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemViewType"); + } + } + return -1; + } + + // = + + /** + * RecyclerView notifyItemRemoved + * @param view {@link View} + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemRemoved( + final View view, + final int position + ) { + return notifyItemRemoved(getAdapter(view), position); + } + + /** + * RecyclerView notifyItemRemoved + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemRemoved( + final RecyclerView recyclerView, + final int position + ) { + return notifyItemRemoved(getAdapter(recyclerView), position); + } + + /** + * RecyclerView notifyItemRemoved + * @param adapter RecyclerView.Adapter + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemRemoved( + final RecyclerView.Adapter adapter, + final int position + ) { + if (adapter != null) { + try { + adapter.notifyItemRemoved(position); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyItemRemoved"); + } + } + return false; + } + + // = + + /** + * RecyclerView notifyItemInserted + * @param view {@link View} + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemInserted( + final View view, + final int position + ) { + return notifyItemInserted(getAdapter(view), position); + } + + /** + * RecyclerView notifyItemInserted + * @param recyclerView {@link RecyclerView} + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemInserted( + final RecyclerView recyclerView, + final int position + ) { + return notifyItemInserted(getAdapter(recyclerView), position); + } + + /** + * RecyclerView notifyItemInserted + * @param adapter RecyclerView.Adapter + * @param position 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemInserted( + final RecyclerView.Adapter adapter, + final int position + ) { + if (adapter != null) { + try { + adapter.notifyItemInserted(position); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyItemInserted"); + } + } + return false; + } + + // = + + /** + * RecyclerView notifyItemMoved + * @param view {@link View} + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemMoved( + final View view, + final int fromPosition, + final int toPosition + ) { + return notifyItemMoved(getAdapter(view), fromPosition, toPosition); + } + + /** + * RecyclerView notifyItemMoved + * @param recyclerView {@link RecyclerView} + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemMoved( + final RecyclerView recyclerView, + final int fromPosition, + final int toPosition + ) { + return notifyItemMoved(getAdapter(recyclerView), fromPosition, toPosition); + } + + /** + * RecyclerView notifyItemMoved + * @param adapter RecyclerView.Adapter + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyItemMoved( + final RecyclerView.Adapter adapter, + final int fromPosition, + final int toPosition + ) { + if (adapter != null) { + try { + adapter.notifyItemMoved(fromPosition, toPosition); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyItemMoved"); + } + } + return false; + } + + // = + + /** + * RecyclerView notifyDataSetChanged + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyDataSetChanged(final View view) { + return notifyDataSetChanged(getAdapter(view)); + } + + /** + * RecyclerView notifyDataSetChanged + * @param recyclerView {@link RecyclerView} + * @return {@code true} success, {@code false} fail + */ + public static boolean notifyDataSetChanged(final RecyclerView recyclerView) { + return notifyDataSetChanged(getAdapter(recyclerView)); + } + + /** + * RecyclerView notifyDataSetChanged + * @param adapter RecyclerView.Adapter + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("NotifyDataSetChanged") + public static boolean notifyDataSetChanged(final RecyclerView.Adapter adapter) { + if (adapter != null) { + try { + adapter.notifyDataSetChanged(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "notifyDataSetChanged"); + } + } + return false; + } + + // ============== + // = SnapHelper = + // ============== + + /** + * 设置 RecyclerView LinearSnapHelper + * @param view {@link View} + * @return {@link LinearSnapHelper} + */ + public static LinearSnapHelper attachLinearSnapHelper(final View view) { + return attachLinearSnapHelper(getRecyclerView(view)); + } + + /** + * 设置 RecyclerView LinearSnapHelper + *
+     *     滑动多页居中显示, 类似 Gallery
+     * 
+ * @param recyclerView {@link RecyclerView} + * @return {@link LinearSnapHelper} + */ + public static LinearSnapHelper attachLinearSnapHelper(final RecyclerView recyclerView) { + if (recyclerView != null) { + LinearSnapHelper helper = new LinearSnapHelper(); + helper.attachToRecyclerView(recyclerView); + return helper; + } + return null; + } + + // = + + /** + * 设置 RecyclerView PagerSnapHelper + * @param view {@link View} + * @return {@link PagerSnapHelper} + */ + public static PagerSnapHelper attachPagerSnapHelper(final View view) { + return attachPagerSnapHelper(getRecyclerView(view)); + } + + /** + * 设置 RecyclerView PagerSnapHelper + *
+     *     每次滑动一页居中显示, 类似 ViewPager
+     * 
+ * @param recyclerView {@link RecyclerView} + * @return {@link PagerSnapHelper} + */ + public static PagerSnapHelper attachPagerSnapHelper(final RecyclerView recyclerView) { + if (recyclerView != null) { + PagerSnapHelper helper = new PagerSnapHelper(); + helper.attachToRecyclerView(recyclerView); + return helper; + } + return null; + } + + // ================== + // = ItemDecoration = + // ================== + + /** + * 获取 RecyclerView ItemDecoration 总数 + * @param view {@link View} + * @return RecyclerView ItemDecoration 总数 + */ + public static int getItemDecorationCount(final View view) { + return getItemDecorationCount(getRecyclerView(view)); + } + + /** + * 获取 RecyclerView ItemDecoration 总数 + * @param recyclerView {@link RecyclerView} + * @return RecyclerView ItemDecoration 总数 + */ + public static int getItemDecorationCount(final RecyclerView recyclerView) { + if (recyclerView != null) { + return recyclerView.getItemDecorationCount(); + } + return 0; + } + + // = + + /** + * 获取 RecyclerView ItemDecoration + * @param view {@link View} + * @param index RecyclerView ItemDecoration 索引 + * @return RecyclerView ItemDecoration + */ + public static RecyclerView.ItemDecoration getItemDecorationAt( + final View view, + final int index + ) { + return getItemDecorationAt(getRecyclerView(view), index); + } + + /** + * 获取 RecyclerView ItemDecoration + * @param recyclerView {@link RecyclerView} + * @param index RecyclerView ItemDecoration 索引 + * @return RecyclerView ItemDecoration + */ + public static RecyclerView.ItemDecoration getItemDecorationAt( + final RecyclerView recyclerView, + final int index + ) { + if (recyclerView != null) { + try { + return recyclerView.getItemDecorationAt(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getItemDecorationAt"); + } + } + return null; + } + + // = + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return {@code true} success, {@code false} fail + */ + public static boolean addItemDecoration( + final View view, + final RecyclerView.ItemDecoration decor + ) { + return addItemDecoration(getRecyclerView(view), decor); + } + + /** + * 添加 RecyclerView ItemDecoration + * @param recyclerView {@link RecyclerView} + * @param decor RecyclerView ItemDecoration + * @return {@code true} success, {@code false} fail + */ + public static boolean addItemDecoration( + final RecyclerView recyclerView, + final RecyclerView.ItemDecoration decor + ) { + if (recyclerView != null && decor != null) { + try { + recyclerView.addItemDecoration(decor); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addItemDecoration"); + } + } + return false; + } + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean addItemDecoration( + final View view, + final RecyclerView.ItemDecoration decor, + final int index + ) { + return addItemDecoration(getRecyclerView(view), decor, index); + } + + /** + * 添加 RecyclerView ItemDecoration + * @param recyclerView {@link RecyclerView} + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean addItemDecoration( + final RecyclerView recyclerView, + final RecyclerView.ItemDecoration decor, + final int index + ) { + if (recyclerView != null && decor != null) { + try { + recyclerView.addItemDecoration(decor, index); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addItemDecoration"); + } + } + return false; + } + + // = + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return {@code true} success, {@code false} fail + */ + public static boolean removeItemDecoration( + final View view, + final RecyclerView.ItemDecoration decor + ) { + return removeItemDecoration(getRecyclerView(view), decor); + } + + /** + * 移除 RecyclerView ItemDecoration + * @param recyclerView {@link RecyclerView} + * @param decor RecyclerView ItemDecoration + * @return {@code true} success, {@code false} fail + */ + public static boolean removeItemDecoration( + final RecyclerView recyclerView, + final RecyclerView.ItemDecoration decor + ) { + if (recyclerView != null && decor != null) { + try { + recyclerView.removeItemDecoration(decor); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeItemDecoration"); + } + } + return false; + } + + // = + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param index RecyclerView ItemDecoration 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean removeItemDecorationAt( + final View view, + final int index + ) { + return removeItemDecorationAt(getRecyclerView(view), index); + } + + /** + * 移除 RecyclerView ItemDecoration + * @param recyclerView {@link RecyclerView} + * @param index RecyclerView ItemDecoration 索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean removeItemDecorationAt( + final RecyclerView recyclerView, + final int index + ) { + if (recyclerView != null) { + try { + recyclerView.removeItemDecorationAt(index); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeItemDecorationAt"); + } + } + return false; + } + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public static boolean removeAllItemDecoration(final View view) { + return removeAllItemDecoration(getRecyclerView(view)); + } + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @param recyclerView {@link RecyclerView} + * @return {@code true} success, {@code false} fail + */ + public static boolean removeAllItemDecoration(final RecyclerView recyclerView) { + if (recyclerView != null) { + for (int i = 0, len = recyclerView.getItemDecorationCount(); i < len; i++) { + try { + recyclerView.removeItemDecorationAt(0); + } catch (Exception ignored) { + } + } + return true; + } + return false; + } + + // ==================== + // = OnScrollListener = + // ==================== + + /** + * 设置 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnScrollListener( + final View view, + final RecyclerView.OnScrollListener listener + ) { + return setOnScrollListener(getRecyclerView(view), listener); + } + + /** + * 设置 RecyclerView ScrollListener + * @param recyclerView {@link RecyclerView} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean setOnScrollListener( + final RecyclerView recyclerView, + final RecyclerView.OnScrollListener listener + ) { + if (recyclerView != null) { + try { + recyclerView.setOnScrollListener(listener); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setOnScrollListener"); + } + } + return false; + } + + // = + + /** + * 添加 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean addOnScrollListener( + final View view, + final RecyclerView.OnScrollListener listener + ) { + return addOnScrollListener(getRecyclerView(view), listener); + } + + /** + * 添加 RecyclerView ScrollListener + * @param recyclerView {@link RecyclerView} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean addOnScrollListener( + final RecyclerView recyclerView, + final RecyclerView.OnScrollListener listener + ) { + if (recyclerView != null && listener != null) { + try { + recyclerView.addOnScrollListener(listener); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addOnScrollListener"); + } + } + return false; + } + + // = + + /** + * 移除 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean removeOnScrollListener( + final View view, + final RecyclerView.OnScrollListener listener + ) { + return removeOnScrollListener(getRecyclerView(view), listener); + } + + /** + * 移除 RecyclerView ScrollListener + * @param recyclerView {@link RecyclerView} + * @param listener ScrollListener + * @return {@code true} success, {@code false} fail + */ + public static boolean removeOnScrollListener( + final RecyclerView recyclerView, + final RecyclerView.OnScrollListener listener + ) { + if (recyclerView != null && listener != null) { + try { + recyclerView.removeOnScrollListener(listener); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeOnScrollListener"); + } + } + return false; + } + + // = + + /** + * 清空 RecyclerView ScrollListener + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public static boolean clearOnScrollListeners(final View view) { + return clearOnScrollListeners(getRecyclerView(view)); + } + + /** + * 清空 RecyclerView ScrollListener + * @param recyclerView {@link RecyclerView} + * @return {@code true} success, {@code false} fail + */ + public static boolean clearOnScrollListeners(final RecyclerView recyclerView) { + if (recyclerView != null) { + try { + recyclerView.clearOnScrollListeners(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "clearOnScrollListeners"); + } + } + return false; + } + + // = + + /** + * 获取 RecyclerView 滑动状态 + * @param view {@link View} + * @return RecyclerView 滑动状态 + */ + public static int getScrollState(final View view) { + return getScrollState(getRecyclerView(view)); + } + + /** + * 获取 RecyclerView 滑动状态 + * @param recyclerView {@link RecyclerView} + * @return RecyclerView 滑动状态 + */ + public static int getScrollState(final RecyclerView recyclerView) { + if (recyclerView != null) { + return recyclerView.getScrollState(); + } + return -1; + } + + // = + + /** + * 获取 RecyclerView 嵌套滚动开关 + * @param view {@link View} + * @return RecyclerView 嵌套滚动开关 + */ + public static boolean isNestedScrollingEnabled(final View view) { + return isNestedScrollingEnabled(getRecyclerView(view)); + } + + /** + * 获取 RecyclerView 嵌套滚动开关 + * @param recyclerView {@link RecyclerView} + * @return RecyclerView 嵌套滚动开关 + */ + public static boolean isNestedScrollingEnabled(final RecyclerView recyclerView) { + if (recyclerView != null) { + return recyclerView.isNestedScrollingEnabled(); + } + return false; + } + + // = + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param view {@link View} + * @param enabled 嵌套滚动开关 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNestedScrollingEnabled( + final View view, + final boolean enabled + ) { + return setNestedScrollingEnabled(getRecyclerView(view), enabled); + } + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param recyclerView {@link RecyclerView} + * @param enabled 嵌套滚动开关 + * @return {@code true} success, {@code false} fail + */ + public static boolean setNestedScrollingEnabled( + final RecyclerView recyclerView, + final boolean enabled + ) { + if (recyclerView != null) { + recyclerView.setNestedScrollingEnabled(enabled); + return true; + } + return false; + } + + // ============= + // = 快捷部分实现 = + // ============= + + /** + * detail: 修复子 View 导致滑动 Bug LinearLayoutManager + * @author Ttt + */ + public static class FixChildScrollBugLinearLayoutManager + extends LinearLayoutManager { + + public FixChildScrollBugLinearLayoutManager(Context context) { + super(context); + } + + public FixChildScrollBugLinearLayoutManager( + Context context, + int orientation, + boolean reverseLayout + ) { + super(context, orientation, reverseLayout); + } + + public FixChildScrollBugLinearLayoutManager( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean requestChildRectangleOnScreen( + @NonNull RecyclerView parent, + @NonNull View child, + @NonNull Rect rect, + boolean immediate + ) { + return false; + } + + @Override + public boolean requestChildRectangleOnScreen( + @NonNull RecyclerView parent, + @NonNull View child, + @NonNull Rect rect, + boolean immediate, + boolean focusedChildVisible + ) { + return false; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ResourcePluginUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ResourcePluginUtils.java new file mode 100644 index 0000000000..2b8564bacc --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ResourcePluginUtils.java @@ -0,0 +1,1061 @@ +package dev.utils.app; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.ViewGroup; +import android.view.animation.Animation; + +import androidx.annotation.AnimRes; +import androidx.annotation.AnimatorRes; +import androidx.annotation.AnyRes; +import androidx.annotation.ArrayRes; +import androidx.annotation.BoolRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.RawRes; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + +import java.io.File; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.List; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.assist.ResourceAssist; +import dev.utils.app.info.ApkInfoItem; +import dev.utils.app.info.AppInfoBean; +import dev.utils.app.info.AppInfoUtils; +import dev.utils.common.FileUtils; + +/** + * detail: APK Resource 工具类 + * @author JiZhi-Error
+ * @author Ttt + *
+ *     从 APK 中读取 Resources ( 可实现换肤、组件化工具类 )
+ * 
+ */ +public final class ResourcePluginUtils { + + private ResourcePluginUtils() { + } + + // 日志 TAG + private static final String TAG = ResourcePluginUtils.class.getSimpleName(); + + // Resources 辅助类 + private ResourceAssist mResourceAssist = ResourceAssist.EMPTY_IMPL; + // APK 文件路径 + private String mAPKPath; + // APK 信息 Item + private ApkInfoItem mApkInfoItem; + + // ======================= + // = invokeByPackageName = + // ======================= + + /** + * 通过 packageName 获取 APK Resources + * @param packageName 应用包名 + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByPackageName(final String packageName) { + return invokeByPackageName(packageName, DevUtils.getContext()); + } + + /** + * 通过 packageName 获取 APK Resources + * @param packageName 应用包名 + * @param context {@link Context} + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByPackageName( + final String packageName, + final Context context + ) { + DisplayMetrics metrics = null; + Configuration config = null; + Resources resources = ResourceAssist.staticResources(context); + if (resources != null) { + metrics = resources.getDisplayMetrics(); + config = resources.getConfiguration(); + } + return invokeByPackageName(packageName, metrics, config); + } + + /** + * 通过 packageName 获取 APK Resources + * @param packageName 应用包名 + * @param metrics {@link DisplayMetrics} + * @param config {@link Configuration} + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByPackageName( + final String packageName, + final DisplayMetrics metrics, + final Configuration config + ) { + AppInfoBean appInfoBean = AppInfoUtils.getAppInfoBean(packageName); + String sourceDir = (appInfoBean != null) ? appInfoBean.getSourceDir() : null; + return invokeByAPKPath(sourceDir, metrics, config); + } + + // =================== + // = invokeByAPKPath = + // =================== + + /** + * 通过 APK 文件获取 APK Resources + * @param apkPath APK 文件路径 + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByAPKPath(final String apkPath) { + return invokeByAPKPath(apkPath, DevUtils.getContext()); + } + + /** + * 通过 APK 文件获取 APK Resources + * @param apkPath APK 文件路径 + * @param context {@link Context} + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByAPKPath( + final String apkPath, + final Context context + ) { + DisplayMetrics metrics = null; + Configuration config = null; + Resources resources = ResourceAssist.staticResources(context); + if (resources != null) { + metrics = resources.getDisplayMetrics(); + config = resources.getConfiguration(); + } + return invokeByAPKPath(apkPath, metrics, config); + } + + /** + * 通过 APK 文件获取 APK Resources + * @param apkPath APK 文件路径 + * @param metrics {@link DisplayMetrics} + * @param config {@link Configuration} + * @return {@link ResourcePluginUtils} + */ + public static ResourcePluginUtils invokeByAPKPath( + final String apkPath, + final DisplayMetrics metrics, + final Configuration config + ) { + ResourcePluginUtils utils = new ResourcePluginUtils(); + utils.mAPKPath = apkPath; + // 文件存在才进行处理 + if (FileUtils.isFileExists(apkPath)) { + try { + AssetManager asset = AssetManager.class.newInstance(); + Method addAssetPath = asset.getClass().getMethod( + "addAssetPath", String.class + ); + addAssetPath.invoke(asset, apkPath); + Resources resources = new Resources( + asset, metrics, config + ); + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager != null) { + PackageInfo packageInfo = packageManager.getPackageArchiveInfo( + apkPath, PackageManager.GET_ACTIVITIES + ); + utils.mResourceAssist = ResourceAssist.get( + resources, packageInfo.packageName + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "invokeByAPKPath - apkPath: %s", apkPath); + } + } + return utils; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Resources 辅助类 + * @return {@link ResourceAssist} + */ + public ResourceAssist getResourceAssist() { + return mResourceAssist; + } + + /** + * 获取 Resources + * @return {@link Resources} + */ + public Resources getResources() { + return mResourceAssist.getResources(); + } + + /** + * 获取 APK 包名 + * @return APK 包名 + */ + public String getPackageName() { + return mResourceAssist.getPackageName(); + } + + /** + * 获取 APK 文件路径 + * @return APK 文件路径 + */ + public String getAPKPath() { + return mAPKPath; + } + + /** + * 获取 APK 信息 Item + * @return {@link ApkInfoItem} + */ + public ApkInfoItem getApkInfoItem() { + if (mApkInfoItem == null) { + mApkInfoItem = AppInfoUtils.getApkInfoItem(mAPKPath); + } + return mApkInfoItem; + } + + // = + + /** + * 获取 DisplayMetrics + * @return {@link DisplayMetrics} + */ + public DisplayMetrics getDisplayMetrics() { + return mResourceAssist.getDisplayMetrics(); + } + + /** + * 获取 Configuration + * @return {@link Configuration} + */ + public Configuration getConfiguration() { + return mResourceAssist.getConfiguration(); + } + + /** + * 获取 AssetManager + * @return {@link AssetManager} + */ + public AssetManager getAssets() { + return mResourceAssist.getAssets(); + } + + /** + * 获取资源 id + * @param resName 资源名 + * @param defType 资源类型 + * @return 资源 id + */ + public int getIdentifier( + final String resName, + final String defType + ) { + return mResourceAssist.getIdentifier(resName, defType); + } + + /** + * 获取给定资源标识符的全名 + * @param id resource identifier + * @return Integer + */ + public String getResourceName(@AnyRes final int id) { + return mResourceAssist.getResourceName(id); + } + + // ========== + // = 资源获取 = + // ========== + + /** + * 获取 String id + * @param resName resource name + * @return String id + */ + public int getStringId(final String resName) { + return mResourceAssist.getStringId(resName); + } + + /** + * 获取 String + * @param resName resource name + * @return String + */ + public String getString(final String resName) { + return mResourceAssist.getString(resName); + } + + /** + * 获取 String + * @param resName resource name + * @param formatArgs 格式化参数 + * @return String + */ + public String getString( + final String resName, + final Object... formatArgs + ) { + return mResourceAssist.getString(resName, formatArgs); + } + + /** + * 获取 String + * @param id R.string.id + * @return String + */ + public String getString(@StringRes final int id) { + return mResourceAssist.getString(id); + } + + /** + * 获取 String + * @param id R.string.id + * @param formatArgs 格式化参数 + * @return String + */ + public String getString( + @StringRes final int id, + final Object... formatArgs + ) { + return mResourceAssist.getString(id, formatArgs); + } + + // = + + /** + * 获取 Dimension id + * @param resName resource name + * @return Dimension id + */ + public int getDimenId(final String resName) { + return mResourceAssist.getDimenId(resName); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public float getDimension(final String resName) { + return mResourceAssist.getDimension(resName); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public int getDimensionInt(final String resName) { + return mResourceAssist.getDimensionInt(resName); + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public float getDimension(@DimenRes final int id) { + return mResourceAssist.getDimension(id); + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public int getDimensionInt(@DimenRes final int id) { + return mResourceAssist.getDimensionInt(id); + } + + // = + + /** + * 获取 Color id + * @param resName resource name + * @return Color id + */ + public int getColorId(final String resName) { + return mResourceAssist.getColorId(resName); + } + + /** + * 获取 Color + * @param resName resource name + * @return Color + */ + public int getColor(final String resName) { + return mResourceAssist.getColor(resName); + } + + /** + * 获取 Color + *
+     *     {@link ContextCompat#getColor(Context, int)}
+     * 
+ * @param colorId R.color.id + * @return Color + */ + public int getColor(@ColorRes final int colorId) { + return mResourceAssist.getColor(colorId); + } + + // = + + /** + * 获取 Drawable id + * @param resName resource name + * @return Drawable id + */ + public int getDrawableId(final String resName) { + return mResourceAssist.getDrawableId(resName); + } + + /** + * 获取 Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public Drawable getDrawable(final String resName) { + return mResourceAssist.getDrawable(resName); + } + + /** + * 获取 .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawable(final String resName) { + return mResourceAssist.getNinePatchDrawable(resName); + } + + /** + * 获取 Drawable + *
+     *     {@link ContextCompat#getDrawable(Context, int)}
+     * 
+ * @param drawableId R.drawable.id + * @return {@link Drawable} + */ + public Drawable getDrawable(@DrawableRes final int drawableId) { + return mResourceAssist.getDrawable(drawableId); + } + + /** + * 获取 .9 Drawable + * @param drawableId R.drawable.id + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawable(@DrawableRes final int drawableId) { + return mResourceAssist.getNinePatchDrawable(drawableId); + } + + // = + + /** + * 获取 Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public Bitmap getBitmap(final String resName) { + return mResourceAssist.getBitmap(resName); + } + + /** + * 获取 Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmap( + final String resName, + final BitmapFactory.Options options + ) { + return mResourceAssist.getBitmap(resName, options); + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @return {@link Bitmap} + */ + public Bitmap getBitmap(final int resId) { + return mResourceAssist.getBitmap(resId); + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmap( + final int resId, + final BitmapFactory.Options options + ) { + return mResourceAssist.getBitmap(resId, options); + } + + // = + + /** + * 获取 Mipmap id + * @param resName resource name + * @return Mipmap id + */ + public int getMipmapId(final String resName) { + return mResourceAssist.getMipmapId(resName); + } + + /** + * 获取 Mipmap Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public Drawable getDrawableMipmap(final String resName) { + return mResourceAssist.getDrawableMipmap(resName); + } + + /** + * 获取 Mipmap .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawableMipmap(final String resName) { + return mResourceAssist.getNinePatchDrawableMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public Bitmap getBitmapMipmap(final String resName) { + return mResourceAssist.getBitmapMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmapMipmap( + final String resName, + final BitmapFactory.Options options + ) { + return mResourceAssist.getBitmapMipmap(resName, options); + } + + // = + + /** + * 获取 Anim id + * @param resName resource name + * @return Anim id + */ + public int getAnimId(final String resName) { + return mResourceAssist.getAnimId(resName); + } + + /** + * 获取 Animation Xml + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public XmlResourceParser getAnimationXml(final String resName) { + return mResourceAssist.getAnimationXml(resName); + } + + /** + * 获取 Animation Xml + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public XmlResourceParser getAnimationXml(@AnimatorRes @AnimRes final int id) { + return mResourceAssist.getAnimationXml(id); + } + + /** + * 获取 Animation + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public Animation getAnimation(final String resName) { + return mResourceAssist.getAnimation(resName); + } + + /** + * 获取 Animation + * @param resName resource name + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public Animation getAnimation( + final String resName, + final Context context + ) { + return mResourceAssist.getAnimation(resName, context); + } + + /** + * 获取 Animation + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public Animation getAnimation(@AnimatorRes @AnimRes final int id) { + return mResourceAssist.getAnimation(id); + } + + /** + * 获取 Animation + * @param id resource identifier + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public Animation getAnimation( + @AnimatorRes @AnimRes final int id, + final Context context + ) { + return mResourceAssist.getAnimation(id, context); + } + + // = + + /** + * 获取 Boolean id + * @param resName resource name + * @return Boolean id + */ + public int getBooleanId(final String resName) { + return mResourceAssist.getBooleanId(resName); + } + + /** + * 获取 Boolean + * @param resName resource name + * @return Boolean + */ + public boolean getBoolean(final String resName) { + return mResourceAssist.getBoolean(resName); + } + + /** + * 获取 Boolean + * @param id resource identifier + * @return Boolean + */ + public boolean getBoolean(@BoolRes final int id) { + return mResourceAssist.getBoolean(id); + } + + // = + + /** + * 获取 Integer id + * @param resName resource name + * @return Integer id + */ + public int getIntegerId(final String resName) { + return mResourceAssist.getIntegerId(resName); + } + + /** + * 获取 Integer + * @param resName resource name + * @return Integer + */ + public int getInteger(final String resName) { + return mResourceAssist.getInteger(resName); + } + + /** + * 获取 Integer + * @param id resource identifier + * @return Integer + */ + public int getInteger(@IntegerRes final int id) { + return mResourceAssist.getInteger(id); + } + + // = + + /** + * 获取 Array id + * @param resName resource name + * @return Array id + */ + public int getArrayId(final String resName) { + return mResourceAssist.getArrayId(resName); + } + + /** + * 获取 int[] + * @param resName resource name + * @return int[] + */ + public int[] getIntArray(final String resName) { + return mResourceAssist.getIntArray(resName); + } + + /** + * 获取 String[] + * @param resName resource name + * @return String[] + */ + public String[] getStringArray(final String resName) { + return mResourceAssist.getStringArray(resName); + } + + /** + * 获取 CharSequence[] + * @param resName resource name + * @return CharSequence[] + */ + public CharSequence[] getTextArray(final String resName) { + return mResourceAssist.getTextArray(resName); + } + + /** + * 获取 int[] + * @param id resource identifier + * @return int[] + */ + public int[] getIntArray(@ArrayRes final int id) { + return mResourceAssist.getIntArray(id); + } + + /** + * 获取 String[] + * @param id resource identifier + * @return String[] + */ + public String[] getStringArray(@ArrayRes final int id) { + return mResourceAssist.getStringArray(id); + } + + /** + * 获取 CharSequence[] + * @param id resource identifier + * @return CharSequence[] + */ + public CharSequence[] getTextArray(@ArrayRes final int id) { + return mResourceAssist.getTextArray(id); + } + + // = + + /** + * 获取 id ( view ) + * @param resName resource name + * @return id + */ + public int getId(final String resName) { + return mResourceAssist.getId(resName); + } + + /** + * 获取 Layout id + *
+     *     {@link android.view.LayoutInflater#inflate(int, ViewGroup)}
+     * 
+ * @param resName resource name + * @return Layout id + */ + public int getLayoutId(final String resName) { + return mResourceAssist.getLayoutId(resName); + } + + /** + * 获取 Menu id + *
+     *     {@link android.view.MenuInflater#inflate(int, Menu)}
+     * 
+ * @param resName resource name + * @return Menu id + */ + public int getMenuId(final String resName) { + return mResourceAssist.getMenuId(resName); + } + + /** + * 获取 Raw id + * @param resName resource name + * @return Raw id + */ + public int getRawId(final String resName) { + return mResourceAssist.getRawId(resName); + } + + /** + * 获取 Attr id + * @param resName resource name + * @return Attr id + */ + public int getAttrId(final String resName) { + return mResourceAssist.getAttrId(resName); + } + + /** + * 获取 Style id + * @param resName resource name + * @return Style id + */ + public int getStyleId(final String resName) { + return mResourceAssist.getStyleId(resName); + } + + /** + * 获取 Styleable id + * @param resName resource name + * @return Styleable id + */ + public int getStyleableId(final String resName) { + return mResourceAssist.getStyleableId(resName); + } + + /** + * 获取 Animator id + * @param resName resource name + * @return Animator id + */ + public int getAnimatorId(final String resName) { + return mResourceAssist.getAnimatorId(resName); + } + + /** + * 获取 Xml id + * @param resName resource name + * @return Xml id + */ + public int getXmlId(final String resName) { + return mResourceAssist.getXmlId(resName); + } + + /** + * 获取 Interpolator id + * @param resName resource name + * @return Interpolator id + */ + public int getInterpolatorId(final String resName) { + return mResourceAssist.getInterpolatorId(resName); + } + + /** + * 获取 Plurals id + * @param resName resource name + * @return Plurals id + */ + public int getPluralsId(final String resName) { + return mResourceAssist.getPluralsId(resName); + } + + // = + + /** + * 获取 ColorStateList + * @param resName resource Name + * @return {@link ColorStateList} + */ + public ColorStateList getColorStateList(final String resName) { + return mResourceAssist.getColorStateList(resName); + } + + /** + * 获取 ColorStateList + *
+     *     {@link ContextCompat#getColorStateList(Context, int)}
+     * 
+ * @param id resource identifier of a {@link ColorStateList} + * @return {@link ColorStateList} + */ + public ColorStateList getColorStateList(@ColorRes final int id) { + return mResourceAssist.getColorStateList(id); + } + + /** + * 获取十六进制颜色值 Drawable + * @param color 十六进制颜色值 + * @return 十六进制颜色值 Drawable + */ + public ColorDrawable getColorDrawable(final String color) { + return mResourceAssist.getColorDrawable(color); + } + + /** + * 获取指定颜色 Drawable + * @param color 颜色值 + * @return 指定颜色 Drawable + */ + public ColorDrawable getColorDrawable(@ColorInt final int color) { + return mResourceAssist.getColorDrawable(color); + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager 指定资源 InputStream + * @param fileName 文件名 + * @return {@link InputStream} + */ + public InputStream open(final String fileName) { + return mResourceAssist.open(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openFd(final String fileName) { + return mResourceAssist.openFd(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openNonAssetFd(final String fileName) { + return mResourceAssist.openNonAssetFd(fileName); + } + + /** + * 获取对应资源 InputStream + * @param id resource identifier + * @return {@link InputStream} + */ + public InputStream openRawResource(@RawRes final int id) { + return mResourceAssist.openRawResource(id); + } + + /** + * 获取对应资源 AssetFileDescriptor + * @param id resource identifier + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openRawResourceFd(@RawRes final int id) { + return mResourceAssist.openRawResourceFd(id); + } + + // ============= + // = 读取资源文件 = + // ============= + + /** + * 获取 Assets 资源文件数据 + *
+     *     直接传入文件名、文件夹 / 文件名 等
+     *     根目录 a.txt
+     *     子目录 /www/a.html
+     * 
+ * @param fileName 文件名 + * @return 文件 byte[] 数据 + */ + public byte[] readBytesFromAssets(final String fileName) { + return mResourceAssist.readBytesFromAssets(fileName); + } + + /** + * 获取 Assets 资源文件数据 + * @param fileName 文件名 + * @return 文件字符串内容 + */ + public String readStringFromAssets(final String fileName) { + return mResourceAssist.readStringFromAssets(fileName); + } + + // = + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件 byte[] 数据 + */ + public byte[] readBytesFromRaw(@RawRes final int resId) { + return mResourceAssist.readBytesFromRaw(resId); + } + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件字符串内容 + */ + public String readStringFromRaw(@RawRes final int resId) { + return mResourceAssist.readStringFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param fileName 文件名 + * @return {@link List } + */ + public List geFileToListFromAssets(final String fileName) { + return mResourceAssist.geFileToListFromAssets(fileName); + } + + /** + * 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param resId 资源 id + * @return {@link List} + */ + public List geFileToListFromRaw(@RawRes final int resId) { + return mResourceAssist.geFileToListFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据并保存到本地 + * @param fileName 文件名 + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public boolean saveAssetsFormFile( + final String fileName, + final File file + ) { + return mResourceAssist.saveAssetsFormFile(fileName, file); + } + + /** + * 获取 Raw 资源文件数据并保存到本地 + * @param resId 资源 id + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public boolean saveRawFormFile( + @RawRes final int resId, + final File file + ) { + return mResourceAssist.saveRawFormFile(resId, file); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ResourceUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ResourceUtils.java new file mode 100644 index 0000000000..da400442cf --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ResourceUtils.java @@ -0,0 +1,2527 @@ +package dev.utils.app; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.ViewGroup; +import android.view.animation.Animation; + +import androidx.annotation.AnimRes; +import androidx.annotation.AnimatorRes; +import androidx.annotation.AnyRes; +import androidx.annotation.ArrayRes; +import androidx.annotation.BoolRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.RawRes; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import dev.utils.app.assist.ResourceAssist; +import dev.utils.common.FileIOUtils; + +/** + * detail: 资源文件工具类 + * @author Ttt + */ +public final class ResourceUtils { + + private ResourceUtils() { + } + + // ============= + // = Resources = + // ============= + + /** + * 获取 Resources + * @return {@link Resources} + */ + public static Resources getResources() { + return ResourceAssist.get().getResources(); + } + + /** + * 获取 Resources + * @param context {@link Context} + * @return {@link Resources} + */ + public static Resources getResources(final Context context) { + return ResourceAssist.staticResources(context); + } + + /** + * 获取 Resources + * @param assist {@link ResourceAssist} + * @return {@link Resources} + */ + public static Resources getResources(final ResourceAssist assist) { + if (assist == null) return null; + return assist.getResources(); + } + + // =================== + // = Resources.Theme = + // =================== + + /** + * 获取 Resources.Theme + * @return {@link Resources.Theme} + */ + public static Resources.Theme getTheme() { + return ResourceAssist.staticTheme(); + } + + /** + * 获取 Resources.Theme + * @param context {@link Context} + * @return {@link Resources.Theme} + */ + public static Resources.Theme getTheme(final Context context) { + return ResourceAssist.staticTheme(context); + } + + // =================== + // = ContentResolver = + // =================== + + /** + * 获取 ContentResolver + * @return {@link ContentResolver} + */ + public static ContentResolver getContentResolver() { + return ResourceAssist.staticContentResolver(); + } + + /** + * 获取 ContentResolver + * @param context {@link Context} + * @return {@link ContentResolver} + */ + public static ContentResolver getContentResolver(final Context context) { + return ResourceAssist.staticContentResolver(context); + } + + // ================== + // = DisplayMetrics = + // ================== + + /** + * 获取 DisplayMetrics + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics getDisplayMetrics() { + return ResourceAssist.get().getDisplayMetrics(); + } + + /** + * 获取 DisplayMetrics + * @param context {@link Context} + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics getDisplayMetrics(final Context context) { + return ResourceAssist.staticDisplayMetrics(context); + } + + /** + * 获取 DisplayMetrics + * @param resource {@link Resources} + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics getDisplayMetrics(final Resources resource) { + return ResourceAssist.staticDisplayMetrics(resource); + } + + /** + * 获取 DisplayMetrics + * @param assist {@link ResourceAssist} + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics getDisplayMetrics(final ResourceAssist assist) { + if (assist == null) return null; + return assist.getDisplayMetrics(); + } + + // ================= + // = Configuration = + // ================= + + /** + * 获取 Configuration + * @return {@link Configuration} + */ + public static Configuration getConfiguration() { + return ResourceAssist.get().getConfiguration(); + } + + /** + * 获取 Configuration + * @param context {@link Context} + * @return {@link Configuration} + */ + public static Configuration getConfiguration(final Context context) { + return ResourceAssist.staticConfiguration(context); + } + + /** + * 获取 Configuration + * @param resource {@link Resources} + * @return {@link Configuration} + */ + public static Configuration getConfiguration(final Resources resource) { + return ResourceAssist.staticConfiguration(resource); + } + + /** + * 获取 Configuration + * @param assist {@link ResourceAssist} + * @return {@link Configuration} + */ + public static Configuration getConfiguration(final ResourceAssist assist) { + if (assist == null) return null; + return assist.getConfiguration(); + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager + * @return {@link AssetManager} + */ + public static AssetManager getAssets() { + return ResourceAssist.get().getAssets(); + } + + /** + * 获取 AssetManager + * @param context {@link Context} + * @return {@link AssetManager} + */ + public static AssetManager getAssets(final Context context) { + return ResourceAssist.staticAssets(context); + } + + /** + * 获取 AssetManager + * @param resource {@link Resources} + * @return {@link AssetManager} + */ + public static AssetManager getAssets(final Resources resource) { + return ResourceAssist.staticAssets(resource); + } + + /** + * 获取 AssetManager + * @param assist {@link ResourceAssist} + * @return {@link AssetManager} + */ + public static AssetManager getAssets(final ResourceAssist assist) { + if (assist == null) return null; + return assist.getAssets(); + } + + // ========== + // = 具体方法 = + // ========== + + /** + * 获取资源 id + * @param resName 资源名 + * @param defType 资源类型 + * @return 资源 id + */ + public static int getIdentifier( + final String resName, + final String defType + ) { + return ResourceAssist.get().getIdentifier(resName, defType); + } + + /** + * 获取资源 id + * @param assist {@link ResourceAssist} + * @param resName 资源名 + * @param defType 资源类型 + * @return 资源 id + */ + public static int getIdentifier( + final ResourceAssist assist, + final String resName, + final String defType + ) { + if (assist == null) return 0; + return assist.getIdentifier(resName, defType); + } + + // = + + /** + * 获取给定资源标识符的全名 + * @param id resource identifier + * @return Integer + */ + public static String getResourceName(@AnyRes final int id) { + return ResourceAssist.get().getResourceName(id); + } + + /** + * 获取给定资源标识符的全名 + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return Integer + */ + public static String getResourceName( + final ResourceAssist assist, + @AnyRes final int id + ) { + if (assist == null) return null; + return assist.getResourceName(id); + } + + // =========================== + // = ResourceAssist Instance = + // =========================== + + // ========== + // = 资源获取 = + // ========== + + /** + * 获取 String id + * @param resName resource name + * @return String id + */ + public static int getStringId(final String resName) { + return ResourceAssist.get().getStringId(resName); + } + + /** + * 获取 String + * @param resName resource name + * @return String + */ + public static String getString(final String resName) { + return ResourceAssist.get().getString(resName); + } + + /** + * 获取 String + * @param resName resource name + * @param formatArgs 格式化参数 + * @return String + */ + public static String getString( + final String resName, + final Object... formatArgs + ) { + return ResourceAssist.get().getString(resName, formatArgs); + } + + /** + * 获取 String + * @param id R.string.id + * @return String + */ + public static String getString(@StringRes final int id) { + return ResourceAssist.get().getString(id); + } + + /** + * 获取 String + * @param id R.string.id + * @param formatArgs 格式化参数 + * @return String + */ + public static String getString( + @StringRes final int id, + final Object... formatArgs + ) { + return ResourceAssist.get().getString(id, formatArgs); + } + + // = + + /** + * 获取 Dimension id + * @param resName resource name + * @return Dimension id + */ + public static int getDimenId(final String resName) { + return ResourceAssist.get().getDimenId(resName); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public static float getDimension(final String resName) { + return ResourceAssist.get().getDimension(resName); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public static int getDimensionInt(final String resName) { + return ResourceAssist.get().getDimensionInt(resName); + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public static float getDimension(@DimenRes final int id) { + return ResourceAssist.get().getDimension(id); + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public static int getDimensionInt(@DimenRes final int id) { + return ResourceAssist.get().getDimensionInt(id); + } + + // = + + /** + * 获取 Color id + * @param resName resource name + * @return Color id + */ + public static int getColorId(final String resName) { + return ResourceAssist.get().getColorId(resName); + } + + /** + * 获取 Color + * @param resName resource name + * @return Color + */ + public static int getColor(final String resName) { + return ResourceAssist.get().getColor(resName); + } + + /** + * 获取 Color + *
+     *     {@link ContextCompat#getColor(Context, int)}
+     * 
+ * @param colorId R.color.id + * @return Color + */ + public static int getColor(@ColorRes final int colorId) { + return ResourceAssist.get().getColor(colorId); + } + + // = + + /** + * 获取 Drawable id + * @param resName resource name + * @return Drawable id + */ + public static int getDrawableId(final String resName) { + return ResourceAssist.get().getDrawableId(resName); + } + + /** + * 获取 Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public static Drawable getDrawable(final String resName) { + return ResourceAssist.get().getDrawable(resName); + } + + /** + * 获取 .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawable(final String resName) { + return ResourceAssist.get().getNinePatchDrawable(resName); + } + + /** + * 获取 Drawable + *
+     *     {@link ContextCompat#getDrawable(Context, int)}
+     * 
+ * @param drawableId R.drawable.id + * @return {@link Drawable} + */ + public static Drawable getDrawable(@DrawableRes final int drawableId) { + return ResourceAssist.get().getDrawable(drawableId); + } + + /** + * 获取 .9 Drawable + * @param drawableId R.drawable.id + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawable(@DrawableRes final int drawableId) { + return ResourceAssist.get().getNinePatchDrawable(drawableId); + } + + // = + + /** + * 获取 Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public static Bitmap getBitmap(final String resName) { + return ResourceAssist.get().getBitmap(resName); + } + + /** + * 获取 Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final String resName, + final BitmapFactory.Options options + ) { + return ResourceAssist.get().getBitmap(resName, options); + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @return {@link Bitmap} + */ + public static Bitmap getBitmap(final int resId) { + return ResourceAssist.get().getBitmap(resId); + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final int resId, + final BitmapFactory.Options options + ) { + return ResourceAssist.get().getBitmap(resId, options); + } + + // = + + /** + * 获取 Mipmap id + * @param resName resource name + * @return Mipmap id + */ + public static int getMipmapId(final String resName) { + return ResourceAssist.get().getMipmapId(resName); + } + + /** + * 获取 Mipmap Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public static Drawable getDrawableMipmap(final String resName) { + return ResourceAssist.get().getDrawableMipmap(resName); + } + + /** + * 获取 Mipmap .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawableMipmap(final String resName) { + return ResourceAssist.get().getNinePatchDrawableMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public static Bitmap getBitmapMipmap(final String resName) { + return ResourceAssist.get().getBitmapMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmapMipmap( + final String resName, + final BitmapFactory.Options options + ) { + return ResourceAssist.get().getBitmapMipmap(resName, options); + } + + // = + + /** + * 获取 Anim id + * @param resName resource name + * @return Anim id + */ + public static int getAnimId(final String resName) { + return ResourceAssist.get().getAnimId(resName); + } + + /** + * 获取 Animation Xml + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public static XmlResourceParser getAnimationXml(final String resName) { + return ResourceAssist.get().getAnimationXml(resName); + } + + /** + * 获取 Animation Xml + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public static XmlResourceParser getAnimationXml(@AnimatorRes @AnimRes final int id) { + return ResourceAssist.get().getAnimationXml(id); + } + + /** + * 获取 Animation + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation(final String resName) { + return ResourceAssist.get().getAnimation(resName); + } + + /** + * 获取 Animation + * @param resName resource name + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + final String resName, + final Context context + ) { + return ResourceAssist.get().getAnimation(resName, context); + } + + /** + * 获取 Animation + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation(@AnimatorRes @AnimRes final int id) { + return ResourceAssist.get().getAnimation(id); + } + + /** + * 获取 Animation + * @param id resource identifier + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + @AnimatorRes @AnimRes final int id, + final Context context + ) { + return ResourceAssist.get().getAnimation(id, context); + } + + // = + + /** + * 获取 Boolean id + * @param resName resource name + * @return Boolean id + */ + public static int getBooleanId(final String resName) { + return ResourceAssist.get().getBooleanId(resName); + } + + /** + * 获取 Boolean + * @param resName resource name + * @return Boolean + */ + public static boolean getBoolean(final String resName) { + return ResourceAssist.get().getBoolean(resName); + } + + /** + * 获取 Boolean + * @param id resource identifier + * @return Boolean + */ + public static boolean getBoolean(@BoolRes final int id) { + return ResourceAssist.get().getBoolean(id); + } + + // = + + /** + * 获取 Integer id + * @param resName resource name + * @return Integer id + */ + public static int getIntegerId(final String resName) { + return ResourceAssist.get().getIntegerId(resName); + } + + /** + * 获取 Integer + * @param resName resource name + * @return Integer + */ + public static int getInteger(final String resName) { + return ResourceAssist.get().getInteger(resName); + } + + /** + * 获取 Integer + * @param id resource identifier + * @return Integer + */ + public static int getInteger(@IntegerRes final int id) { + return ResourceAssist.get().getInteger(id); + } + + // = + + /** + * 获取 Array id + * @param resName resource name + * @return Array id + */ + public static int getArrayId(final String resName) { + return ResourceAssist.get().getArrayId(resName); + } + + /** + * 获取 int[] + * @param resName resource name + * @return int[] + */ + public static int[] getIntArray(final String resName) { + return ResourceAssist.get().getIntArray(resName); + } + + /** + * 获取 String[] + * @param resName resource name + * @return String[] + */ + public static String[] getStringArray(final String resName) { + return ResourceAssist.get().getStringArray(resName); + } + + /** + * 获取 CharSequence[] + * @param resName resource name + * @return CharSequence[] + */ + public static CharSequence[] getTextArray(final String resName) { + return ResourceAssist.get().getTextArray(resName); + } + + /** + * 获取 int[] + * @param id resource identifier + * @return int[] + */ + public static int[] getIntArray(@ArrayRes final int id) { + return ResourceAssist.get().getIntArray(id); + } + + /** + * 获取 String[] + * @param id resource identifier + * @return String[] + */ + public static String[] getStringArray(@ArrayRes final int id) { + return ResourceAssist.get().getStringArray(id); + } + + /** + * 获取 CharSequence[] + * @param id resource identifier + * @return CharSequence[] + */ + public static CharSequence[] getTextArray(@ArrayRes final int id) { + return ResourceAssist.get().getTextArray(id); + } + + // = + + /** + * 获取 id ( view ) + * @param resName resource name + * @return id + */ + public static int getId(final String resName) { + return ResourceAssist.get().getId(resName); + } + + /** + * 获取 Layout id + *
+     *     {@link android.view.LayoutInflater#inflate(int, ViewGroup)}
+     * 
+ * @param resName resource name + * @return Layout id + */ + public static int getLayoutId(final String resName) { + return ResourceAssist.get().getLayoutId(resName); + } + + /** + * 获取 Menu id + *
+     *     {@link android.view.MenuInflater#inflate(int, Menu)}
+     * 
+ * @param resName resource name + * @return Menu id + */ + public static int getMenuId(final String resName) { + return ResourceAssist.get().getMenuId(resName); + } + + /** + * 获取 Raw id + * @param resName resource name + * @return Raw id + */ + public static int getRawId(final String resName) { + return ResourceAssist.get().getRawId(resName); + } + + /** + * 获取 Attr id + * @param resName resource name + * @return Attr id + */ + public static int getAttrId(final String resName) { + return ResourceAssist.get().getAttrId(resName); + } + + /** + * 获取 Style id + * @param resName resource name + * @return Style id + */ + public static int getStyleId(final String resName) { + return ResourceAssist.get().getStyleId(resName); + } + + /** + * 获取 Styleable id + * @param resName resource name + * @return Styleable id + */ + public static int getStyleableId(final String resName) { + return ResourceAssist.get().getStyleableId(resName); + } + + /** + * 获取 Animator id + * @param resName resource name + * @return Animator id + */ + public static int getAnimatorId(final String resName) { + return ResourceAssist.get().getAnimatorId(resName); + } + + /** + * 获取 Xml id + * @param resName resource name + * @return Xml id + */ + public static int getXmlId(final String resName) { + return ResourceAssist.get().getXmlId(resName); + } + + /** + * 获取 Interpolator id + * @param resName resource name + * @return Interpolator id + */ + public static int getInterpolatorId(final String resName) { + return ResourceAssist.get().getInterpolatorId(resName); + } + + /** + * 获取 Plurals id + * @param resName resource name + * @return Plurals id + */ + public static int getPluralsId(final String resName) { + return ResourceAssist.get().getPluralsId(resName); + } + + // = + + /** + * 获取 ColorStateList + * @param resName resource Name + * @return {@link ColorStateList} + */ + public static ColorStateList getColorStateList(final String resName) { + return ResourceAssist.get().getColorStateList(resName); + } + + /** + * 获取 ColorStateList + *
+     *     {@link ContextCompat#getColorStateList(Context, int)}
+     * 
+ * @param id resource identifier of a {@link ColorStateList} + * @return {@link ColorStateList} + */ + public static ColorStateList getColorStateList(@ColorRes final int id) { + return ResourceAssist.get().getColorStateList(id); + } + + /** + * 获取十六进制颜色值 Drawable + * @param color 十六进制颜色值 + * @return 十六进制颜色值 Drawable + */ + public static ColorDrawable getColorDrawable(final String color) { + return ResourceAssist.get().getColorDrawable(color); + } + + /** + * 获取指定颜色 Drawable + * @param color 颜色值 + * @return 指定颜色 Drawable + */ + public static ColorDrawable getColorDrawable(@ColorInt final int color) { + return ResourceAssist.get().getColorDrawable(color); + } + + // =================== + // = ContentResolver = + // =================== + + /** + * 获取 Uri InputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri InputStream + */ + public static InputStream openInputStream(final Uri uri) { + return ResourceAssist.get().openInputStream(uri); + } + + /** + * 获取 Uri InputStream + *
+     *     主要用于获取到分享的 FileProvider Uri 存储起来 {@link FileIOUtils#writeFileFromIS(File, InputStream)}
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri InputStream + */ + public static InputStream openInputStream( + final Uri uri, + final ContentResolver resolver + ) { + return ResourceAssist.get().openInputStream(uri, resolver); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri OutputStream + */ + public static OutputStream openOutputStream(final Uri uri) { + return ResourceAssist.get().openOutputStream(uri); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final Uri uri, + final ContentResolver resolver + ) { + return ResourceAssist.get().openOutputStream(uri, resolver); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final Uri uri, + final String mode + ) { + return ResourceAssist.get().openOutputStream(uri, mode); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + return ResourceAssist.get().openOutputStream(uri, mode, resolver); + } + + /** + * 获取 Uri ParcelFileDescriptor + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri ParcelFileDescriptor + */ + public static ParcelFileDescriptor openFileDescriptor( + final Uri uri, + final String mode + ) { + return ResourceAssist.get().openFileDescriptor(uri, mode); + } + + /** + * 获取 Uri ParcelFileDescriptor + *
+     *     通过 new FileInputStream(openFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri ParcelFileDescriptor + */ + public static ParcelFileDescriptor openFileDescriptor( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + return ResourceAssist.get().openFileDescriptor(uri, mode, resolver); + } + + /** + * 获取 Uri AssetFileDescriptor + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri AssetFileDescriptor + */ + public static AssetFileDescriptor openAssetFileDescriptor( + final Uri uri, + final String mode + ) { + return ResourceAssist.get().openAssetFileDescriptor(uri, mode); + } + + /** + * 获取 Uri AssetFileDescriptor + *
+     *     通过 new FileInputStream(openAssetFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri AssetFileDescriptor + */ + public static AssetFileDescriptor openAssetFileDescriptor( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + return ResourceAssist.get().openAssetFileDescriptor(uri, mode, resolver); + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager 指定资源 InputStream + * @param fileName 文件名 + * @return {@link InputStream} + */ + public static InputStream open(final String fileName) { + return ResourceAssist.get().open(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openFd(final String fileName) { + return ResourceAssist.get().openFd(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openNonAssetFd(final String fileName) { + return ResourceAssist.get().openNonAssetFd(fileName); + } + + /** + * 获取对应资源 InputStream + * @param id resource identifier + * @return {@link InputStream} + */ + public static InputStream openRawResource(@RawRes final int id) { + return ResourceAssist.get().openRawResource(id); + } + + /** + * 获取对应资源 AssetFileDescriptor + * @param id resource identifier + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openRawResourceFd(@RawRes final int id) { + return ResourceAssist.get().openRawResourceFd(id); + } + + // ============= + // = 读取资源文件 = + // ============= + + /** + * 获取 Assets 资源文件数据 + *
+     *     直接传入文件名、文件夹 / 文件名 等
+     *     根目录 a.txt
+     *     子目录 /www/a.html
+     * 
+ * @param fileName 文件名 + * @return 文件 byte[] 数据 + */ + public static byte[] readBytesFromAssets(final String fileName) { + return ResourceAssist.get().readBytesFromAssets(fileName); + } + + /** + * 获取 Assets 资源文件数据 + * @param fileName 文件名 + * @return 文件字符串内容 + */ + public static String readStringFromAssets(final String fileName) { + return ResourceAssist.get().readStringFromAssets(fileName); + } + + // = + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件 byte[] 数据 + */ + public static byte[] readBytesFromRaw(@RawRes final int resId) { + return ResourceAssist.get().readBytesFromRaw(resId); + } + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件字符串内容 + */ + public static String readStringFromRaw(@RawRes final int resId) { + return ResourceAssist.get().readStringFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param fileName 文件名 + * @return {@link List } + */ + public static List geFileToListFromAssets(final String fileName) { + return ResourceAssist.get().geFileToListFromAssets(fileName); + } + + /** + * 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param resId 资源 id + * @return {@link List} + */ + public static List geFileToListFromRaw(@RawRes final int resId) { + return ResourceAssist.get().geFileToListFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据并保存到本地 + * @param fileName 文件名 + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveAssetsFormFile( + final String fileName, + final File file + ) { + return ResourceAssist.get().saveAssetsFormFile(fileName, file); + } + + /** + * 获取 Raw 资源文件数据并保存到本地 + * @param resId 资源 id + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveRawFormFile( + @RawRes final int resId, + final File file + ) { + return ResourceAssist.get().saveRawFormFile(resId, file); + } + + // ========================= + // = ResourceAssist Params = + // ========================= + + // ========== + // = 资源获取 = + // ========== + + /** + * 获取 String id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return String id + */ + public static int getStringId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getStringId(resName); + } + + /** + * 获取 String + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return String + */ + public static String getString( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getString(resName); + } + + /** + * 获取 String + * @param assist {@link ResourceAssist} + * @param resName resource name + * @param formatArgs 格式化参数 + * @return String + */ + public static String getString( + final ResourceAssist assist, + final String resName, + final Object... formatArgs + ) { + if (assist == null) return null; + return assist.getString(resName, formatArgs); + } + + /** + * 获取 String + * @param assist {@link ResourceAssist} + * @param id R.string.id + * @return String + */ + public static String getString( + final ResourceAssist assist, + @StringRes final int id + ) { + if (assist == null) return null; + return assist.getString(id); + } + + /** + * 获取 String + * @param assist {@link ResourceAssist} + * @param id R.string.id + * @param formatArgs 格式化参数 + * @return String + */ + public static String getString( + final ResourceAssist assist, + @StringRes final int id, + final Object... formatArgs + ) { + if (assist == null) return null; + return assist.getString(id, formatArgs); + } + + // = + + /** + * 获取 Dimension id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Dimension id + */ + public static int getDimenId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getDimenId(resName); + } + + /** + * 获取 Dimension + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Dimension + */ + public static float getDimension( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0F; + return assist.getDimension(resName); + } + + /** + * 获取 Dimension + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Dimension + */ + public static int getDimensionInt( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getDimensionInt(resName); + } + + /** + * 获取 Dimension + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return Dimension + */ + public static float getDimension( + final ResourceAssist assist, + @DimenRes final int id + ) { + if (assist == null) return 0F; + return assist.getDimension(id); + } + + /** + * 获取 Dimension + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return Dimension + */ + public static int getDimensionInt( + final ResourceAssist assist, + @DimenRes final int id + ) { + if (assist == null) return 0; + return assist.getDimensionInt(id); + } + + // = + + /** + * 获取 Color id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Color id + */ + public static int getColorId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return -1; + return assist.getColorId(resName); + } + + /** + * 获取 Color + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Color + */ + public static int getColor( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return -1; + return assist.getColor(resName); + } + + /** + * 获取 Color + *
+     *     {@link ContextCompat#getColor(Context, int)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param colorId R.color.id + * @return Color + */ + public static int getColor( + final ResourceAssist assist, + @ColorRes final int colorId + ) { + if (assist == null) return -1; + return assist.getColor(colorId); + } + + // = + + /** + * 获取 Drawable id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Drawable id + */ + public static int getDrawableId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getDrawableId(resName); + } + + /** + * 获取 Drawable + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link Drawable} + */ + public static Drawable getDrawable( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getDrawable(resName); + } + + /** + * 获取 .9 Drawable + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawable( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getNinePatchDrawable(resName); + } + + /** + * 获取 Drawable + *
+     *     {@link ContextCompat#getDrawable(Context, int)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param drawableId R.drawable.id + * @return {@link Drawable} + */ + public static Drawable getDrawable( + final ResourceAssist assist, + @DrawableRes final int drawableId + ) { + if (assist == null) return null; + return assist.getDrawable(drawableId); + } + + /** + * 获取 .9 Drawable + * @param assist {@link ResourceAssist} + * @param drawableId R.drawable.id + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawable( + final ResourceAssist assist, + @DrawableRes final int drawableId + ) { + if (assist == null) return null; + return assist.getNinePatchDrawable(drawableId); + } + + // = + + /** + * 获取 Bitmap + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getBitmap(resName); + } + + /** + * 获取 Bitmap + * @param assist {@link ResourceAssist} + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final ResourceAssist assist, + final String resName, + final BitmapFactory.Options options + ) { + if (assist == null) return null; + return assist.getBitmap(resName, options); + } + + /** + * 获取 Bitmap + * @param assist {@link ResourceAssist} + * @param resId resource identifier + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final ResourceAssist assist, + final int resId + ) { + if (assist == null) return null; + return assist.getBitmap(resId); + } + + /** + * 获取 Bitmap + * @param assist {@link ResourceAssist} + * @param resId resource identifier + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final ResourceAssist assist, + final int resId, + final BitmapFactory.Options options + ) { + if (assist == null) return null; + return assist.getBitmap(resId, options); + } + + // = + + /** + * 获取 Mipmap id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Mipmap id + */ + public static int getMipmapId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getMipmapId(resName); + } + + /** + * 获取 Mipmap Drawable + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link Drawable} + */ + public static Drawable getDrawableMipmap( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getDrawableMipmap(resName); + } + + /** + * 获取 Mipmap .9 Drawable + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable getNinePatchDrawableMipmap( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getNinePatchDrawableMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link Bitmap} + */ + public static Bitmap getBitmapMipmap( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getBitmapMipmap(resName); + } + + /** + * 获取 Mipmap Bitmap + * @param assist {@link ResourceAssist} + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap getBitmapMipmap( + final ResourceAssist assist, + final String resName, + final BitmapFactory.Options options + ) { + if (assist == null) return null; + return assist.getBitmapMipmap(resName, options); + } + + // = + + /** + * 获取 Anim id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Anim id + */ + public static int getAnimId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getAnimId(resName); + } + + /** + * 获取 Animation Xml + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public static XmlResourceParser getAnimationXml( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getAnimationXml(resName); + } + + /** + * 获取 Animation Xml + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public static XmlResourceParser getAnimationXml( + final ResourceAssist assist, + @AnimatorRes @AnimRes final int id + ) { + if (assist == null) return null; + return assist.getAnimationXml(id); + } + + /** + * 获取 Animation + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getAnimation(resName); + } + + /** + * 获取 Animation + * @param assist {@link ResourceAssist} + * @param resName resource name + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + final ResourceAssist assist, + final String resName, + final Context context + ) { + if (assist == null) return null; + return assist.getAnimation(resName, context); + } + + /** + * 获取 Animation + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + final ResourceAssist assist, + @AnimatorRes @AnimRes final int id + ) { + if (assist == null) return null; + return assist.getAnimation(id); + } + + /** + * 获取 Animation + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public static Animation getAnimation( + final ResourceAssist assist, + @AnimatorRes @AnimRes final int id, + final Context context + ) { + if (assist == null) return null; + return assist.getAnimation(id, context); + } + + // = + + /** + * 获取 Boolean id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Boolean id + */ + public static int getBooleanId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getBooleanId(resName); + } + + /** + * 获取 Boolean + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Boolean + */ + public static boolean getBoolean( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return false; + return assist.getBoolean(resName); + } + + /** + * 获取 Boolean + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return Boolean + */ + public static boolean getBoolean( + final ResourceAssist assist, + @BoolRes final int id + ) { + if (assist == null) return false; + return assist.getBoolean(id); + } + + // = + + /** + * 获取 Integer id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Integer id + */ + public static int getIntegerId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return -1; + return assist.getIntegerId(resName); + } + + /** + * 获取 Integer + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Integer + */ + public static int getInteger( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return -1; + return assist.getInteger(resName); + } + + /** + * 获取 Integer + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return Integer + */ + public static int getInteger( + final ResourceAssist assist, + @IntegerRes final int id + ) { + if (assist == null) return -1; + return assist.getInteger(id); + } + + // = + + /** + * 获取 Array id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Array id + */ + public static int getArrayId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getArrayId(resName); + } + + /** + * 获取 int[] + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return int[] + */ + public static int[] getIntArray( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getIntArray(resName); + } + + /** + * 获取 String[] + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return String[] + */ + public static String[] getStringArray( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getStringArray(resName); + } + + /** + * 获取 CharSequence[] + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return CharSequence[] + */ + public static CharSequence[] getTextArray( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getTextArray(resName); + } + + /** + * 获取 int[] + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return int[] + */ + public static int[] getIntArray( + final ResourceAssist assist, + @ArrayRes final int id + ) { + if (assist == null) return null; + return assist.getIntArray(id); + } + + /** + * 获取 String[] + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return String[] + */ + public static String[] getStringArray( + final ResourceAssist assist, + @ArrayRes final int id + ) { + if (assist == null) return null; + return assist.getStringArray(id); + } + + /** + * 获取 CharSequence[] + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return CharSequence[] + */ + public static CharSequence[] getTextArray( + final ResourceAssist assist, + @ArrayRes final int id + ) { + if (assist == null) return null; + return assist.getTextArray(id); + } + + // = + + /** + * 获取 id ( view ) + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return id + */ + public static int getId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getId(resName); + } + + /** + * 获取 Layout id + *
+     *     {@link android.view.LayoutInflater#inflate(int, ViewGroup)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Layout id + */ + public static int getLayoutId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getLayoutId(resName); + } + + /** + * 获取 Menu id + *
+     *     {@link android.view.MenuInflater#inflate(int, Menu)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Menu id + */ + public static int getMenuId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getMenuId(resName); + } + + /** + * 获取 Raw id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Raw id + */ + public static int getRawId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getRawId(resName); + } + + /** + * 获取 Attr id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Attr id + */ + public static int getAttrId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getAttrId(resName); + } + + /** + * 获取 Style id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Style id + */ + public static int getStyleId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getStyleId(resName); + } + + /** + * 获取 Styleable id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Styleable id + */ + public static int getStyleableId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getStyleableId(resName); + } + + /** + * 获取 Animator id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Animator id + */ + public static int getAnimatorId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getAnimatorId(resName); + } + + /** + * 获取 Xml id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Xml id + */ + public static int getXmlId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getXmlId(resName); + } + + /** + * 获取 Interpolator id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Interpolator id + */ + public static int getInterpolatorId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getInterpolatorId(resName); + } + + /** + * 获取 Plurals id + * @param assist {@link ResourceAssist} + * @param resName resource name + * @return Plurals id + */ + public static int getPluralsId( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return 0; + return assist.getPluralsId(resName); + } + + // = + + /** + * 获取 ColorStateList + * @param assist {@link ResourceAssist} + * @param resName resource Name + * @return {@link ColorStateList} + */ + public static ColorStateList getColorStateList( + final ResourceAssist assist, + final String resName + ) { + if (assist == null) return null; + return assist.getColorStateList(resName); + } + + /** + * 获取 ColorStateList + *
+     *     {@link ContextCompat#getColorStateList(Context, int)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param id resource identifier of a {@link ColorStateList} + * @return {@link ColorStateList} + */ + public static ColorStateList getColorStateList( + final ResourceAssist assist, + @ColorRes final int id + ) { + if (assist == null) return null; + return assist.getColorStateList(id); + } + + /** + * 获取十六进制颜色值 Drawable + * @param assist {@link ResourceAssist} + * @param color 十六进制颜色值 + * @return 十六进制颜色值 Drawable + */ + public static ColorDrawable getColorDrawable( + final ResourceAssist assist, + final String color + ) { + if (assist == null) return null; + return assist.getColorDrawable(color); + } + + /** + * 获取指定颜色 Drawable + * @param assist {@link ResourceAssist} + * @param color 颜色值 + * @return 指定颜色 Drawable + */ + public static ColorDrawable getColorDrawable( + final ResourceAssist assist, + @ColorInt final int color + ) { + if (assist == null) return null; + return assist.getColorDrawable(color); + } + + // =================== + // = ContentResolver = + // =================== + + /** + * 获取 Uri InputStream + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri InputStream + */ + public static InputStream openInputStream( + final ResourceAssist assist, + final Uri uri + ) { + if (assist == null) return null; + return assist.openInputStream(uri); + } + + /** + * 获取 Uri InputStream + *
+     *     主要用于获取到分享的 FileProvider Uri 存储起来 {@link FileIOUtils#writeFileFromIS(File, InputStream)}
+     * 
+ * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri InputStream + */ + public static InputStream openInputStream( + final ResourceAssist assist, + final Uri uri, + final ContentResolver resolver + ) { + if (assist == null) return null; + return assist.openInputStream(uri, resolver); + } + + /** + * 获取 Uri OutputStream + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final ResourceAssist assist, + final Uri uri + ) { + if (assist == null) return null; + return assist.openOutputStream(uri); + } + + /** + * 获取 Uri OutputStream + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final ResourceAssist assist, + final Uri uri, + final ContentResolver resolver + ) { + if (assist == null) return null; + return assist.openOutputStream(uri, resolver); + } + + /** + * 获取 Uri OutputStream + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final ResourceAssist assist, + final Uri uri, + final String mode + ) { + if (assist == null) return null; + return assist.openOutputStream(uri, mode); + } + + /** + * 获取 Uri OutputStream + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public static OutputStream openOutputStream( + final ResourceAssist assist, + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (assist == null) return null; + return assist.openOutputStream(uri, mode, resolver); + } + + /** + * 获取 Uri ParcelFileDescriptor + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri ParcelFileDescriptor + */ + public static ParcelFileDescriptor openFileDescriptor( + final ResourceAssist assist, + final Uri uri, + final String mode + ) { + if (assist == null) return null; + return assist.openFileDescriptor(uri, mode); + } + + /** + * 获取 Uri ParcelFileDescriptor + *
+     *     通过 new FileInputStream(openFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri ParcelFileDescriptor + */ + public static ParcelFileDescriptor openFileDescriptor( + final ResourceAssist assist, + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (assist == null) return null; + return assist.openFileDescriptor(uri, mode, resolver); + } + + /** + * 获取 Uri AssetFileDescriptor + * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri AssetFileDescriptor + */ + public static AssetFileDescriptor openAssetFileDescriptor( + final ResourceAssist assist, + final Uri uri, + final String mode + ) { + if (assist == null) return null; + return assist.openAssetFileDescriptor(uri, mode); + } + + /** + * 获取 Uri AssetFileDescriptor + *
+     *     通过 new FileInputStream(openAssetFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param assist {@link ResourceAssist} + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri AssetFileDescriptor + */ + public static AssetFileDescriptor openAssetFileDescriptor( + final ResourceAssist assist, + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (assist == null) return null; + return assist.openAssetFileDescriptor(uri, mode, resolver); + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager 指定资源 InputStream + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return {@link InputStream} + */ + public static InputStream open( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.open(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openFd( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.openFd(fileName); + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openNonAssetFd( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.openNonAssetFd(fileName); + } + + /** + * 获取对应资源 InputStream + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return {@link InputStream} + */ + public static InputStream openRawResource( + final ResourceAssist assist, + @RawRes final int id + ) { + if (assist == null) return null; + return assist.openRawResource(id); + } + + /** + * 获取对应资源 AssetFileDescriptor + * @param assist {@link ResourceAssist} + * @param id resource identifier + * @return {@link AssetFileDescriptor} + */ + public static AssetFileDescriptor openRawResourceFd( + final ResourceAssist assist, + @RawRes final int id + ) { + if (assist == null) return null; + return assist.openRawResourceFd(id); + } + + // ============= + // = 读取资源文件 = + // ============= + + /** + * 获取 Assets 资源文件数据 + *
+     *     直接传入文件名、文件夹 / 文件名 等
+     *     根目录 a.txt
+     *     子目录 /www/a.html
+     * 
+ * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return 文件 byte[] 数据 + */ + public static byte[] readBytesFromAssets( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.readBytesFromAssets(fileName); + } + + /** + * 获取 Assets 资源文件数据 + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return 文件字符串内容 + */ + public static String readStringFromAssets( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.readStringFromAssets(fileName); + } + + // = + + /** + * 获取 Raw 资源文件数据 + * @param assist {@link ResourceAssist} + * @param resId 资源 id + * @return 文件 byte[] 数据 + */ + public static byte[] readBytesFromRaw( + final ResourceAssist assist, + @RawRes final int resId + ) { + if (assist == null) return null; + return assist.readBytesFromRaw(resId); + } + + /** + * 获取 Raw 资源文件数据 + * @param assist {@link ResourceAssist} + * @param resId 资源 id + * @return 文件字符串内容 + */ + public static String readStringFromRaw( + final ResourceAssist assist, + @RawRes final int resId + ) { + if (assist == null) return null; + return assist.readStringFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @return {@link List } + */ + public static List geFileToListFromAssets( + final ResourceAssist assist, + final String fileName + ) { + if (assist == null) return null; + return assist.geFileToListFromAssets(fileName); + } + + /** + * 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param assist {@link ResourceAssist} + * @param resId 资源 id + * @return {@link List} + */ + public static List geFileToListFromRaw( + final ResourceAssist assist, + @RawRes final int resId + ) { + if (assist == null) return null; + return assist.geFileToListFromRaw(resId); + } + + // = + + /** + * 获取 Assets 资源文件数据并保存到本地 + * @param assist {@link ResourceAssist} + * @param fileName 文件名 + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveAssetsFormFile( + final ResourceAssist assist, + final String fileName, + final File file + ) { + if (assist == null) return false; + return assist.saveAssetsFormFile(fileName, file); + } + + /** + * 获取 Raw 资源文件数据并保存到本地 + * @param assist {@link ResourceAssist} + * @param resId 资源 id + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveRawFormFile( + final ResourceAssist assist, + @RawRes final int resId, + final File file + ) { + if (assist == null) return false; + return assist.saveRawFormFile(resId, file); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/SDCardUtils.java b/lib/DevApp/src/main/java/dev/utils/app/SDCardUtils.java new file mode 100644 index 0000000000..f0e7a062b8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/SDCardUtils.java @@ -0,0 +1,359 @@ +package dev.utils.app; + +import android.os.Build; +import android.os.Environment; +import android.os.StatFs; +import android.os.storage.StorageManager; +import android.text.format.Formatter; + +import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: SDCard 工具类 + * @author Ttt + */ +public final class SDCardUtils { + + private SDCardUtils() { + } + + // 日志 TAG + private static final String TAG = SDCardUtils.class.getSimpleName(); + + /** + * 判断 SDCard 是否正常挂载 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSDCardEnable() { + return PathUtils.getSDCard().isSDCardEnable(); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @return /storage/emulated/0/ + */ + @Deprecated + public static File getSDCardFile() { + return PathUtils.getSDCard().getSDCardFile(); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @return /storage/emulated/0/ + */ + @Deprecated + public static String getSDCardPath() { + return PathUtils.getSDCard().getSDCardPath(); + } + + // = + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @param fileName 文件名 + * @return /storage/emulated/0/ + */ + @Deprecated + public static File getSDCardFile(final String fileName) { + return PathUtils.getSDCard().getSDCardFile(fileName); + } + + /** + * 获取 SDCard 外部存储路径 ( path /storage/emulated/0/ ) + * @param fileName 文件名 + * @return /storage/emulated/0/ + */ + @Deprecated + public static String getSDCardPath(final String fileName) { + return PathUtils.getSDCard().getSDCardPath(fileName); + } + + // = + + /** + * 判断 SDCard 是否可用 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSDCardEnablePath() { + return !getSDCardPaths().isEmpty(); + } + + /** + * 获取 SDCard 路径 + * @return SDCard 路径 + */ + public static List getSDCardPaths() { + try { + StorageManager storageManager = AppUtils.getStorageManager(); + Method getVolumePathsMethod = StorageManager.class.getMethod("getVolumePaths"); + getVolumePathsMethod.setAccessible(true); + Object invoke = getVolumePathsMethod.invoke(storageManager); + if (invoke != null) { + return new ArrayList<>(Arrays.asList((String[]) invoke)); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSDCardPaths"); + } + return new ArrayList<>(); + } + + /** + * 获取 SDCard 路径 + * @param removable {@code true} 外置 SDCard, {@code false} 内置 SDCard + * @return SDCard 路径 + */ + public static List getSDCardPaths(final boolean removable) { + List listPaths = new ArrayList<>(); + try { + StorageManager storageManager = AppUtils.getStorageManager(); + Class storageVolumeClazz = Class.forName("android.os.storage.StorageVolume"); + Method getVolumeList = StorageManager.class.getMethod("getVolumeList"); + Method getPath = storageVolumeClazz.getMethod("getPath"); + Method isRemovable = storageVolumeClazz.getMethod("isRemovable"); + Object result = getVolumeList.invoke(storageManager); + if (result != null) { + final int length = Array.getLength(result); + for (int i = 0; i < length; i++) { + Object storageVolumeElement = Array.get(result, i); + String path = (String) getPath.invoke(storageVolumeElement); + boolean res = (Boolean) isRemovable.invoke(storageVolumeElement); + if (removable == res) { + listPaths.add(path); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSDCardPaths"); + } + return listPaths; + } + + // ========== + // = 空间信息 = + // ========== + + /** + * 获取内置 SDCard 空间总大小 + * @return 内置 SDCard 空间总大小 + */ + public static long getAllBlockSize() { + try { + return getAllBlockSize(Environment.getExternalStorageDirectory().getPath()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllBlockSize"); + return 0L; + } + } + + /** + * 获取内置 SDCard 空间总大小 + * @return 内置 SDCard 空间总大小 + */ + public static String getAllBlockSizeFormat() { + try { + long size = getAllBlockSize(Environment.getExternalStorageDirectory().getPath()); + return Formatter.formatFileSize(DevUtils.getContext(), size); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllBlockSizeFormat"); + return null; + } + } + + // = + + /** + * 获取内置 SDCard 空闲空间大小 + * @return 内置 SDCard 空闲空间大小 + */ + public static long getAvailableBlocks() { + try { + return getAvailableBlocks(Environment.getExternalStorageDirectory().getPath()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAvailableBlocks"); + return 0L; + } + } + + /** + * 获取内置 SDCard 空闲空间大小 + * @return 内置 SDCard 空闲空间大小 + */ + public static String getAvailableBlocksFormat() { + try { + long size = getAvailableBlocks(Environment.getExternalStorageDirectory().getPath()); + return Formatter.formatFileSize(DevUtils.getContext(), size); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAvailableBlocksFormat"); + return null; + } + } + + // = + + /** + * 获取内置 SDCard 已使用空间大小 + * @return 内置 SDCard 已使用空间大小 + */ + public static long getUsedBlocks() { + try { + return getUsedBlocks(Environment.getExternalStorageDirectory().getPath()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUsedBlocks"); + return 0L; + } + } + + /** + * 获取内置 SDCard 已使用空间大小 + * @return 内置 SDCard 已使用空间大小 + */ + public static String getUsedBlocksFormat() { + try { + long size = getUsedBlocks(Environment.getExternalStorageDirectory().getPath()); + return Formatter.formatFileSize(DevUtils.getContext(), size); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUsedBlocksFormat"); + return null; + } + } + + // = + + /** + * 返回内置 SDCard 空间大小信息 + * @return long[], 0 = 总空间大小, 1 = 空闲空间大小, 2 = 已使用空间大小 + */ + public static long[] getBlockSizeInfos() { + try { + return getBlockSizeInfos(Environment.getExternalStorageDirectory().getPath()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBlockSizeInfos"); + return null; + } + } + + /** + * 获取对应路径的空间总大小 + * @param path 路径 + * @return 对应路径的空间总大小 + */ + public static long getAllBlockSize(final String path) { + try { + // 获取路径的存储空间信息 + StatFs statFs = new StatFs(path); + // 单个数据块的大小、数据块数量 + long blockSize, blockCount; + // 版本兼容 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = statFs.getBlockSizeLong(); + blockCount = statFs.getBlockCountLong(); + } else { + blockSize = statFs.getBlockSize(); + blockCount = statFs.getBlockCount(); + } + // 返回空间总大小 + return (blockCount * blockSize); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllBlockSize"); + } + return 0L; + } + + /** + * 获取对应路径的空闲空间大小 + * @param path 路径 + * @return 对应路径的空闲空间大小 + */ + public static long getAvailableBlocks(final String path) { + try { + // 获取路径的存储空间信息 + StatFs statFs = new StatFs(path); + // 单个数据块的大小、空闲的数据块数量 + long blockSize, availableBlocks; + // 版本兼容 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = statFs.getBlockSizeLong(); + availableBlocks = statFs.getAvailableBlocksLong(); + } else { + blockSize = statFs.getBlockSize(); + availableBlocks = statFs.getAvailableBlocks(); + } + // 返回空闲空间 + return (availableBlocks * blockSize); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAvailableBlocks"); + } + return 0L; + } + + /** + * 获取对应路径已使用空间大小 + * @param path 路径 + * @return 对应路径已使用空间大小 + */ + public static long getUsedBlocks(final String path) { + try { + // 获取路径的存储空间信息 + StatFs statFs = new StatFs(path); + // 单个数据块的大小、数据块数量、空闲的数据块数量 + long blockSize, blockCount, availableBlocks; + // 版本兼容 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = statFs.getBlockSizeLong(); + blockCount = statFs.getBlockCountLong(); + availableBlocks = statFs.getAvailableBlocksLong(); + } else { + blockSize = statFs.getBlockSize(); + blockCount = statFs.getBlockCount(); + availableBlocks = statFs.getAvailableBlocks(); + } + // 返回已使用空间大小 + return ((blockCount - availableBlocks) * blockSize); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUsedBlocks"); + } + return 0L; + } + + /** + * 返回对应路径的空间大小信息 + * @param path 路径 + * @return long[], 0 = 总空间大小, 1 = 空闲空间大小, 2 = 已使用空间大小 + */ + public static long[] getBlockSizeInfos(final String path) { + try { + // 获取路径的存储空间信息 + StatFs statFs = new StatFs(path); + // 单个数据块的大小、数据块数量、空闲的数据块数量 + long blockSize, blockCount, availableBlocks; + // 版本兼容 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + blockSize = statFs.getBlockSizeLong(); + blockCount = statFs.getBlockCountLong(); + availableBlocks = statFs.getAvailableBlocksLong(); + } else { + blockSize = statFs.getBlockSize(); + blockCount = statFs.getBlockCount(); + availableBlocks = statFs.getAvailableBlocks(); + } + // 计算空间信息 + long[] blocks = new long[3]; + blocks[0] = blockSize * blockCount; + blocks[1] = blockSize * availableBlocks; + blocks[2] = ((blockCount - availableBlocks) * blockSize); + // 返回空间信息 + return blocks; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBlockSizeInfos"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ScreenUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ScreenUtils.java new file mode 100644 index 0000000000..d17d9711fa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ScreenUtils.java @@ -0,0 +1,578 @@ +package dev.utils.app; + +import android.app.Activity; +import android.app.KeyguardManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.Point; +import android.os.Build; +import android.provider.Settings; +import android.util.DisplayMetrics; +import android.view.Surface; +import android.view.WindowManager; + +import androidx.annotation.RequiresApi; + +import java.lang.reflect.Method; +import java.text.DecimalFormat; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 屏幕相关工具类 + * @author Ttt + *
+ *     计算屏幕尺寸
+ *     @see 
+ *     

+ * 所需权限 + * + *
+ */ +public final class ScreenUtils { + + private ScreenUtils() { + } + + // 日志 TAG + private static final String TAG = ScreenUtils.class.getSimpleName(); + + /** + * 获取 DisplayMetrics + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics getDisplayMetrics() { + try { + WindowManager windowManager = AppUtils.getWindowManager(); + DisplayMetrics displayMetrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + return displayMetrics; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDisplayMetrics"); + } + return null; + } + + // ========== + // = 宽高获取 = + // ========== + + /** + * 获取屏幕宽度 + * @return 屏幕宽度 + */ + public static int getScreenWidth() { + return getScreenWidthHeight()[0]; + } + + /** + * 获取屏幕高度 + * @return 屏幕高度 + */ + public static int getScreenHeight() { + return getScreenWidthHeight()[1]; + } + + // = + + /** + * 获取屏幕宽高 + * @return int[], 0 = 宽度, 1 = 高度 + */ + public static int[] getScreenWidthHeight() { + try { + WindowManager windowManager = AppUtils.getWindowManager(); + Point point = new Point(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + windowManager.getDefaultDisplay().getRealSize(point); + } else { + windowManager.getDefaultDisplay().getSize(point); + } + return new int[]{point.x, point.y}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getScreenWidthHeight"); + } + return new int[]{0, 0}; + } + + /** + * 获取屏幕宽高 + * @return {@link Point}, point.x 宽, point.y 高 + */ + public static Point getScreenWidthHeightToPoint() { + try { + WindowManager windowManager = AppUtils.getWindowManager(); + Point point = new Point(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + windowManager.getDefaultDisplay().getRealSize(point); + } else { + windowManager.getDefaultDisplay().getSize(point); + } + return point; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getScreenWidthHeightToPoint"); + } + return null; + } + + // = + + /** + * 获取屏幕分辨率 + * @return 屏幕分辨率 + */ + public static String getScreenSize() { + return getScreenSize("x"); + } + + /** + * 获取屏幕分辨率 + * @param symbol 拼接符号 + * @return 屏幕分辨率 + */ + public static String getScreenSize(final String symbol) { + // 获取分辨率 + int[] widthHeight = getScreenWidthHeight(); + // 返回分辨率信息 + return widthHeight[1] + symbol + widthHeight[0]; + } + + /** + * 获取屏幕英寸 ( 例 5.5 英寸 ) + * @return 屏幕英寸 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static String getScreenSizeOfDevice() { + try { + Point point = new Point(); + DisplayMetrics displayMetrics = new DisplayMetrics(); + WindowManager windowManager = AppUtils.getWindowManager(); + windowManager.getDefaultDisplay().getRealSize(point); + windowManager.getDefaultDisplay().getMetrics(displayMetrics); + // 计算尺寸 + double x = Math.pow(point.x / displayMetrics.xdpi, 2); + double y = Math.pow(point.y / displayMetrics.ydpi, 2); + double screenInches = Math.sqrt(x + y); + // 转换大小 + return new DecimalFormat("#.0").format(screenInches); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getScreenSizeOfDevice"); + } + return "unknown"; + } + + // = + + /** + * 获取屏幕密度 + * @return 屏幕密度 + */ + public static float getDensity() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + // 屏幕密度, 如 (0.75 / 1.0 / 1.5 / 2.0) + return (displayMetrics != null) ? displayMetrics.density : 0F; + } + + /** + * 获取屏幕密度 dpi + * @return 屏幕密度 dpi + */ + public static int getDensityDpi() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + // 屏幕密度 DPI, 如 (120 / 160 / 240 / 320) + return (displayMetrics != null) ? displayMetrics.densityDpi : 0; + } + + /** + * 获取屏幕缩放密度 + * @return 屏幕缩放密度 + */ + public static float getScaledDensity() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + return (displayMetrics != null) ? displayMetrics.scaledDensity : 0F; + } + + /** + * 获取 X 轴 dpi + * @return X 轴 dpi + */ + public static float getXDpi() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + return (displayMetrics != null) ? displayMetrics.xdpi : 0F; + } + + /** + * 获取 Y 轴 dpi + * @return Y 轴 dpi + */ + public static float getYDpi() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + return (displayMetrics != null) ? displayMetrics.ydpi : 0F; + } + + /** + * 获取宽度比例 dpi 基准 + * @return 宽度比例 dpi 基准 + */ + public static float getWidthDpi() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + return (displayMetrics != null) ? (displayMetrics.widthPixels / displayMetrics.density) : 0F; + } + + /** + * 获取高度比例 dpi 基准 + * @return 高度比例 dpi 基准 + */ + public static float getHeightDpi() { + DisplayMetrics displayMetrics = getDisplayMetrics(); + return (displayMetrics != null) ? (displayMetrics.heightPixels / displayMetrics.density) : 0F; + } + + /** + * 获取屏幕信息 + * @return 屏幕信息 + */ + public static String getScreenInfo() { + StringBuilder builder = new StringBuilder(); + DisplayMetrics displayMetrics = getDisplayMetrics(); + if (displayMetrics != null) { + try { + int heightPixels = displayMetrics.heightPixels; + int widthPixels = displayMetrics.widthPixels; + + float xdpi = displayMetrics.xdpi; + float ydpi = displayMetrics.ydpi; + int densityDpi = displayMetrics.densityDpi; + + float density = displayMetrics.density; + float scaledDensity = displayMetrics.scaledDensity; + + float heightDpi = heightPixels / density; + float widthDpi = widthPixels / density; + + builder.append("heightPixels: ").append(heightPixels).append("px"); + builder.append("\nwidthPixels: ").append(widthPixels).append("px"); + + builder.append("\nxdpi: ").append(xdpi).append("dpi"); + builder.append("\nydpi: ").append(ydpi).append("dpi"); + builder.append("\ndensityDpi: ").append(densityDpi).append("dpi"); + + builder.append("\ndensity: ").append(density); + builder.append("\nscaledDensity: ").append(scaledDensity); + + builder.append("\nheightDpi: ").append(heightDpi).append("dpi"); + builder.append("\nwidthDpi: ").append(widthDpi).append("dpi"); + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getScreenInfo"); + } + } + return builder.toString(); + } + + // = + + /** + * 设置禁止截屏 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setWindowSecure(final Activity activity) { + return WindowUtils.get(activity).setFlagSecure(); + } + + /** + * 是否屏幕为全屏 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFullScreen(final Activity activity) { + return WindowUtils.get(activity).isFullScreenFlag(); + } + + /** + * 设置屏幕为全屏 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setFullScreen(final Activity activity) { + return WindowUtils.get(activity).setFlagFullScreen(); + } + + /** + * 设置屏幕为全屏无标题 + *
+     *     需要在 setContentView() 之前调用
+     * 
+ * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setFullScreenNoTitle(final Activity activity) { + return WindowUtils.get(activity).setFlagFullScreenAndNoTitle(); + } + + /** + * 设置屏幕为横屏 + *
+     *     还有一种就是在 Activity 中加属性 android:screenOrientation="landscape"
+     *     不设置 Activity 的 android:configChanges 时
+     *     切屏会重新调用各个生命周期, 切横屏时会执行一次, 切竖屏时会执行两次
+     *     设置 Activity 的 android:configChanges="orientation" 时
+     *     切屏还是会重新调用各个生命周期, 切横、竖屏时只会执行一次
+     *     设置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize"
+     *     4.0 以上必须带最后一个参数时
+     *     切屏不会重新调用各个生命周期, 只会执行 onConfigurationChanged 方法
+     * 
+ * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setLandscape(final Activity activity) { + try { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLandscape"); + } + return false; + } + + /** + * 设置屏幕为竖屏 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + public static boolean setPortrait(final Activity activity) { + try { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setPortrait"); + } + return false; + } + + /** + * 判断是否横屏 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLandscape() { + return isLandscape(DevUtils.getContext()); + } + + /** + * 判断是否横屏 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean isLandscape(final Context context) { + try { + return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isLandscape"); + } + return false; + } + + /** + * 判断是否竖屏 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPortrait() { + return isPortrait(DevUtils.getContext()); + } + + /** + * 判断是否竖屏 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPortrait(final Context context) { + try { + return context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isPortrait"); + } + return false; + } + + /** + * 切换屏幕方向 + * @param activity {@link Activity} + * @return {@code true} 横屏, {@code false} 竖屏 + */ + public static boolean toggleScreenOrientation(final Activity activity) { + try { + // 判断是否竖屏 + if (activity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + return true; // 切换横屏, 并且表示属于横屏 + } else { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + return false; // 切换竖屏, 并且表示属于竖屏 + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "toggleScreenOrientation"); + } + return false; + } + + // = + + /** + * 获取屏幕旋转角度 + * @param activity {@link Activity} + * @return 屏幕旋转角度 + */ + public static int getScreenRotation(final Activity activity) { + try { + switch (activity.getWindowManager().getDefaultDisplay().getRotation()) { + case Surface.ROTATION_0: + return 0; + case Surface.ROTATION_90: + return 90; + case Surface.ROTATION_180: + return 180; + case Surface.ROTATION_270: + return 270; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getScreenRotation"); + } + return 0; + } + + /** + * 判断是否锁屏 + * @return {@code true} yes, {@code false} no + */ + public static boolean isScreenLock() { + try { + KeyguardManager keyguardManager = AppUtils.getKeyguardManager(); + return keyguardManager != null && keyguardManager.inKeyguardRestrictedInputMode(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isScreenLock"); + } + return false; + } + + /** + * 判断是否是平板 + * @return {@code true} yes, {@code false} no + */ + public static boolean isTablet() { + return DeviceUtils.isTablet(); + } + + // = + + /** + * 获取 StatusBar 高度 + * @return StatusBar 高度 + */ + public static int getStatusBarHeight() { + return BarUtils.getStatusBarHeight(); + } + + /** + * 获取 StatusBar 高度 + * @return StatusBar 高度 + */ + public static int getStatusBarHeight2() { + return BarUtils.getStatusBarHeight2(); + } + + /** + * 设置进入休眠时长 + * @param duration 时长 + * @return {@code true} success, {@code false} fail + */ + public static boolean setSleepDuration(final int duration) { + try { + Settings.System.putInt(ResourceUtils.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT, duration); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setSleepDuration"); + } + return false; + } + + /** + * 获取进入休眠时长 + * @return 进入休眠时长 + */ + public static int getSleepDuration() { + try { + return Settings.System.getInt(ResourceUtils.getContentResolver(), Settings.System.SCREEN_OFF_TIMEOUT); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSleepDuration"); + return -1; + } + } + + // = + + /** + * 获取底部导航栏高度 + * @return 底部导航栏高度 + */ + public static int getNavigationBarHeight() { + return getNavigationBarHeight(DevUtils.getContext()); + } + + /** + * 获取底部导航栏高度 + * @param context {@link Context} + * @return 底部导航栏高度 + */ + public static int getNavigationBarHeight(final Context context) { + try { + Resources resources = ResourceUtils.getResources(context); + // 获取对应方向字符串 + String orientation = resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape"; + // 获取对应的 id + int resourceId = resources.getIdentifier(orientation, "dimen", "android"); + if (resourceId > 0 && checkDeviceHasNavigationBar()) { + return resources.getDimensionPixelSize(resourceId); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNavigationBarHeight"); + } + return 0; + } + + /** + * 检测是否具有底部导航栏 + *
+     *     一加手机上判断不准确
+     * 
+ * @return {@code true} yes, {@code false} no + */ + public static boolean checkDeviceHasNavigationBar() { + boolean hasNavigationBar = false; + try { + Resources resources = ResourceUtils.getResources(); + int id = resources.getIdentifier("config_showNavigationBar", "bool", "android"); + if (id > 0) { + hasNavigationBar = resources.getBoolean(id); + } + try { + Class systemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method method = systemPropertiesClass.getMethod("get", String.class); + String navBarOverride = (String) method.invoke(systemPropertiesClass, "qemu.hw.mainkeys"); + if ("1".equals(navBarOverride)) { + hasNavigationBar = false; + } else if ("0".equals(navBarOverride)) { + hasNavigationBar = true; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "checkDeviceHasNavigationBar - SystemProperties"); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "checkDeviceHasNavigationBar"); + } + return hasNavigationBar; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ScreenshotUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ScreenshotUtils.java new file mode 100644 index 0000000000..507b478271 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ScreenshotUtils.java @@ -0,0 +1,509 @@ +package dev.utils.app; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.provider.MediaStore; + +import java.util.Arrays; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 截图监听工具类 + * @author Ttt + *
+ *     View 截图使用 {@link CapturePictureUtils}
+ *     

+ * {@link ScreenshotUtils} 使用方法 + * ScreenshotUtils.getInstance().setListener(new ScreenshotUtils.OnScreenshotListener() { + * public void onScreenshot(Uri contentUri, boolean selfChange, long rowId, String dataPath, long dateTaken) { + * } + * }).startListener(); + *

+ * 注意事项 + * 1.部分机型会多次触发 onChange 需自行处理 + * 2.目前 onChecker 使用时间 + 文件前缀校验, 可自行新增截图宽高校验 + * 3.支持自定义 ScreenshotChecker 截图校验逻辑接口, 可自行实现特殊校验需求 + * 4.如截图无触发先检查读写权限、以及 notifyForDescendants 传参为 true 处理 + *
+ */ +public final class ScreenshotUtils { + + private ScreenshotUtils() { + } + + // 日志 TAG + private static final String TAG = ScreenshotUtils.class.getSimpleName(); + + // ScreenshotUtils 实例 + private static volatile ScreenshotUtils sInstance; + + /** + * 获取 ScreenshotUtils 实例 + * @return {@link ScreenshotUtils} + */ + public static ScreenshotUtils getInstance() { + if (sInstance == null) { + synchronized (ScreenshotUtils.class) { + if (sInstance == null) { + sInstance = new ScreenshotUtils(); + } + } + } + return sInstance; + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 截图校验成功回调接口 + * @author Ttt + */ + public interface OnScreenshotListener { + + /** + * 截图校验成功回调 + * @param contentUri 监听 Uri + * @param selfChange True if this is a self-change notification + * @param rowId 数据 id + * @param dataPath 数据路径 ( 截图路径 ) + * @param dateTaken 截图时间 + */ + void onScreenshot( + Uri contentUri, + boolean selfChange, + long rowId, + String dataPath, + long dateTaken + ); + } + + /** + * detail: 截图校验接口 + * @author Ttt + */ + public interface ScreenshotChecker { + + /** + * 内容变更通知 + * @param contentUri 监听 Uri + * @param selfChange True if this is a self-change notification + */ + void onChange( + Uri contentUri, + boolean selfChange + ); + + /** + * 检查方法 + * @param contentUri 监听 Uri + * @param selfChange True if this is a self-change notification + * @param rowId 数据 id + * @param dataPath 数据路径 ( 截图路径 ) + * @param dateTaken 截图时间 + */ + void onChecker( + Uri contentUri, + boolean selfChange, + long rowId, + String dataPath, + long dateTaken + ); + } + + // ========== + // = 事件操作 = + // ========== + + // 内部存储器内容观察者 + private MediaContentObserver mInternalObserver; + // 外部存储器内容观察者 + private MediaContentObserver mExternalObserver; + + /** + * detail: 媒体内容观察者 ( 监听媒体数据库改变 ) + * @author Ttt + */ + private class MediaContentObserver + extends ContentObserver { + + // 监听 Uri + private final Uri mContentUri; + + public MediaContentObserver( + Uri contentUri, + Handler handler + ) { + super(handler); + mContentUri = contentUri; + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + // 触发回调 + getScreenshotChecker().onChange(mContentUri, selfChange); + } + } + + /** + * 注册 ContentResolver 内容改变监听事件 + * @param resolver {@link ContentResolver} + * @param handler {@link Handler} + * @param notifyForDescendants 是否精准匹配 Uri + * @return {@code true} success, {@code false} fail + */ + private boolean registerContentObserver( + final ContentResolver resolver, + final Handler handler, + final boolean notifyForDescendants + ) { + if (resolver == null) return false; + // 注销内容观察者 + unregisterContentObserver(resolver); + // 创建内容观察者 + mInternalObserver = new MediaContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, handler); + mExternalObserver = new MediaContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, handler); + // 注册内容观察者 + resolver.registerContentObserver( + MediaStore.Images.Media.INTERNAL_CONTENT_URI, + notifyForDescendants, mInternalObserver + ); + resolver.registerContentObserver( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + notifyForDescendants, mExternalObserver + ); + return true; + } + + /** + * 注销 ContentResolver 内容改变监听事件 + * @param resolver {@link ContentResolver} + * @return {@code true} success, {@code false} fail + */ + private boolean unregisterContentObserver(final ContentResolver resolver) { + if (resolver == null) return false; + // 注销内容观察者 + if (mInternalObserver != null) { + try { + resolver.unregisterContentObserver(mInternalObserver); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregisterContentObserver"); + } + mInternalObserver = null; + } + if (mExternalObserver != null) { + try { + resolver.unregisterContentObserver(mExternalObserver); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregisterContentObserver"); + } + mExternalObserver = null; + } + return true; + } + + // ============= + // = 对外公开方法 = + // ============= + + // 开始监听时间 + private long mStartListenTime; + // 是否判断文件名前缀 + private boolean mCheckPrefix = true; + // 截图校验接口 + private ScreenshotChecker mScreenshotChecker; + // 截图校验成功回调接口 + private OnScreenshotListener mListener; + + /** + * 获取开始监听时间 + * @return 开始监听时间 + */ + public long getStartListenTime() { + return mStartListenTime; + } + + /** + * 是否判断文件名前缀 + * @return {@code true} yes, {@code false} no + */ + public boolean isCheckPrefix() { + return mCheckPrefix; + } + + /** + * 设置是否判断文件名前缀 + * @param checkPrefix 是否判断文件名前缀 + * @return {@link ScreenshotUtils} + */ + public ScreenshotUtils setCheckPrefix(final boolean checkPrefix) { + this.mCheckPrefix = checkPrefix; + return this; + } + + /** + * 获取截图校验接口 + * @return {@link ScreenshotChecker} + */ + public ScreenshotChecker getScreenshotChecker() { + if (mScreenshotChecker != null) { + return mScreenshotChecker; + } + return CHECKER; + } + + /** + * 设置截图校验接口 + * @param checker {@link ScreenshotChecker} + * @return {@link ScreenshotUtils} + */ + public ScreenshotUtils setScreenshotChecker(final ScreenshotChecker checker) { + this.mScreenshotChecker = checker; + return this; + } + + /** + * 获取截图校验成功回调接口 + * @return {@link OnScreenshotListener} + */ + public OnScreenshotListener getListener() { + return mListener; + } + + /** + * 设置截图校验成功回调接口 + * @param listener {@link OnScreenshotListener} + * @return {@link ScreenshotUtils} + */ + public ScreenshotUtils setListener(final OnScreenshotListener listener) { + this.mListener = listener; + return this; + } + + // = + + /** + * 启动截图监听 + * @return {@code true} success, {@code false} fail + */ + public boolean startListener() { + return startListener(true, DevUtils.getHandler()); + } + + /** + * 启动截图监听 + * @param notifyForDescendants 是否精准匹配 Uri + * @return {@code true} success, {@code false} fail + */ + public boolean startListener(final boolean notifyForDescendants) { + return startListener(notifyForDescendants, DevUtils.getHandler()); + } + + /** + * 启动截图监听 + * @param notifyForDescendants 是否精准匹配 Uri + * @param handler {@link Handler} + * @return {@code true} success, {@code false} fail + */ + public boolean startListener( + final boolean notifyForDescendants, + final Handler handler + ) { + this.mStartListenTime = System.currentTimeMillis(); + return registerContentObserver( + ResourceUtils.getContentResolver(), handler, notifyForDescendants + ); + } + + /** + * 停止截图监听 + * @return {@code true} success, {@code false} fail + */ + public boolean stopListener() { + return unregisterContentObserver(ResourceUtils.getContentResolver()); + } + + // ========== + // = 内部方法 = + // ========== + + // 读取媒体数据库时需要读取的列 + private static final String[] MEDIA_PROJECTIONS = { + MediaStore.Images.ImageColumns._ID, + MediaStore.Images.ImageColumns.DATA, + MediaStore.Images.ImageColumns.DATE_TAKEN, + }; + // 排序字段 + public static final String SORT_ORDER = MediaStore.Images.ImageColumns.DATE_ADDED + " desc limit 1"; + // 截图关键字前缀判断 + public static final String PREFIX_SCREEN = "screen"; + // 检测间隔时间 + public static final long INTERVAL_TIME = 8000L; + + // 截图校验接口 + public static final ScreenshotChecker CHECKER = new ScreenshotChecker() { + @Override + public void onChange( + Uri contentUri, + boolean selfChange + ) { + handleMediaContentChange(contentUri, selfChange, SORT_ORDER, this); + } + + @Override + public void onChecker( + Uri contentUri, + boolean selfChange, + long rowId, + String dataPath, + long dateTaken + ) { + handleMediaChecker( + contentUri, selfChange, rowId, dataPath, dateTaken, + getInstance().mStartListenTime, INTERVAL_TIME, + getInstance().isCheckPrefix(), PREFIX_SCREEN, + getInstance().getListener() + ); + } + }; + + /** + * 内容变更处理 + * @param contentUri 监听 Uri + * @param selfChange True if this is a self-change notification + * @param sortOrder 排序方式 + * @param checker 搜索成功则会触发 {@link ScreenshotChecker#onChecker} 方法 + */ + public static void handleMediaContentChange( + final Uri contentUri, + final boolean selfChange, + final String sortOrder, + final ScreenshotChecker checker + ) { + Cursor cursor = ContentResolverUtils.query(contentUri, MEDIA_PROJECTIONS, + null, null, sortOrder + ); + try { + if (cursor == null) { + LogPrintUtils.dTag(TAG, "搜索失败 uri: %s, projection: %s", + contentUri, Arrays.toString(MEDIA_PROJECTIONS) + ); + return; + } + if (!cursor.moveToFirst()) { + LogPrintUtils.dTag(TAG, "搜索成功, 但无符合条件数据 uri: %s, projection: %s", + contentUri, Arrays.toString(MEDIA_PROJECTIONS) + ); + return; + } + // 获取数据 + long rowId = cursor.getLong( + cursor.getColumnIndex(MediaStore.Images.ImageColumns._ID) + ); + String data = cursor.getString( + cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA) + ); + long dateTaken = cursor.getLong( + cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN) + ); + if (checker != null) { + checker.onChecker( + contentUri, selfChange, rowId, data, dateTaken + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "handleMediaContentChange"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + } + + /** + * 内容校验处理 + * @param contentUri 监听 Uri + * @param selfChange True if this is a self-change notification + * @param rowId 数据 id + * @param dataPath 数据路径 ( 截图路径 ) + * @param dateTaken 截图时间 + * @param startListenTime 开始监听时间 ( 防止出现开始监听时间前的资源 ) + * @param intervalTime 文件创建时间间隔 + * @param checkPrefix 是否判断文件名前缀 + * @param keyWork 文件名前缀判断 + * @param listener 校验成功则会触发 {@link OnScreenshotListener#onScreenshot} 方法 + * @return {@code true} yes, {@code false} no + */ + public static boolean handleMediaChecker( + final Uri contentUri, + final boolean selfChange, + final long rowId, + final String dataPath, + final long dateTaken, + final long startListenTime, + final long intervalTime, + final boolean checkPrefix, + final String keyWork, + final OnScreenshotListener listener + ) { + // ========== + // = 时间判断 = + // ========== + + if (dateTaken <= 0) { + LogPrintUtils.dTag(TAG, "创建时间异常 dateTaken: %s", dateTaken); + return false; + } + + if (dateTaken < startListenTime) { // 创建时间小于开始监听时间 + LogPrintUtils.dTag(TAG, + "开始监听时间校验不通过 dateTaken: %s, startListenTime: %s, diff: %s", + dateTaken, startListenTime, (dateTaken - startListenTime) + ); + return false; + } + + // 获取当前时间 + long curTime = System.currentTimeMillis(); + if (curTime - dateTaken > intervalTime) { // 文件创建时间超过间隔时间 + LogPrintUtils.dTag(TAG, + "文件间隔时间校验不通过 dateTaken: %s, curTime: %s, intervalTime: %s, diff: %s", + dateTaken, curTime, intervalTime, (curTime - dateTaken) + ); + return false; + } + + // ========== + // = 文件前缀 = + // ========== + + if (checkPrefix) { // 如果检验前缀才进行处理 + String fileName = FileUtils.getFileName(dataPath); + // 前缀判断结果 + boolean checkResult = ( + fileName != null && keyWork != null + && fileName.toLowerCase().startsWith(keyWork.toLowerCase()) + ); + if (!checkResult) { + LogPrintUtils.dTag(TAG, + "文件前缀校验不通过 dataPath: %s, fileName: %s, keyWork: %s", + dataPath, fileName, keyWork + ); + return false; + } + } + + // 触发回调 + if (listener != null) { + listener.onScreenshot(contentUri, selfChange, rowId, dataPath, dateTaken); + } + return true; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ServiceUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ServiceUtils.java new file mode 100644 index 0000000000..415acf1b6a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ServiceUtils.java @@ -0,0 +1,222 @@ +package dev.utils.app; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningServiceInfo; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 服务相关工具类 + * @author Ttt + */ +public final class ServiceUtils { + + private ServiceUtils() { + } + + // 日志 TAG + private static final String TAG = ServiceUtils.class.getSimpleName(); + + // ================= + // = 判断服务是否运行 = + // ================= + + /** + * 判断服务是否运行 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isServiceRunning(final Class clazz) { + return (clazz != null) && isServiceRunning(clazz.getName()); + } + + /** + * 判断服务是否运行 + * @param className package.ServiceClassName ( class.getName() ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isServiceRunning(final String className) { + if (className == null) return false; + try { + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningServices(Integer.MAX_VALUE); + for (RunningServiceInfo info : lists) { + if (className.equals(info.service.getClassName())) return true; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isServiceRunning"); + } + return false; + } + + // = + + /** + * 获取所有运行的服务 + * @return 服务名集合 + */ + public static Set getAllRunningService() { + try { + Set names = new HashSet<>(); + ActivityManager activityManager = AppUtils.getActivityManager(); + List lists = activityManager.getRunningServices(Integer.MAX_VALUE); + for (RunningServiceInfo info : lists) { + names.add(info.service.getClassName()); + } + return names; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAllRunningService"); + } + return Collections.emptySet(); + } + + // ========== + // = 启动服务 = + // ========== + + /** + * 启动服务 + * @param className package.ServiceClassName ( class.getName() ) + * @return {@code true} success, {@code false} fail + */ + public static boolean startService(final String className) { + try { + return startService(Class.forName(className)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startService"); + } + return false; + } + + /** + * 启动服务 + * @param clazz {@link Class} + * @return {@code true} success, {@code false} fail + */ + public static boolean startService(final Class clazz) { + try { + return AppUtils.startService(new Intent(DevUtils.getContext(), clazz)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startService"); + } + return false; + } + + // ========== + // = 停止服务 = + // ========== + + /** + * 停止服务 + * @param className package.ServiceClassName ( class.getName() ) + * @return {@code true} success, {@code false} fail + */ + public static boolean stopService(final String className) { + try { + return stopService(Class.forName(className)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopService"); + return false; + } + } + + /** + * 停止服务 + * @param clazz {@link Class} + * @return {@code true} success, {@code false} fail + */ + public static boolean stopService(final Class clazz) { + try { + return AppUtils.stopService(new Intent(DevUtils.getContext(), clazz)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stopService"); + return false; + } + } + + // ========== + // = 绑定服务 = + // ========== + + /** + * 绑定服务 + * @param className package.ServiceClassName ( class.getName() ) + * @param conn {@link ServiceConnection} + * @param flags 绑定选项 + * {@link Context#BIND_AUTO_CREATE} + * {@link Context#BIND_DEBUG_UNBIND} + * {@link Context#BIND_NOT_FOREGROUND} + * {@link Context#BIND_ABOVE_CLIENT} + * {@link Context#BIND_ALLOW_OOM_MANAGEMENT} + * {@link Context#BIND_WAIVE_PRIORITY} + * @return {@code true} success, {@code false} fail + */ + public static boolean bindService( + final String className, + final ServiceConnection conn, + final int flags + ) { + try { + return bindService(Class.forName(className), conn, flags); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "bindService"); + } + return false; + } + + /** + * 绑定服务 + * @param clazz {@link Class} + * @param conn {@link ServiceConnection} + * @param flags 绑定选项 + * {@link Context#BIND_AUTO_CREATE} + * {@link Context#BIND_DEBUG_UNBIND} + * {@link Context#BIND_NOT_FOREGROUND} + * {@link Context#BIND_ABOVE_CLIENT} + * {@link Context#BIND_ALLOW_OOM_MANAGEMENT} + * {@link Context#BIND_WAIVE_PRIORITY} + * @return {@code true} success, {@code false} fail + */ + public static boolean bindService( + final Class clazz, + final ServiceConnection conn, + final int flags + ) { + try { + Intent intent = new Intent(DevUtils.getContext(), clazz); + DevUtils.getContext().bindService(intent, conn, flags); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "bindService"); + } + return false; + } + + // ========== + // = 解绑服务 = + // ========== + + /** + * 解绑服务 + * @param conn {@link ServiceConnection} + * @return {@code true} success, {@code false} fail + */ + public static boolean unbindService(final ServiceConnection conn) { + try { + DevUtils.getContext().unbindService(conn); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unbindService"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ShapeUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ShapeUtils.java new file mode 100644 index 0000000000..8d1e44130b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ShapeUtils.java @@ -0,0 +1,593 @@ +package dev.utils.app; + +import android.content.res.ColorStateList; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.RequiresApi; + +/** + * detail: Shape 工具类 + * @author Ttt + *
+ *     Android XML shape 标签使用详解 ( apk 瘦身, 减少内存好帮手 )
+ *     @see 
+ *     Android GradientDrawable 静态使用和动态使用
+ *     @see 
+ *     Android 开发之 Shape 详细解读
+ *     @see 
+ *     layer-list 的基本使用介绍
+ *     @see 
+ *     使用 layer-list 实现特殊的效果
+ *     @see 
+ * 
+ *
+ *     
+ *     
+ *     
+ *     
+ *     
+ *     
+ *     
+ * 
+ *
+ *     gradient android:angle
+ *     当 angle 为 0 时, 颜色渐变方向是从左往右
+ *     当 angle 为 90 时, 颜色渐变方向是从下往上
+ *     当 angle 为 180 时, 颜色渐变方向是从右往左
+ *     当 angle 为 270 时, 颜色渐变方向是从上往下
+ * 
+ */ +public final class ShapeUtils { + + // Shape Drawable + private final GradientDrawable mDrawable; + + /** + * 构造函数 + */ + private ShapeUtils() { + this(new GradientDrawable()); + } + + /** + * 构造函数 + * @param drawable {@link GradientDrawable} + */ + private ShapeUtils(GradientDrawable drawable) { + if (drawable == null) drawable = new GradientDrawable(); + this.mDrawable = drawable; + } + + /** + * 获取 GradientDrawable + * @return {@link GradientDrawable} + */ + public GradientDrawable getDrawable() { + return mDrawable; + } + + /** + * 设置 Drawable 背景 + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public T setDrawable(final T view) { + ViewUtils.setBackground(view, mDrawable); + return view; + } + + /** + * 获取渐变角度 + * @param angle 角度 + * @return {@link GradientDrawable.Orientation} + */ + public static GradientDrawable.Orientation getOrientation(final int angle) { + switch (angle) { + case 0: + return GradientDrawable.Orientation.LEFT_RIGHT; + case 45: + return GradientDrawable.Orientation.BL_TR; + case 90: + return GradientDrawable.Orientation.BOTTOM_TOP; + case 135: + return GradientDrawable.Orientation.BR_TL; + case 180: + return GradientDrawable.Orientation.RIGHT_LEFT; + case 225: + return GradientDrawable.Orientation.TR_BL; + case 270: + return GradientDrawable.Orientation.TOP_BOTTOM; + case 315: + return GradientDrawable.Orientation.TL_BR; + } + return null; + } + + // ========== + // = 静态构建 = + // ========== + + /** + * 创建 Shape + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape() { + return new ShapeUtils(); + } + + /** + * 创建 Shape + * @param drawable {@link GradientDrawable} + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape(final GradientDrawable drawable) { + return new ShapeUtils(drawable); + } + + /** + * 创建圆角 Shape + * @param radius 圆角值 + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape(final float radius) { + return new ShapeUtils().setCornerRadius(radius); + } + + /** + * 创建圆角 Shape + * @param radius 圆角值 + * @param color 背景色 + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape( + final float radius, + @ColorInt final int color + ) { + return new ShapeUtils().setCornerRadius(radius).setColor(color); + } + + /** + * 创建渐变 Shape + * @param angle 渐变角度 + * @param colors 渐变颜色 + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape( + final int angle, + @ColorInt final int[] colors + ) { + return new ShapeUtils(new GradientDrawable(getOrientation(angle), colors)); + } + + /** + * 创建渐变 Shape + * @param orientation 渐变角度 + * @param colors 渐变颜色 + * @return {@link ShapeUtils} + */ + public static ShapeUtils newShape( + final GradientDrawable.Orientation orientation, + @ColorInt final int[] colors + ) { + return new ShapeUtils(new GradientDrawable(orientation, colors)); + } + + // ========== + // = 设置方法 = + // ========== + + /** + * 设置透明度 + * @param alpha 透明度 + * @return {@link ShapeUtils} + */ + public ShapeUtils setAlpha(@IntRange(from = 0, to = 255) final int alpha) { + if (mDrawable != null) mDrawable.setAlpha(alpha); + return this; + } + + // ========= + // = shape = + // ========= + + /** + * 设置形状类型 + * @param shape rectangle | oval | line | ring + * @return {@link ShapeUtils} + */ + public ShapeUtils setShape(final int shape) { + if (mDrawable != null) mDrawable.setShape(shape); + return this; + } + + /** + * 设置内环半径 + * @param innerRadius 内环半径 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setInnerRadius(final int innerRadius) { + if (mDrawable != null) mDrawable.setInnerRadius(innerRadius); + return this; + } + + /** + * 设置内环半径相对于环的宽度比例 + * @param innerRadiusRatio 内环半径宽度比例 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setInnerRadiusRatio(final float innerRadiusRatio) { + if (mDrawable != null) mDrawable.setInnerRadiusRatio(innerRadiusRatio); + return this; + } + + /** + * 设置环厚度 + * @param thickness 环厚度 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setThickness(final int thickness) { + if (mDrawable != null) mDrawable.setThickness(thickness); + return this; + } + + /** + * 设置环厚度相对于环的宽度比例 + * @param thicknessRatio 环厚度宽度比例 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setThicknessRatio(final float thicknessRatio) { + if (mDrawable != null) mDrawable.setThicknessRatio(thicknessRatio); + return this; + } + + // ========= + // = solid = + // ========= + + /** + * 设置背景填充颜色 + * @param argb 背景色 + * @return {@link ShapeUtils} + */ + public ShapeUtils setColor(@ColorInt final int argb) { + if (mDrawable != null) mDrawable.setColor(argb); + return this; + } + + /** + * 设置背景填充颜色 + * @param colors 背景色 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ShapeUtils setColor(final ColorStateList colors) { + if (mDrawable != null) mDrawable.setColor(colors); + return this; + } + + // ========== + // = stroke = + // ========== + + /** + * 设置描边 + * @param width 描边宽度 + * @param color 描边颜色 + * @return {@link ShapeUtils} + */ + public ShapeUtils setStroke( + final int width, + @ColorInt final int color + ) { + if (mDrawable != null) mDrawable.setStroke(width, color); + return this; + } + + /** + * 设置描边 + * @param width 描边宽度 + * @param colors 描边颜色 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ShapeUtils setStroke( + final int width, + final ColorStateList colors + ) { + if (mDrawable != null) mDrawable.setStroke(width, colors); + return this; + } + + /** + * 设置描边 + * @param width 描边宽度 + * @param color 描边颜色 + * @param dashWidth 描边虚线宽度, 值为 0 时, 表示为实线, 值大于 0 则为虚线 + * @param dashGap 描边为虚线时, 虚线之间的间隔 即「 - - - - 」 + * @return {@link ShapeUtils} + */ + public ShapeUtils setStroke( + final int width, + @ColorInt final int color, + final float dashWidth, + final float dashGap + ) { + if (mDrawable != null) mDrawable.setStroke(width, color, dashWidth, dashGap); + return this; + } + + /** + * 设置描边 + * @param width 描边宽度 + * @param colors 描边颜色 + * @param dashWidth 描边虚线宽度, 值为 0 时, 表示为实线, 值大于 0 则为虚线 + * @param dashGap 描边为虚线时, 虚线之间的间隔 即「 - - - - 」 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ShapeUtils setStroke( + final int width, + final ColorStateList colors, + final float dashWidth, + final float dashGap + ) { + if (mDrawable != null) mDrawable.setStroke(width, colors, dashWidth, dashGap); + return this; + } + + // =========== + // = corners = + // =========== + + /** + * 设置圆角 + * @param radius 圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadius(final float radius) { + if (mDrawable != null) mDrawable.setCornerRadius(radius); + return this; + } + + /** + * 设置圆角 + * @param leftTop 左上圆角值 + * @param rightTop 右上圆角值 + * @param rightBottom 右下圆角值 + * @param leftBottom 左下圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadius( + final float leftTop, + final float rightTop, + final float rightBottom, + final float leftBottom + ) { + if (mDrawable != null) { + // radii 数组分别指定四个圆角的半径, 每个角可以指定 [X_Radius, Y_Radius] + // 四个圆角的顺序为左上、右上、右下、左下, 如果 X_Radius, Y_Radius 为 0 表示还是直角 + mDrawable.setCornerRadii(new float[]{leftTop, leftTop, rightTop, rightTop, rightBottom, rightBottom, leftBottom, leftBottom}); + } + return this; + } + + /** + * 设置 leftTop、leftBottom 圆角 + * @param left leftTop、leftBottom 圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadiusLeft(final float left) { + setCornerRadius(left, 0, 0, left); + return this; + } + + /** + * 设置 rightTop、rightBottom 圆角 + * @param right rightTop、rightBottom 圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadiusRight(final float right) { + setCornerRadius(0, right, right, 0); + return this; + } + + /** + * 设置 leftTop、rightTop 圆角 + * @param top leftTop、rightTop 圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadiusTop(final float top) { + setCornerRadius(top, top, 0, 0); + return this; + } + + /** + * 设置 leftBottom、rightBottom 圆角 + * @param bottom leftBottom、rightBottom 圆角值 + * @return {@link ShapeUtils} + */ + public ShapeUtils setCornerRadiusBottom(final float bottom) { + setCornerRadius(0, 0, bottom, bottom); + return this; + } + + // ============ + // = gradient = + // ============ + + /** + * 设置渐变颜色 + * @param colors [ 起始颜色、结束颜色 ] | [ 起始颜色、中间颜色、结束颜色 ] + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public ShapeUtils setColors(@ColorInt final int[] colors) { + if (mDrawable != null) mDrawable.setColors(colors); + return this; + } + + /** + * 设置渐变类型 + * @param gradient 渐变类型 linear ( 线性渐变 ) 、radial ( 放射性渐变 ) 、sweep ( 扫描线式渐变 ) + * @return {@link ShapeUtils} + */ + public ShapeUtils setGradientType(final int gradient) { + if (mDrawable != null) mDrawable.setGradientType(gradient); + return this; + } + + /** + * 设置渐变角度 + * @param orientation 渐变角度 ( 当 angle = 0 时, 渐变色是从左向右, 然后逆时针方向转, 当 angle = 90 时为从下往上, angle 必须为 45 的整数倍 ) + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public ShapeUtils setOrientation(final GradientDrawable.Orientation orientation) { + if (mDrawable != null) mDrawable.setOrientation(orientation); + return this; + } + + /** + * 设置渐变角度 + * @param angle 渐变角度 ( 当 angle = 0 时, 渐变色是从左向右, 然后逆时针方向转, 当 angle = 90 时为从下往上, angle 必须为 45 的整数倍 ) + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public ShapeUtils setOrientation(final int angle) { + return setOrientation(getOrientation(angle)); + } + + /** + * 设置渐变中心坐标值 + * @param x 渐变中心 X 点坐标的相对位置, 范围: 0 ~ 1 + * @param y 渐变中心 Y 点坐标的相对位置, 范围: 0 ~ 1 + * @return {@link ShapeUtils} + */ + public ShapeUtils setGradientCenter( + final float x, + final float y + ) { + if (mDrawable != null) mDrawable.setGradientCenter(x, y); + return this; + } + + /** + * 设置渐变色半径 + * @param gradientRadius 渐变色半径, 当 android:type="radial" 时才使用, 单独使用 android:type="radial" 会报错 + * @return {@link ShapeUtils} + */ + public ShapeUtils setGradientRadius(final float gradientRadius) { + if (mDrawable != null) mDrawable.setGradientRadius(gradientRadius); + return this; + } + + /** + * 是否使用 LevelListDrawable + * @param useLevel 使用 LevelListDrawable 对象需设置为 true, 设置为 true 无渐变, false 有渐变色 + * @return {@link ShapeUtils} + */ + public ShapeUtils setUseLevel(final boolean useLevel) { + if (mDrawable != null) mDrawable.setUseLevel(useLevel); + return this; + } + + // =========== + // = padding = + // =========== + + /** + * 设置内边距 + * @param padding 边距值 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setPadding(final int padding) { + return setPadding(padding, padding, padding, padding); + } + + /** + * 设置内边距 + * @param left 左内边距 + * @param top 上内边距 + * @param right 右内边距 + * @param bottom 下内边距 + * @return {@link ShapeUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + public ShapeUtils setPadding( + final int left, + final int top, + final int right, + final int bottom + ) { + if (mDrawable != null) mDrawable.setPadding(left, top, right, bottom); + return this; + + } + + // ======== + // = size = + // ======== + + /** + * 设置 shape drawable 宽高 + * @param width shape drawable 宽 + * @param height shape drawable 高 + * @return {@link ShapeUtils} + */ + public ShapeUtils setSize( + final int width, + final int height + ) { + if (mDrawable != null) mDrawable.setSize(width, height); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ShellUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ShellUtils.java new file mode 100644 index 0000000000..eb9720e9e3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ShellUtils.java @@ -0,0 +1,248 @@ +package dev.utils.app; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: Shell 命令工具类 + * @author Ttt + */ +public final class ShellUtils { + + private ShellUtils() { + } + + // 日志 TAG + private static final String TAG = ShellUtils.class.getSimpleName(); + + // 操作成功状态码 + private static final int SUCCESS = 0; + + /** + * 执行 shell 命令 + * @param command 待执行命令 + * @param isRoot 是否以 root 权限执行 + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final String command, + final boolean isRoot + ) { + return execCmd(new String[]{command}, isRoot, true); + } + + /** + * 执行 shell 命令 + * @param commands 多条待执行命令 + * @param isRoot 是否以 root 权限执行 + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final List commands, + final boolean isRoot + ) { + return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRoot, true); + } + + /** + * 执行 shell 命令 + * @param commands 多条待执行命令 + * @param isRoot 是否以 root 权限执行 + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final String[] commands, + final boolean isRoot + ) { + return execCmd(commands, isRoot, true); + } + + /** + * 执行 shell 命令 + * @param command 待执行命令 + * @param isRoot 是否以 root 权限执行 + * @param isNeedResultMsg 是否需要返回结果消息 (error、success message) + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final String command, + final boolean isRoot, + final boolean isNeedResultMsg + ) { + return execCmd(new String[]{command}, isRoot, isNeedResultMsg); + } + + /** + * 执行 shell 命令 + * @param commands 多条待执行命令 + * @param isRoot 是否以 root 权限执行 + * @param isNeedResultMsg 是否需要结果消息 (error、success message) + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final List commands, + final boolean isRoot, + final boolean isNeedResultMsg + ) { + return execCmd(commands == null ? null : commands.toArray(new String[]{}), isRoot, isNeedResultMsg); + } + + /** + * 执行 shell 命令 + * @param commands 多条待执行命令 + * @param isRoot 是否以 root 权限执行 + * @param isNeedResultMsg 是否需要结果消息 (error、success message) + * @return {@link CommandResult} + */ + public static CommandResult execCmd( + final String[] commands, + final boolean isRoot, + final boolean isNeedResultMsg + ) { + int result = -1; + if (commands == null || commands.length == 0) { + return new CommandResult(result, null, null); + } + Process process = null; + DataOutputStream dos = null; + String successMsg = null; + String errorMsg = null; + try { + process = Runtime.getRuntime().exec(isRoot ? "su" : "sh"); + dos = new DataOutputStream(process.getOutputStream()); + // 循环写入待执行命令 + for (String command : commands) { + if (command == null) continue; + dos.write(command.getBytes()); + dos.writeBytes(DevFinal.SYMBOL.NEW_LINE); + dos.flush(); + } + dos.writeBytes("exit" + DevFinal.SYMBOL.NEW_LINE); + dos.flush(); + // 为了避免 Process.waitFor() 导致主线程堵塞问题, 最好读取信息 + if (isNeedResultMsg) { // 如果程序不断在向输出流和错误流写数据, 而 JVM 不读取的话, 当缓冲区满之后将无法继续写入数据, 最终造成阻塞在 waitFor() 这里 + // 读取成功数据 + successMsg = consumeInputStream( + new InputStreamReader(process.getInputStream(), DevFinal.ENCODE.UTF_8) + ); + // 读取异常数据 + errorMsg = consumeInputStream( + new InputStreamReader(process.getErrorStream(), DevFinal.ENCODE.UTF_8) + ); + } + // 执行结果状态码 + result = process.waitFor(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "execCmd"); + } finally { + CloseUtils.closeIOQuietly(dos); + // 进程销毁 + if (process != null) { + process.destroy(); + } + } + return new CommandResult(result, successMsg, errorMsg); + } + + /** + * 消费 InputStream 并且返回字符串 + * @param reader {@link InputStreamReader} + * @return 读取流数据 + */ + private static String consumeInputStream(final InputStreamReader reader) { + BufferedReader br = null; + try { + StringBuilder builder = new StringBuilder(); + br = new BufferedReader(reader); + String str; + if ((str = br.readLine()) != null) { + builder.append(str); + while ((str = br.readLine()) != null) { + builder.append(DevFinal.SYMBOL.NEW_LINE).append(str); + } + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "consumeInputStream"); + } finally { + CloseUtils.closeIOQuietly(br); + } + return null; + } + + // ============ + // = 对外实体类 = + // ============ + + /** + * detail: 命令执行结果实体类 + * @author Ttt + */ + public static final class CommandResult { + + // 执行结果状态码 + public int result; + // 成功信息 + public String successMsg; + // 错误信息 + public String errorMsg; + + /** + * 构造函数 + * @param result 执行结果状态码 + * @param successMsg 成功信息 + * @param errorMsg 错误信息 + */ + public CommandResult( + final int result, + final String successMsg, + final String errorMsg + ) { + this.result = result; + this.successMsg = successMsg; + this.errorMsg = errorMsg; + } + + /** + * 判断是否执行成功 + * @return {@code true} yes, {@code false} no + */ + public boolean isSuccess() { + return result == SUCCESS; + } + + /** + * 判断是否执行成功 ( 判断 errorMsg ) + * @return {@code true} yes, {@code false} no + */ + public boolean isSuccess2() { + return result == SUCCESS && (errorMsg == null || errorMsg.length() == 0); + } + + /** + * 判断是否执行成功 ( 判断 successMsg ) + * @return {@code true} yes, {@code false} no + */ + public boolean isSuccess3() { + return result == SUCCESS && successMsg != null && successMsg.length() != 0; + } + + /** + * 判断是否执行成功 ( 判断 successMsg ) , 并且 successMsg 是否包含某个字符串 + * @param contains 待校验包含字符串 + * @return {@code true} yes, {@code false} no + */ + public boolean isSuccess4(final String contains) { + if (result == SUCCESS && successMsg != null && successMsg.length() != 0) { + return contains != null && contains.length() != 0 && successMsg.toLowerCase().contains(contains); + } + return false; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ShortCutUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ShortCutUtils.java new file mode 100644 index 0000000000..c4134d6f55 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ShortCutUtils.java @@ -0,0 +1,363 @@ +package dev.utils.app; + +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.database.Cursor; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Build; + +import androidx.annotation.AnyRes; + +import java.util.List; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: 快捷方式工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ *     
+ *     
+ *     
+ *     

+ * READ_SETTINGS 用于判断是否存在快捷图标 + *

+ * @see
+ * @see + *
+ */ +public final class ShortCutUtils { + + private ShortCutUtils() { + } + + // 日志 TAG + private static final String TAG = ShortCutUtils.class.getSimpleName(); + + // ==================== + // = 检测是否存在快捷方式 = + // ==================== + + /** + * 检测是否存在桌面快捷方式 + * @param name 快捷方式名称 + * @return {@code true} yes, {@code false} no + */ + public static boolean hasShortcut(final String name) { + Cursor cursor = null; + try { + Context context = DevUtils.getContext(); + Uri uri = Uri.parse("content://" + getAuthority(context) + "/favorites?notify=true"); + ContentResolver resolver = context.getContentResolver(); + cursor = resolver.query(uri, new String[]{"title", "iconResource"}, "title=?", new String[]{name}, null); + // 判断是否存在快捷方式 + return (cursor != null && cursor.getCount() > 0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "hasShortcut"); + } finally { + CloseUtils.closeIOQuietly(cursor); + } + return false; + } + + // ================= + // = 创建桌面快捷方式 = + // ================= + + /** + * 获取桌面快捷方式点击 Intent + * @param clazz 快捷方式点击 Intent className(class.getName()) + * @return {@link Intent} + */ + public static Intent getShortCutIntent(final Class clazz) { + return (clazz != null) ? getShortCutIntent(clazz.getName()) : null; + } + + /** + * 获取桌面快捷方式点击 Intent + * @param className 快捷方式点击 Intent className(class.getName()) + * @return {@link Intent} + */ + public static Intent getShortCutIntent(final String className) { + if (className != null) { + try { + // 快捷方式点击 Intent 跳转 + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClassName(DevUtils.getContext(), className); + shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return shortcutIntent; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getShortCutIntent"); + } + } + return null; + } + + // = + + /** + * 创建桌面快捷方式 + * @param clazz 快捷方式点击 Intent class + * @param name 快捷方式名称 + * @param icon 快捷方式图标 + * @return {@code true} success, {@code false} fail + */ + public static boolean addShortcut( + final Class clazz, + final String name, + @AnyRes final int icon + ) { + return (clazz != null) && addShortcut(clazz.getName(), name, icon); + } + + /** + * 创建桌面快捷方式 + * @param className 快捷方式点击 Intent className(class.getName()) + * @param name 快捷方式名称 + * @param icon 快捷方式图标 + * @return {@code true} success, {@code false} fail + */ + public static boolean addShortcut( + final String className, + final String name, + @AnyRes final int icon + ) { + if (className != null && name != null) { + try { + // 快捷方式点击 Intent 跳转 + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClassName(DevUtils.getContext(), className); + shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return addShortcut(shortcutIntent, name, icon); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addShortcut"); + } + } + return false; + } + + /** + * 创建桌面快捷方式 + * @param shortcutIntent 快捷方式点击 Intent 跳转 + * @param name 快捷方式名称 + * @param icon 快捷方式图标 + * @return {@code true} success, {@code false} fail + */ + public static boolean addShortcut( + final Intent shortcutIntent, + final String name, + @AnyRes final int icon + ) { + return addShortcut(shortcutIntent, name, icon, null); + } + + /** + * 创建桌面快捷方式 + * @param shortcutIntent 快捷方式点击 Intent 跳转 + * @param name 快捷方式名称 + * @param icon 快捷方式图标 + * @param pendingIntent 创建结果通知 (Android 8.0) + * @return {@code true} success, {@code false} fail + */ + public static boolean addShortcut( + final Intent shortcutIntent, + final String name, + @AnyRes final int icon, + final PendingIntent pendingIntent + ) { + if (shortcutIntent != null && name != null) { + // Android 8.0 之前 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + try { + Context context = DevUtils.getContext(); + // 快捷方式图标 + ShortcutIconResource iconRes = ShortcutIconResource.fromContext(context, icon); + // 创建快捷方式 Intent + Intent shortcut = new Intent("com.android.launcher.action.INSTALL_SHORTCUT"); + shortcut.putExtra("duplicate", false); // 不允许重复创建 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷方式名称 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); // 快捷方式点击跳转 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconRes); + // 发送广播, 创建快捷方式 + context.sendBroadcast(shortcut); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addShortcut"); + } + } else { // Android 8.0 之后 + try { + Context context = DevUtils.getContext(); + // 获取 ShortcutManager + ShortcutManager shortcutManager = AppUtils.getShortcutManager(); + // 如果默认桌面支持 requestPinShortcut(ShortcutInfo、IntentSender) 方法 + if (shortcutManager != null && shortcutManager.isRequestPinShortcutSupported()) { + // 快捷方式创建相关信息 + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(context, String.valueOf(name.hashCode())) + .setIcon(Icon.createWithResource(context, icon)) // 快捷方式图标 + .setShortLabel(name) // 快捷方式名字 + .setIntent(shortcutIntent) // 快捷方式跳转 Intent + .build(); + // 创建快捷方式 + if (pendingIntent != null) { + shortcutManager.requestPinShortcut(shortcutInfo, pendingIntent.getIntentSender()); + } else { + shortcutManager.requestPinShortcut(shortcutInfo, null); + } + return true; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addShortcut"); + } + } + } + return false; + } + + // ================= + // = 删除桌面快捷方式 = + // ================= + + /** + * 删除桌面快捷方式 + * @param clazz 快捷方式点击 Intent class + * @param name 快捷方式名称 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteShortcut( + final Class clazz, + final String name + ) { + return (clazz != null) && deleteShortcut(clazz.getName(), name); + } + + /** + * 删除桌面快捷方式 + *
+     *     Android 6.0 以后因存在安全隐患 Google 移除了 UninstallShortcutReceiver 无法进行删除桌面快捷
+     * 
+ * @param className 快捷方式点击 Intent className(class.getName()) + * @param name 快捷方式名称 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteShortcut( + final String className, + final String name + ) { + if (className != null && name != null) { + try { + Context context = DevUtils.getContext(); + // 快捷方式 Intent + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + shortcutIntent.setClassName(context, className); + // 删除快捷方式 Intent + Intent shortcut = new Intent("com.android.launcher.action.UNINSTALL_SHORTCUT"); + shortcut.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); // 快捷方式名称 + shortcut.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + context.sendBroadcast(shortcut); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "deleteShortcut"); + } + } + return false; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Authority + * @param context {@link Context} + * @return Authority + */ + private static String getAuthority(final Context context) { + // 获取 Authority + String authority = getAuthorityFromPermission(context); + // 如果等于 null + if (authority == null) { + int version = android.os.Build.VERSION.SDK_INT; + if (version < 8) { // Android 2.1.x (API 7) 以及以下的 + authority = "com.android.launcher.settings"; + } else if (version < 19) { // Android 4.4 以下 + authority = "com.android.launcher2.settings"; + } else { // 4.4 以及以上 + authority = "com.android.launcher3.settings"; + } + return authority; + } + return authority; + } + + /** + * 通过权限获取 Authority + * @param context {@link Context} + * @return Authority + */ + private static String getAuthorityFromPermission(final Context context) { + // 权限判断 + String[] permissions = {"com.android.launcher.permission.WRITE_SETTINGS", "com.android.launcher.permission.READ_SETTINGS"}; + // 遍历权限 + for (String permission : permissions) { + try { + String authority = getAuthorityFromPermission(context, permission); + if (authority != null) { + return authority; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAuthorityFromPermission"); + } + } + return null; + } + + /** + * 通过权限获取 Authority + *
+     *     android 默认的 AUTHORITY 在 2.2 以后是 com.android.launcher2.settings
+     *     但是不同的厂商可能会做不同的修改, 由于不同的厂商 uri 的前缀不同
+     *     所以需要去查询 provider 获取真实的 content 的 uri 前缀
+     * 
+ * @param context {@link Context} + * @param permission 权限 + * @return Authority + */ + private static String getAuthorityFromPermission( + final Context context, + final String permission + ) { + if (permission != null) { + List lists = context.getPackageManager().getInstalledPackages(PackageManager.GET_PROVIDERS); + if (lists != null) { + for (PackageInfo packageInfo : lists) { + ProviderInfo[] providers = packageInfo.providers; + if (providers != null) { + for (ProviderInfo provider : providers) { + if (permission.equals(provider.readPermission)) { + return provider.authority; + } + if (permission.equals(provider.writePermission)) { + return provider.authority; + } + } + } + } + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/SignaturesUtils.java b/lib/DevApp/src/main/java/dev/utils/app/SignaturesUtils.java new file mode 100644 index 0000000000..7865c8a166 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/SignaturesUtils.java @@ -0,0 +1,334 @@ +package dev.utils.app; + +import android.content.pm.Signature; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.security.MessageDigest; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import javax.security.auth.x500.X500Principal; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; + +/** + * detail: 签名工具类 ( 获取 APP 签名信息 ) + * @author Ttt + *
+ *     Android 的 APK 应用签名机制以及读取签名的方法
+ *     @see 
+ * 
+ */ +public final class SignaturesUtils { + + private SignaturesUtils() { + } + + // 日志 TAG + private static final String TAG = SignaturesUtils.class.getSimpleName(); + + /** + * 判断签名是 debug 签名还是 release 签名 + * 检测应用程序是否是用 "CN=Android Debug,O=Android,C=US" 的 debug 信息进行签名 + */ + private static final X500Principal DEBUG_DN = new X500Principal("CN=Android Debug,O=Android,C=US"); + + /** + * 获取 APP Signature + * @return {@link Signature} 数组 + */ + public static Signature[] getAppSignature() { + return AppUtils.getAppSignature(); + } + + /** + * 获取 APP Signature + * @param packageName 应用包名 + * @return {@link Signature} 数组 + */ + public static Signature[] getAppSignature(final String packageName) { + return AppUtils.getAppSignature(packageName); + } + + /** + * 获取 MD5 签名 + * @param signatures {@link Signature}[] + * @return MD5 签名 + */ + public static String signatureMD5(final Signature[] signatures) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + if (signatures != null) { + for (Signature sign : signatures) { + if (sign != null) { + digest.update(sign.toByteArray()); + } + } + } + return ConvertUtils.toHexString(digest.digest()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "signatureMD5"); + return ""; + } + } + + /** + * 获取签名 SHA1 加密字符串 + * @param signatures {@link Signature}[] + * @return 签名 SHA1 加密字符串 + */ + public static String signatureSHA1(final Signature[] signatures) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + if (signatures != null) { + for (Signature sign : signatures) { + if (sign != null) { + digest.update(sign.toByteArray()); + } + } + } + return ConvertUtils.toHexString(digest.digest()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "signatureSHA1"); + return ""; + } + } + + /** + * 获取签名 SHA256 加密字符串 + * @param signatures {@link Signature}[] + * @return 签名 SHA256 加密字符串 + */ + public static String signatureSHA256(final Signature[] signatures) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + if (signatures != null) { + for (Signature sign : signatures) { + if (sign != null) { + digest.update(sign.toByteArray()); + } + } + } + return ConvertUtils.toHexString(digest.digest()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "signatureSHA256"); + return ""; + } + } + + /** + * 判断 debug 签名还是 release 签名 + * @param signatures {@link Signature}[] + * @return {@code true} debug.keystore, {@code false} release.keystore + */ + public static boolean isDebuggable(final Signature[] signatures) { + // 默认属于 debug 签名 + boolean debuggable = true; + if (signatures != null) { + try { + for (Signature sign : signatures) { + if (sign != null) { + X509Certificate cert = getX509Certificate(sign); + if (cert != null && cert.getSubjectX500Principal() != null) { + debuggable = DEBUG_DN.equals(cert.getSubjectX500Principal()); + if (debuggable) { + break; + } + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isDebuggable"); + } + } + return debuggable; + } + + /** + * 获取证书对象 + * @param signatures {@link Signature}[] + * @return {@link X509Certificate} + */ + public static X509Certificate getX509Certificate(final Signature[] signatures) { + if (signatures != null && signatures.length != 0) { + return getX509Certificate(signatures[0]); + } + return null; + } + + /** + * 获取证书对象 + * @param signature {@link Signature} + * @return {@link X509Certificate} + */ + public static X509Certificate getX509Certificate(final Signature signature) { + if (signature != null) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + ByteArrayInputStream bais = new ByteArrayInputStream(signature.toByteArray()); + return (X509Certificate) cf.generateCertificate(bais); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getX509Certificate"); + } + } + return null; + } + + /** + * 打印签名信息 + * @param signatures {@link Signature}[] + * @return 签名信息 {@link List} + */ + public static List> printSignatureInfo(final Signature[] signatures) { + List> lists = new ArrayList<>(); + try { + // 格式化日期 + SimpleDateFormat sdf = DateUtils.getDefaultFormat(); + // 遍历获取 + for (Signature sign : signatures) { + if (sign != null) { + X509Certificate cert = getX509Certificate(sign); + + // 证书生成日期 + Date notBefore = cert.getNotBefore(); + // 证书有效期 + Date notAfter = cert.getNotAfter(); + // 设置有效期 + StringBuilder builder = new StringBuilder(); + builder.append(sdf.format(notBefore)); + builder.append(" to "); // 至 + builder.append(sdf.format(notAfter)); + builder.append(DevFinal.SYMBOL.NEW_LINE_X2); + builder.append(notBefore); + builder.append(" to "); + builder.append(notAfter); + // 保存有效期转换信息 + String effectiveStr = builder.toString(); + // 证书是否过期 + boolean effective = false; + try { + cert.checkValidity(); + // CertificateExpiredException ( 证书已过期 ) + // CertificateNotYetValidException ( 证书不再有效 ) + } catch (CertificateExpiredException ce) { + effective = true; + } catch (CertificateNotYetValidException ce) { + effective = true; + } + // 证书发布方 + String certPrincipal = cert.getIssuerX500Principal().toString(); + // 证书版本号 + String certVersion = String.valueOf(cert.getVersion()); + // 证书算法名称 + String certSigAlgName = cert.getSigAlgName(); + // 证书算法 OID + String certSigAlgOID = cert.getSigAlgOID(); + // 证书机器码 + String certSerialnumber = cert.getSerialNumber().toString(); + // 证书 DER 编码 + String certDERCode = null; + try { + // 证书 DER 编码 + certDERCode = ConvertUtils.toHexString(cert.getTBSCertificate()); + } catch (CertificateEncodingException ignored) { + } + // 证书公钥 + String pubKey = cert.getPublicKey().toString(); + + // 保存信息 + Map maps = new LinkedHashMap<>(); + maps.put("证书有效期信息", effectiveStr); + maps.put("证书是否过期", Boolean.toString(effective)); + maps.put("证书发布方", certPrincipal); + maps.put("证书版本号", certVersion); + maps.put("证书算法名称", certSigAlgName); + maps.put("证书算法 OID", certSigAlgOID); + maps.put("证书机器码", certSerialnumber); + maps.put("证书 DER 编码", certDERCode); + maps.put("证书公钥", pubKey); + lists.add(maps); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "printSignatureInfo"); + } + return lists; + } + + // = + + /** + * 从 APK 中读取签名 + * @param file 文件 + * @return {@link Signature}[] + */ + public static Signature[] getSignaturesFromApk(final File file) { + Certificate[] certificates = getCertificateFromApk(file); + if (certificates == null) return null; + try { + return new Signature[]{new Signature(certificates[0].getEncoded())}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSignaturesFromApk"); + } + return null; + } + + /** + * 从 APK 中读取签名 + * @param file 文件 + * @return {@link Certificate}[] + */ + public static Certificate[] getCertificateFromApk(final File file) { + try { + JarFile jarFile = new JarFile(file); + JarEntry je = jarFile.getJarEntry("AndroidManifest.xml"); + byte[] readBuffer = new byte[8192]; + return loadCertificates(jarFile, je, readBuffer); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getCertificateFromApk"); + } + return null; + } + + /** + * 加载文件, 获取签名信息 + * @param jarFile {@link JarFile} + * @param jarEntry {@link JarEntry} + * @param readBuffer 文件 Buffer + * @return {@link Certificate}[] + */ + private static Certificate[] loadCertificates( + final JarFile jarFile, + final JarEntry jarEntry, + final byte[] readBuffer + ) { + try { + InputStream is = jarFile.getInputStream(jarEntry); + while (is.read(readBuffer, 0, readBuffer.length) != -1) { + } + CloseUtils.closeIOQuietly(is); + return jarEntry != null ? jarEntry.getCertificates() : null; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "loadCertificates"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/SizeUtils.java b/lib/DevApp/src/main/java/dev/utils/app/SizeUtils.java new file mode 100644 index 0000000000..6d434ca260 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/SizeUtils.java @@ -0,0 +1,436 @@ +package dev.utils.app; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 大小工具类 ( dp, px, sp 转换、View 获取宽高等 ) + * @author Ttt + */ +public final class SizeUtils { + + private SizeUtils() { + } + + // 日志 TAG + private static final String TAG = SizeUtils.class.getSimpleName(); + + /** + * dp 转 px + * @param dpValue 待转换值 + * @return 转换后的值 + */ + public static int dp2px(final float dpValue) { + return dp2px(DevUtils.getContext(), dpValue); + } + + /** + * dp 转 px ( float ) + * @param dpValue 待转换值 + * @return 转换后的值 + */ + public static float dp2pxf(final float dpValue) { + return dp2pxf(DevUtils.getContext(), dpValue); + } + + /** + * px 转 dp + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static int px2dp(final float pxValue) { + return px2dp(DevUtils.getContext(), pxValue); + } + + /** + * px 转 dp ( float ) + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static float px2dpf(final float pxValue) { + return px2dpf(DevUtils.getContext(), pxValue); + } + + /** + * sp 转 px + * @param spValue 待转换值 + * @return 转换后的值 + */ + public static int sp2px(final float spValue) { + return sp2px(DevUtils.getContext(), spValue); + } + + /** + * sp 转 px ( float ) + * @param spValue 待转换值 + * @return 转换后的值 + */ + public static float sp2pxf(final float spValue) { + return sp2pxf(DevUtils.getContext(), spValue); + } + + /** + * px 转 sp + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static int px2sp(final float pxValue) { + return px2sp(DevUtils.getContext(), pxValue); + } + + /** + * px 转 sp ( float ) + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static float px2spf(final float pxValue) { + return px2spf(DevUtils.getContext(), pxValue); + } + + // = + + /** + * dp 转 px + * @param context {@link Context} + * @param dpValue 待转换值 + * @return 转换后的值 + */ + public static int dp2px( + final Context context, + final float dpValue + ) { + return dp2px(dpValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * dp 转 px ( float ) + * @param context {@link Context} + * @param dpValue 待转换值 + * @return 转换后的值 + */ + public static float dp2pxf( + final Context context, + final float dpValue + ) { + return dp2pxf(dpValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * px 转 dp + * @param context {@link Context} + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static int px2dp( + final Context context, + final float pxValue + ) { + return px2dp(pxValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * px 转 dp ( float ) + * @param context {@link Context} + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static float px2dpf( + final Context context, + final float pxValue + ) { + return px2dpf(pxValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * sp 转 px + * @param context {@link Context} + * @param spValue 待转换值 + * @return 转换后的值 + */ + public static int sp2px( + final Context context, + final float spValue + ) { + return sp2px(spValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * sp 转 px ( float ) + * @param context {@link Context} + * @param spValue 待转换值 + * @return 转换后的值 + */ + public static float sp2pxf( + final Context context, + final float spValue + ) { + return sp2pxf(spValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * px 转 sp + * @param context {@link Context} + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static int px2sp( + final Context context, + final float pxValue + ) { + return px2sp(pxValue, ResourceUtils.getDisplayMetrics(context)); + } + + /** + * px 转 sp ( float ) + * @param context {@link Context} + * @param pxValue 待转换值 + * @return 转换后的值 + */ + public static float px2spf( + final Context context, + final float pxValue + ) { + return px2spf(pxValue, ResourceUtils.getDisplayMetrics(context)); + } + + // ================== + // = DisplayMetrics = + // ================== + + /** + * dp 转 px + * @param dpValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static int dp2px( + final float dpValue, + final DisplayMetrics metrics + ) { + return (int) dp2pxf(dpValue, metrics); + } + + /** + * dp 转 px ( float ) + * @param dpValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static float dp2pxf( + final float dpValue, + final DisplayMetrics metrics + ) { + return applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, metrics); + } + + /** + * px 转 dp + * @param pxValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static int px2dp( + final float pxValue, + final DisplayMetrics metrics + ) { + return (int) px2dpf(pxValue, metrics); + } + + /** + * px 转 dp ( float ) + * @param pxValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static float px2dpf( + final float pxValue, + final DisplayMetrics metrics + ) { + if (metrics != null) { + try { + float scale = metrics.density; + return (pxValue / scale); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "px2dpf"); + } + } + return 0F; + } + + /** + * sp 转 px + * @param spValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static int sp2px( + final float spValue, + final DisplayMetrics metrics + ) { + return (int) sp2pxf(spValue, metrics); + } + + /** + * sp 转 px ( float ) + * @param spValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static float sp2pxf( + final float spValue, + final DisplayMetrics metrics + ) { + return applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, metrics); + } + + /** + * px 转 sp + * @param pxValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static int px2sp( + final float pxValue, + final DisplayMetrics metrics + ) { + return (int) px2spf(pxValue, metrics); + } + + /** + * px 转 sp ( float ) + * @param pxValue 待转换值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static float px2spf( + final float pxValue, + final DisplayMetrics metrics + ) { + if (metrics != null) { + try { + float scale = metrics.scaledDensity; + return (pxValue / scale); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "px2spf"); + } + } + return 0F; + } + + // ================== + // = applyDimension = + // ================== + + /** + * 各种单位转换 ( 该方法存在于 TypedValue.applyDimension ) + * @param unit 单位 + * @param value 值 + * @return 转换后的值 + */ + public static float applyDimension( + final int unit, + final float value + ) { + return applyDimension(unit, value, ResourceUtils.getDisplayMetrics()); + } + + /** + * 各种单位转换 ( 该方法存在于 TypedValue.applyDimension ) + * @param unit 单位 + * @param value 值 + * @param metrics {@link DisplayMetrics} + * @return 转换后的值 + */ + public static float applyDimension( + final int unit, + final float value, + final DisplayMetrics metrics + ) { + if (metrics != null) { + return TypedValue.applyDimension(unit, value, metrics); + } + return 0F; + } + + // = + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + *
+     *     用法示例如下所示
+     *     

+ * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() { + * Override + * public void onGetSize(final View view) { + * view.getWidth(); + * } + * }); + *
+ * @param view {@link View} + * @param listener {@link OnGetSizeListener} + * @return {@code true} success, {@code false} fail + */ + public static boolean forceGetViewSize( + final View view, + final OnGetSizeListener listener + ) { + if (view != null) { + view.post(() -> { + if (listener != null) { + listener.onGetSize(view); + } + }); + return true; + } + return false; + } + + /** + * detail: 获取 View 宽高监听 + * @author Ttt + */ + public interface OnGetSizeListener { + + /** + * 获取到 View 宽高监听通知 + * @param view {@link View} + */ + void onGetSize(View view); + } + + // ============= + // = ViewUtils = + // ============= + + /** + * 测量 View + * @param view {@link View} + * @return int[] 0 = 宽度, 1 = 高度 + */ + public static int[] measureView(final View view) { + return WidgetUtils.measureView(view); + } + + /** + * 获取 View 的宽度 + * @param view {@link View} + * @return View 的宽度 + */ + public static int getMeasuredWidth(final View view) { + return WidgetUtils.getMeasuredWidth(view); + } + + /** + * 获取 View 的高度 + * @param view {@link View} + * @return View 的高度 + */ + public static int getMeasuredHeight(final View view) { + return WidgetUtils.getMeasuredHeight(view); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/SnackbarUtils.java b/lib/DevApp/src/main/java/dev/utils/app/SnackbarUtils.java new file mode 100644 index 0000000000..8992137273 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/SnackbarUtils.java @@ -0,0 +1,1595 @@ +package dev.utils.app; + +import android.app.Activity; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.LayoutRes; +import androidx.annotation.StringRes; +import androidx.fragment.app.Fragment; + +import com.google.android.material.snackbar.Snackbar; +import com.google.android.material.snackbar.SnackbarContentLayout; + +import java.lang.ref.WeakReference; + +import dev.utils.LogPrintUtils; +import dev.utils.R; +import dev.utils.common.StringUtils; + +/** + * detail: Snackbar 工具类 + * @author Ttt + */ +public final class SnackbarUtils { + + // 日志 TAG + private final String TAG = SnackbarUtils.class.getSimpleName(); + + // 持有 弱引用 Snackbar, 防止所属 View 销毁等 + private static WeakReference sSnackbarReference; + // 样式构造对象 + private StyleBuilder mStyleBuilder = new StyleBuilder(); + + /** + * 构造函数 + * @param view {@link View} + */ + private SnackbarUtils(final View view) { + if (view != null) { + try { + sSnackbarReference = new WeakReference<>(Snackbar.make(view, "", Snackbar.LENGTH_SHORT)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "SnackbarUtils"); + } + } + } + + // ========== + // = 构建方法 = + // ========== + + /** + * 获取 SnackbarUtils 对象 + * @param activity {@link Activity} + * @return {@link SnackbarUtils} + */ + public static SnackbarUtils with(final Activity activity) { + if (activity != null && activity.getWindow() != null) { + return new SnackbarUtils(activity.getWindow().getDecorView()); + } + return new SnackbarUtils(null); + } + + /** + * 获取 SnackbarUtils 对象 + * @param fragment {@link Fragment} + * @return {@link SnackbarUtils} + */ + public static SnackbarUtils with(final Fragment fragment) { + return new SnackbarUtils((fragment != null) ? fragment.getView() : null); + } + + /** + * 获取 SnackbarUtils 对象 + * @param window {@link Window} + * @return {@link SnackbarUtils} + */ + public static SnackbarUtils with(final Window window) { + return new SnackbarUtils((window != null) ? window.getDecorView() : null); + } + + /** + * 获取 SnackbarUtils 对象 + * @param view {@link View} + * @return {@link SnackbarUtils} + */ + public static SnackbarUtils with(final View view) { + return new SnackbarUtils(view); + } + + // ========== + // = 样式相关 = + // ========== + + /** + * 获取样式 + * @return {@link StyleBuilder} + */ + public StyleBuilder getStyle() { + return mStyleBuilder; + } + + /** + * 设置样式 + * @param style {@link SnackbarUtils.Style} + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setStyle(final Style style) { + this.mStyleBuilder = new StyleBuilder(style); + return this; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Snackbar + * @return {@link Snackbar} + */ + public Snackbar getSnackbar() { + return (sSnackbarReference != null) ? sSnackbarReference.get() : null; + } + + /** + * 获取 Snackbar View + * @return {@link Snackbar#getView()} + */ + public View getSnackbarView() { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + return snackbar.getView(); + } + return null; + } + + /** + * 获取 Snackbar TextView ( snackbar_text ) + * @return Snackbar {@link TextView} + */ + public TextView getTextView() { + View view = getSnackbarView(); + if (view != null) { + return view.findViewById(R.id.snackbar_text); + } + return null; + } + + /** + * 获取 Snackbar Action Button ( snackbar_action ) + *
+     *     右边按钮 ( 如: 撤销 )
+     * 
+ * @return Snackbar {@link Button} + */ + public Button getActionButton() { + View view = getSnackbarView(); + if (view != null) { + return view.findViewById(R.id.snackbar_action); + } + return null; + } + + /** + * 获取 Snackbar.SnackbarLayout ( FrameLayout ) + * @return {@link Snackbar.SnackbarLayout} + */ + public Snackbar.SnackbarLayout getSnackbarLayout() { + try { + return (Snackbar.SnackbarLayout) getSnackbarView(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSnackbarLayout"); + } + return null; + } + + /** + * 获取 SnackbarContentLayout ( LinearLayout ( messageView、actionView ) ) + * @return {@link SnackbarContentLayout} + */ + public SnackbarContentLayout getSnackbarContentLayout() { + Snackbar.SnackbarLayout snackbarLayout = getSnackbarLayout(); + if (snackbarLayout == null) return null; + try { + return (SnackbarContentLayout) snackbarLayout.getChildAt(0); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSnackbarContentLayout"); + } + return null; + } + + /** + * 向 Snackbar 布局中添加 View ( Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示 ) + * @param layoutId R.layout.id + * @param index 添加索引 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils addView( + @LayoutRes final int layoutId, + final int index + ) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + try { + // 加载布局文件新建 View + View view = LayoutInflater.from(snackbar.getView().getContext()).inflate(layoutId, null); + return addView(view, index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return this; + } + + /** + * 向 Snackbar 布局中添加 View ( Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示 ) + * @param view {@link View} + * @param index 添加索引 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils addView( + final View view, + final int index + ) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null && view != null) { + try { + ((Snackbar.SnackbarLayout) snackbar.getView()).addView(view, index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return this; + } + + /** + * 设置 Snackbar 展示完成、隐藏完成 的监听 + * @param callback {@link Snackbar.Callback} + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setCallback(final Snackbar.Callback callback) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + snackbar.addCallback(callback); + } + return this; + } + + // = + + /** + * 设置 Action 按钮文字内容及点击监听 + * @param resId R.string.id + * @param formatArgs 格式化参数 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setAction( + @StringRes final int resId, + final Object... formatArgs + ) { + return setAction(ClickUtils.EMPTY_CLICK, resId, formatArgs); + } + + /** + * 设置 Action 按钮文字内容及点击监听 + * @param listener 按钮点击事件 + * @param resId R.string.id + * @param formatArgs 格式化参数 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setAction( + final View.OnClickListener listener, + @StringRes final int resId, + final Object... formatArgs + ) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + String content = ResourceUtils.getString(resId, formatArgs); + if (!TextUtils.isEmpty(content)) { + snackbar.setAction(content, listener); + } + } + return this; + } + + /** + * 设置 Action 按钮文字内容及点击监听 + * @param text 按钮文本 + * @param formatArgs 格式化参数 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setAction( + final String text, + final Object... formatArgs + ) { + return setAction(ClickUtils.EMPTY_CLICK, text, formatArgs); + } + + /** + * 设置 Action 按钮文字内容及点击监听 + * @param listener 按钮点击事件 + * @param text 按钮文本 + * @param formatArgs 格式化参数 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setAction( + final View.OnClickListener listener, + final String text, + final Object... formatArgs + ) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + String content = StringUtils.format(text, formatArgs); + if (!TextUtils.isEmpty(content)) { + snackbar.setAction(content, listener); + } + } + return this; + } + + // = + + /** + * 关闭 Snackbar + */ + public void dismiss() { + dismiss(true); + } + + /** + * 关闭 Snackbar + * @param setNull 是否设置 null + */ + public void dismiss(final boolean setNull) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null) { + snackbar.dismiss(); + if (setNull) { + sSnackbarReference = null; + } + } + } + + // ========== + // = 显示方法 = + // ========== + + /** + * 显示 Short Snackbar + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public void showShort( + @StringRes final int resId, + final Object... formatArgs + ) { + innerShow(ResourceUtils.getString(resId, formatArgs), Snackbar.LENGTH_SHORT); + } + + /** + * 显示 Long Snackbar + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public void showLong( + @StringRes final int resId, + final Object... formatArgs + ) { + innerShow(ResourceUtils.getString(resId, formatArgs), Snackbar.LENGTH_LONG); + } + + /** + * 显示 Indefinite Snackbar ( 无限时, 一直显示 ) + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public void showIndefinite( + @StringRes final int resId, + final Object... formatArgs + ) { + innerShow(ResourceUtils.getString(resId, formatArgs), Snackbar.LENGTH_INDEFINITE); + } + + // = + + /** + * 显示 Short Snackbar + * @param text 显示文本 + * @param formatArgs 格式化参数 + */ + public void showShort( + final String text, + final Object... formatArgs + ) { + innerShow(StringUtils.format(text, formatArgs), Snackbar.LENGTH_SHORT); + } + + /** + * 显示 Long Snackbar + * @param text 显示文本 + * @param formatArgs 格式化参数 + */ + public void showLong( + final String text, + final Object... formatArgs + ) { + innerShow(StringUtils.format(text, formatArgs), Snackbar.LENGTH_LONG); + } + + /** + * 显示 Indefinite Snackbar ( 无限时, 一直显示 ) + * @param text 显示文本 + * @param formatArgs 格式化参数 + */ + public void showIndefinite( + final String text, + final Object... formatArgs + ) { + innerShow(StringUtils.format(text, formatArgs), Snackbar.LENGTH_INDEFINITE); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 内部显示方法 + * @param text 显示文本 + * @param duration 显示时长 {@link Snackbar#LENGTH_SHORT}、{@link Snackbar#LENGTH_LONG}、{@link Snackbar#LENGTH_INDEFINITE} + */ + private void innerShow( + final String text, + final int duration + ) { + Snackbar snackbar = getSnackbar(); + if (snackbar != null && !snackbar.isShownOrQueued()) { + // 防止内容为 null + if (!TextUtils.isEmpty(text)) { + // 设置样式 + setSnackbarStyle(snackbar); + try { + // 设置坐标位置 + setSnackbarLocation(snackbar); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShow - setSnackbarLocation"); + } + // 显示 SnackBar + snackbar.setText(text).setDuration(duration).show(); + } + } + } + + // = + + /** + * detail: Snackbar 样式配置构造类 + * @author Ttt + */ + public static final class StyleBuilder + extends Style { + + // ============ + // = RootView = + // ============ + + // RootView 的重心 + private int rootGravity; + // RootView 背景圆角大小 + private float rootCornerRadius; + @ColorInt // RootView 背景着色颜色 + private int rootBackgroundTintColor; + // RootView 背景图片 + private Drawable rootBackground; + // RootView margin 边距 new int[] { left, top, right, bottom } + private int[] rootMargin; + // RootView 透明度 + private float rootAlpha = 1.0F; + + // ============================== + // = snackbar_text TextView 相关 = + // ============================== + + // TextView 的重心 + private int textGravity; + @ColorInt // TextView 文本颜色 + private int textColor; + // TextView 字体大小 + private float textSize; + // TextView 最大行数 + private int textMaxLines; + // TextView Ellipsize 效果 + private TextUtils.TruncateAt textEllipsize; + // TextView 字体样式 + private Typeface textTypeface; + // TextView padding 边距 new int[] { left, top, right, bottom } + private int[] textPadding; + + // ============================== + // = snackbar_action Button 相关 = + // ============================== + + // Action Button 的重心 + private int actionGravity; + @ColorInt // Action Button 文本颜色 + private int actionColor; + // Action Button 字体大小 + private float actionSize; + // Action Button padding 边距 new int[] { left, top, right, bottom } + private int[] actionPadding; + // RootView 背景圆角大小 + private float actionCornerRadius; + @ColorInt // RootView 背景着色颜色 + private int actionBackgroundTintColor; + // RootView 背景图片 + private Drawable actionBackground; + + // = + + /** + * 构造函数 + */ + public StyleBuilder() { + } + + /** + * 构造函数 + * @param style {@link SnackbarUtils.Style} + */ + public StyleBuilder(final SnackbarUtils.Style style) { + if (style != null) { + + // ============ + // = RootView = + // ============ + + // RootView 的重心 + this.rootGravity = style.getRootGravity(); + // RootView 背景圆角大小 + this.rootCornerRadius = style.getRootCornerRadius(); + // RootView 背景着色颜色 + this.rootBackgroundTintColor = style.getRootBackgroundTintColor(); + // RootView 背景图片 + this.rootBackground = style.getRootBackground(); + // RootView margin 边距 new int[] { left, top, right, bottom } + this.rootMargin = style.getRootMargin(); + // RootView 透明度 + this.rootAlpha = style.getRootAlpha(); + + // ============================== + // = snackbar_text TextView 相关 = + // ============================== + + // TextView 的重心 + this.textGravity = style.getTextGravity(); + // TextView 文本颜色 + this.textColor = style.getTextColor(); + // TextView 字体大小 + this.textSize = style.getTextSize(); + // TextView 最大行数 + this.textMaxLines = style.getTextMaxLines(); + // TextView Ellipsize 效果 + this.textEllipsize = style.getTextEllipsize(); + // TextView 字体样式 + this.textTypeface = style.getTextTypeface(); + // TextView padding 边距 new int[] { left, top, right, bottom } + this.textPadding = style.getTextPadding(); + + // ============================== + // = snackbar_action Button 相关 = + // ============================== + + // Action Button 的重心 + this.actionGravity = style.getActionGravity(); + // Action Button 文本颜色 + this.actionColor = style.getActionColor(); + // Action Button 字体大小 + this.actionSize = style.getActionSize(); + // Action Button padding 边距 new int[] { left, top, right, bottom } + this.actionPadding = style.getActionPadding(); + // RootView 背景圆角大小 + this.actionCornerRadius = style.getActionCornerRadius(); + // RootView 背景着色颜色 + this.actionBackgroundTintColor = style.getActionBackgroundTintColor(); + // RootView 背景图片 + this.actionBackground = style.getActionBackground(); + } + } + + // =========== + // = get/set = + // =========== + + // ============ + // = RootView = + // ============ + + /** + * 获取 RootView 的重心 + * @return RootView 的重心 + */ + @Override + public int getRootGravity() { + return rootGravity; + } + + /** + * 设置 RootView 的重心 + * @param rootGravity RootView 的重心 + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootGravity(int rootGravity) { + this.rootGravity = rootGravity; + return this; + } + + /** + * 获取 RootView 背景圆角大小 + * @return RootView 背景圆角大小 + */ + @Override + public float getRootCornerRadius() { + return rootCornerRadius; + } + + /** + * 设置 RootView 背景圆角大小 + * @param rootCornerRadius RootView 背景圆角大小 + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootCornerRadius(float rootCornerRadius) { + this.rootCornerRadius = rootCornerRadius; + return this; + } + + /** + * 获取 RootView 背景着色颜色 + * @return RootView 背景着色颜色 + */ + @ColorInt + @Override + public int getRootBackgroundTintColor() { + return rootBackgroundTintColor; + } + + /** + * 设置 RootView 背景着色颜色 + * @param rootBackgroundTintColor RootView 背景着色颜色 + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootBackgroundTintColor(@ColorInt int rootBackgroundTintColor) { + this.rootBackgroundTintColor = rootBackgroundTintColor; + return this; + } + + /** + * 获取 RootView 背景图片 + * @return {@link Drawable} + */ + @Override + public Drawable getRootBackground() { + return rootBackground; + } + + /** + * 设置 RootView 背景图片 + * @param rootBackground {@link Drawable} + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootBackground(Drawable rootBackground) { + this.rootBackground = rootBackground; + return this; + } + + /** + * 获取 RootView margin 边距 + * @return RootView margin[] + */ + @Override + public int[] getRootMargin() { + return rootMargin; + } + + /** + * 设置 RootView margin 边距 + * @param rootMargin margin[] + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootMargin(int[] rootMargin) { + this.rootMargin = rootMargin; + return this; + } + + /** + * 获取 RootView 透明度 + * @return RootView 透明度 + */ + @Override + public float getRootAlpha() { + return rootAlpha; + } + + /** + * 设置 RootView 透明度 + * @param rootAlpha RootView 透明度 + * @return {@link StyleBuilder} + */ + public StyleBuilder setRootAlpha(float rootAlpha) { + this.rootAlpha = rootAlpha; + return this; + } + + // ============================== + // = snackbar_text TextView 相关 = + // ============================== + + /** + * 获取 TextView 的重心 + * @return TextView 的重心 + */ + @Override + public int getTextGravity() { + return textGravity; + } + + /** + * 设置 TextView 的重心 + * @param textGravity TextView 的重心 + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextGravity(int textGravity) { + this.textGravity = textGravity; + return this; + } + + /** + * 获取 TextView 文本颜色 + * @return TextView 文本颜色 + */ + @ColorInt + @Override + public int getTextColor() { + return textColor; + } + + /** + * 设置 TextView 文本颜色 + * @param textColor TextView 文本颜色 + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextColor(@ColorInt int textColor) { + this.textColor = textColor; + return this; + } + + /** + * 获取 TextView 字体大小 + * @return TextView 字体大小 + */ + @Override + public float getTextSize() { + return textSize; + } + + /** + * 设置 TextView 字体大小 + * @param textSize TextView 字体大小 + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextSize(float textSize) { + this.textSize = textSize; + return this; + } + + /** + * 获取 TextView 最大行数 + * @return TextView 最大行数 + */ + @Override + public int getTextMaxLines() { + return textMaxLines; + } + + /** + * 设置 TextView 最大行数 + * @param textMaxLines TextView 最大行数 + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextMaxLines(int textMaxLines) { + this.textMaxLines = textMaxLines; + return this; + } + + /** + * 获取 TextView Ellipsize 效果 + * @return {@link TextUtils.TruncateAt} + */ + @Override + public TextUtils.TruncateAt getTextEllipsize() { + return textEllipsize; + } + + /** + * 设置 TextView Ellipsize 效果 + * @param textEllipsize {@link TextUtils.TruncateAt} Ellipsize + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextEllipsize(TextUtils.TruncateAt textEllipsize) { + this.textEllipsize = textEllipsize; + return this; + } + + /** + * 获取 TextView 字体样式 + * @return {@link Typeface} + */ + @Override + public Typeface getTextTypeface() { + return textTypeface; + } + + /** + * 设置 TextView 字体样式 + * @param textTypeface {@link Typeface} + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextTypeface(Typeface textTypeface) { + this.textTypeface = textTypeface; + return this; + } + + /** + * 获取 TextView padding 边距 ( new int[] { left, top, right, bottom } ) + * @return TextView padding[] + */ + @Override + public int[] getTextPadding() { + return textPadding; + } + + /** + * 设置 TextView padding 边距 + * @param textPadding TextView padding[] + * @return {@link StyleBuilder} + */ + public StyleBuilder setTextPadding(int[] textPadding) { + this.textPadding = textPadding; + return this; + } + + // ============================== + // = snackbar_action Button 相关 = + // ============================== + + /** + * 获取 Action Button 的重心 + * @return Action Button 的重心 + */ + @Override + public int getActionGravity() { + return actionGravity; + } + + /** + * 设置 Action Button 的重心 + * @param actionGravity Action Button 的重心 + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionGravity(int actionGravity) { + this.actionGravity = actionGravity; + return this; + } + + /** + * 获取 Action Button 文本颜色 + * @return Action Button 文本颜色 + */ + @ColorInt + @Override + public int getActionColor() { + return actionColor; + } + + /** + * 设置 Action Button 文本颜色 + * @param actionColor Action Button 文本颜色 + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionColor(@ColorInt int actionColor) { + this.actionColor = actionColor; + return this; + } + + /** + * 获取 Action Button 字体大小 + * @return Action Button 字体大小 + */ + @Override + public float getActionSize() { + return actionSize; + } + + /** + * 设置 Action Button 字体大小 + * @param actionSize Action Button 字体大小 + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionSize(float actionSize) { + this.actionSize = actionSize; + return this; + } + + /** + * 获取 Action Button padding 边距 + * @return Action Button padding[] + */ + @Override + public int[] getActionPadding() { + return actionPadding; + } + + /** + * 设置 Action Button padding 边距 + * @param actionPadding Action Button padding[] + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionPadding(int[] actionPadding) { + this.actionPadding = actionPadding; + return this; + } + + /** + * 获取 Action Button 背景圆角大小 + * @return Action Button 背景圆角大小 + */ + @Override + public float getActionCornerRadius() { + return actionCornerRadius; + } + + /** + * 设置 Action Button 背景圆角大小 + * @param actionCornerRadius Action Button 背景圆角大小 + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionCornerRadius(float actionCornerRadius) { + this.actionCornerRadius = actionCornerRadius; + return this; + } + + /** + * 获取 Action Button 背景着色颜色 + * @return Action Button 背景着色颜色 + */ + @ColorInt + @Override + public int getActionBackgroundTintColor() { + return actionBackgroundTintColor; + } + + /** + * 设置 Action Button 背景着色颜色 + * @param actionBackgroundTintColor Action Button 背景着色颜色 + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionBackgroundTintColor(@ColorInt int actionBackgroundTintColor) { + this.actionBackgroundTintColor = actionBackgroundTintColor; + return this; + } + + /** + * 获取 Action Button 背景图片 + * @return {@link Drawable} + */ + @Override + public Drawable getActionBackground() { + return actionBackground; + } + + /** + * 设置 Action Button 背景图片 + * @param actionBackground {@link Drawable} + * @return {@link StyleBuilder} + */ + public StyleBuilder setActionBackground(Drawable actionBackground) { + this.actionBackground = actionBackground; + return this; + } + } + + // ========== + // = 其他接口 = + // ========== + + /** + * detail: Snackbar 样式配置 + * @author Ttt + */ + public static abstract class Style { + + // ============ + // = RootView = + // ============ + + /** + * 获取 RootView 的重心 + * @return RootView 的重心 + */ + public int getRootGravity() { + return 0; + } + + /** + * 获取 RootView 背景圆角大小 + * @return RootView 背景圆角大小 + */ + public float getRootCornerRadius() { + return 0F; + } + + /** + * 获取 RootView 背景着色颜色 + * @return RootView 背景着色颜色 + */ + @ColorInt + public int getRootBackgroundTintColor() { + return 0; + } + + /** + * 获取 RootView 背景图片 + * @return {@link Drawable} + */ + public Drawable getRootBackground() { + return null; + } + + /** + * 获取 RootView margin 边距 + * @return RootView margin[] + */ + public int[] getRootMargin() { + return null; + } + + /** + * 获取 RootView 透明度 + * @return RootView 透明度 + */ + public float getRootAlpha() { + return 1.0F; + } + + // ============================== + // = snackbar_text TextView 相关 = + // ============================== + + /** + * 获取 TextView 的重心 + * @return TextView 的重心 + */ + public int getTextGravity() { + return 0; + } + + /** + * 获取 TextView 文本颜色 + * @return TextView 文本颜色 + */ + @ColorInt + public int getTextColor() { + return 0; + } + + /** + * 获取 TextView 字体大小 + * @return TextView 字体大小 + */ + public float getTextSize() { + return 0F; + } + + /** + * 获取 TextView 最大行数 + * @return TextView 最大行数 + */ + public int getTextMaxLines() { + return 0; + } + + /** + * 获取 TextView Ellipsize 效果 + * @return {@link TextUtils.TruncateAt} + */ + public TextUtils.TruncateAt getTextEllipsize() { + return null; + } + + /** + * 获取 TextView 字体样式 + * @return {@link Typeface} + */ + public Typeface getTextTypeface() { + return null; + } + + /** + * 获取 TextView padding 边距 ( new int[] { left, top, right, bottom } ) + * @return TextView padding[] + */ + public int[] getTextPadding() { + return null; + } + + // ============================== + // = snackbar_action Button 相关 = + // ============================== + + /** + * 获取 Action Button 的重心 + * @return Action Button 的重心 + */ + public int getActionGravity() { + return 0; + } + + /** + * 获取 Action Button 文本颜色 + * @return Action Button 文本颜色 + */ + @ColorInt + public int getActionColor() { + return 0; + } + + /** + * 获取 Action Button 字体大小 + * @return Action Button 字体大小 + */ + public float getActionSize() { + return 0F; + } + + /** + * 获取 Action Button padding 边距 + * @return Action Button padding[] + */ + public int[] getActionPadding() { + return null; + } + + /** + * 获取 Action Button 背景圆角大小 + * @return Action Button 背景圆角大小 + */ + public float getActionCornerRadius() { + return 0F; + } + + /** + * 获取 Action Button 背景着色颜色 + * @return Action Button 背景着色颜色 + */ + @ColorInt + public int getActionBackgroundTintColor() { + return 0; + } + + /** + * 获取 Action Button 背景图片 + * @return {@link Drawable} + */ + public Drawable getActionBackground() { + return null; + } + } + + // ========== + // = 设置样式 = + // ========== + + /** + * 设置 Snackbar 样式配置 + * @param snackbar {@link Snackbar} + * @return {@link Snackbar} + */ + public Snackbar setSnackbarStyle(final Snackbar snackbar) { + return setSnackbarStyle(snackbar, mStyleBuilder); + } + + /** + * 设置 Snackbar 样式配置 + * @param snackbar {@link Snackbar} + * @param style {@link SnackbarUtils.Style} + * @return {@link Snackbar} + */ + public Snackbar setSnackbarStyle( + final Snackbar snackbar, + final SnackbarUtils.Style style + ) { + if (snackbar == null || style == null) { + return snackbar; + } + // 获取显示的 View + View rootView = snackbar.getView(); + + // ============ + // = RootView = + // ============ + + // 设置 RootView Gravity 处理 + if (style.getRootGravity() != 0) { + setLayoutGravity(rootView, style.getRootGravity()); + } + + // 设置 RootView margin 边距 + int[] rootMargin = style.getRootMargin(); + if (rootMargin != null && rootMargin.length == 4) { + setMargin(rootView, rootMargin, rootMargin[1], rootMargin[3]); + } + + // 设置 RootView 透明度 + if (style.getRootAlpha() >= 0F) { + float rootAlpha = style.getRootAlpha(); + rootAlpha = rootAlpha >= 1.0F ? 1.0F : Math.max(rootAlpha, 0.0F); + rootView.setAlpha(rootAlpha); + } + + // 设置 RootView 背景相关 + // 获取背景图片 + Drawable rootBackgroundDrawable = style.getRootBackground(); + // 如果等于 null + if (rootBackgroundDrawable != null) { + // 设置背景 + ViewUtils.setBackground(rootView, rootBackgroundDrawable); + } else { + if (style.getRootBackgroundTintColor() != 0) { + GradientDrawable drawable = new GradientDrawable(); + // 设置背景色 + drawable.setColor(style.getRootBackgroundTintColor()); + // 设置圆角大小 + drawable.setCornerRadius(style.getRootCornerRadius()); + // 设置背景 + ViewUtils.setBackground(rootView, drawable); + } + } + + // ============================== + // = snackbar_text TextView 相关 = + // ============================== + + TextView textView = getTextView(); + // 防止 snackbar_text 为 null + if (textView != null) { + + // TextView 的重心 + if (style.getTextGravity() != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + textView.setTextAlignment(View.TEXT_ALIGNMENT_GRAVITY); + } + textView.setGravity(style.getTextGravity()); + } + + // TextView 文本颜色 + if (style.getTextColor() != 0) { + textView.setTextColor(style.getTextColor()); + } + + // TextView 字体大小 + if (style.getTextSize() != 0F) { + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.getTextSize()); + } + + // TextView 最大行数 + if (style.getTextMaxLines() >= 1) { + textView.setMaxLines(style.getTextMaxLines()); + } + + // TextView Ellipsize 效果 + if (style.getTextEllipsize() != null) { + textView.setEllipsize(style.getTextEllipsize()); + } + + // TextView 字体样式 + if (style.getTextTypeface() != null) { + textView.setTypeface(style.getTextTypeface()); + } + + // TextView padding 边距 + int[] textPadding = style.getTextPadding(); + if (textPadding != null && textPadding.length == 4) { + textView.setPadding(textPadding[0], textPadding[1], textPadding[2], textPadding[3]); + } + } + + // ============================== + // = snackbar_action Button 相关 = + // ============================== + + Button actionButton = getActionButton(); + // 防止 snackbar_action Button 为 null + if (actionButton != null) { + + // Action Button 的重心 + if (style.getActionGravity() != 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + actionButton.setTextAlignment(View.TEXT_ALIGNMENT_GRAVITY); + } + actionButton.setGravity(style.getActionGravity()); + } + + // Action Button 文本颜色 + if (style.getActionColor() != 0) { + actionButton.setTextColor(style.getActionColor()); + } + + // Action Button 字体大小 + if (style.getActionSize() != 0F) { + actionButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.getActionSize()); + } + + // Action Button padding 边距 + int[] actionPadding = style.getActionPadding(); + if (actionPadding != null && actionPadding.length == 4) { + actionButton.setPadding(actionPadding[0], actionPadding[1], actionPadding[2], actionPadding[3]); + } + + // 设置 Action Button 背景相关 + // 获取背景图片 + Drawable actionBackgroundDrawable = style.getActionBackground(); + // 如果等于 null + if (actionBackgroundDrawable != null) { + // 设置背景 + ViewUtils.setBackground(actionButton, actionBackgroundDrawable); + } else { + if (style.getActionBackgroundTintColor() != 0) { + GradientDrawable drawable = new GradientDrawable(); + // 设置背景色 + drawable.setColor(style.getActionBackgroundTintColor()); + // 设置圆角大小 + drawable.setCornerRadius(style.getActionCornerRadius()); + // 设置背景 + ViewUtils.setBackground(actionButton, drawable); + } + } + } + return snackbar; + } + + // = + + // View 坐标 + private int[] mViewLocations = null; + // View 高度 + private int mViewHeight = 0; + // 指定 View 坐标, 显示的重心方向 ( 只有 TOP、BOTTOM) + private int mViewGravity = -1; + // 追加向上边距 ( 如: 状态栏高度 ) + private int mAppendTopMargin = 0; + // View 阴影 + private int mShadowMargin = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? 2 : 0; + // 判断是否自动计算 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + private boolean mAutoCalc = true; + + /** + * 获取阴影边距 + * @return 阴影边距 + */ + public int getShadowMargin() { + return mShadowMargin; + } + + /** + * 设置阴影边距 + * @param shadowMargin 阴影边距 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setShadowMargin(final int shadowMargin) { + this.mShadowMargin = shadowMargin; + return this; + } + + /** + * 判断是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isAutoCalc() { + return mAutoCalc; + } + + /** + * 设置是否自动计算边距 ( 如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示 ) + * @param autoCalc 是否自动计算边距 + * @return {@link SnackbarUtils} + */ + public SnackbarUtils setAutoCalc(final boolean autoCalc) { + this.mAutoCalc = autoCalc; + return this; + } + + /** + * 清空坐标相关信息 + */ + private void clearLocations() { + // 重置处理 + mViewHeight = 0; + mViewGravity = -1; + mViewLocations = null; + mAppendTopMargin = 0; + } + + /** + * 设置 Snackbar 显示在指定 View 的上方 + * @param targetView 目标 View + * @param appendTopMargin 追加边距 ( 如: 状态栏高度 ) {@link BarUtils#getStatusBarHeight} + * @return {@link SnackbarUtils} + */ + public SnackbarUtils above( + final View targetView, + final int appendTopMargin + ) { + // 清空重置处理 + clearLocations(); + // 防止为 null + if (targetView != null) { + mViewHeight = targetView.getHeight(); + mViewGravity = Gravity.TOP; + mViewLocations = new int[2]; + mAppendTopMargin = appendTopMargin; + targetView.getLocationOnScreen(mViewLocations); + } + return this; + } + + /** + * 设置 Snackbar 显示在指定 View 的下方 + * @param targetView 目标 View + * @param appendTopMargin 追加边距 ( 如: 状态栏高度 ) {@link BarUtils#getStatusBarHeight} + * @return {@link SnackbarUtils} + */ + public SnackbarUtils bellow( + final View targetView, + final int appendTopMargin + ) { + // 清空重置处理 + clearLocations(); + // 防止为 null + if (targetView != null) { + mViewHeight = targetView.getHeight(); + mViewGravity = Gravity.BOTTOM; + mViewLocations = new int[2]; + mAppendTopMargin = appendTopMargin; + targetView.getLocationOnScreen(mViewLocations); + } + return this; + } + + /** + * 设置 Snackbar 显示的坐标位置 + * @param snackbar {@link Snackbar} + */ + private void setSnackbarLocation(final Snackbar snackbar) { + if (snackbar == null) return; + // 获取显示的 View + View rootView = snackbar.getView(); + // = 特殊处理 = + // 属于显示在指定 View 坐标, 对应重心方向 + if (mViewLocations != null && mViewGravity != -1 && mViewHeight > 0) { + // View ( 坐标 ) 边距 + int[] margin = new int[4]; + // 判断 Style 是否为 null + if (mStyleBuilder != null) { + // 默认边距 + int[] rootMargin = mStyleBuilder.getRootMargin(); + if (rootMargin != null && rootMargin.length == 4) { + margin[0] = rootMargin[0]; + margin[2] = rootMargin[2]; + } + } + + // 获取 View 上方距离 + int mViewTop = mViewLocations[1]; + // 获取屏幕高度 + int screenHeight = ScreenUtils.getScreenHeight(); + // 防止等于 0 + if (screenHeight != 0) { + // 获取测量高度 ( 不一定准确 ) + int measuredHeight = WidgetUtils.getMeasuredHeight(rootView); + // 判断方向, 在指定坐标上方, 判断是否够空间 + if (mViewGravity == Gravity.TOP) { + // 判断是否超出可显示高度 + if (mViewTop - mShadowMargin - mAppendTopMargin >= measuredHeight) { + // 思路: 没有超出高度, 则正常显示在指定 View 上方 + // 改为布局居下 ( 相反方向 ), 然后设置 bottomMargin 为 屏幕高度 - view mWindowTop + 阴影大小 + // 这样的思路, 主要是只用知道 view 的 Y 轴位置, 然后用屏幕高度减去 Y 得到的就是需要向下的边距, 不需要计算 Snackbar View 高度 + + setLayoutGravity(rootView, Gravity.BOTTOM) + .setMargin(rootView, margin, 0, screenHeight - mViewTop + mShadowMargin); + } else { // 超出可视范围 + // 判断是否自动计算处理 + if (mAutoCalc) { + // 思路如上: 超出高度后, 则直接设置居上, 计算边距则 view mWindowTop - 追加边距 ( 状态栏高度 ) + view height, 设置到 View 的下方 + // 计算处理主要是, 只需要知道 view Y 轴位置 + view height - 追加边距 ( 状态栏高度 ) = 需要的边距 + // 为什么需要减 状态栏高度, 是因为 view Y (view mWindowTop) 就包含状态栏高度信息 + + setLayoutGravity(rootView, Gravity.TOP) + .setMargin(rootView, margin, mViewTop - mAppendTopMargin + mViewHeight, 0); + } + } + } else { // 在指定坐标下方 + // 判断是否超出可显示高度 + if (screenHeight - (mViewTop + mShadowMargin + mAppendTopMargin + mViewHeight) >= measuredHeight) { + // 思路: 没有超出高度, 则正常显示在指定 View 下方 + // 并且改为布局居上, 然后设置 topMargin 为 view mWindowTop - ( 阴影大小 + 追加边距 ( 状态栏高度 )) + // 这样的思路, 主要是不居下, 不用知道 Snackbar view 高度, 导致向下边距计算错误, 转换思路从上处理 + + setLayoutGravity(rootView, Gravity.TOP) + .setMargin(rootView, margin, mViewTop - (mShadowMargin + mAppendTopMargin), 0); + } else { // 超出可视范围 + // 判断是否自动计算处理 + if (mAutoCalc) { + // 思路如上: 超出高度后, 则直接设置居下, 计算边距则 用屏幕高度 - view mWindowTop + 阴影边距 + // 计算处理的值则是 view mWindowTop 距离底部的边距, 刚好设置 bottomMargin, 实现思路转换处理 + + setLayoutGravity(rootView, Gravity.BOTTOM) + .setMargin(rootView, margin, 0, screenHeight - mViewTop + mShadowMargin); + } + } + } + } + } + // 清空重置处理 + clearLocations(); + } + + /** + * 设置 View Layout Gravity + * @param view {@link View} + * @param gravity Gravity + * @return {@link SnackbarUtils} + */ + private SnackbarUtils setLayoutGravity( + final View view, + final int gravity + ) { + try { + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( + view.getLayoutParams().width, view.getLayoutParams().height + ); + layoutParams.gravity = gravity; + view.setLayoutParams(layoutParams); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLayoutGravity"); + } + return this; + } + + /** + * 设置 Margin 边距 + * @param view {@link View} + * @param margin left、right margin + * @param topMargin top margin + * @param bottomMargin bottom margin + * @return {@link SnackbarUtils} + */ + private SnackbarUtils setMargin( + final View view, + final int[] margin, + final int topMargin, + final int bottomMargin + ) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + ((ViewGroup.MarginLayoutParams) layoutParams).setMargins( + margin[0], topMargin, margin[2], bottomMargin + ); + view.setLayoutParams(layoutParams); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setMargin"); + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/SpanUtils.java b/lib/DevApp/src/main/java/dev/utils/app/SpanUtils.java new file mode 100644 index 0000000000..b673f1ac36 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/SpanUtils.java @@ -0,0 +1,1609 @@ +package dev.utils.app; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.Layout; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.method.LinkMovementMethod; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.CharacterStyle; +import android.text.style.ClickableSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.LeadingMarginSpan; +import android.text.style.LineHeightSpan; +import android.text.style.MaskFilterSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.ReplacementSpan; +import android.text.style.ScaleXSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.SubscriptSpan; +import android.text.style.SuperscriptSpan; +import android.text.style.TypefaceSpan; +import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; +import android.text.style.UpdateAppearance; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IntDef; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + +import java.io.InputStream; +import java.io.Serializable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: SpannableString 工具类 + * @author Blankj + * @author Ttt + *
+ *     SpannableStringBuilder 工具类 ( 用于设置文字的前景色、背景色、Typeface、粗体、斜体、字号、超链接、删除线、下划线、上下标等 )
+ *     

+ * Android 开发 SpannableString 和 SpannableStringBuilder 的使用详解 + * @see
+ *

+ * Android 强大的 SpannableStringBuilder + * @see
+ *

+ * SpannableString 之富文本显示效果 + * @see
+ *

+ * SpannableString 与 SpannableStringBuilder 区别就比如 String 和 StringBuilder 一样 + *

+ * setSpan() - int flags + *

+ * Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + * 前后都不包括 ( 在标志位 [start, end) 前后添加文字, 新添加的文字不会有任何设置的属性 ) + *

+ * Spannable.SPAN_EXCLUSIVE_INCLUSIVE + * 前面不包括, 后面包括 ( 在标志位 [start, end) 前添加文字, 新添加的文字不会有任何设置的属性, 后边的添加的文字会带有设置的 what 属性 ) + *

+ * Spannable.SPAN_INCLUSIVE_EXCLUSIVE + * 前面包括, 后面不包括 ( 在标志位 [start, end) 后添加文字, 新添加的文字不会有任何设置的属性, 前边边的添加的文字会带有设置的 what 属性 ) + *

+ * Spannable.SPAN_INCLUSIVE_INCLUSIVE + * 前后都包括 前后都不包括 ( 在标志位 [start, end) 前后添加文字, 新添加的文字会有设置的属性 ) + *
+ */ +public final class SpanUtils { + + // 日志 TAG + private static final String TAG = SpanUtils.class.getSimpleName(); + + // 对齐类型 + public static final int ALIGN_BOTTOM = 0; + public static final int ALIGN_BASELINE = 1; + public static final int ALIGN_CENTER = 2; + public static final int ALIGN_TOP = 3; + + @IntDef({ALIGN_BOTTOM, ALIGN_BASELINE, ALIGN_CENTER, ALIGN_TOP}) + @Retention(RetentionPolicy.SOURCE) + public @interface Align { + } + + // 内部 SpannableString 实现类 + private final SerializableSpannableStringBuilder mBuilder; + // TextView create setText + private TextView mTextView; + // 中转文本 + private CharSequence mText; + + // 内部标记应用类型 + private int mType; + private static final int TYPE_CHAR_SEQUENCE = 0; + private static final int TYPE_IMAGE = 1; + private static final int TYPE_SPACE = 2; + + // 默认颜色 + private static final int COLOR_DEFAULT = Color.WHITE; + // 配置参数 + private int flag; // setSpan flag + private int foregroundColor; // 前景色 + private int backgroundColor; // 背景色 + private int lineHeight; // 行高 + private int alignLine; // 行对齐类型 + private int quoteColor; // 引用线颜色 + private int stripeWidth; // 线条宽度 + private int quoteGapWidth; // 间隙宽度 + private int first; // 第一行缩进 + private int rest; // 其他行缩进 + private int bulletColor; // 列表项颜色 + private int bulletRadius; // 列表项半径 + private int bulletGapWidth; // 列表项间隙宽度 + private int fontSize; // 字体大小 px + private boolean fontSizeIsDp; // 字体大小是否使用 dp + private float proportion; // 字体缩放比例 + private float xProportion; // 字体横向缩放比例 + private boolean isStrikethrough; // 是否中划线 + private boolean isUnderline; // 是否下划线 + private boolean isSuperscript; // 是否上标 + private boolean isSubscript; // 是否下标 + private boolean isBold; // 是否加粗 + private boolean isItalic; // 是否斜体 + private boolean isBoldItalic; // 是否粗斜体 + private String fontFamily; // 字体系列 + private Typeface typeface; // 字体 + private Layout.Alignment alignment; // 对齐类型 ( 水平对齐 ) + private int verticalAlign; // 垂直对齐类型 + private ClickableSpan clickSpan; // 点击区域 + private String url; // Url + private float blurRadius; // 模糊程度 + private BlurMaskFilter.Blur style; // 模糊样式 + private Shader shader; // 着色 + private float shadowRadius; // 阴影半径 + private float shadowDx; // X 轴阴影偏移值 + private float shadowDy; // Y 轴阴影偏移值 + private int shadowColor; // 阴影颜色 + private int spaceSize; // 空格大小 + private int spaceColor; // 空格颜色 + private Object[] spans; // 自定义 setSpan 参数 + + // 图片相关 + private Bitmap imageBitmap; // Bitmap + private Drawable imageDrawable; // Drawable + private Uri imageUri; // Uri + private int imageResourceId; // resource id + private int alignImage; // 图片对齐类型 + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param textView {@link TextView} + */ + private SpanUtils(TextView textView) { + this(); + mTextView = textView; + } + + /** + * 构造函数 + */ + public SpanUtils() { + mBuilder = new SerializableSpannableStringBuilder(); + mText = ""; + mType = -1; + setDefault(); + } + + /** + * 获取持有 TextView SpannableString Utils + * @param textView {@link TextView} + * @return {@link SpanUtils} + */ + public static SpanUtils with(final TextView textView) { + return new SpanUtils(textView); + } + + // ========== + // = 实现方法 = + // ========== + + /** + * 设置标识 + * @param flag 标识 + *
    + *
  • {@link Spanned#SPAN_INCLUSIVE_EXCLUSIVE}
  • + *
  • {@link Spanned#SPAN_INCLUSIVE_INCLUSIVE}
  • + *
  • {@link Spanned#SPAN_EXCLUSIVE_EXCLUSIVE}
  • + *
  • {@link Spanned#SPAN_EXCLUSIVE_INCLUSIVE}
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setFlag(final int flag) { + this.flag = flag; + return this; + } + + /** + * 设置前景色 + * @param color 前景色 + * @return {@link SpanUtils} + */ + public SpanUtils setForegroundColor(@ColorInt final int color) { + this.foregroundColor = color; + return this; + } + + /** + * 设置背景色 + * @param color 背景色 + * @return {@link SpanUtils} + */ + public SpanUtils setBackgroundColor(@ColorInt final int color) { + this.backgroundColor = color; + return this; + } + + /** + * 设置行高 + * @param lineHeight 行高 + * @return {@link SpanUtils} + */ + public SpanUtils setLineHeight(@IntRange(from = 0) final int lineHeight) { + return setLineHeight(lineHeight, ALIGN_CENTER); + } + + /** + * 设置行高 + * @param lineHeight 行高 + * @param align 行对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER}
  • + *
  • {@link Align#ALIGN_BOTTOM}
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setLineHeight( + @IntRange(from = 0) final int lineHeight, + @Align final int align + ) { + this.lineHeight = lineHeight; + this.alignLine = align; + return this; + } + + /** + * 设置引用线的颜色 + * @param color 引用线颜色 + * @return {@link SpanUtils} + */ + public SpanUtils setQuoteColor(@ColorInt final int color) { + return setQuoteColor(color, 2, 2); + } + + /** + * 设置引用线的颜色 + * @param color 引用线颜色 + * @param stripeWidth 线条宽度 + * @param gapWidth 间隙宽度 + * @return {@link SpanUtils} + */ + public SpanUtils setQuoteColor( + @ColorInt final int color, + @IntRange(from = 1) final int stripeWidth, + @IntRange(from = 0) final int gapWidth + ) { + this.quoteColor = color; + this.stripeWidth = stripeWidth; + this.quoteGapWidth = gapWidth; + return this; + } + + /** + * 设置缩进 + * @param first 段落第一行的缩进值 + * @param rest 段落其余行的缩进值 + * @return {@link SpanUtils} + */ + public SpanUtils setLeadingMargin( + @IntRange(from = 0) final int first, + @IntRange(from = 0) final int rest + ) { + this.first = first; + this.rest = rest; + return this; + } + + /** + * 设置列表标记 + * @param gapWidth 列表间隙宽度 + * @return {@link SpanUtils} + */ + public SpanUtils setBullet(@IntRange(from = 0) final int gapWidth) { + return setBullet(0, 3, gapWidth); + } + + /** + * 设置列表标记 + * @param color 列表颜色 + * @param radius 列表半径 + * @param gapWidth 列表间隙宽度 + * @return {@link SpanUtils} + */ + public SpanUtils setBullet( + @ColorInt final int color, + @IntRange(from = 0) final int radius, + @IntRange(from = 0) final int gapWidth + ) { + this.bulletColor = color; + this.bulletRadius = radius; + this.bulletGapWidth = gapWidth; + return this; + } + + /** + * 设置字体尺寸 + * @param size 字体大小 + * @return {@link SpanUtils} + */ + public SpanUtils setFontSize(@IntRange(from = 0) final int size) { + return setFontSize(size, false); + } + + /** + * 设置字体尺寸 + * @param size 字体大小 + * @param isDp 是否使用 dp + * @return {@link SpanUtils} + */ + public SpanUtils setFontSize( + @IntRange(from = 0) final int size, + final boolean isDp + ) { + this.fontSize = size; + this.fontSizeIsDp = isDp; + return this; + } + + /** + * 设置字体比例 + * @param proportion 字体比例 + * @return {@link SpanUtils} + */ + public SpanUtils setFontProportion(final float proportion) { + this.proportion = proportion; + return this; + } + + /** + * 设置字体横向比例 + * @param proportion 字体横向比例 + * @return {@link SpanUtils} + */ + public SpanUtils setFontXProportion(final float proportion) { + this.xProportion = proportion; + return this; + } + + // = + + /** + * 设置删除线 + * @return {@link SpanUtils} + */ + public SpanUtils setStrikethrough() { + this.isStrikethrough = true; + return this; + } + + /** + * 设置下划线 + * @return {@link SpanUtils} + */ + public SpanUtils setUnderline() { + this.isUnderline = true; + return this; + } + + /** + * 设置上标 + * @return {@link SpanUtils} + */ + public SpanUtils setSuperscript() { + this.isSuperscript = true; + return this; + } + + /** + * 设置下标 + * @return {@link SpanUtils} + */ + public SpanUtils setSubscript() { + this.isSubscript = true; + return this; + } + + /** + * 设置粗体 + * @return {@link SpanUtils} + */ + public SpanUtils setBold() { + isBold = true; + return this; + } + + /** + * 设置斜体 + * @return {@link SpanUtils} + */ + public SpanUtils setItalic() { + isItalic = true; + return this; + } + + /** + * 设置粗斜体 + * @return {@link SpanUtils} + */ + public SpanUtils setBoldItalic() { + isBoldItalic = true; + return this; + } + + /** + * 设置字体系列 + * @param fontFamily 字体系列 + *
    + *
  • monospace
  • + *
  • serif
  • + *
  • sans-serif
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setFontFamily(@NonNull final String fontFamily) { + this.fontFamily = fontFamily; + return this; + } + + /** + * 设置字体 + * @param typeface {@link Typeface} + * @return {@link SpanUtils} + */ + public SpanUtils setTypeface(@NonNull final Typeface typeface) { + this.typeface = typeface; + return this; + } + + /** + * 设置水平对齐 + * @param alignment 对齐类型 + *
    + *
  • {@link Layout.Alignment#ALIGN_NORMAL }
  • + *
  • {@link Layout.Alignment#ALIGN_OPPOSITE}
  • + *
  • {@link Layout.Alignment#ALIGN_CENTER }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setHorizontalAlign(@NonNull final Layout.Alignment alignment) { + this.alignment = alignment; + return this; + } + + /** + * 设置垂直对齐 + * @param align 对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER }
  • + *
  • {@link Align#ALIGN_BASELINE}
  • + *
  • {@link Align#ALIGN_BOTTOM }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setVerticalAlign(@Align final int align) { + this.verticalAlign = align; + return this; + } + + /** + * 设置点击事件 + *
+     *     需设置 {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
+     * 
+ * @param clickSpan {@link ClickableSpan} + * @return {@link SpanUtils} + */ + public SpanUtils setClickSpan(@NonNull final ClickableSpan clickSpan) { + if (mTextView != null && mTextView.getMovementMethod() == null) { + mTextView.setMovementMethod(LinkMovementMethod.getInstance()); + } + this.clickSpan = clickSpan; + return this; + } + + /** + * 设置超链接 + *
+     *     需设置 {@code view.setMovementMethod(LinkMovementMethod.getInstance())}
+     * 
+ * @param url 超链接 + * @return {@link SpanUtils} + */ + public SpanUtils setUrl(@NonNull final String url) { + if (mTextView != null && mTextView.getMovementMethod() == null) { + mTextView.setMovementMethod(LinkMovementMethod.getInstance()); + } + this.url = url; + return this; + } + + /** + * 设置模糊 + * @param radius 模糊程度 + * @param style 模糊样式 + *
    + *
  • {@link BlurMaskFilter.Blur#NORMAL}
  • + *
  • {@link BlurMaskFilter.Blur#SOLID}
  • + *
  • {@link BlurMaskFilter.Blur#OUTER}
  • + *
  • {@link BlurMaskFilter.Blur#INNER}
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils setBlur( + @FloatRange(from = 0, fromInclusive = false) final float radius, + final BlurMaskFilter.Blur style + ) { + this.blurRadius = radius; + this.style = style; + return this; + } + + /** + * 设置着色器 + * @param shader {@link Shader} + * @return {@link SpanUtils} + */ + public SpanUtils setShader(@NonNull final Shader shader) { + this.shader = shader; + return this; + } + + /** + * 设置阴影 + * @param radius 阴影半径 + * @param dx X 轴阴影偏移值 + * @param dy Y 轴阴影偏移值 + * @param shadowColor 阴影颜色 + * @return {@link SpanUtils} + */ + public SpanUtils setShadow( + @FloatRange(from = 0, fromInclusive = false) final float radius, + final float dx, + final float dy, + final int shadowColor + ) { + this.shadowRadius = radius; + this.shadowDx = dx; + this.shadowDy = dy; + this.shadowColor = shadowColor; + return this; + } + + // = + + /** + * 自定义 setSpan 参数 + * @param spans span 数组参数 + * @return {@link SpanUtils} + */ + public SpanUtils setSpans(@NonNull final Object... spans) { + if (spans.length > 0) { + this.spans = spans; + } + return this; + } + + /** + * 追加文本 + * @param text 文本内容 + * @return {@link SpanUtils} + */ + public SpanUtils append(@NonNull final CharSequence text) { + apply(TYPE_CHAR_SEQUENCE); + mText = text; + return this; + } + + /** + * 追加换行 + * @return {@link SpanUtils} + */ + public SpanUtils appendLine() { + apply(TYPE_CHAR_SEQUENCE); + mText = DevFinal.SYMBOL.NEW_LINE; + return this; + } + + /** + * 追加文本 ( 换行 ) + * @param text 文本内容 + * @return {@link SpanUtils} + */ + public SpanUtils appendLine(@NonNull final CharSequence text) { + apply(TYPE_CHAR_SEQUENCE); + mText = text + DevFinal.SYMBOL.NEW_LINE; + return this; + } + + // = + + /** + * 追加 Image + * @param bitmap {@link Bitmap} image + * @return {@link SpanUtils} + */ + public SpanUtils appendImage(@NonNull final Bitmap bitmap) { + return appendImage(bitmap, ALIGN_BOTTOM); + } + + /** + * 追加 Image + * @param bitmap {@link Bitmap} image + * @param align 对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER }
  • + *
  • {@link Align#ALIGN_BASELINE}
  • + *
  • {@link Align#ALIGN_BOTTOM }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils appendImage( + @NonNull final Bitmap bitmap, + @Align final int align + ) { + apply(TYPE_IMAGE); + this.imageBitmap = bitmap; + this.alignImage = align; + return this; + } + + /** + * 追加 Image + * @param drawable {@link Drawable} image + * @return {@link SpanUtils} + */ + public SpanUtils appendImage(@NonNull final Drawable drawable) { + return appendImage(drawable, ALIGN_BOTTOM); + } + + /** + * 追加 Image + * @param drawable {@link Drawable} image + * @param align 对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER }
  • + *
  • {@link Align#ALIGN_BASELINE}
  • + *
  • {@link Align#ALIGN_BOTTOM }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils appendImage( + @NonNull final Drawable drawable, + @Align final int align + ) { + apply(TYPE_IMAGE); + this.imageDrawable = drawable; + this.alignImage = align; + return this; + } + + /** + * 追加 Image + * @param uri {@link Uri} image + * @return {@link SpanUtils} + */ + public SpanUtils appendImage(@NonNull final Uri uri) { + return appendImage(uri, ALIGN_BOTTOM); + } + + /** + * 追加 Image + * @param uri {@link Uri} image + * @param align 对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER }
  • + *
  • {@link Align#ALIGN_BASELINE}
  • + *
  • {@link Align#ALIGN_BOTTOM }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils appendImage( + @NonNull final Uri uri, + @Align final int align + ) { + apply(TYPE_IMAGE); + this.imageUri = uri; + this.alignImage = align; + return this; + } + + /** + * 追加 Image + * @param resourceId The resource id of image + * @return {@link SpanUtils} + */ + public SpanUtils appendImage(@DrawableRes final int resourceId) { + return appendImage(resourceId, ALIGN_BOTTOM); + } + + /** + * 追加 Image + * @param resourceId The resource id of image + * @param align 对齐类型 + *
    + *
  • {@link Align#ALIGN_TOP }
  • + *
  • {@link Align#ALIGN_CENTER }
  • + *
  • {@link Align#ALIGN_BASELINE}
  • + *
  • {@link Align#ALIGN_BOTTOM }
  • + *
+ * @return {@link SpanUtils} + */ + public SpanUtils appendImage( + @DrawableRes final int resourceId, + @Align final int align + ) { + apply(TYPE_IMAGE); + this.imageResourceId = resourceId; + this.alignImage = align; + return this; + } + + // = + + /** + * 追加空格 + * @param size 空格大小 + * @return {@link SpanUtils} + */ + public SpanUtils appendSpace(@IntRange(from = 0) final int size) { + return appendSpace(size, Color.TRANSPARENT); + } + + /** + * 追加空格 + * @param size 空格大小 + * @param color 空格颜色 + * @return {@link SpanUtils} + */ + public SpanUtils appendSpace( + @IntRange(from = 0) final int size, + @ColorInt final int color + ) { + apply(TYPE_SPACE); + this.spaceSize = size; + this.spaceColor = color; + return this; + } + + // = + + /** + * 获取 SpannableStringBuilder + * @return {@link SpannableStringBuilder} + */ + public SpannableStringBuilder get() { + return mBuilder; + } + + /** + * 创建 SpannableStringBuilder + * @return {@link SpannableStringBuilder} + */ + public SpannableStringBuilder create() { + applyLast(); + if (mTextView != null) { + mTextView.setText(mBuilder); + } + return mBuilder; + } + + // ========== + // = 私有方法 = + // ========== + + /** + * 应用效果 + * @param type 效果 ( 应用 ) 类型 + */ + private void apply(final int type) { + applyLast(); + mType = type; + } + + /** + * 应用效果, 并重置配置 + */ + private void applyLast() { + if (mType == TYPE_CHAR_SEQUENCE) { + updateCharCharSequence(); + } else if (mType == TYPE_IMAGE) { + updateImage(); + } else if (mType == TYPE_SPACE) { + updateSpace(); + } + setDefault(); + } + + /** + * 更新 CharSequence 字符 + */ + private void updateCharCharSequence() { + if (mText.length() == 0) return; + int start = mBuilder.length(); + if (start == 0 && lineHeight != -1) { // bug of LineHeightSpan when first line + mBuilder.append(Character.toString((char) 2)) + .append(DevFinal.SYMBOL.NEW_LINE) + .setSpan(new AbsoluteSizeSpan(0), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + start = 2; + } + mBuilder.append(mText); + int end = mBuilder.length(); + if (verticalAlign != -1) { + mBuilder.setSpan(new VerticalAlignSpan(verticalAlign), start, end, flag); + } + if (foregroundColor != COLOR_DEFAULT) { + mBuilder.setSpan(new ForegroundColorSpan(foregroundColor), start, end, flag); + } + if (backgroundColor != COLOR_DEFAULT) { + mBuilder.setSpan(new BackgroundColorSpan(backgroundColor), start, end, flag); + } + if (first != -1) { + mBuilder.setSpan(new LeadingMarginSpan.Standard(first, rest), start, end, flag); + } + if (quoteColor != COLOR_DEFAULT) { + mBuilder.setSpan(new CustomQuoteSpan(quoteColor, stripeWidth, quoteGapWidth), start, end, flag); + } + if (bulletColor != COLOR_DEFAULT) { + mBuilder.setSpan(new CustomBulletSpan(bulletColor, bulletRadius, bulletGapWidth), start, end, flag); + } + if (fontSize != -1) { + mBuilder.setSpan(new AbsoluteSizeSpan(fontSize, fontSizeIsDp), start, end, flag); + } + if (proportion != -1) { + mBuilder.setSpan(new RelativeSizeSpan(proportion), start, end, flag); + } + if (xProportion != -1) { + mBuilder.setSpan(new ScaleXSpan(xProportion), start, end, flag); + } + if (lineHeight != -1) { + mBuilder.setSpan(new CustomLineHeightSpan(lineHeight, alignLine), start, end, flag); + } + if (isStrikethrough) { + mBuilder.setSpan(new StrikethroughSpan(), start, end, flag); + } + if (isUnderline) { + mBuilder.setSpan(new UnderlineSpan(), start, end, flag); + } + if (isSuperscript) { + mBuilder.setSpan(new SuperscriptSpan(), start, end, flag); + } + if (isSubscript) { + mBuilder.setSpan(new SubscriptSpan(), start, end, flag); + } + if (isBold) { + mBuilder.setSpan(new StyleSpan(Typeface.BOLD), start, end, flag); + } + if (isItalic) { + mBuilder.setSpan(new StyleSpan(Typeface.ITALIC), start, end, flag); + } + if (isBoldItalic) { + mBuilder.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), start, end, flag); + } + if (fontFamily != null) { + mBuilder.setSpan(new TypefaceSpan(fontFamily), start, end, flag); + } + if (typeface != null) { + mBuilder.setSpan(new CustomTypefaceSpan(typeface), start, end, flag); + } + if (alignment != null) { + mBuilder.setSpan(new AlignmentSpan.Standard(alignment), start, end, flag); + } + if (clickSpan != null) { + mBuilder.setSpan(clickSpan, start, end, flag); + } + if (url != null) { + mBuilder.setSpan(new URLSpan(url), start, end, flag); + } + if (blurRadius != -1) { + mBuilder.setSpan(new MaskFilterSpan(new BlurMaskFilter(blurRadius, style)), start, end, flag); + } + if (shader != null) { + mBuilder.setSpan(new ShaderSpan(shader), start, end, flag); + } + if (shadowRadius != -1) { + mBuilder.setSpan(new ShadowSpan(shadowRadius, shadowDx, shadowDy, shadowColor), start, end, flag); + } + if (spans != null) { + for (Object span : spans) { + mBuilder.setSpan(span, start, end, flag); + } + } + } + + /** + * 更新 Image + */ + private void updateImage() { + int start = mBuilder.length(); + mText = ""; + updateCharCharSequence(); + int end = mBuilder.length(); + if (imageBitmap != null) { + mBuilder.setSpan(new CustomImageSpan(imageBitmap, alignImage), start, end, flag); + } else if (imageDrawable != null) { + mBuilder.setSpan(new CustomImageSpan(imageDrawable, alignImage), start, end, flag); + } else if (imageUri != null) { + mBuilder.setSpan(new CustomImageSpan(imageUri, alignImage), start, end, flag); + } else if (imageResourceId != -1) { + mBuilder.setSpan(new CustomImageSpan(imageResourceId, alignImage), start, end, flag); + } + } + + /** + * 更新 Span + */ + private void updateSpace() { + int start = mBuilder.length(); + mText = "< >"; + updateCharCharSequence(); + int end = mBuilder.length(); + mBuilder.setSpan(new SpaceSpan(spaceSize, spaceColor), start, end, flag); + } + + /** + * 初始化默认值 + */ + private void setDefault() { + flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; + foregroundColor = COLOR_DEFAULT; + backgroundColor = COLOR_DEFAULT; + lineHeight = -1; + quoteColor = COLOR_DEFAULT; + first = -1; + bulletColor = COLOR_DEFAULT; + fontSize = -1; + proportion = -1; + xProportion = -1; + isStrikethrough = false; + isUnderline = false; + isSuperscript = false; + isSubscript = false; + isBold = false; + isItalic = false; + isBoldItalic = false; + fontFamily = null; + typeface = null; + alignment = null; + verticalAlign = -1; + clickSpan = null; + url = null; + blurRadius = -1; + shader = null; + shadowRadius = -1; + spaceSize = -1; + spans = null; + + imageBitmap = null; + imageDrawable = null; + imageUri = null; + imageResourceId = -1; + } + + // ======================== + // = 内部自定义 Span 效果实现 = + // ======================== + + /** + * detail: 垂直对齐 Span + * @author Ttt + */ + static class VerticalAlignSpan + extends ReplacementSpan { + + static final int ALIGN_CENTER = 2; + static final int ALIGN_TOP = 3; + + final int mVerticalAlignment; + + VerticalAlignSpan(int verticalAlignment) { + mVerticalAlignment = verticalAlignment; + } + + @Override + public int getSize( + @NonNull Paint paint, + CharSequence text, + int start, + int end, + @Nullable Paint.FontMetricsInt fm + ) { + text = text.subSequence(start, end); + return (int) paint.measureText(text.toString()); + } + + @Override + public void draw( + @NonNull Canvas canvas, + CharSequence text, + int start, + int end, + float x, + int top, + int y, + int bottom, + @NonNull Paint paint + ) { + text = text.subSequence(start, end); + Paint.FontMetricsInt fm = paint.getFontMetricsInt(); + canvas.drawText( + text.toString(), x, + y - ((y + fm.descent + y + fm.ascent) / 2 - (bottom + top) / 2), + paint + ); + } + } + + /** + * detail: 行高 Span + * @author Ttt + */ + static class CustomLineHeightSpan + implements LineHeightSpan { + + private final int height; + + static final int ALIGN_CENTER = 2; + static final int ALIGN_TOP = 3; + + final int mVerticalAlignment; + static Paint.FontMetricsInt sfm; + + CustomLineHeightSpan( + int height, + int verticalAlignment + ) { + this.height = height; + mVerticalAlignment = verticalAlignment; + } + + @Override + public void chooseHeight( + final CharSequence text, + final int start, + final int end, + final int spanstartv, + final int v, + final Paint.FontMetricsInt fm + ) { + if (sfm == null) { + sfm = new Paint.FontMetricsInt(); + sfm.top = fm.top; + sfm.ascent = fm.ascent; + sfm.descent = fm.descent; + sfm.bottom = fm.bottom; + sfm.leading = fm.leading; + } else { + fm.top = sfm.top; + fm.ascent = sfm.ascent; + fm.descent = sfm.descent; + fm.bottom = sfm.bottom; + fm.leading = sfm.leading; + } + int need = height - (v + fm.descent - fm.ascent - spanstartv); + if (need > 0) { + if (mVerticalAlignment == ALIGN_TOP) { + fm.descent += need; + } else if (mVerticalAlignment == ALIGN_CENTER) { + fm.descent += need / 2; + fm.ascent -= need / 2; + } else { + fm.ascent -= need; + } + } + need = height - (v + fm.bottom - fm.top - spanstartv); + if (need > 0) { + if (mVerticalAlignment == ALIGN_TOP) { + fm.bottom += need; + } else if (mVerticalAlignment == ALIGN_CENTER) { + fm.bottom += need / 2; + fm.top -= need / 2; + } else { + fm.top -= need; + } + } + if (end == ((Spanned) text).getSpanEnd(this)) { + sfm = null; + } + } + } + + /** + * detail: 空格 Span + * @author Ttt + */ + static class SpaceSpan + extends ReplacementSpan { + + private final int width; + private final Paint paint = new Paint(); + + private SpaceSpan(final int width) { + this(width, Color.TRANSPARENT); + } + + private SpaceSpan( + final int width, + final int color + ) { + super(); + this.width = width; + paint.setColor(color); + paint.setStyle(Paint.Style.FILL); + } + + @Override + public int getSize( + @NonNull final Paint paint, + final CharSequence text, + @IntRange(from = 0) final int start, + @IntRange(from = 0) final int end, + @Nullable final Paint.FontMetricsInt fm + ) { + return width; + } + + @Override + public void draw( + @NonNull final Canvas canvas, + final CharSequence text, + @IntRange(from = 0) final int start, + @IntRange(from = 0) final int end, + final float x, + final int top, + final int y, + final int bottom, + @NonNull final Paint paint + ) { + canvas.drawRect(x, top, x + width, bottom, this.paint); + } + } + + /** + * detail: 自定义引用样式 Span + * @author Ttt + */ + static class CustomQuoteSpan + implements LeadingMarginSpan { + + private final int color; + private final int stripeWidth; + private final int gapWidth; + + private CustomQuoteSpan( + final int color, + final int stripeWidth, + final int gapWidth + ) { + super(); + this.color = color; + this.stripeWidth = stripeWidth; + this.gapWidth = gapWidth; + } + + public int getLeadingMargin(final boolean first) { + return stripeWidth + gapWidth; + } + + public void drawLeadingMargin( + final Canvas c, + final Paint p, + final int x, + final int dir, + final int top, + final int baseline, + final int bottom, + final CharSequence text, + final int start, + final int end, + final boolean first, + final Layout layout + ) { + Paint.Style style = p.getStyle(); + int color = p.getColor(); + + p.setStyle(Paint.Style.FILL); + p.setColor(this.color); + + c.drawRect(x, top, x + dir * stripeWidth, bottom, p); + + p.setStyle(style); + p.setColor(color); + } + } + + /** + * detail: 自定义列表项样式 Span + * @author Ttt + */ + static class CustomBulletSpan + implements LeadingMarginSpan { + + private final int color; + private final int radius; + private final int gapWidth; + + private Path sBulletPath = null; + + private CustomBulletSpan( + final int color, + final int radius, + final int gapWidth + ) { + this.color = color; + this.radius = radius; + this.gapWidth = gapWidth; + } + + public int getLeadingMargin(final boolean first) { + return 2 * radius + gapWidth; + } + + public void drawLeadingMargin( + final Canvas c, + final Paint p, + final int x, + final int dir, + final int top, + final int baseline, + final int bottom, + final CharSequence text, + final int start, + final int end, + final boolean first, + final Layout l + ) { + if (((Spanned) text).getSpanStart(this) == start) { + Paint.Style style = p.getStyle(); + int oldColor; + oldColor = p.getColor(); + p.setColor(color); + p.setStyle(Paint.Style.FILL); + if (c.isHardwareAccelerated()) { + if (sBulletPath == null) { + sBulletPath = new Path(); + // Bullet is slightly better to avoid aliasing artifacts on mdpi devices. + sBulletPath.addCircle(0.0F, 0.0F, radius, Path.Direction.CW); + } + c.save(); + c.translate(x + dir * radius, (top + bottom) / 2.0F); + c.drawPath(sBulletPath, p); + c.restore(); + } else { + c.drawCircle(x + dir * radius, (top + bottom) / 2.0F, radius, p); + } + p.setColor(oldColor); + p.setStyle(style); + } + } + } + + /** + * detail: 自定义字体样式 Span + * @author Ttt + */ + @SuppressLint("ParcelCreator") + static class CustomTypefaceSpan + extends TypefaceSpan { + + private final Typeface newType; + + private CustomTypefaceSpan(final Typeface type) { + super(""); + newType = type; + } + + @Override + public void updateDrawState(final TextPaint textPaint) { + apply(textPaint, newType); + } + + @Override + public void updateMeasureState(final TextPaint paint) { + apply(paint, newType); + } + + private void apply( + final Paint paint, + final Typeface tf + ) { + int oldStyle; + Typeface old = paint.getTypeface(); + if (old == null) { + oldStyle = 0; + } else { + oldStyle = old.getStyle(); + } + + int fake = oldStyle & ~tf.getStyle(); + if ((fake & Typeface.BOLD) != 0) { + paint.setFakeBoldText(true); + } + + if ((fake & Typeface.ITALIC) != 0) { + paint.setTextSkewX(-0.25F); + } +// paint.getShader(); + paint.setTypeface(tf); + } + } + + /** + * detail: 自定义图片绘制效果 Span + * @author Ttt + */ + static class CustomImageSpan + extends CustomDynamicDrawableSpan { + + private Drawable mDrawable; + private Uri mContentUri; + private int mResourceId; + + private CustomImageSpan( + final Bitmap bitmap, + final int verticalAlignment + ) { + super(verticalAlignment); + mDrawable = new BitmapDrawable(getContext().getResources(), bitmap); + mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); + } + + private CustomImageSpan( + final Drawable drawable, + final int verticalAlignment + ) { + super(verticalAlignment); + mDrawable = drawable; + mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); + } + + private CustomImageSpan( + final Uri uri, + final int verticalAlignment + ) { + super(verticalAlignment); + mContentUri = uri; + } + + private CustomImageSpan( + @DrawableRes final int resourceId, + final int verticalAlignment + ) { + super(verticalAlignment); + mResourceId = resourceId; + } + + @Override + public Drawable getDrawable() { + Drawable drawable = null; + if (mDrawable != null) { + drawable = mDrawable; + } else if (mContentUri != null) { + Bitmap bitmap; + try { + InputStream is = getContext().getContentResolver().openInputStream(mContentUri); + bitmap = BitmapFactory.decodeStream(is); + drawable = new BitmapDrawable(getContext().getResources(), bitmap); + drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); + CloseUtils.closeIOQuietly(is); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "Failed to loaded content %s", mContentUri); + } + } else { + try { + drawable = ContextCompat.getDrawable(getContext(), mResourceId); + if (drawable != null) { + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight() + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "Unable to find resource: %s", mResourceId); + } + } + return drawable; + } + } + + /** + * detail: 自定义 Drawable 绘制效果 Span + * @author Ttt + */ + static abstract class CustomDynamicDrawableSpan + extends ReplacementSpan { + + static final int ALIGN_BOTTOM = 0; + static final int ALIGN_BASELINE = 1; + static final int ALIGN_CENTER = 2; + static final int ALIGN_TOP = 3; + final int mVerticalAlignment; + + private CustomDynamicDrawableSpan() { + mVerticalAlignment = ALIGN_BOTTOM; + } + + private CustomDynamicDrawableSpan(final int verticalAlignment) { + mVerticalAlignment = verticalAlignment; + } + + public abstract Drawable getDrawable(); + + @Override + public int getSize( + @NonNull final Paint paint, + final CharSequence text, + final int start, + final int end, + final Paint.FontMetricsInt fm + ) { + Drawable drawable = getCachedDrawable(); + Rect rect = drawable.getBounds(); + if (fm != null) { + int lineHeight = fm.bottom - fm.top; + if (lineHeight < rect.height()) { + if (mVerticalAlignment == ALIGN_TOP) { + fm.bottom = rect.height() + fm.top; + } else if (mVerticalAlignment == ALIGN_CENTER) { + fm.top = -rect.height() / 2 - lineHeight / 4; + fm.bottom = rect.height() / 2 - lineHeight / 4; + } else { + fm.top = -rect.height() + fm.bottom; + } + fm.ascent = fm.top; + fm.descent = fm.bottom; + } + } + return rect.right; + } + + @Override + public void draw( + @NonNull final Canvas canvas, + final CharSequence text, + final int start, + final int end, + final float x, + final int top, + final int y, + final int bottom, + @NonNull final Paint paint + ) { + Drawable drawable = getCachedDrawable(); + Rect rect = drawable.getBounds(); + canvas.save(); + float transY; + int lineHeight = bottom - top; + if (rect.height() < lineHeight) { + if (mVerticalAlignment == ALIGN_TOP) { + transY = top; + } else if (mVerticalAlignment == ALIGN_CENTER) { + transY = (bottom + top - rect.height()) / 2; + } else if (mVerticalAlignment == ALIGN_BASELINE) { + transY = y - rect.height(); + } else { + transY = bottom - rect.height(); + } + canvas.translate(x, transY); + } else { + canvas.translate(x, top); + } + drawable.draw(canvas); + canvas.restore(); + } + + private Drawable getCachedDrawable() { + WeakReference wr = mDrawableRef; + Drawable drawable = null; + if (wr != null) { + drawable = wr.get(); + } + if (drawable == null) { + drawable = getDrawable(); + mDrawableRef = new WeakReference<>(drawable); + } + return drawable; + } + + private WeakReference mDrawableRef; + } + + /** + * detail: 着色效果 Span + * @author Ttt + */ + static class ShaderSpan + extends CharacterStyle + implements UpdateAppearance { + private final Shader mShader; + + private ShaderSpan(final Shader shader) { + this.mShader = shader; + } + + @Override + public void updateDrawState(final TextPaint tp) { + tp.setShader(mShader); + } + } + + /** + * detail: 阴影效果 Span + * @author Ttt + */ + static class ShadowSpan + extends CharacterStyle + implements UpdateAppearance { + private final float radius; + private final float dx; + private final float dy; + private final int shadowColor; + + private ShadowSpan( + final float radius, + final float dx, + final float dy, + final int shadowColor + ) { + this.radius = radius; + this.dx = dx; + this.dy = dy; + this.shadowColor = shadowColor; + } + + @Override + public void updateDrawState(final TextPaint tp) { + tp.setShadowLayer(radius, dx, dy, shadowColor); + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * detail: SpannableStringBuilder 内部类, 实现序列化方便传值 + * @author Ttt + */ + private static class SerializableSpannableStringBuilder + extends SpannableStringBuilder + implements Serializable { + + private static final long serialVersionUID = 2514562037168478805L; + } + + /** + * 获取 Context + * @return {@link Context} + */ + private static Context getContext() { + return DevUtils.getContext(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/StateListUtils.java b/lib/DevApp/src/main/java/dev/utils/app/StateListUtils.java new file mode 100644 index 0000000000..5a7806b4e2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/StateListUtils.java @@ -0,0 +1,387 @@ +package dev.utils.app; + +import android.content.res.ColorStateList; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; + +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; + +import dev.utils.LogPrintUtils; + +/** + * detail: 颜色状态列表工具类 + * @author Ttt + *
+ *     android:state_active	        是否处于激活状态
+ *     android:state_checkable	    是否可勾选
+ *     android:state_checked	    是否已勾选
+ *     android:state_enabled	    是否可用
+ *     android:state_first	        是否开始状态
+ *     android:state_focused	    是否已获取焦点
+ *     android:state_last	        是否处于结束
+ *     android:state_middle	        是否处于中间
+ *     android:state_pressed	    是否处于按下状态
+ *     android:state_selected	    是否处于选中状态
+ *     android:state_window_focused	是否窗口已获取焦点
+ * 
+ */ +public final class StateListUtils { + + private StateListUtils() { + } + + // 日志 TAG + private static final String TAG = StateListUtils.class.getSimpleName(); + + /** + * 获取 ColorStateList + * @param id resource identifier of a {@link ColorStateList} + * @return {@link ColorStateList} + */ + public static ColorStateList getColorStateList(@ColorRes final int id) { + return ResourceUtils.getColorStateList(id); + } + + // ============= + // = 设置字体颜色 = + // ============= + + // =========== + // = 字符串颜色 = + // =========== + + /** + * 创建 ColorStateList + *
+     *     createColorStateList("#ffffffff", "#ff44e6ff")
+     * 
+ * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + final String pressed, + final String normal + ) { + try { + // 颜色值 + int[] colors = new int[]{Color.parseColor(pressed), Color.parseColor(normal)}; + // 状态值 + int[][] states = new int[2][]; + states[0] = new int[]{android.R.attr.state_pressed}; // 选中状态 + states[1] = new int[]{}; // 默认状态 + // 生成 ColorStateList + return new ColorStateList(states, colors); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createColorStateList"); + } + return null; + } + + /** + * 创建 ColorStateList + * @param selected 选中状态 + * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + final String selected, + final String pressed, + final String normal + ) { + try { + // 颜色值 + int[] colors = new int[]{Color.parseColor(selected), Color.parseColor(pressed), Color.parseColor(normal)}; + // 状态值 + int[][] states = new int[3][]; + states[0] = new int[]{android.R.attr.state_selected}; // 选中状态 + states[1] = new int[]{android.R.attr.state_pressed}; // 点击状态 + states[2] = new int[]{}; // 默认状态 + // 生成 ColorStateList + return new ColorStateList(states, colors); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createColorStateList"); + } + return null; + } + + /** + * 创建 ColorStateList + * @param selected 选中状态 + * @param pressed 按下状态 + * @param focused 获取焦点状态 + * @param checked 已勾选状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + final String selected, + final String pressed, + final String focused, + final String checked, + final String normal + ) { + try { + // 颜色值 + int[] colors = new int[]{Color.parseColor(selected), Color.parseColor(pressed), + Color.parseColor(focused), Color.parseColor(checked), Color.parseColor(normal)}; + // 状态值 + int[][] states = new int[5][]; + states[0] = new int[]{android.R.attr.state_selected}; // 选中状态 + states[1] = new int[]{android.R.attr.state_pressed}; // 点击状态 + states[2] = new int[]{android.R.attr.state_focused}; // 获取焦点状态 + states[3] = new int[]{android.R.attr.state_checked}; // 选中状态 + states[4] = new int[]{}; // 默认状态 + // 生成 ColorStateList + return new ColorStateList(states, colors); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createColorStateList"); + } + return null; + } + + // ======= + // = int = + // ======= + + /** + * 创建 ColorStateList + *
+     *     createColorStateList(R.color.white, R.color.black)
+     * 
+ * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + @ColorRes final int pressed, + @ColorRes final int normal + ) { + // 颜色值 + int[] colors = new int[2]; + colors[0] = ResourceUtils.getColor(pressed); + colors[1] = ResourceUtils.getColor(normal); + // 状态值 + int[][] states = new int[2][]; + states[0] = new int[]{android.R.attr.state_pressed}; + states[1] = new int[]{}; + // 生成 ColorStateList + return new ColorStateList(states, colors); + } + + /** + * 创建 ColorStateList + * @param selected 选中状态 + * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + @ColorRes final int selected, + @ColorRes final int pressed, + @ColorRes final int normal + ) { + // 颜色值 + int[] colors = new int[3]; + colors[0] = ResourceUtils.getColor(selected); + colors[1] = ResourceUtils.getColor(pressed); + colors[2] = ResourceUtils.getColor(normal); + // 状态值 + int[][] states = new int[3][]; + states[0] = new int[]{android.R.attr.state_selected}; + states[1] = new int[]{android.R.attr.state_pressed}; + states[2] = new int[]{}; + // 生成 ColorStateList + return new ColorStateList(states, colors); + } + + /** + * 创建 ColorStateList + * @param selected 选中状态 + * @param pressed 按下状态 + * @param focused 获取焦点状态 + * @param checked 已勾选状态 + * @param normal 默认状态 + * @return {@link ColorStateList} + */ + public static ColorStateList createColorStateList( + @ColorRes final int selected, + @ColorRes final int pressed, + @ColorRes final int focused, + @ColorRes final int checked, + @ColorRes final int normal + ) { + // 颜色值 + int[] colors = new int[5]; + colors[0] = ResourceUtils.getColor(selected); + colors[1] = ResourceUtils.getColor(pressed); + colors[2] = ResourceUtils.getColor(focused); + colors[3] = ResourceUtils.getColor(checked); + colors[4] = ResourceUtils.getColor(normal); + // 状态值 + int[][] states = new int[5][]; + states[0] = new int[]{android.R.attr.state_selected}; + states[1] = new int[]{android.R.attr.state_pressed}; + states[2] = new int[]{android.R.attr.state_focused}; + states[3] = new int[]{android.R.attr.state_checked}; + states[4] = new int[]{}; + // 生成 ColorStateList + return new ColorStateList(states, colors); + } + + // ====================== + // = 设置背景切换 Drawable = + // ====================== + + /** + * 创建 StateListDrawable + *
+     *     view.setBackground(Drawable)
+     * 
+ * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + @DrawableRes final int pressed, + @DrawableRes final int normal + ) { + // 获取 Drawable + Drawable pressedDraw = ResourceUtils.getDrawable(pressed); + Drawable normalDraw = ResourceUtils.getDrawable(normal); + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDraw); + stateListDrawable.addState(new int[]{}, normalDraw); + return stateListDrawable; + } + + /** + * 创建 StateListDrawable + * @param selected 选中状态 + * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + @DrawableRes final int selected, + @DrawableRes final int pressed, + @DrawableRes final int normal + ) { + // 获取 Drawable + Drawable selectedDraw = ResourceUtils.getDrawable(selected); + Drawable pressedDraw = ResourceUtils.getDrawable(pressed); + Drawable normalDraw = ResourceUtils.getDrawable(normal); + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, selectedDraw); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDraw); + stateListDrawable.addState(new int[]{}, normalDraw); + return stateListDrawable; + } + + /** + * 创建 StateListDrawable + * @param selected 选中状态 + * @param pressed 按下状态 + * @param focused 获取焦点状态 + * @param checked 已勾选状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + @DrawableRes final int selected, + @DrawableRes final int pressed, + @DrawableRes final int focused, + @DrawableRes final int checked, + @DrawableRes final int normal + ) { + // 获取 Drawable + Drawable selectedDraw = ResourceUtils.getDrawable(selected); + Drawable pressedDraw = ResourceUtils.getDrawable(pressed); + Drawable focusedDraw = ResourceUtils.getDrawable(focused); + Drawable checkedDraw = ResourceUtils.getDrawable(checked); + Drawable normalDraw = ResourceUtils.getDrawable(normal); + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, selectedDraw); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDraw); + stateListDrawable.addState(new int[]{android.R.attr.state_focused}, focusedDraw); + stateListDrawable.addState(new int[]{android.R.attr.state_checked}, checkedDraw); + stateListDrawable.addState(new int[]{}, normalDraw); + return stateListDrawable; + } + + // ====================== + // = 设置背景切换 Drawable = + // ====================== + + /** + * 创建 StateListDrawable + *
+     *     view.setBackground(Drawable)
+     * 
+ * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + final Drawable pressed, + final Drawable normal + ) { + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressed); + stateListDrawable.addState(new int[]{}, normal); + return stateListDrawable; + } + + /** + * 创建 StateListDrawable + * @param selected 选中状态 + * @param pressed 按下状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + final Drawable selected, + final Drawable pressed, + final Drawable normal + ) { + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, selected); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressed); + stateListDrawable.addState(new int[]{}, normal); + return stateListDrawable; + } + + /** + * 创建 StateListDrawable + * @param selected 选中状态 + * @param pressed 按下状态 + * @param focused 获取焦点状态 + * @param checked 已勾选状态 + * @param normal 默认状态 + * @return {@link StateListDrawable} + */ + public static StateListDrawable newSelector( + final Drawable selected, + final Drawable pressed, + final Drawable focused, + final Drawable checked, + final Drawable normal + ) { + // 创建 StateListDrawable + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, selected); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressed); + stateListDrawable.addState(new int[]{android.R.attr.state_focused}, focused); + stateListDrawable.addState(new int[]{android.R.attr.state_checked}, checked); + stateListDrawable.addState(new int[]{}, normal); + return stateListDrawable; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/TextViewUtils.java b/lib/DevApp/src/main/java/dev/utils/app/TextViewUtils.java new file mode 100644 index 0000000000..11e55d77a7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/TextViewUtils.java @@ -0,0 +1,3163 @@ +package dev.utils.app; + +import android.content.res.ColorStateList; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.Html; +import android.text.InputFilter; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.method.HideReturnsTransformationMethod; +import android.text.method.PasswordTransformationMethod; +import android.text.method.TransformationMethod; +import android.util.TypedValue; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.RequiresApi; +import androidx.core.widget.TextViewCompat; + +import java.util.ArrayList; +import java.util.List; + +import dev.utils.LogPrintUtils; + +/** + * detail: TextView 工具类 + * @author Ttt + *
+ *     获取字体信息 Paint.FontMetrics
+ *     @see 
+ *     @see 
+ *     

+ * TextView 设置行间距、行高, 以及字间距 + * @see
+ *

+ * android:includeFontPadding + * @see
+ *

+ * 设置文字水平间距: {@link TextViewUtils#setLetterSpacing(View, float)} + * android:letterSpacing + * 设置文字行间距 ( 行高 ): {@link TextViewUtils#setLineSpacing(View, float)}、{@link TextViewUtils#setLineSpacingAndMultiplier(View, float, float)} + * android:lineSpacingExtra + * android:lineSpacingMultiplier + *

+ * setPaintFlags: + * Paint.ANTI_ALIAS_FLAG 抗锯齿标志 + * Paint.FILTER_BITMAP_FLAG 使位图过滤的位掩码标志 + * Paint.DITHER_FLAG 使位图进行有利的抖动的位掩码标志 + * Paint.UNDERLINE_TEXT_FLAG 下划线 + * Paint.STRIKE_THRU_TEXT_FLAG 中划线 + * Paint.FAKE_BOLD_TEXT_FLAG 加粗 + * Paint.LINEAR_TEXT_FLAG 使文本平滑线性扩展的油漆标志 + * Paint.SUBPIXEL_TEXT_FLAG 使文本的亚像素定位的绘图标志 + * Paint.EMBEDDED_BITMAP_TEXT_FLAG 绘制文本时允许使用位图字体的绘图标志 + *
+ */ +public final class TextViewUtils { + + private TextViewUtils() { + } + + // 日志 TAG + private static final String TAG = TextViewUtils.class.getSimpleName(); + + // ================ + // = 获取 TextView = + // ================ + + /** + * 获取 TextView + * @param view {@link View} + * @param 泛型 + * @return {@link TextView} + */ + public static T getTextView(final View view) { + if (view instanceof TextView) { + try { + return (T) view; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTextView"); + } + } + return null; + } + + // ======== + // = Hint = + // ======== + + /** + * 获取 Hint 文本 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView#getHint()} + */ + public static String getHint(final T textView) { + if (textView != null) { + if (textView.getHint() != null) { + return textView.getHint().toString(); + } + } + return null; + } + + /** + * 获取 Hint 文本 + * @param view {@link TextView} + * @return {@link TextView#getHint()} + */ + public static String getHint(final View view) { + return getHint(getTextView(view)); + } + + /** + * 获取多个 TextView Hint 文本 + * @param views View[] + * @return {@link List} 多个 TextView Hint 文本 + */ + public static List getHints(final View... views) { + List lists = new ArrayList<>(); + if (views != null) { + for (View view : views) { + String text = getHint(view); + if (text != null) { + lists.add(text); + } + } + } + return lists; + } + + /** + * 获取多个 TextView Hint 文本 + * @param views TextView[] + * @param 泛型 + * @return {@link List} 多个 TextView Hint 文本 + */ + public static List getHints(final T... views) { + List lists = new ArrayList<>(); + if (views != null) { + for (T view : views) { + String text = getHint(view); + if (text != null) { + lists.add(text); + } + } + } + return lists; + } + + /** + * 设置 Hint 文本 + * @param textView {@link TextView} + * @param text Hint text + * @param 泛型 + * @return {@link TextView} + */ + public static T setHint( + final T textView, + final CharSequence text + ) { + if (textView != null) { + textView.setHint(text); + } + return textView; + } + + /** + * 设置 Hint 文本 + * @param view {@link TextView} + * @param text Hint text + * @return {@link View} + */ + public static View setHint( + final View view, + final CharSequence text + ) { + setHint(getTextView(view), text); + return view; + } + + /** + * 获取 Hint 字体颜色 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link ColorStateList} + */ + public static ColorStateList getHintTextColors(final T textView) { + if (textView != null) { + return textView.getHintTextColors(); + } + return null; + } + + /** + * 获取 Hint 字体颜色 + * @param view {@link TextView} + * @return {@link ColorStateList} + */ + public static ColorStateList getHintTextColors(final View view) { + return getHintTextColors(getTextView(view)); + } + + /** + * 设置 Hint 字体颜色 + * @param textView {@link TextView} + * @param color R.color.id + * @param 泛型 + * @return {@link TextView} + */ + public static T setHintTextColor( + final T textView, + @ColorInt final int color + ) { + if (textView != null) { + textView.setHintTextColor(color); + } + return textView; + } + + /** + * 设置 Hint 字体颜色 + * @param view {@link TextView} + * @param color R.color.id + * @return {@link View} + */ + public static View setHintTextColor( + final View view, + @ColorInt final int color + ) { + setHintTextColor(getTextView(view), color); + return view; + } + + /** + * 设置 Hint 字体颜色 + * @param textView {@link TextView} + * @param colors {@link ColorStateList} + * @param 泛型 + * @return {@link TextView} + */ + public static T setHintTextColor( + final T textView, + final ColorStateList colors + ) { + if (textView != null) { + textView.setHintTextColor(colors); + } + return textView; + } + + /** + * 设置 Hint 字体颜色 + * @param view {@link TextView} + * @param colors {@link ColorStateList} + * @return {@link View} + */ + public static View setHintTextColor( + final View view, + final ColorStateList colors + ) { + setHintTextColor(getTextView(view), colors); + return view; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setHintTextColors( + @ColorInt final int color, + final View... views + ) { + if (views != null) { + for (View view : views) { + setHintTextColor(view, color); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setHintTextColors( + @ColorInt final int color, + final T... views + ) { + if (views != null) { + for (T view : views) { + setHintTextColor(view, color); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setHintTextColors( + final ColorStateList colors, + final View... views + ) { + if (views != null) { + for (View view : views) { + setHintTextColor(view, colors); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setHintTextColors( + final ColorStateList colors, + final T... views + ) { + if (views != null) { + for (T view : views) { + setHintTextColor(view, colors); + } + return true; + } + return false; + } + + // ======== + // = Text = + // ======== + + /** + * 获取文本 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView#getText()} + */ + public static String getText(final T textView) { + if (textView != null) { + return textView.getText().toString(); + } + return null; + } + + /** + * 获取文本 + * @param view {@link TextView} + * @return {@link TextView#getText()} + */ + public static String getText(final View view) { + return getText(getTextView(view)); + } + + /** + * 获取多个 TextView 文本 + * @param views View[] + * @return {@link List} 多个 TextView 文本 + */ + public static List getTexts(final View... views) { + List lists = new ArrayList<>(); + if (views != null) { + for (View view : views) { + String text = getText(view); + if (text != null) { + lists.add(text); + } + } + } + return lists; + } + + /** + * 获取多个 TextView 文本 + * @param views TextView[] + * @param 泛型 + * @return {@link List} 多个 TextView 文本 + */ + public static List getTexts(final T... views) { + List lists = new ArrayList<>(); + if (views != null) { + for (T view : views) { + String text = getText(view); + if (text != null) { + lists.add(text); + } + } + } + return lists; + } + + /** + * 设置文本 + * @param textView {@link TextView} + * @param text TextView text + * @param 泛型 + * @return {@link TextView} + */ + public static T setText( + final T textView, + final CharSequence text + ) { + if (textView != null) { + textView.setText(text); + } + return textView; + } + + /** + * 设置文本 + * @param view {@link TextView} + * @param text TextView text + * @return {@link View} + */ + public static View setText( + final View view, + final CharSequence text + ) { + setText(getTextView(view), text); + return view; + } + + /** + * 设置多个 TextView 文本 + * @param text TextView text + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setTexts( + final CharSequence text, + final View... views + ) { + if (views != null) { + for (View view : views) { + setText(view, text); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView 文本 + * @param text TextView text + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTexts( + final CharSequence text, + final T... views + ) { + if (views != null) { + for (T view : views) { + setText(view, text); + } + return true; + } + return false; + } + + /** + * 获取字体颜色 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link ColorStateList} + */ + public static ColorStateList getTextColors(final T textView) { + if (textView != null) { + return textView.getTextColors(); + } + return null; + } + + /** + * 获取字体颜色 + * @param view {@link TextView} + * @return {@link ColorStateList} + */ + public static ColorStateList getTextColors(final View view) { + return getTextColors(getTextView(view)); + } + + /** + * 设置字体颜色 + * @param textView {@link TextView} + * @param color R.color.id + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextColor( + final T textView, + @ColorInt final int color + ) { + if (textView != null) { + textView.setTextColor(color); + } + return textView; + } + + /** + * 设置字体颜色 + * @param view {@link TextView} + * @param color R.color.id + * @return {@link View} + */ + public static View setTextColor( + final View view, + @ColorInt final int color + ) { + setTextColor(getTextView(view), color); + return view; + } + + /** + * 设置字体颜色 + * @param textView {@link TextView} + * @param colors {@link ColorStateList} + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextColor( + final T textView, + final ColorStateList colors + ) { + if (textView != null) { + textView.setTextColor(colors); + } + return textView; + } + + /** + * 设置字体颜色 + * @param view {@link TextView} + * @param colors {@link ColorStateList} + * @return {@link View} + */ + public static View setTextColor( + final View view, + final ColorStateList colors + ) { + setTextColor(getTextView(view), colors); + return view; + } + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextColors( + @ColorInt final int color, + final View... views + ) { + if (views != null) { + for (View view : views) { + setTextColor(view, color); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextColors( + @ColorInt final int color, + final T... views + ) { + if (views != null) { + for (T view : views) { + setTextColor(view, color); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextColors( + final ColorStateList colors, + final View... views + ) { + if (views != null) { + for (View view : views) { + setTextColor(view, colors); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextColors( + final ColorStateList colors, + final T... views + ) { + if (views != null) { + for (T view : views) { + setTextColor(view, colors); + } + return true; + } + return false; + } + + // ======== + // = Html = + // ======== + + /** + * 设置 Html 内容 + * @param textView {@link TextView} + * @param content Html content + * @param 泛型 + * @return {@link TextView} + */ + public static T setHtmlText( + final T textView, + final String content + ) { + if (textView != null && content != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + textView.setText(Html.fromHtml(content, Html.FROM_HTML_MODE_LEGACY)); + } else { + textView.setText(Html.fromHtml(content)); + } + } + return textView; + } + + /** + * 设置 Html 内容 + * @param view {@link TextView} + * @param content Html content + * @return {@link View} + */ + public static View setHtmlText( + final View view, + final String content + ) { + setHtmlText(getTextView(view), content); + return view; + } + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean setHtmlTexts( + final String content, + final View... views + ) { + if (content != null && views != null) { + for (View view : views) { + setHtmlText(view, content); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @param views TextView[] + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setHtmlTexts( + final String content, + final T... views + ) { + if (content != null && views != null) { + for (T view : views) { + setHtmlText(view, content); + } + return true; + } + return false; + } + + // ========== + // = 字体相关 = + // ========== + + /** + * 获取字体 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link Typeface} + */ + public static Typeface getTypeface(final T textView) { + if (textView != null) { + return textView.getTypeface(); + } + return null; + } + + /** + * 设置字体 + * @param textView {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTypeface( + final T textView, + final Typeface typeface + ) { + if (textView != null && typeface != null) { + textView.setTypeface(typeface); + } + return textView; + } + + /** + * 设置字体 + * @param textView {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTypeface( + final T textView, + final Typeface typeface, + final int style + ) { + if (textView != null && typeface != null) { + textView.setTypeface(typeface, style); + } + return textView; + } + + /** + * 设置字体 + * @param view {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @return {@link View} + */ + public static View setTypeface( + final View view, + final Typeface typeface + ) { + setTypeface(getTextView(view), typeface); + return view; + } + + /** + * 设置字体 + * @param view {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @return {@link View} + */ + public static View setTypeface( + final View view, + final Typeface typeface, + final int style + ) { + setTypeface(getTextView(view), typeface, style); + return view; + } + + // = + + /** + * 设置字体大小 ( px 像素 ) + * @param textView {@link TextView} + * @param size 字体大小 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextSizeByPx( + final T textView, + final float size + ) { + return setTextSize(textView, TypedValue.COMPLEX_UNIT_PX, size); + } + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param textView {@link TextView} + * @param size 字体大小 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextSizeBySp( + final T textView, + final float size + ) { + return setTextSize(textView, TypedValue.COMPLEX_UNIT_SP, size); + } + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param textView {@link TextView} + * @param size 字体大小 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextSizeByDp( + final T textView, + final float size + ) { + return setTextSize(textView, TypedValue.COMPLEX_UNIT_DIP, size); + } + + /** + * 设置字体大小 ( inches 英寸 ) + * @param textView {@link TextView} + * @param size 字体大小 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextSizeByIn( + final T textView, + final float size + ) { + return setTextSize(textView, TypedValue.COMPLEX_UNIT_IN, size); + } + + // = + + /** + * 设置字体大小 ( px 像素 ) + * @param view {@link TextView} + * @param size 字体大小 + * @return {@link View} + */ + public static View setTextSizeByPx( + final View view, + final float size + ) { + setTextSize(getTextView(view), TypedValue.COMPLEX_UNIT_PX, size); + return view; + } + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param view {@link TextView} + * @param size 字体大小 + * @return {@link View} + */ + public static View setTextSizeBySp( + final View view, + final float size + ) { + setTextSize(getTextView(view), TypedValue.COMPLEX_UNIT_SP, size); + return view; + } + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param view {@link TextView} + * @param size 字体大小 + * @return {@link View} + */ + public static View setTextSizeByDp( + final View view, + final float size + ) { + setTextSize(getTextView(view), TypedValue.COMPLEX_UNIT_DIP, size); + return view; + } + + /** + * 设置字体大小 ( inches 英寸 ) + * @param view {@link TextView} + * @param size 字体大小 + * @return {@link View} + */ + public static View setTextSizeByIn( + final View view, + final float size + ) { + setTextSize(getTextView(view), TypedValue.COMPLEX_UNIT_IN, size); + return view; + } + + // = + + /** + * 设置字体大小 + * @param textView {@link TextView} + * @param unit 字体参数类型 + * @param size 字体大小 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextSize( + final T textView, + final int unit, + final float size + ) { + if (textView != null) { + textView.setTextSize(unit, size); + } + return textView; + } + + /** + * 设置字体大小 + * @param view {@link TextView} + * @param unit 字体参数类型 + * @param size 字体大小 + * @return {@link View} + */ + public static View setTextSize( + final View view, + final int unit, + final float size + ) { + setTextSize(getTextView(view), unit, size); + return view; + } + + // = + + /** + * 设置多个 TextView 字体大小 + * @param views View[] + * @param unit 参数类型 + * @param size 字体大小 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextSizes( + final View[] views, + final int unit, + final float size + ) { + if (views != null) { + for (View view : views) { + setTextSize(view, unit, size); + } + return true; + } + return false; + } + + /** + * 设置多个 TextView 字体大小 + * @param views TextView[] + * @param unit 参数类型 + * @param size 字体大小 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setTextSizes( + final T[] views, + final int unit, + final float size + ) { + if (views != null) { + for (T view : views) { + setTextSize(view, unit, size); + } + return true; + } + return false; + } + + // = + + /** + * 获取 TextView 字体大小 ( px ) + * @param textView {@link TextView} + * @param 泛型 + * @return 字体大小 (px) + */ + public static float getTextSize(final T textView) { + if (textView != null) { + return textView.getTextSize(); + } + return -1F; + } + + /** + * 获取 TextView 字体大小 ( px ) + * @param view {@link TextView} + * @return 字体大小 (px) + */ + public static float getTextSize(final View view) { + return getTextSize(getTextView(view)); + } + + // = + + /** + * 清空 flags + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView} + */ + public static T clearFlags(final T textView) { + if (textView != null) { + textView.setPaintFlags(0); + } + return textView; + } + + /** + * 清空 flags + * @param view {@link TextView} + * @return {@link View} + */ + public static View clearFlags(final View view) { + clearFlags(getTextView(view)); + return view; + } + + // = + + /** + * 设置 TextView flags + * @param textView {@link TextView} + * @param flags flags + * @param 泛型 + * @return {@link TextView} + */ + public static T setPaintFlags( + final T textView, + final int flags + ) { + if (textView != null) { + textView.setPaintFlags(flags); + } + return textView; + } + + /** + * 设置 TextView flags + * @param view {@link TextView} + * @param flags flags + * @return {@link View} + */ + public static View setPaintFlags( + final View view, + final int flags + ) { + setPaintFlags(getTextView(view), flags); + return view; + } + + /** + * 设置 TextView 抗锯齿 flags + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView} + */ + public static T setAntiAliasFlag(final T textView) { + if (textView != null) { + textView.setPaintFlags(Paint.ANTI_ALIAS_FLAG); + } + return textView; + } + + /** + * 设置 TextView 抗锯齿 flags + * @param view {@link TextView} + * @return {@link View} + */ + public static View setAntiAliasFlag(final View view) { + setAntiAliasFlag(getTextView(view)); + return view; + } + + /** + * 设置 TextView 是否加粗 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView} + */ + public static T setBold(final T textView) { + return setBold(textView, true); + } + + /** + * 设置 TextView 是否加粗 + * @param textView {@link TextView} + * @param isBold {@code true} yes, {@code false} no + * @param 泛型 + * @return {@link TextView} + */ + public static T setBold( + final T textView, + final boolean isBold + ) { + if (textView != null) { + textView.setTypeface(Typeface.defaultFromStyle(isBold ? Typeface.BOLD : Typeface.NORMAL)); + } + return textView; + } + + /** + * 设置 TextView 是否加粗 + * @param textView {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @param 泛型 + * @return {@link TextView} + */ + public static T setBold( + final T textView, + final Typeface typeface, + final boolean isBold + ) { + if (textView != null && typeface != null) { + textView.setTypeface(typeface, isBold ? Typeface.BOLD : Typeface.NORMAL); + } + return textView; + } + + /** + * 设置 TextView 是否加粗 + * @param view {@link TextView} + * @return {@link View} + */ + public static View setBold(final View view) { + setBold(getTextView(view), true); + return view; + } + + /** + * 设置 TextView 是否加粗 + * @param view {@link TextView} + * @param isBold {@code true} yes, {@code false} no + * @return {@link View} + */ + public static View setBold( + final View view, + final boolean isBold + ) { + setBold(getTextView(view), isBold); + return view; + } + + /** + * 设置 TextView 是否加粗 + * @param view {@link TextView} + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @return {@link View} + */ + public static View setBold( + final View view, + final Typeface typeface, + final boolean isBold + ) { + setBold(getTextView(view), typeface, isBold); + return view; + } + + // = + + /** + * 设置下划线 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView} + */ + public static T setUnderlineText(final T textView) { + return setUnderlineText(textView, true); + } + + /** + * 设置下划线并加清晰 + * @param textView {@link TextView} + * @param isAntiAlias 是否消除锯齿 + * @param 泛型 + * @return {@link TextView} + */ + public static T setUnderlineText( + final T textView, + final boolean isAntiAlias + ) { + if (textView != null) { + textView.setPaintFlags(textView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); + if (isAntiAlias) { + textView.setPaintFlags(textView.getPaintFlags() | Paint.ANTI_ALIAS_FLAG); + } + } + return textView; + } + + // = + + /** + * 设置下划线 + * @param view {@link TextView} + * @return {@link View} + */ + public static View setUnderlineText(final View view) { + setUnderlineText(getTextView(view), true); + return view; + } + + /** + * 设置下划线并加清晰 + * @param view {@link TextView} + * @param isAntiAlias 是否消除锯齿 + * @return {@link View} + */ + public static View setUnderlineText( + final View view, + final boolean isAntiAlias + ) { + setUnderlineText(getTextView(view), isAntiAlias); + return view; + } + + // = + + /** + * 设置中划线 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TextView} + */ + public static T setStrikeThruText(final T textView) { + return setStrikeThruText(textView, true); + } + + /** + * 设置中划线并加清晰 + * @param textView {@link TextView} + * @param isAntiAlias 是否消除锯齿 + * @param 泛型 + * @return {@link TextView} + */ + public static T setStrikeThruText( + final T textView, + final boolean isAntiAlias + ) { + if (textView != null) { + textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + if (isAntiAlias) { + textView.setPaintFlags(textView.getPaintFlags() | Paint.ANTI_ALIAS_FLAG); + } + } + return textView; + } + + // = + + /** + * 设置中划线 + * @param view {@link TextView} + * @return {@link View} + */ + public static View setStrikeThruText(final View view) { + setStrikeThruText(getTextView(view), true); + return view; + } + + /** + * 设置中划线并加清晰 + * @param view {@link TextView} + * @param isAntiAlias 是否消除锯齿 + * @return {@link View} + */ + public static View setStrikeThruText( + final View view, + final boolean isAntiAlias + ) { + setStrikeThruText(getTextView(view), isAntiAlias); + return view; + } + + // = + + /** + * 获取文字水平间距 + * @param textView {@link TextView} + * @param 泛型 + * @return 文字水平间距 + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static float getLetterSpacing(final T textView) { + if (textView != null) { + return textView.getLetterSpacing(); + } + return 0F; + } + + /** + * 设置文字水平间距 + *
+     *     android:letterSpacing
+     * 
+ * @param textView {@link TextView} + * @param letterSpacing 文字水平间距 + * @param 泛型 + * @return {@link TextView} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static T setLetterSpacing( + final T textView, + final float letterSpacing + ) { + if (textView != null) { + textView.setLetterSpacing(letterSpacing); + } + return textView; + } + + /** + * 设置文字水平间距 + * @param view {@link TextView} + * @param letterSpacing 文字水平间距 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static View setLetterSpacing( + final View view, + final float letterSpacing + ) { + setLetterSpacing(getTextView(view), letterSpacing); + return view; + } + + // = + + /** + * 获取文字行间距 ( 行高 ) + * @param textView {@link TextView} + * @param 泛型 + * @return 文字行间距 ( 行高 ) + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static float getLineSpacingExtra(final T textView) { + if (textView != null) { + return textView.getLineSpacingExtra(); + } + return 0F; + } + + /** + * 获取文字行间距倍数 + * @param textView {@link TextView} + * @param 泛型 + * @return 文字行间距倍数 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static float getLineSpacingMultiplier(final T textView) { + if (textView != null) { + return textView.getLineSpacingMultiplier(); + } + return 0F; + } + + /** + * 设置文字行间距 ( 行高 ) + * @param textView {@link TextView} + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param 泛型 + * @return {@link TextView} + */ + public static T setLineSpacing( + final T textView, + final float lineSpacing + ) { + return setLineSpacingAndMultiplier(textView, lineSpacing, 1.0F); + } + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param textView {@link TextView} + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @param 泛型 + * @return {@link TextView} + */ + public static T setLineSpacingAndMultiplier( + final T textView, + final float lineSpacing, + final float multiplier + ) { + if (textView != null) { + textView.setLineSpacing(lineSpacing, multiplier); + } + return textView; + } + + // = + + /** + * 设置文字行间距 ( 行高 ) + * @param view {@link TextView} + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @return {@link View} + */ + public static View setLineSpacing( + final View view, + final float lineSpacing + ) { + setLineSpacingAndMultiplier(getTextView(view), lineSpacing, 1.0F); + return view; + } + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param view {@link TextView} + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @return {@link View} + */ + public static View setLineSpacingAndMultiplier( + final View view, + final float lineSpacing, + final float multiplier + ) { + setLineSpacingAndMultiplier(getTextView(view), lineSpacing, multiplier); + return view; + } + + // = + + /** + * 获取字体水平方向的缩放 + * @param textView {@link TextView} + * @param 泛型 + * @return 字体水平方向的缩放 + */ + public static float getTextScaleX(final T textView) { + if (textView != null) { + return textView.getTextScaleX(); + } + return 0F; + } + + /** + * 设置字体水平方向的缩放 + *
+     *     android:textScaleX
+     * 
+ * @param textView {@link TextView} + * @param size 缩放比例 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTextScaleX( + final T textView, + final float size + ) { + if (textView != null) { + textView.setTextScaleX(size); + } + return textView; + } + + /** + * 设置字体水平方向的缩放 + * @param view {@link TextView} + * @param size 缩放比例 + * @return {@link View} + */ + public static View setTextScaleX( + final View view, + final float size + ) { + setTextScaleX(getTextView(view), size); + return view; + } + + // = + + /** + * 是否保留字体留白间隙区域 + * @param textView {@link TextView} + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean getIncludeFontPadding(final T textView) { + if (textView != null) { + return textView.getIncludeFontPadding(); + } + return false; + } + + /** + * 设置是否保留字体留白间隙区域 + *
+     *     android:includeFontPadding
+     * 
+ * @param textView {@link TextView} + * @param includePadding 是否保留字体留白间隙区域 + * @param 泛型 + * @return {@link TextView} + */ + public static T setIncludeFontPadding( + final T textView, + final boolean includePadding + ) { + if (textView != null) { + textView.setIncludeFontPadding(includePadding); + } + return textView; + } + + /** + * 设置是否保留字体留白间隙区域 + * @param view {@link TextView} + * @param includePadding 是否保留字体留白间隙区域 + * @return {@link View} + */ + public static View setIncludeFontPadding( + final View view, + final boolean includePadding + ) { + setIncludeFontPadding(getTextView(view), includePadding); + return view; + } + + // = + + /** + * 获取输入类型 + * @param textView {@link TextView} + * @param 泛型 + * @return 输入类型 + */ + public static int getInputType(final T textView) { + if (textView != null) { + return textView.getInputType(); + } + return 0; + } + + /** + * 设置输入类型 + * @param textView {@link TextView} + * @param type 类型 + * @param 泛型 + * @return {@link TextView} + */ + public static T setInputType( + final T textView, + final int type + ) { + if (textView != null) { + textView.setInputType(type); + } + return textView; + } + + /** + * 设置输入类型 + * @param view {@link TextView} + * @param type 类型 + * @return {@link View} + */ + public static View setInputType( + final View view, + final int type + ) { + setInputType(getTextView(view), type); + return view; + } + + // = + + /** + * 获取软键盘右下角按钮类型 + * @param textView {@link TextView} + * @param 泛型 + * @return 软键盘右下角按钮类型 + */ + public static int getImeOptions(final T textView) { + if (textView != null) { + return textView.getImeOptions(); + } + return 0; + } + + /** + * 设置软键盘右下角按钮类型 + * @param textView {@link TextView} + * @param imeOptions 软键盘按钮类型 + * @param 泛型 + * @return {@link TextView} + */ + public static T setImeOptions( + final T textView, + final int imeOptions + ) { + if (textView != null) { + textView.setImeOptions(imeOptions); + } + return textView; + } + + /** + * 设置软键盘右下角按钮类型 + * @param view {@link TextView} + * @param imeOptions 软键盘按钮类型 + * @return {@link View} + */ + public static View setImeOptions( + final View view, + final int imeOptions + ) { + setImeOptions(getTextView(view), imeOptions); + return view; + } + + // = + + /** + * 设置行数 + * @param textView {@link TextView} + * @param lines 行数 + * @param 泛型 + * @return {@link TextView} + */ + public static T setLines( + final T textView, + final int lines + ) { + if (textView != null) { + textView.setLines(lines); + } + return textView; + } + + /** + * 设置行数 + * @param view {@link TextView} + * @param lines 行数 + * @return {@link View} + */ + public static View setLines( + final View view, + final int lines + ) { + setLines(getTextView(view), lines); + return view; + } + + // = + + /** + * 获取最大行数 + * @param textView {@link TextView} + * @param 泛型 + * @return 最大行数 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMaxLines(final T textView) { + if (textView != null) { + return textView.getMaxLines(); + } + return 0; + } + + /** + * 设置最大行数 + * @param textView {@link TextView} + * @param maxLines 最大行数 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMaxLines( + final T textView, + final int maxLines + ) { + if (textView != null) { + textView.setMaxLines(maxLines); + } + return textView; + } + + /** + * 设置最大行数 + * @param view {@link TextView} + * @param maxLines 最大行数 + * @return {@link View} + */ + public static View setMaxLines( + final View view, + final int maxLines + ) { + setMaxLines(getTextView(view), maxLines); + return view; + } + + // = + + /** + * 获取最小行数 + * @param textView {@link TextView} + * @param 泛型 + * @return 最小行数 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMinLines(final T textView) { + if (textView != null) { + return textView.getMinLines(); + } + return 0; + } + + /** + * 设置最小行数 + * @param textView {@link TextView} + * @param minLines 最小行数 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMinLines( + final T textView, + final int minLines + ) { + if (textView != null && minLines > 0) { + textView.setMinLines(minLines); + } + return textView; + } + + /** + * 设置最小行数 + * @param view {@link TextView} + * @param minLines 最小行数 + * @return {@link View} + */ + public static View setMinLines( + final View view, + final int minLines + ) { + setMinLines(getTextView(view), minLines); + return view; + } + + // = + + /** + * 获取最大字符宽度限制 + * @param textView {@link TextView} + * @param 泛型 + * @return 最大字符宽度限制 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMaxEms(final T textView) { + if (textView != null) { + return textView.getMaxEms(); + } + return 0; + } + + /** + * 设置最大字符宽度限制 + * @param textView {@link TextView} + * @param maxEms 最大字符 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMaxEms( + final T textView, + final int maxEms + ) { + if (textView != null && maxEms > 0) { + textView.setMaxEms(maxEms); + } + return textView; + } + + /** + * 设置最大字符宽度限制 + * @param view {@link TextView} + * @param maxEms 最大字符 + * @return {@link View} + */ + public static View setMaxEms( + final View view, + final int maxEms + ) { + setMaxEms(getTextView(view), maxEms); + return view; + } + + // = + + /** + * 获取最小字符宽度限制 + * @param textView {@link TextView} + * @param 泛型 + * @return 最小字符宽度限制 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMinEms(final T textView) { + if (textView != null) { + return textView.getMinEms(); + } + return 0; + } + + /** + * 设置最小字符宽度限制 + * @param textView {@link TextView} + * @param minEms 最小字符 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMinEms( + final T textView, + final int minEms + ) { + if (textView != null && minEms > 0) { + textView.setMinEms(minEms); + } + return textView; + } + + /** + * 设置最小字符宽度限制 + * @param view {@link TextView} + * @param minEms 最小字符 + * @return {@link View} + */ + public static View setMinEms( + final View view, + final int minEms + ) { + setMinEms(getTextView(view), minEms); + return view; + } + + // = + + /** + * 设置指定字符宽度 + * @param textView {@link TextView} + * @param ems 字符 + * @param 泛型 + * @return {@link TextView} + */ + public static T setEms( + final T textView, + final int ems + ) { + if (textView != null && ems > 0) { + textView.setEms(ems); + } + return textView; + } + + /** + * 设置指定字符宽度 + * @param view {@link TextView} + * @param ems 字符 + * @return {@link View} + */ + public static View setEms( + final View view, + final int ems + ) { + setEms(getTextView(view), ems); + return view; + } + + // = + + /** + * 设置长度限制 + * @param textView {@link TextView} + * @param maxLength 长度限制 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMaxLength( + final T textView, + final int maxLength + ) { + if (textView != null && maxLength > 0) { + // 设置最大长度限制 + InputFilter[] filters = {new InputFilter.LengthFilter(maxLength)}; + textView.setFilters(filters); + } + return textView; + } + + /** + * 设置长度限制 + * @param view {@link TextView} + * @param maxLength 长度限制 + * @return {@link View} + */ + public static View setMaxLength( + final View view, + final int maxLength + ) { + setMaxLength(getTextView(view), maxLength); + return view; + } + + // = + + /** + * 设置长度限制, 并且设置内容 + * @param textView {@link TextView} + * @param content 文本内容 + * @param maxLength 长度限制 + * @param 泛型 + * @return {@link TextView} + */ + public static T setMaxLengthAndText( + final T textView, + final CharSequence content, + final int maxLength + ) { + setText(setMaxLength(textView, maxLength), content); + return textView; + } + + /** + * 设置长度限制, 并且设置内容 + * @param view {@link TextView} + * @param content 文本内容 + * @param maxLength 长度限制 + * @return {@link View} + */ + public static View setMaxLengthAndText( + final View view, + final CharSequence content, + final int maxLength + ) { + return setText(setMaxLength(view, maxLength), content); + } + + // = + + /** + * 获取 Ellipsize 效果 + * @param textView {@link TextView} + * @param 泛型 + * @return Ellipsize 效果 + */ + public static TextUtils.TruncateAt getEllipsize(final T textView) { + if (textView != null) { + return textView.getEllipsize(); + } + return null; + } + + /** + * 设置 Ellipsize 效果 + * @param textView {@link TextView} + * @param where {@link TextUtils.TruncateAt} + * @param 泛型 + * @return {@link TextView} + */ + public static T setEllipsize( + final T textView, + final TextUtils.TruncateAt where + ) { + if (textView != null) { + textView.setEllipsize(where); + } + return textView; + } + + /** + * 设置 Ellipsize 效果 + * @param view {@link TextView} + * @param where {@link TextUtils.TruncateAt} + * @return {@link View} + */ + public static View setEllipsize( + final View view, + final TextUtils.TruncateAt where + ) { + setEllipsize(getTextView(view), where); + return view; + } + + // = + + /** + * 获取自动识别文本类型 + * @param textView {@link TextView} + * @param 泛型 + * @return 自动识别文本类型 + */ + public static int getAutoLinkMask(final T textView) { + if (textView != null) { + return textView.getAutoLinkMask(); + } + return 0; + } + + /** + * 设置自动识别文本链接 + * @param textView {@link TextView} + * @param mask {@link android.text.util.Linkify} + * @param 泛型 + * @return {@link TextView} + */ + public static T setAutoLinkMask( + final T textView, + final int mask + ) { + if (textView != null) { + textView.setAutoLinkMask(mask); + } + return textView; + } + + /** + * 设置自动识别文本链接 + * @param view {@link TextView} + * @param mask {@link android.text.util.Linkify} + * @return {@link View} + */ + public static View setAutoLinkMask( + final View view, + final int mask + ) { + setAutoLinkMask(getTextView(view), mask); + return view; + } + + // = + + /** + * 设置文本全为大写 + * @param textView {@link TextView} + * @param allCaps 是否全部大写 + * @param 泛型 + * @return {@link TextView} + */ + public static T setAllCaps( + final T textView, + final boolean allCaps + ) { + if (textView != null) { + textView.setAllCaps(allCaps); + } + return textView; + } + + /** + * 设置文本全为大写 + * @param view {@link TextView} + * @param allCaps 是否全部大写 + * @return {@link View} + */ + public static View setAllCaps( + final View view, + final boolean allCaps + ) { + setAllCaps(getTextView(view), allCaps); + return view; + } + + // = + + /** + * 获取 Gravity + * @param textView {@link TextView} + * @param 泛型 + * @return {@link android.view.Gravity} + */ + public static int getGravity(final T textView) { + if (textView != null) { + return textView.getGravity(); + } + return 0; + } + + /** + * 设置 Gravity + * @param textView {@link TextView} + * @param gravity {@link android.view.Gravity} + * @param 泛型 + * @return {@link TextView} + */ + public static T setGravity( + final T textView, + final int gravity + ) { + if (textView != null) { + textView.setGravity(gravity); + } + return textView; + } + + /** + * 设置 Gravity + * @param view {@link TextView} + * @param gravity {@link android.view.Gravity} + * @return {@link View} + */ + public static View setGravity( + final View view, + final int gravity + ) { + setGravity(getTextView(view), gravity); + return view; + } + + // = + + /** + * 获取文本视图显示转换 + * @param textView {@link TextView} + * @param 泛型 + * @return {@link TransformationMethod} + */ + public static TransformationMethod getTransformationMethod(final T textView) { + if (textView != null) { + return textView.getTransformationMethod(); + } + return null; + } + + /** + * 设置文本视图显示转换 + * @param textView {@link TextView} + * @param method {@link TransformationMethod} + * @param 泛型 + * @return {@link TextView} + */ + public static T setTransformationMethod( + final T textView, + final TransformationMethod method + ) { + if (textView != null) { + textView.setTransformationMethod(method); + } + return textView; + } + + /** + * 设置文本视图显示转换 + * @param view {@link TextView} + * @param method {@link TransformationMethod} + * @return {@link View} + */ + public static View setTransformationMethod( + final View view, + final TransformationMethod method + ) { + setTransformationMethod(getTextView(view), method); + return view; + } + + // = + + /** + * 设置密码文本视图显示转换 + * @param textView {@link TextView} + * @param isDisplayPassword 是否显示密码 + * @param 泛型 + * @return {@link TextView} + */ + public static T setTransformationMethod( + final T textView, + final boolean isDisplayPassword + ) { + if (textView != null) { + textView.setTransformationMethod(isDisplayPassword ? + HideReturnsTransformationMethod.getInstance() : PasswordTransformationMethod.getInstance()); + } + return textView; + } + + /** + * 设置密码文本视图显示转换 + * @param view {@link TextView} + * @param isDisplayPassword 是否显示密码 + * @return {@link View} + */ + public static View setTransformationMethod( + final View view, + final boolean isDisplayPassword + ) { + setTransformationMethod(getTextView(view), isDisplayPassword); + return view; + } + + // = + + /** + * 获取 TextView Paint + * @param view {@link TextView} + * @param 泛型 + * @return {@link Paint} + */ + public static Paint getPaint(final View view) { + return getPaint(getTextView(view)); + } + + /** + * 获取 TextView Paint + * @param textView {@link TextView} + * @param 泛型 + * @return {@link Paint} + */ + public static Paint getPaint(final T textView) { + if (textView != null) { + return textView.getPaint(); + } + return null; + } + + /** + * 获取字体高度 + * @param textView {@link TextView} + * @param 泛型 + * @return 字体高度 + */ + public static int getTextHeight(final T textView) { + return getTextHeight(getPaint(textView)); + } + + /** + * 获取字体高度 + * @param paint {@link TextView#getPaint()} + * @return 字体高度 + */ + public static int getTextHeight(final Paint paint) { + if (paint != null) { + // 获取字体高度 + Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); + // 计算内容高度 + return (int) Math.ceil((fontMetrics.descent - fontMetrics.ascent)); + } + return -1; + } + + // = + + /** + * 获取字体顶部偏移高度 + * @param textView {@link TextView} + * @param 泛型 + * @return 字体顶部偏移高度 + */ + public static int getTextTopOffsetHeight(final T textView) { + return getTextTopOffsetHeight(getPaint(textView)); + } + + /** + * 获取字体顶部偏移高度 + * @param paint {@link TextView#getPaint()} + * @return 字体顶部偏移高度 + */ + public static int getTextTopOffsetHeight(final Paint paint) { + if (paint != null) { + // 获取字体高度 + Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); + // 计算字体偏差 ( 顶部偏差 ) baseLine + return (int) Math.ceil(Math.abs(fontMetrics.top) - Math.abs(fontMetrics.ascent)); + } + return -1; + } + + // = + + /** + * 计算字体宽度 + * @param textView {@link TextView} + * @param text 待测量文本 + * @param 泛型 + * @return 字体宽度 + */ + public static float getTextWidth( + final T textView, + final String text + ) { + return getTextWidth(getPaint(textView), text); + } + + /** + * 计算字体宽度 + * @param textView {@link TextView} + * @param 泛型 + * @return 字体宽度 + */ + public static float getTextWidth(final T textView) { + return getTextWidth(getPaint(textView), getText(textView)); + } + + /** + * 计算字体宽度 + * @param paint {@link TextView#getPaint()} + * @param text 待测量文本 + * @return 字体宽度 + */ + public static float getTextWidth( + final Paint paint, + final String text + ) { + if (paint != null && text != null) { + return paint.measureText(text); + } + return -1F; + } + + // = + + /** + * 计算字体宽度 + * @param view {@link TextView} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final View view, + final String text, + final int start, + final int end + ) { + return getTextWidth(getPaint(view), text, start, end); + } + + /** + * 计算字体宽度 + * @param view {@link TextView} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final View view, + final CharSequence text, + final int start, + final int end + ) { + return getTextWidth(getPaint(view), text, start, end); + } + + /** + * 计算字体宽度 + * @param view {@link TextView} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final View view, + final char[] text, + final int start, + final int end + ) { + return getTextWidth(getPaint(view), text, start, end); + } + + // = + + /** + * 计算字体宽度 + * @param paint {@link TextView#getPaint()} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final Paint paint, + final String text, + final int start, + final int end + ) { + if (paint != null && text != null) { + try { + return paint.measureText(text, start, end); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTextWidth"); + } + } + return -1F; + } + + /** + * 计算字体宽度 + * @param paint {@link TextView#getPaint()} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final Paint paint, + final CharSequence text, + final int start, + final int end + ) { + if (paint != null && text != null) { + try { + return paint.measureText(text, start, end); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTextWidth"); + } + } + return -1F; + } + + /** + * 计算字体宽度 + * @param paint {@link TextView#getPaint()} + * @param text 待测量文本 + * @param start 开始位置 + * @param end 结束位置 + * @return 字体宽度 + */ + public static float getTextWidth( + final Paint paint, + final char[] text, + final int start, + final int end + ) { + if (paint != null && text != null) { + try { + return paint.measureText(text, start, end); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTextWidth"); + } + } + return -1F; + } + + // = + + /** + * 获取画布中间居中位置 + * @param targetRect {@link Rect} 目标坐标 + * @param paint {@link TextView#getPaint()} + * @return 画布 Y 轴居中位置 + */ + public static int getCenterRectY( + final Rect targetRect, + final Paint paint + ) { + if (targetRect != null && paint != null) { + // 获取字体高度 + Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); + // 获取底部 Y 轴居中位置 + return targetRect.top + (targetRect.bottom - targetRect.top) / 2 - (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.top; + // canvas.drawText(testString, targetRect.centerX(), baseline, paint); + } + return -1; + } + + /** + * 通过需要的高度, 计算字体大小 + * @param height 需要的高度 + * @return 字体大小 + */ + public static float reckonTextSizeByHeight(final int height) { + return reckonTextSizeByHeight(height, 40.0F); + } + + /** + * 通过需要的高度, 计算字体大小 + * @param height 需要的高度 + * @param startSize 字体开始预估大小 + * @return 字体大小 + */ + public static float reckonTextSizeByHeight( + final int height, + final float startSize + ) { + if (height <= 0 || startSize <= 0) return 0F; + Paint paint = new Paint(); + // 默认字体大小 + float textSize = startSize; + // 计算内容高度 + int calcTextHeight; + // 特殊处理 ( 防止死循环记录控制 ) + int state = 0; // 1 -=, 2 += + // 循环计算 + while (true) { + // 设置画笔大小 + paint.setTextSize(textSize); + // 获取字体高度 + Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt(); + // 计算内容高度 + calcTextHeight = (int) Math.ceil((fontMetrics.descent - fontMetrics.ascent)); + // 符合条件则直接返回 + if (calcTextHeight == height) { + return textSize; + } else if (calcTextHeight > height) { // 如果计算的字体高度大于 + textSize -= 0.5F; + state = 1; + } else { + textSize += 0.5F; + if (state == 1) { + return textSize; + } + state = 2; + } + } + } + + /** + * 通过需要的宽度, 计算字体大小 ( 最接近该宽度的字体大小 ) + * @param width 需要的宽度 + * @param textView {@link TextView} + * @return 字体大小 + */ + public static float reckonTextSizeByWidth( + final int width, + final TextView textView + ) { + return reckonTextSizeByWidth(width, textView, TextViewUtils.getText(textView)); + } + + /** + * 通过需要的宽度, 计算字体大小 ( 最接近该宽度的字体大小 ) + * @param width 需要的宽度 + * @param textView {@link TextView} + * @param content 待计算内容 + * @return 字体大小 + */ + public static float reckonTextSizeByWidth( + final int width, + final TextView textView, + final String content + ) { + if (textView == null || content == null) return 0F; + return reckonTextSizeByWidth(width, TextViewUtils.getPaint(textView), + TextViewUtils.getTextSize(textView), content + ); + } + + /** + * 通过需要的宽度, 计算字体大小 ( 最接近该宽度的字体大小 ) + * @param width 需要的宽度 + * @param curTextSize 当前字体大小 + * @param content 待计算内容 + * @return 字体大小 + */ + public static float reckonTextSizeByWidth( + final int width, + final float curTextSize, + final String content + ) { + if (width <= 0 || curTextSize <= 0 || content == null) return 0F; + return reckonTextSizeByWidth(width, new Paint(), curTextSize, content); + } + + /** + * 通过需要的宽度, 计算字体大小 ( 最接近该宽度的字体大小 ) + * @param width 需要的宽度 + * @param paint {@link Paint} + * @param curTextSize 当前字体大小 + * @param content 待计算内容 + * @return 字体大小 + */ + public static float reckonTextSizeByWidth( + final int width, + final Paint paint, + final float curTextSize, + final String content + ) { + if (paint == null || width <= 0 || curTextSize <= 0 || content == null) return 0F; + if (TextUtils.isEmpty(content)) return curTextSize; + // 初始化内容画笔, 计算宽高 + TextPaint tvPaint = new TextPaint(paint); + // 字体大小 + float textSize = curTextSize; + // 字体内容宽度 + int calcTextWidth; + // 特殊处理 ( 防止死循环记录控制 ) + int state = 0; // 1 -=, 2 += + // 循环计算 + while (true) { + // 设置画笔大小 + tvPaint.setTextSize(textSize); + // 获取字体内容宽度 + calcTextWidth = (int) tvPaint.measureText(content); + // 符合条件则直接返回 + if (calcTextWidth == width) { + return textSize; + } else if (calcTextWidth > width) { // 如果计算的字体宽度大于 + textSize -= 0.5F; + state = 1; + } else { + textSize += 0.5F; + if (state == 1) { + return textSize; + } + state = 2; + } + } + } + + // = + + /** + * 计算第几位超过宽度 + * @param textView {@link TextView} + * @param text 待测量文本 + * @param width 指定的宽度 + * @param 泛型 + * @return -1 表示没超过, 其他值表示对应的索引位置 + */ + public static int calcTextWidth( + final T textView, + final String text, + final float width + ) { + return calcTextWidth(getPaint(textView), text, width); + } + + /** + * 计算第几位超过宽度 + * @param textView {@link TextView} + * @param width 指定的宽度 + * @param 泛型 + * @return -1 表示没超过, 其他值表示对应的索引位置 + */ + public static int calcTextWidth( + final T textView, + final float width + ) { + return calcTextWidth(getPaint(textView), getText(textView), width); + } + + /** + * 计算第几位超过宽度 + * @param paint {@link TextView#getPaint()} + * @param text 文本内容 + * @param width 指定的宽度 + * @return -1 表示没超过, 其他值表示对应的索引位置 + */ + public static int calcTextWidth( + final Paint paint, + final String text, + final float width + ) { + if (paint != null && text != null && width > 0) { + // 全部文本宽度 + float allTextWidth = getTextWidth(paint, text); + // 判断是否超过 + if (allTextWidth <= width) return -1; // 表示没超过 + // 获取数据长度 + int length = text.length(); + // 超过长度且只有一个数据, 那么只能是第一个就超过了 + if (length == 1) return 1; + // 二分法寻找最佳位置 + int start = 0; + int end = length; + int mid = 0; + // 判断是否大于位置 + while (start < end) { + mid = (start + end) / 2; + // 获取字体宽度 + float textWidth = getTextWidth(paint, text, 0, mid); + // 如果相等直接返回 + if (textWidth == width) { + return mid; + } else if (textWidth > width) { + end = mid - 1; + } else { + start = mid + 1; + } + } + // 计算最符合的位置 + for (int i = Math.min(Math.min(start, mid), end); i <= length; i++) { + float textWidth = TextViewUtils.getTextWidth(paint, text, 0, i); + if (textWidth >= width) return i; + } + return start; + } + return -1; + } + + /** + * 计算文本换行行数 + * @param textView {@link TextView} + * @param text 待测量文本 + * @param width 指定的宽度 + * @param 泛型 + * @return 行数 + */ + public static int calcTextLine( + final T textView, + final String text, + final float width + ) { + return calcTextLine(getPaint(textView), text, width); + } + + /** + * 计算文本行数 + * @param textView {@link TextView} + * @param width 指定的宽度 + * @param 泛型 + * @return 行数 + */ + public static int calcTextLine( + final T textView, + final float width + ) { + return calcTextLine(getPaint(textView), getText(textView), width); + } + + /** + * 计算文本行数 + * @param paint {@link TextView#getPaint()} + * @param text 文本内容 + * @param width 指定的宽度 + * @return 行数 + */ + public static int calcTextLine( + final Paint paint, + final String text, + final float width + ) { + if (paint != null && text != null && width > 0) { + // 全部文本宽度 + float allTextWidth = getTextWidth(paint, text); + // 判断是否超过 + if (allTextWidth <= width) return 1; + int result = (int) (allTextWidth / width); + return ((allTextWidth - width * result == 0F) ? result : result + 1); + } + return 0; + } + + // ===================== + // = CompoundDrawables = + // ===================== + + /** + * 获取 CompoundDrawables + * @param textView {@link TextView} + * @param 泛型 + * @return Drawable[] { left, top, right, bottom } + */ + public static Drawable[] getCompoundDrawables(final T textView) { + if (textView != null) { + return textView.getCompoundDrawables(); + } + return new Drawable[]{null, null, null, null}; + } + + /** + * 获取 CompoundDrawables Padding + * @param textView {@link TextView} + * @param 泛型 + * @return CompoundDrawables Padding + */ + public static int getCompoundDrawablePadding(final T textView) { + if (textView != null) { + return textView.getCompoundDrawablePadding(); + } + return 0; + } + + /** + * 设置 CompoundDrawables Padding + * @param textView {@link TextView} + * @param padding CompoundDrawables Padding + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablePadding( + final T textView, + final int padding + ) { + if (textView != null) textView.setCompoundDrawablePadding(padding); + return textView; + } + + // ======================== + // = setCompoundDrawables = + // ======================== + + /** + * 设置 Left CompoundDrawables + * @param textView {@link TextView} + * @param left left Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesByLeft( + final T textView, + final Drawable left + ) { + return setCompoundDrawables(textView, left, null, null, null); + } + + /** + * 设置 Top CompoundDrawables + * @param textView {@link TextView} + * @param top top Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesByTop( + final T textView, + final Drawable top + ) { + return setCompoundDrawables(textView, null, top, null, null); + } + + /** + * 设置 Right CompoundDrawables + * @param textView {@link TextView} + * @param right right Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesByRight( + final T textView, + final Drawable right + ) { + return setCompoundDrawables(textView, null, null, right, null); + } + + /** + * 设置 Bottom CompoundDrawables + * @param textView {@link TextView} + * @param bottom bottom Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesByBottom( + final T textView, + final Drawable bottom + ) { + return setCompoundDrawables(textView, null, null, null, bottom); + } + + /** + * 设置 CompoundDrawables + *
+     *     CompoundDrawable 的大小控制是通过 drawable.setBounds() 控制
+     *     需要先设置 Drawable 的 setBounds
+     *     {@link dev.utils.app.image.ImageUtils#setBounds}
+     * 
+ * @param textView {@link TextView} + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawables( + final T textView, + final Drawable left, + final Drawable top, + final Drawable right, + final Drawable bottom + ) { + if (textView != null) { + try { + textView.setCompoundDrawables(left, top, right, bottom); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setCompoundDrawables"); + } + } + return textView; + } + + // =========================================== + // = setCompoundDrawablesWithIntrinsicBounds = + // =========================================== + + /** + * 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param textView {@link TextView} + * @param left left Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesWithIntrinsicBoundsByLeft( + final T textView, + final Drawable left + ) { + return setCompoundDrawablesWithIntrinsicBounds(textView, left, null, null, null); + } + + /** + * 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param textView {@link TextView} + * @param top top Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesWithIntrinsicBoundsByTop( + final T textView, + final Drawable top + ) { + return setCompoundDrawablesWithIntrinsicBounds(textView, null, top, null, null); + } + + /** + * 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param textView {@link TextView} + * @param right right Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesWithIntrinsicBoundsByRight( + final T textView, + final Drawable right + ) { + return setCompoundDrawablesWithIntrinsicBounds(textView, null, null, right, null); + } + + /** + * 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param textView {@link TextView} + * @param bottom bottom Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesWithIntrinsicBoundsByBottom( + final T textView, + final Drawable bottom + ) { + return setCompoundDrawablesWithIntrinsicBounds(textView, null, null, null, bottom); + } + + /** + * 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param textView {@link TextView} + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param 泛型 + * @return {@link View} + */ + public static T setCompoundDrawablesWithIntrinsicBounds( + final T textView, + final Drawable left, + final Drawable top, + final Drawable right, + final Drawable bottom + ) { + if (textView != null) { + try { + textView.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setCompoundDrawablesWithIntrinsicBounds"); + } + } + return textView; + } + + // ============= + // = AppCompat = + // ============= + + // ================ + // = AutoSizeable = + // ================ + + /** + * 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 + * @param view {@link TextView} + * @param autoSizeTextType 自动调整大小类型 + * @return {@code true} success, {@code false} fail + */ + public static boolean setAutoSizeTextTypeWithDefaults( + final View view, + @TextViewCompat.AutoSizeTextType final int autoSizeTextType + ) { + TextView textView = getTextView(view); + if (textView != null) { + try { + TextViewCompat.setAutoSizeTextTypeWithDefaults( + textView, autoSizeTextType + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAutoSizeTextTypeWithDefaults"); + } + } + return false; + } + + /** + * 设置 TextView 自动调整字体大小配置 + * @param view {@link TextView} + * @param autoSizeMinTextSize 自动调整最小字体大小 + * @param autoSizeMaxTextSize 自动调整最大字体大小 + * @param autoSizeStepGranularity 自动调整大小变动粒度 ( 跨度区间值 ) + * @param unit 字体参数类型 + */ + public static boolean setAutoSizeTextTypeUniformWithConfiguration( + final View view, + final int autoSizeMinTextSize, + final int autoSizeMaxTextSize, + final int autoSizeStepGranularity, + final int unit + ) { + TextView textView = getTextView(view); + if (textView != null) { + try { + TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration( + textView, autoSizeMinTextSize, autoSizeMaxTextSize, + autoSizeStepGranularity, unit + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAutoSizeTextTypeUniformWithConfiguration"); + } + } + return false; + } + + /** + * 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM + * @param view {@link TextView} + * @param presetSizes 预设字体大小范围像素为单位 + * @param unit 字体参数类型 + */ + public static boolean setAutoSizeTextTypeUniformWithPresetSizes( + final View view, + final int[] presetSizes, + final int unit + ) { + TextView textView = getTextView(view); + if (textView != null) { + try { + TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes( + textView, presetSizes, unit + ); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAutoSizeTextTypeUniformWithPresetSizes"); + } + } + return false; + } + + /** + * 获取 TextView 自动调整大小类型 + * @param view {@link TextView} + * @return 自动调整大小类型 + */ + @TextViewCompat.AutoSizeTextType + public static int getAutoSizeTextType(final View view) { + TextView textView = getTextView(view); + if (textView != null) { + try { + return TextViewCompat.getAutoSizeTextType(textView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAutoSizeTextType"); + } + } + return TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; + } + + /** + * 获取 TextView 自动调整大小变动粒度 ( 跨度区间值 ) + * @param view {@link TextView} + * @return 自动调整大小变动粒度 ( 跨度区间值 ) + */ + public static int getAutoSizeStepGranularity(final View view) { + TextView textView = getTextView(view); + if (textView != null) { + try { + return TextViewCompat.getAutoSizeStepGranularity(textView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAutoSizeStepGranularity"); + } + } + return -1; + } + + /** + * 获取 TextView 自动调整最小字体大小 + * @param view {@link TextView} + * @return 自动调整最小字体大小 + */ + public static int getAutoSizeMinTextSize(final View view) { + TextView textView = getTextView(view); + if (textView != null) { + try { + return TextViewCompat.getAutoSizeMinTextSize(textView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAutoSizeMinTextSize"); + } + } + return -1; + } + + /** + * 获取 TextView 自动调整最大字体大小 + * @param view {@link TextView} + * @return 自动调整最大字体大小 + */ + public static int getAutoSizeMaxTextSize(final View view) { + TextView textView = getTextView(view); + if (textView != null) { + try { + return TextViewCompat.getAutoSizeMaxTextSize(textView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAutoSizeMaxTextSize"); + } + } + return -1; + } + + /** + * 获取 TextView 自动调整大小预设范围数组 + * @param view {@link TextView} + * @return 自动调整大小预设范围数组 + */ + public static int[] getAutoSizeTextAvailableSizes(final View view) { + TextView textView = getTextView(view); + if (textView != null) { + try { + return TextViewCompat.getAutoSizeTextAvailableSizes(textView); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAutoSizeTextAvailableSizes"); + } + } + return new int[0]; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/UriUtils.java b/lib/DevApp/src/main/java/dev/utils/app/UriUtils.java new file mode 100644 index 0000000000..4599d50936 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/UriUtils.java @@ -0,0 +1,604 @@ +package dev.utils.app; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.core.content.FileProvider; + +import java.io.File; +import java.io.InputStream; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileIOUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Uri 工具类 + * @author Ttt + *
+ *     
+ *          
+ *     
+ * 
+ */ +public final class UriUtils { + + private UriUtils() { + } + + // 日志 TAG + private static final String TAG = UriUtils.class.getSimpleName(); + + // ================ + // = FileProvider = + // ================ + + /** + * 获取 FileProvider File Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForFile(final File file) { + return getUriForFile(file, DevUtils.getAuthority()); + } + + /** + * 获取 FileProvider File Path Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForPath(final String filePath) { + return getUriForFile(FileUtils.getFileByPath(filePath), DevUtils.getAuthority()); + } + + /** + * 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) + * @param file 文件 + * @param fileProvider android:authorities = ${applicationId}.fileProvider + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForFileToName( + final File file, + final String fileProvider + ) { + if (file == null || fileProvider == null) return null; + try { + String authority = AppUtils.getPackageName() + "." + fileProvider; + return getUriForFile(file, authority); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUriForFileToName"); + return null; + } + } + + /** + * 获取 FileProvider File Path Uri + * @param file 文件 + * @param authority android:authorities + * @return 指定文件 {@link Uri} + */ + public static Uri getUriForFile( + final File file, + final String authority + ) { + if (file == null || authority == null) return null; + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(DevUtils.getContext(), authority, file); + } else { + return Uri.fromFile(file); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getUriForFile"); + return null; + } + } + + /** + * 通过 String 获取 Uri + * @param uriString uri 路径 + * @return {@link Uri} + */ + public static Uri getUriForString(final String uriString) { + if (TextUtils.isEmpty(uriString)) return null; + return Uri.parse(uriString); + } + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return {@link Uri} + */ + public static Uri fromFile(final String filePath) { + return fromFile(FileUtils.getFile(filePath)); + } + + /** + * 通过 File 创建 Uri + *
+     *     File 的文件夹需要存在才能够对文件进行写入
+     *     可以传入前调用 {@link FileUtils#createFolderByPath(File)} 进行创建文件夹
+     * 
+ * @param file 文件 + * @return {@link Uri} + */ + public static Uri fromFile(final File file) { + if (file != null) { + try { + return Uri.fromFile(file); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "fromFile"); + } + } + return null; + } + + /** + * 通过 String 获取 Uri + * @param uriString uri 路径 + * @return {@link Uri} + */ + public static Uri ofUri(final String uriString) { + return ofUri(uriString, false); + } + + /** + * 通过 String 获取 Uri + *
+     *     内部判断 uriString 是 path 还是 uri string
+     * 
+ * @param uriString uri 路径 + * @param fromFile 是否直接使用 {@link #fromFile} 方法 + * @return {@link Uri} + */ + public static Uri ofUri( + final String uriString, + final boolean fromFile + ) { + Uri pathUri = getUriForString(uriString); + if (isUri(pathUri)) { + return pathUri; + } + if (fromFile) return fromFile(uriString); + return getUriForPath(uriString); + } + + // ======= + // = Uri = + // ======= + + /** + * 判断是否 Uri + * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUri(final String uriString) { + Uri uri = getUriForString(uriString); + return StringUtils.isNotEmpty(getUriScheme(uri)); + } + + /** + * 判断是否 Uri + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isUri(final Uri uri) { + return StringUtils.isNotEmpty(getUriScheme(uri)); + } + + // = + + /** + * 获取 Uri Scheme + * @param uriString uri 路径 + * @return Uri Scheme + */ + public static String getUriScheme(final String uriString) { + return getUriScheme(getUriForString(uriString)); + } + + /** + * 获取 Uri Scheme + * @param uri {@link Uri} + * @return Uri Scheme + */ + public static String getUriScheme(final Uri uri) { + if (uri == null) return null; + return uri.getScheme(); + } + + // = + + /** + * 判断 Uri 路径资源是否存在 + *
+     *     uri 非 FilePath, 可通过 {@link UriUtils#getMediaUri} 获取
+     * 
+ * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUriExists(final String uriString) { + if (TextUtils.isEmpty(uriString)) return false; + return isUriExists(Uri.parse(uriString)); + } + + /** + * 判断 Uri 路径资源是否存在 + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isUriExists(final Uri uri) { + AssetFileDescriptor afd = ResourceUtils.openAssetFileDescriptor(uri, "r"); + try { + return (afd != null); + } finally { + CloseUtils.closeIOQuietly(afd); + } + } + + // ============= + // = Media Uri = + // ============= + + /** + * 通过 File 获取 Media Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri(final File file) { + return ContentResolverUtils.getMediaUri(file); + } + + /** + * 通过 File 获取 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri( + final Uri uri, + final File file + ) { + return ContentResolverUtils.getMediaUri(uri, file); + } + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri(final String filePath) { + return ContentResolverUtils.getMediaUri(filePath); + } + + /** + * 通过 File Path 获取 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + public static Uri getMediaUri( + final Uri uri, + final String filePath + ) { + return ContentResolverUtils.getMediaUri(uri, filePath); + } + + // ========== + // = 复制文件 = + // ========== + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @return 复制后的文件路径 + */ + public static String copyByUri(final Uri uri) { + return copyByUri(uri, ContentResolverUtils.getDisplayNameColumn(uri)); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + public static String copyByUri( + final Uri uri, + final String fileName + ) { + return copyByUri(uri, PathUtils.getAppExternal().getAppCachePath(), fileName); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param file 文件 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + public static String copyByUri( + final Uri uri, + final File file, + final String fileName + ) { + return copyByUri(uri, FileUtils.getAbsolutePath(file), fileName); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param filePath 文件路径 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + public static String copyByUri( + final Uri uri, + final String filePath, + final String fileName + ) { + if (uri == null || TextUtils.isEmpty(filePath)) return null; + FileUtils.createFolder(filePath); + InputStream is = null; + try { + String child = TextUtils.isEmpty(fileName) ? String.valueOf(System.currentTimeMillis()) : fileName; + is = ResourceUtils.openInputStream(uri); + File file = new File(filePath, child); + FileIOUtils.writeFileFromIS(file.getAbsolutePath(), is); + return file.getAbsolutePath(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "copyByUri"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } + + // ============= + // = 获取文件路径 = + // ============= + + /** + * 通过 Uri 获取文件路径 + * @param uri {@link Uri} + * @return 文件路径 + */ + public static String getFilePathByUri(final Uri uri) { + return getFilePathByUri(uri, false); + } + + /** + * 通过 Uri 获取文件路径 + *
+     *     默认不复制文件, 防止影响已经使用该方法的功能 ( 文件过大导致 ANR、耗时操作等 )
+     * 
+ * @param uri {@link Uri} + * @param isQCopy Android 10 ( Q ) 及其以上版本是否复制文件 + * @return 文件路径 + */ + public static String getFilePathByUri( + final Uri uri, + final boolean isQCopy + ) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isQCopy) { + return copyByUri(uri); + } + return getFilePathByUri(DevUtils.getContext(), uri); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getFilePathByUri"); + return null; + } + } + + /** + * 通过 Uri 获取文件路径 + * @param context {@link Context} + * @param uri {@link Uri} + * @return 文件路径 + */ + private static String getFilePathByUri( + final Context context, + final Uri uri + ) { + if (context == null || uri == null) return null; + + // 以 file:// 开头 + if (ContentResolver.SCHEME_FILE.equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + // 当前 Android SDK 是否大于等于 4.4 + boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + // 4.4 之前以 content:// 开头, 比如 content://media/extenral/images/media/17766 + if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme()) && !isKitKat) { + if (isGooglePhotosUri(uri)) return uri.getLastPathSegment(); + return ContentResolverUtils.getDataColumn(uri, null, null); + } + + // 4.4 及之后以 content:// 开头, 比如 content://com.android.providers.media.documents/document/image%3A235700 + if (ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // isKitKat + if (DocumentsContract.isDocumentUri(context, uri)) { // DocumentProvider + if (isExternalStorageDocument(uri)) { // ExternalStorageProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/" + split[1]; + } else { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + } + } else if (isDownloadsDocument(uri)) { // DownloadsProvider + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), + Long.parseLong(id) + ); + return ContentResolverUtils.getDataColumn(contentUri, null, null); + } else if (isMediaDocument(uri)) { // MediaProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + return ContentResolverUtils.getDataColumn(contentUri, selection, selectionArgs); + } + } + } + return ContentResolverUtils.getDataColumn(uri, null, null); + } + + /** + * 判读 Uri authority 是否为 ExternalStorage Provider + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isExternalStorageDocument(final Uri uri) { + if (uri == null) return false; + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * 判读 Uri authority 是否为 Downloads Provider + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isDownloadsDocument(final Uri uri) { + if (uri == null) return false; + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * 判断 Uri authority 是否为 Media Provider + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isMediaDocument(final Uri uri) { + if (uri == null) return false; + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * 判断 Uri authority 是否为 Google Photos Provider + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isGooglePhotosUri(final Uri uri) { + if (uri == null) return false; + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + // ============== + // = Uri Scheme = + // ============== + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_ANDROID_RESOURCE + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isAndroidResourceScheme(final Uri uri) { + return isUriScheme(ContentResolver.SCHEME_ANDROID_RESOURCE, uri); + } + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_ANDROID_RESOURCE + * @param scheme 待校验 Scheme + * @return {@code true} yes, {@code false} no + */ + public static boolean isAndroidResourceScheme(final String scheme) { + return isUriScheme(ContentResolver.SCHEME_ANDROID_RESOURCE, scheme); + } + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_FILE + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileScheme(final Uri uri) { + return isUriScheme(ContentResolver.SCHEME_FILE, uri); + } + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_FILE + * @param scheme 待校验 Scheme + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileScheme(final String scheme) { + return isUriScheme(ContentResolver.SCHEME_FILE, scheme); + } + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_CONTENT + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isContentScheme(final Uri uri) { + return isUriScheme(ContentResolver.SCHEME_CONTENT, uri); + } + + /** + * 判断 Uri Scheme 是否 ContentResolver.SCHEME_CONTENT + * @param scheme 待校验 Scheme + * @return {@code true} yes, {@code false} no + */ + public static boolean isContentScheme(final String scheme) { + return isUriScheme(ContentResolver.SCHEME_CONTENT, scheme); + } + + /** + * 判断是否指定的 Uri Scheme + * @param uriScheme 如 ContentResolver.SCHEME_CONTENT + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + public static boolean isUriScheme( + final String uriScheme, + final Uri uri + ) { + return isUriScheme(uriScheme, getUriScheme(uri)); + } + + /** + * 判断是否指定的 Uri Scheme ( 忽略大小写 ) + * @param uriScheme 如 ContentResolver.SCHEME_CONTENT + * @param scheme 待校验 Scheme + * @return {@code true} yes, {@code false} no + */ + public static boolean isUriScheme( + final String uriScheme, + final String scheme + ) { + return StringUtils.equalsIgnoreCaseNotNull(uriScheme, scheme); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/VersionUtils.java b/lib/DevApp/src/main/java/dev/utils/app/VersionUtils.java new file mode 100644 index 0000000000..5634e3bcf1 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/VersionUtils.java @@ -0,0 +1,379 @@ +package dev.utils.app; + +import android.os.Build; + +/** + * detail: 版本工具类 + * @author Ttt + */ +public final class VersionUtils { + + private VersionUtils() { + } + + // =============== + // = Android SDK = + // =============== + + /** + * 获取 SDK 版本 + * @return SDK 版本 + */ + public static int getSDKVersion() { + return Build.VERSION.SDK_INT; + } + + /** + * 是否在 2.1 版本及以上 + * @return 是否在 2.1 版本及以上 + */ + public static boolean isEclair() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR; + } + + /** + * 是否在 2.2 版本及以上 + * @return 是否在 2.2 版本及以上 + */ + public static boolean isFroyo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; + } + + /** + * 是否在 2.3 版本及以上 + * @return 是否在 2.3 版本及以上 + */ + public static boolean isGingerbread() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + } + + /** + * 是否在 2.3.3 版本及以上 + * @return 是否在 2.3.3 版本及以上 + */ + public static boolean isGingerbreadMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD_MR1; + } + + /** + * 是否在 3.0 版本及以上 + * @return 是否在 3.0 版本及以上 + */ + public static boolean isHoneycomb() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + } + + /** + * 是否在 3.1 版本及以上 + * @return 是否在 3.1 版本及以上 + */ + public static boolean isHoneycombMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1; + } + + /** + * 是否在 4.0 版本及以上 + * @return 是否在 4.0 版本及以上 + */ + public static boolean isIceCreamSandwich() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + /** + * 是否在 4.0.3 版本及以上 + * @return 是否在 4.0.3 版本及以上 + */ + public static boolean isIceCreamSandwichMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; + } + + /** + * 是否在 4.1 版本及以上 + * @return 是否在 4.1 版本及以上 + */ + public static boolean isJellyBean() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; + } + + /** + * 是否在 4.2 版本及以上 + * @return 是否在 4.2 版本及以上 + */ + public static boolean isJellyBeanMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; + } + + /** + * 是否在 4.3 版本及以上 + * @return 是否在 4.3 版本及以上 + */ + public static boolean isJellyBeanMR2() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; + } + + /** + * 是否在 4.4.2 版本及以上 + * @return 是否在 4.4.2 版本及以上 + */ + public static boolean isKitkat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } + + /** + * 是否在 4.4W 版本及以上 + * @return 是否在 4.4W 版本及以上 + */ + public static boolean isKitkat_Watch() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH; + } + + /** + * 是否在 5.0 版本及以上 + * @return 是否在 5.0 版本及以上 + */ + public static boolean isLollipop() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + /** + * 是否在 5.1 版本及以上 + * @return 是否在 5.1 版本及以上 + */ + public static boolean isLollipop_MR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1; + } + + /** + * 是否在 6.0 版本及以上 + * @return 是否在 6.0 版本及以上 + */ + public static boolean isM() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + + /** + * 是否在 7.0 版本及以上 + * @return 是否在 7.0 版本及以上 + */ + public static boolean isN() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; + } + + /** + * 是否在 7.1.1 版本及以上 + * @return 是否在 7.1.1 版本及以上 + */ + public static boolean isN_MR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1; + } + + /** + * 是否在 8.0 版本及以上 + * @return 是否在 8.0 版本及以上 + */ + public static boolean isO() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + } + + /** + * 是否在 8.1 版本及以上 + * @return 是否在 8.1 版本及以上 + */ + public static boolean isO_MR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1; + } + + /** + * 是否在 9.0 版本及以上 + * @return 是否在 9.0 版本及以上 + */ + public static boolean isP() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; + } + + /** + * 是否在 10.0 版本及以上 + * @return 是否在 10.0 版本及以上 + */ + public static boolean isQ() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; + } + + /** + * 是否在 11.0 版本及以上 + * @return 是否在 11.0 版本及以上 + */ + public static boolean isR() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; + } + + /** + * 是否在 12.0 版本及以上 + * @return 是否在 12.0 版本及以上 + */ + public static boolean isS() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + } + + /** + * 转换 SDK 版本 convertSDKVersion(31) = Android 12.0 + * @return SDK 版本 + */ + public static String convertSDKVersion() { + return convertSDKVersion(Build.VERSION.SDK_INT); + } + + /** + * 转换 SDK 版本 convertSDKVersion(31) = Android 12.0 + * @param sdkVersion SDK 版本 + * @return SDK 版本 + */ + public static String convertSDKVersion(final int sdkVersion) { + switch (sdkVersion) { + case 1: + return "Android 1.0"; + case 2: + return "Android 1.1"; + case 3: + return "Android 1.5"; + case 4: + return "Android 1.6"; + case 5: + return "Android 2.0"; + case 6: + return "Android 2.0.1"; + case 7: + return "Android 2.1.x"; + case 8: + return "Android 2.2.x"; + case 9: + return "Android 2.3.0-2"; + case 10: + return "Android 2.3.3-4"; + case 11: + return "Android 3.0.x"; + case 12: + return "Android 3.1.x"; + case 13: + return "Android 3.2"; + case 14: + return "Android 4.0.0-2"; + case 15: + return "Android 4.0.3-4"; + case 16: + return "Android 4.1.x"; + case 17: + return "Android 4.2.x"; + case 18: + return "Android 4.3"; + case 19: + return "Android 4.4"; + case 20: + return "Android 4.4W"; + case 21: + return "Android 5.0"; + case 22: + return "Android 5.1"; + case 23: + return "Android 6.0"; + case 24: + return "Android 7.0"; + case 25: + return "Android 7.1.1"; + case 26: + return "Android 8.0"; + case 27: + return "Android 8.1"; + case 28: + return "Android 9.0"; + case 29: + return "Android 10.0"; + case 30: + return "Android 11.0"; + case 31: + return "Android 12.0"; + } + return "unknown"; + } + + /** + * 转换 SDK 版本名字 convertSDKVersionName(31) = Android S + * @return SDK 版本名字 + */ + public static String convertSDKVersionName() { + return convertSDKVersionName(Build.VERSION.SDK_INT); + } + + /** + * 转换 SDK 版本名字 convertSDKVersionName(31) = Android S + * @param sdkVersion SDK 版本 + * @return SDK 版本名字 + */ + public static String convertSDKVersionName(final int sdkVersion) { + switch (sdkVersion) { + case 1: + return "Android Base"; + case 2: + return "Android Base_1_1"; + case 3: + return "Android Cupcake"; + case 4: + return "Android Donut"; + case 5: + return "Android Eclair"; + case 6: + return "Android Eclair_0_1"; + case 7: + return "Android Eclair_MR1"; + case 8: + return "Android Froyo"; + case 9: + return "Android Gingerbread"; + case 10: + return "Android Gingerbread_MR1"; + case 11: + return "Android Honeycomb"; + case 12: + return "Android Honeycomb_MR1"; + case 13: + return "Android Honeycomb_MR2"; + case 14: + return "Android Ice_Cream_Sandwich"; + case 15: + return "Android Ice_Cream_Sandwich_MR1"; + case 16: + return "Android Jelly_Bean"; + case 17: + return "Android Jelly_Bean_MR1"; + case 18: + return "Android Jelly_Bean_MR2"; + case 19: + return "Android Kitkat"; + case 20: + return "Android Kitkat_Watch"; + case 21: + return "Android Lollipop"; + case 22: + return "Android Lollipop_MR1"; + case 23: + return "Android M"; + case 24: + return "Android N"; + case 25: + return "Android N_MR1"; + case 26: + return "Android O"; + case 27: + return "Android O_MR1"; + case 28: + return "Android P"; + case 29: + return "Android Q"; + case 30: + return "Android R"; + case 31: + return "Android S"; + } + return "unknown"; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/VibrationUtils.java b/lib/DevApp/src/main/java/dev/utils/app/VibrationUtils.java new file mode 100644 index 0000000000..502cb9e385 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/VibrationUtils.java @@ -0,0 +1,80 @@ +package dev.utils.app; + +import android.Manifest; +import android.os.Vibrator; + +import androidx.annotation.RequiresPermission; + +import dev.utils.LogPrintUtils; + +/** + * detail: 震动相关工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public final class VibrationUtils { + + private VibrationUtils() { + } + + // 日志 TAG + private static final String TAG = VibrationUtils.class.getSimpleName(); + + /** + * 震动 + * @param millis 震动时长 ( 毫秒 ) + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.VIBRATE) + public static boolean vibrate(final long millis) { + try { + Vibrator vibrator = AppUtils.getVibrator(); + vibrator.vibrate(millis); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "vibrate"); + } + return false; + } + + /** + * pattern 模式震动 + * @param pattern new long[]{400, 800, 1200, 1600}, 就是指定在 400ms、800ms、1200ms、1600ms 这些时间点交替启动、关闭手机震动器 + * @param repeat 指定 pattern 数组的索引, 指定 pattern 数组中从 repeat 索引开始的震动进行循环, + * -1 表示只震动一次, 非 -1 表示从 pattern 数组指定下标开始重复震动 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.VIBRATE) + public static boolean vibrate( + final long[] pattern, + final int repeat + ) { + if (pattern == null) return false; + try { + Vibrator vibrator = AppUtils.getVibrator(); + vibrator.vibrate(pattern, repeat); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "vibrate"); + } + return false; + } + + /** + * 取消震动 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.VIBRATE) + public static boolean cancel() { + try { + AppUtils.getVibrator().cancel(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cancel"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/ViewUtils.java b/lib/DevApp/src/main/java/dev/utils/app/ViewUtils.java new file mode 100644 index 0000000000..0a49cf843f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/ViewUtils.java @@ -0,0 +1,4347 @@ +package dev.utils.app; + +import android.app.Activity; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.RequiresApi; + +import java.lang.reflect.Field; + +import dev.utils.LogPrintUtils; +import dev.utils.app.anim.AnimationUtils; +import dev.utils.app.image.ImageUtils; +import dev.utils.common.FieldUtils; + +/** + * detail: View 操作相关工具类 + * @author Ttt + *
+ *     组件设置 setCompoundDrawables 不生效解决办法
+ *     @see 
+ *     Android 常用布局属性
+ *     @see 
+ *     Android 应用坐标系统全面详解
+ *     @see 
+ *     Android 获取 View 准确宽高的三种方法
+ *     @see 
+ *     getLocalVisibleRect()、getGlobalVisibleRect()、getLocationOnScreen()、getLocationInWindow() 方法浅析
+ *     @see 
+ *     

+ * RelativeLayout 的特有属性 + * 属性值为 true、false + * android:layout_centerHrizontal 位于父控件的横向中间位置 + * android:layout_centerVertical 位于父控件的纵向中间位置 + * android:layout_centerInparent 位于父控件的纵横向中间位置 + * android:layout_alignParentBottom 贴紧父元素的下边缘 + * android:layout_alignParentLeft 贴紧父元素的左边缘 + * android:layout_alignParentRight 贴紧父元素的右边缘 + * android:layout_alignParentTop 贴紧父元素的上边缘 + * android:layout_alignParentStart 将控件开始位置与父控件的开始位置对齐 + * android:layout_alignParentEnd 将控件结束位置与父控件的结束位置对齐 + * 属性值为引用 id + * android:layout_below 在某元素的下方 + * android:layout_above 在某元素的的上方 + * android:layout_toLeftOf 在某元素的左边 + * android:layout_toRightOf 在某元素的右边 + * android:layout_toStartOf 在某元素的开始位置 + * android:layout_toEndOf 在某元素的结束位置 + * android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐 + * android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐 + * android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐 + * android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐 + * android:layout_alignStart 本元素与某元素开始位置对齐 + * android:layout_alignEnd 本元素与某元素结束位置对齐 + * android:layout_alignBaseline 将当前控件的基线与指定 id 控件 t 的基线对齐 + *
+ */ +public final class ViewUtils { + + private ViewUtils() { + } + + // 日志 TAG + private static final String TAG = ViewUtils.class.getSimpleName(); + // MATCH_PARENT + public static final int MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT; + // WRAP_CONTENT + public static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT; + // VERTICAL + public static final int VERTICAL = LinearLayout.VERTICAL; + // HORIZONTAL + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + // Used to mark a View that has no ID + public static final int NO_ID = View.NO_ID; + + /** + * 获取 View Context + * @param view {@link View} + * @return {@link Context} + */ + public static Context getContext(final View view) { + if (view == null) return null; + return view.getContext(); + } + + /** + * 获取 View Context 所属的 Activity + * @param view {@link View} + * @return {@link Activity} + */ + public static Activity getActivity(final View view) { + return ActivityUtils.getActivity(view); + } + + // = + + /** + * 获取 View + * @param context {@link Context} + * @param resource R.layout.id + * @return {@link View} + */ + public static View inflate( + final Context context, + @LayoutRes final int resource + ) { + return inflate(context, resource, null, false); + } + + /** + * 获取 View + * @param context {@link Context} + * @param resource R.layout.id + * @param root {@link ViewGroup} + * @return {@link View} + */ + public static View inflate( + final Context context, + @LayoutRes final int resource, + final ViewGroup root + ) { + return inflate(context, resource, root, root != null); + } + + /** + * 获取 View + *
+     *     获取含有 android.support View 需要传入当前 Activity Context
+     * 
+ * @param context {@link Context} + * @param resource R.layout.id + * @param root {@link ViewGroup} + * @param attachToRoot 是否添加到 root 上 + * @return {@link View} + */ + public static View inflate( + final Context context, + @LayoutRes final int resource, + final ViewGroup root, + final boolean attachToRoot + ) { + try { + return LayoutInflater.from(context).inflate(resource, root, attachToRoot); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "inflate"); + } + return null; + } + + // = + + /** + * 获取 View Id + * @param view {@link View} + * @return View Id + */ + public static int getId(final View view) { + if (view != null) return view.getId(); + return View.NO_ID; + } + + /** + * 设置 View Id + * @param view {@link View} + * @param id View Id + * @return {@code true} success, {@code false} fail + */ + public static boolean setId( + final View view, + final int id + ) { + if (view != null) { + view.setId(id); + return true; + } + return false; + } + + /** + * 获取指定 View 父布局 + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T getParent(final View view) { + if (view != null) { + try { + return (T) view.getParent(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getParent"); + } + } + return null; + } + + /** + * 获取 android.R.id.content View + * @param activity {@link Activity} + * @param 泛型 + * @return {@link View} + */ + public static T getContentView(final Activity activity) { + return ViewUtils.findViewById(activity, android.R.id.content); + } + + /** + * 获取 android.R.id.content View + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T getContentView(final View view) { + if (view != null) { + try { + ViewParent parent = view.getParent(); + while (parent instanceof View) { + View root = (View) parent; + if (root.getId() == android.R.id.content) { + return (T) root; + } + parent = parent.getParent(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getContentView"); + } + } + return null; + } + + /** + * 获取指定 View 根布局 ( 最底层布局 ) + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T getRootParent(final View view) { + if (view != null) { + try { + View root = null; + ViewParent parent = view.getParent(); + while (parent instanceof View) { + root = (View) parent; + parent = parent.getParent(); + } + return (T) root; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRootParent"); + } + } + return null; + } + + /** + * 获取是否限制子 View 在其边界内绘制 + * @param viewGroup {@link ViewGroup} + * @return {@code true} yes, {@code false} no + */ + public static boolean getClipChildren(final ViewGroup viewGroup) { + if (viewGroup != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + return viewGroup.getClipChildren(); + } + } + return true; + } + + /** + * 设置是否限制子 View 在其边界内绘制 + * @param viewGroup {@link ViewGroup} + * @param clipChildren {@code true} yes, {@code false} no + * @return {@code true} success, {@code false} fail + */ + public static boolean setClipChildren( + final ViewGroup viewGroup, + final boolean clipChildren + ) { + if (viewGroup != null) { + viewGroup.setClipChildren(clipChildren); + return true; + } + return false; + } + + // = + + /** + * 获取子 View 总数 + * @param viewGroup {@link ViewGroup} + * @return 子 View 总数 + */ + public static int getChildCount(final ViewGroup viewGroup) { + if (viewGroup != null) { + return viewGroup.getChildCount(); + } + return 0; + } + + /** + * 获取指定索引 View + * @param viewGroup {@link ViewGroup} + * @param 泛型 + * @return {@link View} + */ + public static T getChildAt(final ViewGroup viewGroup) { + return getChildAt(viewGroup, 0); + } + + /** + * 获取最后一个索引 View + * @param viewGroup {@link ViewGroup} + * @param 泛型 + * @return {@link View} + */ + public static T getChildAtLast(final ViewGroup viewGroup) { + if (viewGroup == null) return null; + return getChildAt(viewGroup, viewGroup.getChildCount() - 1); + } + + /** + * 获取指定索引 View + * @param viewGroup {@link ViewGroup} + * @param index 索引 + * @param 泛型 + * @return {@link View} + */ + public static T getChildAt( + final ViewGroup viewGroup, + final int index + ) { + if (viewGroup != null && index >= 0) { + try { + return (T) viewGroup.getChildAt(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getChildAt"); + } + } + return null; + } + + /** + * 移除全部子 View + * @param viewGroup {@link ViewGroup} + * @param 泛型 + * @return 传入 View + */ + public static T removeAllViews(final T viewGroup) { + if (viewGroup != null) { + viewGroup.removeAllViews(); + } + return viewGroup; + } + + /** + * 获取全部子 View + * @param viewGroup {@link ViewGroup} + * @return View[] + */ + public static View[] getChilds(final ViewGroup viewGroup) { + View[] views = new View[getChildCount(viewGroup)]; + for (int i = 0, len = views.length; i < len; i++) { + views[i] = getChildAt(viewGroup, i); + } + return views; + } + + // = + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @return {@link ViewGroup} + */ + public static View addView( + final ViewGroup viewGroup, + final View child + ) { + return addView(viewGroup, child, -1); + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @return {@link ViewGroup} + */ + public static View addView( + final ViewGroup viewGroup, + final View child, + final int index + ) { + if (viewGroup != null && child != null) { + try { + viewGroup.addView(child, index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return viewGroup; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @param params LayoutParams + * @return {@link ViewGroup} + */ + public static View addView( + final ViewGroup viewGroup, + final View child, + final int index, + final ViewGroup.LayoutParams params + ) { + if (viewGroup != null && child != null) { + try { + viewGroup.addView(child, index, params); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return viewGroup; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param params LayoutParams + * @return {@link ViewGroup} + */ + public static View addView( + final ViewGroup viewGroup, + final View child, + final ViewGroup.LayoutParams params + ) { + if (viewGroup != null && child != null) { + try { + viewGroup.addView(child, params); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return viewGroup; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param width View 宽度 + * @param height View 高度 + * @return {@link ViewGroup} + */ + public static View addView( + final ViewGroup viewGroup, + final View child, + final int width, + final int height + ) { + if (viewGroup != null && child != null) { + try { + viewGroup.addView(child, width, height); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return viewGroup; + } + + // = + + /** + * 获取 LayoutParams + * @param view {@link View} + * @param 泛型 + * @return LayoutParams + */ + public static T getLayoutParams(final View view) { + if (view != null) { + try { + return (T) view.getLayoutParams(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLayoutParams"); + } + } + return null; + } + + /** + * 设置 View LayoutParams + * @param view {@link View} + * @param params LayoutParams + * @return {@code true} success, {@code false} fail + */ + public static boolean setLayoutParams( + final View view, + final ViewGroup.LayoutParams params + ) { + if (view != null) { + try { + view.setLayoutParams(params); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLayoutParams"); + } + } + return false; + } + + // =================== + // = 初始化 View 操作等 = + // =================== + + /** + * 初始化 View + * @param view {@link View} + * @param id R.id.viewId + * @param 泛型 + * @return {@link View} + */ + public static T findViewById( + final View view, + @IdRes final int id + ) { + if (view != null) { + try { + return view.findViewById(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findViewById"); + } + } + return null; + } + + /** + * 初始化 View + * @param window {@link Window} + * @param id R.id.viewId + * @param 泛型 + * @return {@link View} + */ + public static T findViewById( + final Window window, + @IdRes final int id + ) { + if (window != null) { + try { + return window.findViewById(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findViewById"); + } + } + return null; + } + + /** + * 初始化 View + * @param activity {@link Activity} + * @param id R.id.viewId + * @param 泛型 + * @return {@link View} + */ + public static T findViewById( + final Activity activity, + @IdRes final int id + ) { + if (activity != null) { + try { + return activity.findViewById(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "findViewById"); + } + } + return null; + } + + /** + * 转换 View + * @param view {@link View} + * @param 泛型 + * @return {@link View} + */ + public static T convertView(final View view) { + try { + return (T) view; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "convertView"); + } + return null; + } + + /** + * 转换 ViewGroup + * @param view {@link View} + * @return {@link ViewGroup} + */ + public static ViewGroup convertViewGroup(final View view) { + if (view instanceof ViewGroup) { + return (ViewGroup) view; + } + return null; + } + + // ============ + // = View 判空 = + // ============ + + /** + * 判断 View 是否为 null + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final View view) { + return view == null; + } + + /** + * 判断 View 是否为 null + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (view == null) { + return true; + } + } + return false; + } + return true; + } + + /** + * 判断 View 是否不为 null + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final View view) { + return view != null; + } + + /** + * 判断 View 是否不为 null + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (view == null) { + return false; + } + } + return true; + } + return false; + } + + // = + + /** + * 获取 View 宽高 + * @param view {@link View} + * @return int[], 0 = 宽度, 1 = 高度 + */ + public static int[] getWidthHeight(final View view) { + if (view != null) { + return new int[]{view.getWidth(), view.getHeight()}; + } + return new int[]{0, 0}; + } + + /** + * 设置 View 宽度、高度 + * @param view {@link View} + * @param width View 宽度 + * @param height View 高度 + * @return {@link View} + */ + public static View setWidthHeight( + final View view, + final int width, + final int height + ) { + return setWidthHeight(view, width, height, true); + } + + /** + * 设置 View 宽度、高度 + * @param view {@link View} + * @param width View 宽度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return {@link View} + */ + public static View setWidthHeight( + final View view, + final int width, + final int height, + final boolean nullNewLP + ) { + if (view != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams != null) { + layoutParams.width = width; + layoutParams.height = height; + view.setLayoutParams(layoutParams); + } else if (nullNewLP) { + layoutParams = new ViewGroup.LayoutParams(width, height); + view.setLayoutParams(layoutParams); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setWidthHeight"); + } + } + return view; + } + + // = + + /** + * 设置 View weight 权重 + * @param view {@link View} + * @param weight 权重比例 + * @return {@link View} + */ + public static View setWeight( + final View view, + final float weight + ) { + if (view != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams instanceof LinearLayout.LayoutParams) { + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) layoutParams; + params.weight = weight; + view.setLayoutParams(params); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setWeight"); + } + } + return view; + } + + // = + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return {@code true} success, {@code false} fail + */ + public static boolean getWidthHeightExact( + final View view, + final OnWHListener listener + ) { + if (view != null && listener != null) { + view.post(() -> listener.onWidthHeight(view, view.getWidth(), view.getHeight())); + return true; + } + return false; + } + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return {@code true} success, {@code false} fail + */ + public static boolean getWidthHeightExact2( + final View view, + final OnWHListener listener + ) { + if (view != null && listener != null) { + view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + try { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } catch (Exception ignored) { + } + } + listener.onWidthHeight(view, view.getWidth(), view.getHeight()); + } + }); + return true; + } + return false; + } + + // = + + /** + * 获取 View 在屏幕中坐标区域 + * @param view {@link View} + * @return 坐标信息 + */ + public static int[] getLocationOnScreen(final View view) { + return getLocationOnScreen(view, new int[2]); + } + + /** + * 获取 View 在屏幕中坐标区域 + * @param view {@link View} + * @param locations 传入数组, 自动赋值 x、y + * @return 坐标信息 + */ + public static int[] getLocationOnScreen( + final View view, + final int[] locations + ) { + if (view != null) { + try { + view.getLocationOnScreen(locations); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLocationOnScreen"); + } + } + return locations; + } + + // = + + /** + * 获取 View 在父窗口中坐标区域 + * @param view {@link View} + * @return 坐标信息 + */ + public static int[] getLocationInWindow(final View view) { + return getLocationInWindow(view, new int[2]); + } + + /** + * 获取 View 在父窗口中坐标区域 + * @param view {@link View} + * @param locations 传入数组, 自动赋值 x、y + * @return 坐标信息 + */ + public static int[] getLocationInWindow( + final View view, + final int[] locations + ) { + if (view != null) { + try { + view.getLocationInWindow(locations); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLocationInWindow"); + } + } + return locations; + } + + // = + + /** + * 获取 View 在屏幕中可见的坐标区域 + * @param view {@link View} + * @return {@code true} View 全部或者部分可见, {@code false} View 全部不可见 + */ + public static boolean getGlobalVisibleRect(final View view) { + if (view == null) return false; + return getGlobalVisibleRect(view, new Rect()); + } + + /** + * 获取 View 在屏幕中可见的坐标区域 + * @param view {@link View} + * @param rect 可见坐标区域 + * @return {@code true} View 全部或者部分可见, {@code false} View 全部不可见 + */ + public static boolean getGlobalVisibleRect( + final View view, + final Rect rect + ) { + if (view != null && rect != null) { + return view.getGlobalVisibleRect(rect); + } + return false; + } + + /** + * 获取 View 本身可见的坐标区域 + * @param view {@link View} + * @return {@code true} View 全部或者部分可见, {@code false} View 全部不可见 + */ + public static boolean getLocalVisibleRect(final View view) { + if (view == null) return false; + return getLocalVisibleRect(view, new Rect()); + } + + /** + * 获取 View 本身可见的坐标区域 + *
+     *     坐标以自己的左上角为原点 ( 0, 0 )
+     * 
+ * @param view {@link View} + * @param rect 可见坐标区域 + * @return {@code true} View 全部或者部分可见, {@code false} View 全部不可见 + */ + public static boolean getLocalVisibleRect( + final View view, + final Rect rect + ) { + if (view != null && rect != null) { + return view.getLocalVisibleRect(rect); + } + return false; + } + + // = + + /** + * 判断 View 是否完全显示 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isCompletelyVisible(final View view) { + if (view == null) return false; + Rect rect = new Rect(); + boolean isVisible = getGlobalVisibleRect(view, rect); + return isVisible && (rect.bottom - rect.top >= view.getHeight()); + } + + // = + + /** + * 获取 View 宽度 + * @param view {@link View} + * @return View 宽度 + */ + public static int getWidth(final View view) { + if (view != null) { + return view.getWidth(); + } + return 0; + } + + /** + * 设置 View 宽度 + * @param view {@link View} + * @param width View 宽度 + * @return {@link View} + */ + public static View setWidth( + final View view, + final int width + ) { + return setWidth(view, width, true); + } + + /** + * 设置 View 宽度 + * @param view {@link View} + * @param width View 宽度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return {@link View} + */ + public static View setWidth( + final View view, + final int width, + final boolean nullNewLP + ) { + if (view != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams != null) { + layoutParams.width = width; + view.setLayoutParams(layoutParams); + } else if (nullNewLP) { + layoutParams = new ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT); + view.setLayoutParams(layoutParams); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setWidth"); + } + } + return view; + } + + // = + + /** + * 获取 View 高度 + * @param view {@link View} + * @return View 高度 + */ + public static int getHeight(final View view) { + if (view != null) { + return view.getHeight(); + } + return 0; + } + + /** + * 设置 View 高度 + * @param view {@link View} + * @param height View 高度 + * @return {@link View} + */ + public static View setHeight( + final View view, + final int height + ) { + return setHeight(view, height, true); + } + + /** + * 设置 View 高度 + * @param view {@link View} + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return {@link View} + */ + public static View setHeight( + final View view, + final int height, + final boolean nullNewLP + ) { + if (view != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams != null) { + layoutParams.height = height; + view.setLayoutParams(layoutParams); + } else if (nullNewLP) { + layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, height); + view.setLayoutParams(layoutParams); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setHeight"); + } + } + return view; + } + + // = + + /** + * 获取 View 最小高度 + * @param view View + * @return View 最小高度 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMinimumHeight(final View view) { + if (view != null) { + return view.getMinimumHeight(); + } + return 0; + } + + /** + * 设置 View 最小高度 + * @param view {@link View} + * @param minHeight 最小高度 + * @return {@link View} + */ + public static View setMinimumHeight( + final View view, + final int minHeight + ) { + if (view != null) { + view.setMinimumHeight(minHeight); + } + return view; + } + + /** + * 获取 View 最小宽度 + * @param view View + * @return View 最小宽度 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static int getMinimumWidth(final View view) { + if (view != null) { + return view.getMinimumWidth(); + } + return 0; + } + + /** + * 设置 View 最小宽度 + * @param view {@link View} + * @param minWidth 最小宽度 + * @return {@link View} + */ + public static View setMinimumWidth( + final View view, + final int minWidth + ) { + if (view != null) { + view.setMinimumWidth(minWidth); + } + return view; + } + + // = + + /** + * 获取 View 透明度 + * @param view View + * @return 透明度 + */ + public static float getAlpha(final View view) { + if (view != null) { + return view.getAlpha(); + } + return 1.0F; + } + + /** + * 设置 View 透明度 + * @param view View + * @param alpha 透明度 + * @return {@link View} + */ + public static View setAlpha( + final View view, + @FloatRange(from = 0.0, to = 1.0) final float alpha + ) { + if (view != null) { + view.setAlpha(alpha); + } + return view; + } + + /** + * 获取 View TAG + * @param view View + * @return TAG + */ + public static Object getTag(final View view) { + if (view != null) { + return view.getTag(); + } + return null; + } + + /** + * 设置 View TAG + * @param view View + * @param object TAG + * @return {@link View} + */ + public static View setTag( + final View view, + final Object object + ) { + if (view != null) { + view.setTag(object); + } + return view; + } + + // = + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return {@link View} + */ + public static View scrollTo( + final View view, + final int x, + final int y + ) { + if (view != null) view.scrollTo(x, y); + return view; + } + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param view {@link View} + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return {@link View} + */ + public static View scrollBy( + final View view, + final int x, + final int y + ) { + if (view != null) view.scrollBy(x, y); + return view; + } + + /** + * 设置 View 滑动的 X 轴坐标 + * @param view {@link View} + * @param value X 轴坐标 + * @return {@link View} + */ + public static View setScrollX( + final View view, + final int value + ) { + if (view != null) view.setScrollX(value); + return view; + } + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param view {@link View} + * @param value Y 轴坐标 + * @return {@link View} + */ + public static View setScrollY( + final View view, + final int value + ) { + if (view != null) view.setScrollY(value); + return view; + } + + /** + * 获取 View 滑动的 X 轴坐标 + * @param view {@link View} + * @return 滑动的 X 轴坐标 + */ + public static int getScrollX(final View view) { + return view != null ? view.getScrollX() : 0; + } + + /** + * 获取 View 滑动的 Y 轴坐标 + * @param view {@link View} + * @return 滑动的 Y 轴坐标 + */ + public static int getScrollY(final View view) { + return view != null ? view.getScrollY() : 0; + } + + // = + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param viewGroup {@link ViewGroup} + * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @param 泛型 + * @return {@link ViewGroup} + */ + public static T setDescendantFocusability( + final T viewGroup, + final int focusability + ) { + try { + if (viewGroup != null) viewGroup.setDescendantFocusability(focusability); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setDescendantFocusability"); + } + return viewGroup; + } + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param view {@link View} + * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @param 泛型 + * @return {@link View} + */ + public static T setOverScrollMode( + final T view, + final int overScrollMode + ) { + try { + if (view != null) view.setOverScrollMode(overScrollMode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setOverScrollMode"); + } + return view; + } + + // = + + /** + * 是否绘制横向滚动条 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isHorizontalScrollBarEnabled(final View view) { + return (view != null) && view.isHorizontalScrollBarEnabled(); + } + + /** + * 设置是否绘制横向滚动条 + * @param view {@link View} + * @param horizontalScrollBarEnabled {@code true} yes, {@code false} no + * @return {@link View} + */ + public static View setHorizontalScrollBarEnabled( + final View view, + final boolean horizontalScrollBarEnabled + ) { + if (view != null) view.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled); + return view; + } + + /** + * 是否绘制垂直滚动条 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isVerticalScrollBarEnabled(final View view) { + return (view != null) && view.isVerticalScrollBarEnabled(); + } + + /** + * 设置是否绘制垂直滚动条 + * @param view {@link View} + * @param verticalScrollBarEnabled {@code true} yes, {@code false} no + * @return {@link View} + */ + public static View setVerticalScrollBarEnabled( + final View view, + final boolean verticalScrollBarEnabled + ) { + if (view != null) view.setVerticalScrollBarEnabled(verticalScrollBarEnabled); + return view; + } + + // = + + /** + * 获取 View 是否需要滚动效应 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + public static boolean isScrollContainer(final View view) { + if (view != null) { + return view.isScrollContainer(); + } + return false; + } + + /** + * 设置 View 滚动效应 + * @param view {@link View} + * @param isScrollContainer 是否需要滚动效应 + * @return {@link View} + */ + public static View setScrollContainer( + final View view, + final boolean isScrollContainer + ) { + if (view != null) { + view.setScrollContainer(isScrollContainer); + } + return view; + } + + // = + + /** + * 下一个获取焦点的 View id + * @param view {@link View} + * @return 下一个获取焦点的 View id + */ + public static int getNextFocusForwardId(final View view) { + if (view != null) { + return view.getNextFocusForwardId(); + } + return 0; + } + + /** + * 设置下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusForwardId 下一个获取焦点的 View id + * @return {@link View} + */ + public static View setNextFocusForwardId( + final View view, + @IdRes final int nextFocusForwardId + ) { + if (view != null) { + view.setNextFocusForwardId(nextFocusForwardId); + } + return view; + } + + // = + + /** + * 向下移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @return 向下移动焦点时, 下一个获取焦点的 View id + */ + public static int getNextFocusDownId(final View view) { + if (view != null) { + return view.getNextFocusDownId(); + } + return 0; + } + + /** + * 设置向下移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusDownId 下一个获取焦点的 View id + * @return {@link View} + */ + public static View setNextFocusDownId( + final View view, + @IdRes final int nextFocusDownId + ) { + if (view != null) { + view.setNextFocusDownId(nextFocusDownId); + } + return view; + } + + // = + + /** + * 向左移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @return 向左移动焦点时, 下一个获取焦点的 View id + */ + public static int getNextFocusLeftId(final View view) { + if (view != null) { + return view.getNextFocusLeftId(); + } + return 0; + } + + /** + * 设置向左移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusLeftId 下一个获取焦点的 View id + * @return {@link View} + */ + public static View setNextFocusLeftId( + final View view, + @IdRes final int nextFocusLeftId + ) { + if (view != null) { + view.setNextFocusLeftId(nextFocusLeftId); + } + return view; + } + + // = + + /** + * 向右移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @return 向右移动焦点时, 下一个获取焦点的 View id + */ + public static int getNextFocusRightId(final View view) { + if (view != null) { + return view.getNextFocusRightId(); + } + return 0; + } + + /** + * 设置向右移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusRightId 下一个获取焦点的 View id + * @return {@link View} + */ + public static View setNextFocusRightId( + final View view, + @IdRes final int nextFocusRightId + ) { + if (view != null) { + view.setNextFocusRightId(nextFocusRightId); + } + return view; + } + + // = + + /** + * 向上移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @return 向上移动焦点时, 下一个获取焦点的 View id + */ + public static int getNextFocusUpId(final View view) { + if (view != null) { + return view.getNextFocusUpId(); + } + return 0; + } + + /** + * 设置向上移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusUpId 下一个获取焦点的 View id + * @return {@link View} + */ + public static View setNextFocusUpId( + final View view, + @IdRes final int nextFocusUpId + ) { + if (view != null) { + view.setNextFocusUpId(nextFocusUpId); + } + return view; + } + + // = + + /** + * 获取 View 旋转度数 + * @param view {@link View} + * @return View 旋转度数 + */ + public static float getRotation(final View view) { + if (view != null) { + return view.getRotation(); + } + return 0F; + } + + /** + * 设置 View 旋转度数 + * @param view {@link View} + * @param rotation 旋转度数 + * @return {@link View} + */ + public static View setRotation( + final View view, + final float rotation + ) { + if (view != null) { + view.setRotation(rotation); + } + return view; + } + + // = + + /** + * 获取 View 水平旋转度数 + * @param view {@link View} + * @return View 水平旋转度数 + */ + public static float getRotationX(final View view) { + if (view != null) { + return view.getRotationX(); + } + return 0F; + } + + /** + * 设置 View 水平旋转度数 + * @param view {@link View} + * @param rotationX 水平旋转度数 + * @return {@link View} + */ + public static View setRotationX( + final View view, + final float rotationX + ) { + if (view != null) { + view.setRotationX(rotationX); + } + return view; + } + + // = + + /** + * 获取 View 竖直旋转度数 + * @param view {@link View} + * @return View 竖直旋转度数 + */ + public static float getRotationY(final View view) { + if (view != null) { + return view.getRotationY(); + } + return 0F; + } + + /** + * 设置 View 竖直旋转度数 + * @param view {@link View} + * @param rotationY 竖直旋转度数 + * @return {@link View} + */ + public static View setRotationY( + final View view, + final float rotationY + ) { + if (view != null) { + view.setRotationY(rotationY); + } + return view; + } + + // = + + /** + * 获取 View 水平方向缩放比例 + * @param view View + * @return View 水平方向缩放比例 + */ + public static float getScaleX(final View view) { + if (view != null) { + return view.getScaleX(); + } + return 0F; + } + + /** + * 设置 View 水平方向缩放比例 + * @param view View + * @param scaleX 水平方向缩放比例 + * @return {@link View} + */ + public static View setScaleX( + final View view, + final float scaleX + ) { + if (view != null) { + view.setScaleX(scaleX); + } + return view; + } + + // = + + /** + * 获取 View 竖直方向缩放比例 + * @param view View + * @return View 竖直方向缩放比例 + */ + public static float getScaleY(final View view) { + if (view != null) { + return view.getScaleY(); + } + return 0F; + } + + /** + * 设置 View 竖直方向缩放比例 + * @param view View + * @param scaleY 竖直方向缩放比例 + * @return {@link View} + */ + public static View setScaleY( + final View view, + final float scaleY + ) { + if (view != null) { + view.setScaleY(scaleY); + } + return view; + } + + // = + + /** + * 获取文本的显示方式 + * @param view {@link View} + * @return 文本的显示方式 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static int getTextAlignment(final View view) { + if (view != null) { + return view.getTextAlignment(); + } + return 0; + } + + /** + * 设置文本的显示方式 + * @param view {@link View} + * @param textAlignment 文本的显示方式 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static View setTextAlignment( + final View view, + final int textAlignment + ) { + if (view != null) { + view.setTextAlignment(textAlignment); + } + return view; + } + + // = + + /** + * 获取文本的显示方向 + * @param view {@link View} + * @return 文本的显示方向 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static int getTextDirection(final View view) { + if (view != null) { + return view.getTextDirection(); + } + return 0; + } + + /** + * 设置文本的显示方向 + * @param view {@link View} + * @param textDirection 文本的显示方向 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static View setTextDirection( + final View view, + final int textDirection + ) { + if (view != null) { + view.setTextDirection(textDirection); + } + return view; + } + + // = + + /** + * 获取水平方向偏转量 + * @param view {@link View} + * @return 水平方向偏转量 + */ + public static float getPivotX(final View view) { + if (view != null) { + return view.getPivotX(); + } + return 0F; + } + + /** + * 设置水平方向偏转量 + * @param view View + * @param pivotX 水平方向偏转量 + * @return {@link View} + */ + public static View setPivotX( + final View view, + final float pivotX + ) { + if (view != null) { + view.setPivotX(pivotX); + } + return view; + } + + // = + + /** + * 获取竖直方向偏转量 + * @param view {@link View} + * @return 竖直方向偏转量 + */ + public static float getPivotY(final View view) { + if (view != null) { + return view.getPivotY(); + } + return 0F; + } + + /** + * 设置竖直方向偏转量 + * @param view View + * @param pivotY 竖直方向偏转量 + * @return {@link View} + */ + public static View setPivotY( + final View view, + final float pivotY + ) { + if (view != null) { + view.setPivotY(pivotY); + } + return view; + } + + // = + + /** + * 获取水平方向的移动距离 + * @param view {@link View} + * @return 水平方向的移动距离 + */ + public static float getTranslationX(final View view) { + if (view != null) { + return view.getTranslationX(); + } + return 0F; + } + + /** + * 设置水平方向的移动距离 + * @param view {@link View} + * @param translationX 水平方向的移动距离 + * @return {@link View} + */ + public static View setTranslationX( + final View view, + final float translationX + ) { + if (view != null) { + view.setTranslationX(translationX); + } + return view; + } + + // = + + /** + * 获取竖直方向的移动距离 + * @param view {@link View} + * @return 竖直方向的移动距离 + */ + public static float getTranslationY(final View view) { + if (view != null) { + return view.getTranslationY(); + } + return 0F; + } + + /** + * 设置竖直方向的移动距离 + * @param view {@link View} + * @param translationY 竖直方向的移动距离 + * @return {@link View} + */ + public static View setTranslationY( + final View view, + final float translationY + ) { + if (view != null) { + view.setTranslationY(translationY); + } + return view; + } + + // = + + /** + * 获取 X 轴位置 + * @param view {@link View} + * @return X 轴位置 + */ + public static float getX(final View view) { + if (view != null) { + return view.getX(); + } + return 0F; + } + + /** + * 设置 X 轴位置 + * @param view {@link View} + * @param x X 轴位置 + * @return {@link View} + */ + public static View setX( + final View view, + final float x + ) { + if (view != null) { + view.setX(x); + } + return view; + } + + // = + + /** + * 获取 Y 轴位置 + * @param view {@link View} + * @return Y 轴位置 + */ + public static float getY(final View view) { + if (view != null) { + return view.getY(); + } + return 0F; + } + + /** + * 设置 Y 轴位置 + * @param view {@link View} + * @param y Y 轴位置 + * @return {@link View} + */ + public static View setY( + final View view, + final float y + ) { + if (view != null) { + view.setY(y); + } + return view; + } + + // = + + /** + * 获取 View 硬件加速类型 + * @param view {@link View} + * @return View 硬件加速类型 + */ + public static int getLayerType(final View view) { + if (view != null) { + return view.getLayerType(); + } + return 0; + } + + /** + * 设置 View 硬件加速类型 + * @param view {@link View} + * @param layerType 硬件加速类型 + * @param paint {@link Paint} + * @return {@link View} + */ + public static View setLayerType( + final View view, + final int layerType, + final Paint paint + ) { + if (view != null) { + view.setLayerType(layerType, paint); + } + return view; + } + + // = + + /** + * 请求重新对 View 布局 + * @param view {@link View} + * @return {@link View} + */ + public static View requestLayout(final View view) { + if (view != null) { + view.requestLayout(); + } + return view; + } + + /** + * View 请求获取焦点 + * @param view {@link View} + * @return {@link View} + */ + public static View requestFocus(final View view) { + if (view != null) { + view.requestFocus(); + } + return view; + } + + /** + * View 清除焦点 + * @param view {@link View} + * @return {@link View} + */ + public static View clearFocus(final View view) { + if (view != null) { + view.clearFocus(); + } + return view; + } + + /** + * 获取 View 里获取焦点的 View + * @param view {@link View} + * @return {@link View} + */ + public static View findFocus(final View view) { + if (view != null) { + return view.findFocus(); + } + return null; + } + + /** + * 获取是否当前 View 就是焦点 View + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFocused(final View view) { + if (view != null) { + return view.isFocused(); + } + return false; + } + + /** + * 获取当前 View 是否是焦点 View 或者子 View 里面有焦点 View + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean hasFocus(final View view) { + if (view != null) { + return view.hasFocus(); + } + return false; + } + + /** + * 获取当前 View 或者子 View 是否可以获取焦点 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean hasFocusable(final View view) { + if (view != null) { + return view.hasFocusable(); + } + return false; + } + + // = + + /** + * 获取 View 是否在触摸模式下获得焦点 + * @param view {@link View} + * @return {@code true} 可获取, {@code false} 不可获取 + */ + public static boolean isFocusableInTouchMode(final View view) { + if (view != null) { + return view.isFocusableInTouchMode(); + } + return false; + } + + /** + * 设置 View 是否在触摸模式下获得焦点 + * @param focusableInTouchMode {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return {@code true} 可获取, {@code false} 不可获取 + */ + public static boolean setFocusableInTouchMode( + final boolean focusableInTouchMode, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setFocusableInTouchMode(focusableInTouchMode); + } + } + } + return focusableInTouchMode; + } + + // = + + /** + * 获取 View 是否可以获取焦点 + * @param view {@link View} + * @return {@code true} 可获取, {@code false} 不可获取 + */ + public static boolean isFocusable(final View view) { + if (view != null) { + return view.isFocusable(); + } + return false; + } + + /** + * 设置 View 是否可以获取焦点 + * @param focusable {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return {@code true} 可获取, {@code false} 不可获取 + */ + public static boolean setFocusable( + final boolean focusable, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setFocusable(focusable); + } + } + } + return focusable; + } + + /** + * 切换获取焦点状态 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleFocusable(final View... views) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setFocusable(!view.isFocusable()); + } + } + return true; + } + return false; + } + + // = + + /** + * 获取 View 是否选中 + * @param view {@link View} + * @return {@code true} 选中, {@code false} 非选中 + */ + public static boolean isSelected(final View view) { + if (view != null) { + return view.isSelected(); + } + return false; + } + + /** + * 设置 View 是否选中 + * @param selected {@code true} 选中, {@code false} 非选中 + * @param views View[] + * @return {@code true} 选中, {@code false} 非选中 + */ + public static boolean setSelected( + final boolean selected, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setSelected(selected); + } + } + } + return selected; + } + + /** + * 切换选中状态 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleSelected(final View... views) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setSelected(!view.isSelected()); + } + } + return true; + } + return false; + } + + // = + + /** + * 获取 View 是否启用 + * @param view {@link View} + * @return {@code true} 启用, {@code false} 禁用 + */ + public static boolean isEnabled(final View view) { + if (view != null) { + return view.isEnabled(); + } + return false; + } + + /** + * 设置 View 是否启用 + * @param enabled {@code true} 启用, {@code false} 禁用 + * @param views View[] + * @return {@code true} 启用, {@code false} 禁用 + */ + public static boolean setEnabled( + final boolean enabled, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setEnabled(enabled); + } + } + } + return enabled; + } + + /** + * 切换 View 是否启用状态 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleEnabled(final View... views) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setEnabled(!view.isEnabled()); + } + } + return true; + } + return false; + } + + // = + + /** + * 获取 View 是否可以点击 + * @param view {@link View} + * @return {@code true} 可点击, {@code false} 不可点击 + */ + public static boolean isClickable(final View view) { + if (view != null) { + return view.isClickable(); + } + return false; + } + + /** + * 设置 View 是否可以点击 + * @param clickable {@code true} 可点击, {@code false} 不可点击 + * @param views View[] + * @return {@code true} 可点击, {@code false} 不可点击 + */ + public static boolean setClickable( + final boolean clickable, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setClickable(clickable); + } + } + } + return clickable; + } + + /** + * 切换 View 是否可以点击状态 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleClickable(final View... views) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setClickable(!view.isClickable()); + } + } + return true; + } + return false; + } + + // = + + /** + * 获取 View 是否可以长按 + * @param view {@link View} + * @return {@code true} 可长按, {@code false} 不可长按 + */ + public static boolean isLongClickable(final View view) { + if (view != null) { + return view.isLongClickable(); + } + return false; + } + + /** + * 设置 View 是否可以长按 + * @param longClickable {@code true} 可长按, {@code false} 不可长按 + * @param views View[] + * @return {@code true} 可长按, {@code false} 不可长按 + */ + public static boolean setLongClickable( + final boolean longClickable, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setLongClickable(longClickable); + } + } + } + return longClickable; + } + + /** + * 切换 View 是否可以长按状态 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleLongClickable(final View... views) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setLongClickable(!view.isLongClickable()); + } + } + return true; + } + return false; + } + + // =============== + // = View 显示状态 = + // =============== + + /** + * 判断 View 是否显示 ( 如果存在父级则判断父级 ) + *
+     *     需要父布局已展示到 Window 上
+     * 
+ * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShown(final View view) { + return (view != null) && view.isShown(); + } + + /** + * 判断 View 是否都显示 ( 如果存在父级则判断父级 ) + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isShowns(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (!isShown(view)) { + return false; + } + } + return true; + } + return false; + } + + // = + + /** + * 判断 View 是否显示 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibility(final View view) { + return isVisibility(view, true); + } + + /** + * 判断 View 是否显示 + * @param view {@link View} + * @param defaultValue view 为 null 默认值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibility( + final View view, + final boolean defaultValue + ) { + if (view != null) { + return (view.getVisibility() == View.VISIBLE); + } + return defaultValue; + } + + /** + * 判断 View 是否都显示 + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilitys(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (view == null || view.getVisibility() != View.VISIBLE) { + return false; + } + } + return true; + } + return false; + } + + // = + + /** + * 判断 View 是否隐藏占位 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityIN(final View view) { + return isVisibilityIN(view, false); + } + + /** + * 判断 View 是否隐藏占位 + * @param view {@link View} + * @param defaultValue view 为 null 默认值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityIN( + final View view, + final boolean defaultValue + ) { + if (view != null) { + return (view.getVisibility() == View.INVISIBLE); + } + return defaultValue; + } + + /** + * 判断 View 是否都隐藏占位 + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityINs(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (view == null || view.getVisibility() != View.INVISIBLE) { + return false; + } + } + return true; + } + return false; + } + + // = + + /** + * 判断 View 是否隐藏 + * @param view {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityGone(final View view) { + return isVisibilityGone(view, false); + } + + /** + * 判断 View 是否隐藏 + * @param view {@link View} + * @param defaultValue view 为 null 默认值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityGone( + final View view, + final boolean defaultValue + ) { + if (view != null) { + return (view.getVisibility() == View.GONE); + } + return defaultValue; + } + + /** + * 判断 View 是否都隐藏 + * @param views View[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isVisibilityGones(final View... views) { + if (views != null && views.length != 0) { + for (View view : views) { + if (view == null || view.getVisibility() != View.GONE) { + return false; + } + } + return true; + } + return false; + } + + // = + + /** + * 获取显示的状态 ( View.VISIBLE : View.GONE ) + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @return 显示的状态 {@link View#VISIBLE}、{@link View#GONE} + */ + public static int getVisibility(final boolean isVisibility) { + return isVisibility ? View.VISIBLE : View.GONE; + } + + /** + * 获取显示的状态 ( View.VISIBLE : View.INVISIBLE ) + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @return 显示的状态 {@link View#VISIBLE}、{@link View#INVISIBLE} + */ + public static int getVisibilityIN(final boolean isVisibility) { + return isVisibility ? View.VISIBLE : View.INVISIBLE; + } + + // = + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param view {@link View} + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibility( + final boolean isVisibility, + final View view + ) { + if (view != null) { + view.setVisibility(isVisibility ? View.VISIBLE : View.GONE); + } + return isVisibility; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param view {@link View} + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibility( + final int isVisibility, + final View view + ) { + if (view != null) { + view.setVisibility(isVisibility); + } + return (isVisibility == View.VISIBLE); + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @param view {@link View} + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibilityIN( + final boolean isVisibility, + final View view + ) { + if (view != null) { + view.setVisibility(isVisibility ? View.VISIBLE : View.INVISIBLE); + } + return isVisibility; + } + + // = + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibilitys( + final boolean isVisibility, + final View... views + ) { + return setVisibilitys(getVisibility(isVisibility), views); + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibilitys( + final int isVisibility, + final View... views + ) { + if (views != null) { + for (View view : views) { + if (view != null) { + view.setVisibility(isVisibility); + } + } + } + return (isVisibility == View.VISIBLE); + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean setVisibilityINs( + final boolean isVisibility, + final View... views + ) { + return setVisibilitys(getVisibilityIN(isVisibility), views); + } + + // = + + /** + * 切换 View 显示的状态 + * @param view {@link View} + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleVisibilitys( + final View view, + final View... views + ) { + if (view != null) { + view.setVisibility(View.VISIBLE); + } + setVisibilitys(View.GONE, views); + return true; + } + + /** + * 切换 View 显示的状态 + * @param viewArrays View[] + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleVisibilitys( + final View[] viewArrays, + final View... views + ) { + return toggleVisibilitys(View.GONE, viewArrays, views); + } + + /** + * 切换 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean toggleVisibilitys( + final int state, + final View[] viewArrays, + final View... views + ) { + // 默认显示 + setVisibilitys(View.VISIBLE, viewArrays); + // 根据状态处理 + setVisibilitys(state, views); + return true; + } + + // = + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean reverseVisibilitys( + final int state, + final View[] viewArrays, + final View... views + ) { + return reverseVisibilitys(state == View.VISIBLE, viewArrays, views); + } + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param viewArrays View[] + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean reverseVisibilitys( + final boolean isVisibility, + final View[] viewArrays, + final View... views + ) { + // 默认处理第一个数组 + setVisibilitys(isVisibility, viewArrays); + // 根据状态处理 + setVisibilitys(!isVisibility, views); + return isVisibility; + } + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param view {@link View} + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean reverseVisibilitys( + final int state, + final View view, + final View... views + ) { + return reverseVisibilitys(state == View.VISIBLE, view, views); + } + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param view {@link View} + * @param views View[] + * @return isVisibility 是否传入 {@link View#VISIBLE} + */ + public static boolean reverseVisibilitys( + final boolean isVisibility, + final View view, + final View... views + ) { + // 默认处理第一个 View + setVisibilitys(isVisibility, view); + // 根据状态处理 + setVisibilitys(!isVisibility, views); + return isVisibility; + } + + // = + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param view {@link View} + * @return isChange + */ + public static boolean toggleView( + final boolean isChange, + final int isVisibility, + final View view + ) { + if (isChange && view != null) { + view.setVisibility(isVisibility); + } + return isChange; + } + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return isChange + */ + public static boolean toggleViews( + final boolean isChange, + final int isVisibility, + final View... views + ) { + if (isChange && views != null) { + for (View view : views) { + if (view != null) { + view.setVisibility(isVisibility); + } + } + } + return isChange; + } + + // = + + /** + * 把自身从父 View 中移除 + * @param view {@link View} + * @return {@link View} + */ + public static View removeSelfFromParent(final View view) { + if (view != null) { + try { + ViewParent parent = view.getParent(); + if (parent instanceof ViewGroup) { + ViewGroup group = (ViewGroup) parent; + group.removeView(view); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeSelfFromParent"); + } + } + return view; + } + + /** + * 判断触点是否落在该 View 上 + * @param event {@link MotionEvent} + * @param view 待判断 {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isTouchInView( + final MotionEvent event, + final View view + ) { + if (event != null && view != null) { + int[] locations = new int[2]; + view.getLocationOnScreen(locations); + float motionX = event.getRawX(); + float motionY = event.getRawY(); + return motionX >= locations[0] && motionX <= (locations[0] + view.getWidth()) + && motionY >= locations[1] && motionY <= (locations[1] + view.getHeight()); + } + return false; + } + + /** + * View 请求更新 + * @param view {@link View} + * @param allParent 是否全部父布局 View 都请求 + * @return {@link View} + */ + public static View requestLayoutParent( + final View view, + final boolean allParent + ) { + if (view != null) { + ViewParent parent = view.getParent(); + while (parent instanceof View) { + if (!parent.isLayoutRequested()) { + parent.requestLayout(); + if (!allParent) { + break; + } + } + parent = parent.getParent(); + } + } + return view; + } + + /** + * 测量 View + * @param view {@link View} + * @return int[] 0 = 宽度, 1 = 高度 + */ + public static int[] measureView(final View view) { + return WidgetUtils.measureView(view); + } + + /** + * 获取 View 的宽度 + * @param view {@link View} + * @return View 的宽度 + */ + public static int getMeasuredWidth(final View view) { + return WidgetUtils.getMeasuredWidth(view); + } + + /** + * 获取 View 的高度 + * @param view {@link View} + * @return View 的高度 + */ + public static int getMeasuredHeight(final View view) { + return WidgetUtils.getMeasuredHeight(view); + } + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @return {@code true} success, {@code false} fail + */ + public static boolean measureView( + final View view, + final int specifiedWidth + ) { + return WidgetUtils.measureView(view, specifiedWidth); + } + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return {@code true} success, {@code false} fail + */ + public static boolean measureView( + final View view, + final int specifiedWidth, + final int specifiedHeight + ) { + return WidgetUtils.measureView(view, specifiedWidth, specifiedHeight); + } + + // ================== + // = Layout Gravity = + // ================== + + /** + * 获取 View Layout Gravity + * @param view {@link View} + * @return Layout Gravity + */ + public static int getLayoutGravity(final View view) { + return getLayoutGravity(view, true); + } + + /** + * 获取 View Layout Gravity + * @param view {@link View} + * @param isReflection 是否使用反射 + * @return Layout Gravity + */ + public static int getLayoutGravity( + final View view, + final boolean isReflection + ) { + if (view != null && view.getLayoutParams() != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams instanceof LinearLayout.LayoutParams) { + return ((LinearLayout.LayoutParams) layoutParams).gravity; + } else if (layoutParams instanceof FrameLayout.LayoutParams) { + return ((FrameLayout.LayoutParams) layoutParams).gravity; + } else if (layoutParams instanceof WindowManager.LayoutParams) { + return ((WindowManager.LayoutParams) layoutParams).gravity; + } + if (isReflection) { + Field[] fields = FieldUtils.getFields(layoutParams); + for (Field field : fields) { + if ("gravity".equals(field.getName())) { + return (int) field.get(layoutParams); + } + } + } + // 抛出不支持的类型 + throw new Exception("layoutParams:" + layoutParams.toString()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLayoutGravity"); + } + } + return 0; + } + + /** + * 设置 View Layout Gravity + * @param view {@link View} + * @param gravity Gravity + * @return {@code true} success, {@code false} fail + */ + public static boolean setLayoutGravity( + final View view, + final int gravity + ) { + return setLayoutGravity(view, gravity, true); + } + + /** + * 设置 View Layout Gravity + * @param view {@link View} + * @param gravity Gravity + * @param isReflection 是否使用反射 + * @return {@code true} success, {@code false} fail + */ + public static boolean setLayoutGravity( + final View view, + final int gravity, + final boolean isReflection + ) { + if (view != null && view.getLayoutParams() != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams instanceof LinearLayout.LayoutParams) { + ((LinearLayout.LayoutParams) layoutParams).gravity = gravity; + view.setLayoutParams(layoutParams); + return true; + } else if (layoutParams instanceof FrameLayout.LayoutParams) { + ((FrameLayout.LayoutParams) layoutParams).gravity = gravity; + view.setLayoutParams(layoutParams); + return true; + } else if (layoutParams instanceof WindowManager.LayoutParams) { + ((WindowManager.LayoutParams) layoutParams).gravity = gravity; + view.setLayoutParams(layoutParams); + return true; + } + if (isReflection) { + Field[] fields = FieldUtils.getFields(layoutParams); + for (Field field : fields) { + if ("gravity".equals(field.getName())) { + field.set(layoutParams, gravity); + view.setLayoutParams(layoutParams); + return true; + } + } + } + // 抛出不支持的类型 + throw new Exception("layoutParams:" + layoutParams.toString()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLayoutGravity"); + } + } + return false; + } + + // =============== + // = View Margin = + // =============== + + /** + * 获取 View Left Margin + * @param view {@link View} + * @return Left Margin + */ + public static int getMarginLeft(final View view) { + return getMargin(view)[0]; + } + + /** + * 获取 View Top Margin + * @param view {@link View} + * @return Top Margin + */ + public static int getMarginTop(final View view) { + return getMargin(view)[1]; + } + + /** + * 获取 View Right Margin + * @param view {@link View} + * @return Right Margin + */ + public static int getMarginRight(final View view) { + return getMargin(view)[2]; + } + + /** + * 获取 View Bottom Margin + * @param view {@link View} + * @return Bottom Margin + */ + public static int getMarginBottom(final View view) { + return getMargin(view)[3]; + } + + /** + * 获取 View Margin + * @param view {@link View} + * @return new int[] {left, top, right, bottom} + */ + public static int[] getMargin(final View view) { + int[] margin = new int[]{0, 0, 0, 0}; + if (view != null && view.getLayoutParams() != null) { + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + margin[0] = layoutParams.leftMargin; + margin[1] = layoutParams.topMargin; + margin[2] = layoutParams.rightMargin; + margin[3] = layoutParams.bottomMargin; + } + } + return margin; + } + + // = + + /** + * 设置 View Left Margin + * @param view {@link View} + * @param leftMargin Left Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginLeft( + final View view, + final int leftMargin + ) { + return setMarginLeft(view, leftMargin, true); + } + + /** + * 设置 View Left Margin + * @param view {@link View} + * @param leftMargin Left Margin + * @param reset 是否重置清空其他 margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginLeft( + final View view, + final int leftMargin, + final boolean reset + ) { + if (reset) return setMargin(view, leftMargin, 0, 0, 0); + int[] margin = getMargin(view); + return setMargin(view, leftMargin, margin[1], margin[2], margin[3]); + } + + /** + * 设置 View Top Margin + * @param view {@link View} + * @param topMargin Top Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginTop( + final View view, + final int topMargin + ) { + return setMarginTop(view, topMargin, true); + } + + /** + * 设置 View Top Margin + * @param view {@link View} + * @param topMargin Top Margin + * @param reset 是否重置清空其他 margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginTop( + final View view, + final int topMargin, + final boolean reset + ) { + if (reset) return setMargin(view, 0, topMargin, 0, 0); + int[] margin = getMargin(view); + return setMargin(view, margin[0], topMargin, margin[2], margin[3]); + } + + /** + * 设置 View Right Margin + * @param view {@link View} + * @param rightMargin Right Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginRight( + final View view, + final int rightMargin + ) { + return setMarginRight(view, rightMargin, true); + } + + /** + * 设置 View Right Margin + * @param view {@link View} + * @param rightMargin Right Margin + * @param reset 是否重置清空其他 margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginRight( + final View view, + final int rightMargin, + final boolean reset + ) { + if (reset) return setMargin(view, 0, 0, rightMargin, 0); + int[] margin = getMargin(view); + return setMargin(view, margin[0], margin[1], rightMargin, margin[3]); + } + + /** + * 设置 View Bottom Margin + * @param view {@link View} + * @param bottomMargin Bottom Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginBottom( + final View view, + final int bottomMargin + ) { + return setMarginBottom(view, bottomMargin, true); + } + + /** + * 设置 View Bottom Margin + * @param view {@link View} + * @param bottomMargin Bottom Margin + * @param reset 是否重置清空其他 margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMarginBottom( + final View view, + final int bottomMargin, + final boolean reset + ) { + if (reset) return setMargin(view, 0, 0, 0, bottomMargin); + int[] margin = getMargin(view); + return setMargin(view, margin[0], margin[1], margin[2], bottomMargin); + } + + /** + * 设置 Margin 边距 + * @param view {@link View} + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View view, + final int leftRight, + final int topBottom + ) { + return setMargin(view, leftRight, topBottom, leftRight, topBottom); + } + + /** + * 设置 Margin 边距 + * @param view {@link View} + * @param margin Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View view, + final int margin + ) { + return setMargin(view, margin, margin, margin, margin); + } + + /** + * 设置 Margin 边距 + * @param view {@link View} + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View view, + final int left, + final int top, + final int right, + final int bottom + ) { + if (view != null && view.getLayoutParams() != null) { + if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + layoutParams.setMargins(left, top, right, bottom); + view.setLayoutParams(layoutParams); + return true; + } + } + return false; + } + + // = + + /** + * 设置 Margin 边距 + * @param views View[] + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View[] views, + final int leftRight, + final int topBottom + ) { + return setMargin(views, leftRight, topBottom, leftRight, topBottom); + } + + /** + * 设置 Margin 边距 + * @param views View[] + * @param margin Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View[] views, + final int margin + ) { + return setMargin(views, margin, margin, margin, margin); + } + + /** + * 设置 Margin 边距 + * @param views View[] + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @return {@code true} success, {@code false} fail + */ + public static boolean setMargin( + final View[] views, + final int left, + final int top, + final int right, + final int bottom + ) { + if (views != null) { + for (View view : views) { + setMargin(view, left, top, right, bottom); + } + return true; + } + return false; + } + + // ================ + // = View Padding = + // ================ + + /** + * 获取 View Left Padding + * @param view {@link View} + * @return Left Padding + */ + public static int getPaddingLeft(final View view) { + return getPadding(view)[0]; + } + + /** + * 获取 View Top Padding + * @param view {@link View} + * @return Top Padding + */ + public static int getPaddingTop(final View view) { + return getPadding(view)[1]; + } + + /** + * 获取 View Right Padding + * @param view {@link View} + * @return Right Padding + */ + public static int getPaddingRight(final View view) { + return getPadding(view)[2]; + } + + /** + * 获取 View Bottom Padding + * @param view {@link View} + * @return Bottom Padding + */ + public static int getPaddingBottom(final View view) { + return getPadding(view)[3]; + } + + /** + * 获取 View Padding + * @param view {@link View} + * @return new int[] {left, top, right, bottom} + */ + public static int[] getPadding(final View view) { + int[] padding = new int[]{0, 0, 0, 0}; + if (view != null) { + padding[0] = view.getPaddingLeft(); + padding[1] = view.getPaddingTop(); + padding[2] = view.getPaddingRight(); + padding[3] = view.getPaddingBottom(); + } + return padding; + } + + // = + + /** + * 设置 View Left Padding + * @param view {@link View} + * @param leftPadding Left Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingLeft( + final View view, + final int leftPadding + ) { + return setPaddingLeft(view, leftPadding, true); + } + + /** + * 设置 View Left Padding + * @param view {@link View} + * @param leftPadding Left Padding + * @param reset 是否重置清空其他 Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingLeft( + final View view, + final int leftPadding, + final boolean reset + ) { + if (reset) return setPadding(view, leftPadding, 0, 0, 0); + int[] padding = getPadding(view); + return setPadding(view, leftPadding, padding[1], padding[2], padding[3]); + } + + /** + * 设置 View Top Padding + * @param view {@link View} + * @param topPadding Top Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingTop( + final View view, + final int topPadding + ) { + return setPaddingTop(view, topPadding, true); + } + + /** + * 设置 View Top Padding + * @param view {@link View} + * @param topPadding Top Padding + * @param reset 是否重置清空其他 Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingTop( + final View view, + final int topPadding, + final boolean reset + ) { + if (reset) return setPadding(view, 0, topPadding, 0, 0); + int[] padding = getPadding(view); + return setPadding(view, padding[0], topPadding, padding[2], padding[3]); + } + + /** + * 设置 View Right Padding + * @param view {@link View} + * @param rightPadding Right Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingRight( + final View view, + final int rightPadding + ) { + return setPaddingRight(view, rightPadding, true); + } + + /** + * 设置 View Right Padding + * @param view {@link View} + * @param rightPadding Right Padding + * @param reset 是否重置清空其他 Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingRight( + final View view, + final int rightPadding, + final boolean reset + ) { + if (reset) return setPadding(view, 0, 0, rightPadding, 0); + int[] padding = getPadding(view); + return setPadding(view, padding[0], padding[1], rightPadding, padding[3]); + } + + /** + * 设置 View Bottom Padding + * @param view {@link View} + * @param bottomPadding Bottom Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingBottom( + final View view, + final int bottomPadding + ) { + return setPaddingBottom(view, bottomPadding, true); + } + + /** + * 设置 View Bottom Padding + * @param view {@link View} + * @param bottomPadding Bottom Padding + * @param reset 是否重置清空其他 Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPaddingBottom( + final View view, + final int bottomPadding, + final boolean reset + ) { + if (reset) return setPadding(view, 0, 0, 0, bottomPadding); + int[] padding = getPadding(view); + return setPadding(view, padding[0], padding[1], padding[2], bottomPadding); + } + + /** + * 设置 Padding 边距 + * @param view {@link View} + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View view, + final int leftRight, + final int topBottom + ) { + return setPadding(view, leftRight, topBottom, leftRight, topBottom); + } + + /** + * 设置 Padding 边距 + * @param view {@link View} + * @param padding Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View view, + final int padding + ) { + return setPadding(view, padding, padding, padding, padding); + } + + /** + * 设置 Padding 边距 + * @param view {@link View} + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View view, + final int left, + final int top, + final int right, + final int bottom + ) { + if (view != null) { + view.setPadding(left, top, right, bottom); + return true; + } + return false; + } + + // = + + /** + * 设置 Padding 边距 + * @param views View[] + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View[] views, + final int leftRight, + final int topBottom + ) { + return setPadding(views, leftRight, topBottom, leftRight, topBottom); + } + + /** + * 设置 Padding 边距 + * @param views View[] + * @param padding Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View[] views, + final int padding + ) { + return setPadding(views, padding, padding, padding, padding); + } + + /** + * 设置 Padding 边距 + * @param views View[] + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @return {@code true} success, {@code false} fail + */ + public static boolean setPadding( + final View[] views, + final int left, + final int top, + final int right, + final int bottom + ) { + if (views != null) { + for (View view : views) { + setPadding(view, left, top, right, bottom); + } + return true; + } + return false; + } + + // ================== + // = RelativeLayout = + // ================== + + /** + * 设置 RelativeLayout View 布局规则 + * @param view {@link View} + * @param verb 布局位置 + * @return {@link View} + */ + public static View addRule( + final View view, + final int verb + ) { + return addRule(view, verb, -1); + } + + /** + * 设置 RelativeLayout View 布局规则 + * @param view {@link View} + * @param verb 布局位置 + * @param subject 关联 View id + * @return {@link View} + */ + public static View addRule( + final View view, + final int verb, + final int subject + ) { + if (view != null) { + try { + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); + layoutParams.addRule(verb, subject); + view.setLayoutParams(layoutParams); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addRule"); + } + } + return view; + } + + /** + * 移除 RelativeLayout View 布局规则 + * @param view {@link View} + * @param verb 布局位置 + * @return {@link View} + */ + public static View removeRule( + final View view, + final int verb + ) { + if (view != null) { + try { + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + layoutParams.removeRule(verb); + } else { + layoutParams.addRule(verb, 0); + } + view.setLayoutParams(layoutParams); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeRule"); + } + } + return view; + } + + /** + * 获取 RelativeLayout View 指定布局位置 View id + * @param view {@link View} + * @param verb 布局位置 + * @return 关联 View id + */ + public static int getRule( + final View view, + final int verb + ) { + if (view != null) { + try { + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return layoutParams.getRule(verb); + } else { + return layoutParams.getRules()[verb]; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRule"); + } + } + return 0; + } + + // = + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean addRules( + final int verb, + final View... views + ) { + return addRules(verb, -1, views); + } + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param subject 关联 View id + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean addRules( + final int verb, + final int subject, + final View... views + ) { + if (views != null) { + for (View view : views) { + addRule(view, verb, subject); + } + return true; + } + return false; + } + + /** + * 移除多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return {@code true} success, {@code false} fail + */ + public static boolean removeRules( + final int verb, + final View... views + ) { + if (views != null) { + for (View view : views) { + removeRule(view, verb); + } + return true; + } + return false; + } + + // ============= + // = Animation = + // ============= + + /** + * 设置动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return {@link View} + */ + public static View setAnimation( + final View view, + final Animation animation + ) { + return AnimationUtils.setAnimation(view, animation); + } + + /** + * 获取动画 + * @param view {@link View} + * @return {@link Animation} + */ + public static Animation getAnimation(final View view) { + return AnimationUtils.getAnimation(view); + } + + /** + * 清空动画 + * @param view {@link View} + * @return {@link View} + */ + public static View clearAnimation(final View view) { + return AnimationUtils.clearAnimation(view); + } + + /** + * 启动动画 + * @param view {@link View} + * @return {@link View} + */ + public static View startAnimation(final View view) { + return AnimationUtils.startAnimation(view); + } + + /** + * 启动动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return {@link View} + */ + public static View startAnimation( + final View view, + final Animation animation + ) { + return AnimationUtils.startAnimation(view, animation); + } + + /** + * 启动动画 + * @param animation {@link Animation} + * @param 泛型 + * @return {@link Animation} + */ + public static T startAnimation(final T animation) { + return AnimationUtils.startAnimation(animation); + } + + /** + * 取消动画 + * @param view {@link View} + * @return {@link Animation} + */ + public static Animation cancelAnimation(final View view) { + return AnimationUtils.cancelAnimation(view); + } + + /** + * 取消动画 + * @param animation {@link Animation} + * @param 泛型 + * @return {@link Animation} + */ + public static T cancelAnimation(final T animation) { + return AnimationUtils.cancelAnimation(animation); + } + + // ======= + // = 背景 = + // ======= + + /** + * 设置背景图片 + * @param view {@link View} + * @param background 背景图片 + * @return {@link View} + */ + public static View setBackground( + final View view, + final Drawable background + ) { + if (view != null) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.setBackground(background); + } else { + view.setBackgroundDrawable(background); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackground"); + } + } + return view; + } + + /** + * 设置背景颜色 + * @param view {@link View} + * @param color 背景颜色 + * @return {@link View} + */ + public static View setBackgroundColor( + final View view, + @ColorInt final int color + ) { + if (view != null) { + try { + view.setBackgroundColor(color); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackgroundColor"); + } + } + return view; + } + + /** + * 设置背景资源 + * @param view {@link View} + * @param resId resource identifier + * @return {@link View} + */ + public static View setBackgroundResource( + final View view, + @DrawableRes final int resId + ) { + if (view != null) { + try { + view.setBackgroundResource(resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackgroundResource"); + } + } + return view; + } + + /** + * 设置背景着色颜色 + * @param view {@link View} + * @param tint 着色颜色 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static View setBackgroundTintList( + final View view, + final ColorStateList tint + ) { + if (view != null) { + try { + view.setBackgroundTintList(tint); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackgroundTintList"); + } + } + return view; + } + + /** + * 设置背景着色模式 + * @param view {@link View} + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static View setBackgroundTintMode( + final View view, + final PorterDuff.Mode tintMode + ) { + if (view != null) { + try { + view.setBackgroundTintMode(tintMode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBackgroundTintMode"); + } + } + return view; + } + + // ======= + // = 前景 = + // ======= + + /** + * 设置前景图片 + * @param view {@link View} + * @param foreground 前景图片 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static View setForeground( + final View view, + final Drawable foreground + ) { + if (view != null) { + try { + view.setForeground(foreground); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setForeground"); + } + } + return view; + } + + /** + * 设置前景重心 + * @param view {@link View} + * @param gravity 重心 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static View setForegroundGravity( + final View view, + final int gravity + ) { + if (view != null) { + try { + view.setForegroundGravity(gravity); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setForegroundGravity"); + } + } + return view; + } + + /** + * 设置前景着色颜色 + * @param view {@link View} + * @param tint 着色颜色 + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static View setForegroundTintList( + final View view, + final ColorStateList tint + ) { + if (view != null) { + try { + view.setForegroundTintList(tint); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setForegroundTintList"); + } + } + return view; + } + + /** + * 设置前景着色模式 + * @param view {@link View} + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return {@link View} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static View setForegroundTintMode( + final View view, + final PorterDuff.Mode tintMode + ) { + if (view != null) { + try { + view.setForegroundTintMode(tintMode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setForegroundTintMode"); + } + } + return view; + } + + // ======= + // = 获取 = + // ======= + + /** + * 获取 View 背景 Drawable + * @param view {@link View} + * @return 背景 Drawable + */ + public static Drawable getBackground(final View view) { + if (view != null) return view.getBackground(); + return null; + } + + /** + * 获取 View 背景着色颜色 + * @param view {@link View} + * @return 背景着色颜色 {@link ColorStateList} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static ColorStateList getBackgroundTintList(final View view) { + if (view != null) return view.getBackgroundTintList(); + return null; + } + + /** + * 获取 View 背景着色模式 + * @param view {@link View} + * @return 背景着色模式 {@link PorterDuff.Mode} + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static PorterDuff.Mode getBackgroundTintMode(final View view) { + if (view != null) return view.getBackgroundTintMode(); + return null; + } + + // = + + /** + * 获取 View 前景 Drawable + * @param view {@link View} + * @return 前景 Drawable + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static Drawable getForeground(final View view) { + if (view != null) return view.getForeground(); + return null; + } + + /** + * 获取 View 前景重心 + * @param view {@link View} + * @return 前景重心 {@link Gravity} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static int getForegroundGravity(final View view) { + if (view != null) return view.getForegroundGravity(); + return Gravity.FILL; + } + + /** + * 获取 View 前景着色颜色 + * @param view {@link View} + * @return 前景着色颜色 {@link ColorStateList} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static ColorStateList getForegroundTintList(final View view) { + if (view != null) return view.getForegroundTintList(); + return null; + } + + /** + * 获取 View 前景着色模式 + * @param view {@link View} + * @return 前景着色模式 {@link PorterDuff.Mode} + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static PorterDuff.Mode getForegroundTintMode(final View view) { + if (view != null) return view.getForegroundTintMode(); + return null; + } + + // ========== + // = 着色处理 = + // ========== + + /** + * View 着色处理 + * @param view {@link View} + * @param color 颜色值 + * @return {@link View} + */ + public static View setColorFilter( + final View view, + @ColorInt final int color + ) { + return setColorFilter(view, getBackground(view), color); + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param view {@link View} + * @param drawable {@link Drawable} + * @param color 颜色值 + * @return {@link View} + */ + public static View setColorFilter( + final View view, + final Drawable drawable, + @ColorInt final int color + ) { + try { + setBackground(view, ImageUtils.setColorFilter(drawable, color)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setColorFilter"); + } + return view; + } + + // = + + /** + * View 着色处理 + * @param view {@link View} + * @param colorFilter 颜色过滤 ( 效果 ) + * @return {@link View} + */ + public static View setColorFilter( + final View view, + final ColorFilter colorFilter + ) { + return setColorFilter(view, getBackground(view), colorFilter); + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param view {@link View} + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @return {@link View} + */ + public static View setColorFilter( + final View view, + final Drawable drawable, + final ColorFilter colorFilter + ) { + try { + setBackground(view, ImageUtils.setColorFilter(drawable, colorFilter)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setColorFilter"); + } + return view; + } + + // =============== + // = ProgressBar = + // =============== + + /** + * 设置 ProgressBar 进度条样式 + * @param view {@link View} + * @param drawable {@link Drawable} + * @return {@link View} + */ + public static View setProgressDrawable( + final View view, + final Drawable drawable + ) { + if (view instanceof ProgressBar) { + setProgressDrawable((ProgressBar) view, drawable); + } + return view; + } + + /** + * 设置 ProgressBar 进度值 + * @param view {@link View} + * @param progress 当前进度 + * @return {@link View} + */ + public static View setBarProgress( + final View view, + final int progress + ) { + if (view instanceof ProgressBar) { + setBarProgress((ProgressBar) view, progress); + } + return view; + } + + /** + * 设置 ProgressBar 最大值 + * @param view {@link View} + * @param max 最大值 + * @return {@link View} + */ + public static View setBarMax( + final View view, + final int max + ) { + if (view instanceof ProgressBar) { + setBarMax((ProgressBar) view, max); + } + return view; + } + + /** + * 设置 ProgressBar 最大值 + * @param view {@link View} + * @param progress 当前进度 + * @param max 最大值 + * @return {@link View} + */ + public static View setBarValue( + final View view, + final int progress, + final int max + ) { + setBarMax(view, max); + return setBarProgress(view, progress); + } + + // = + + /** + * 设置 ProgressBar 进度条样式 + * @param view {@link ProgressBar} + * @param drawable {@link Drawable} + * @return {@link ProgressBar} + */ + public static ProgressBar setProgressDrawable( + final ProgressBar view, + final Drawable drawable + ) { + if (view != null) { + view.setProgressDrawable(drawable); + } + return view; + } + + /** + * 设置 ProgressBar 进度值 + * @param view {@link ProgressBar} + * @param progress 当前进度 + * @return {@link ProgressBar} + */ + public static ProgressBar setBarProgress( + final ProgressBar view, + final int progress + ) { + if (view != null) { + view.setProgress(progress); + } + return view; + } + + /** + * 设置 ProgressBar 最大值 + * @param view {@link ProgressBar} + * @param max 最大值 + * @return {@link ProgressBar} + */ + public static ProgressBar setBarMax( + final ProgressBar view, + final int max + ) { + if (view != null) { + view.setMax(max); + } + return view; + } + + /** + * 设置 ProgressBar 最大值 + * @param view {@link ProgressBar} + * @param progress 当前进度 + * @param max 最大值 + * @return {@link ProgressBar} + */ + public static ProgressBar setBarValue( + final ProgressBar view, + final int progress, + final int max + ) { + setBarMax(view, max); + return setBarProgress(view, progress); + } + + // ========== + // = 接口相关 = + // ========== + + /** + * detail: 宽高监听事件 + * @author Ttt + */ + public interface OnWHListener { + + /** + * 获取宽高回调 + * @param view {@link View} + * @param width 宽度 + * @param height 高度 + */ + void onWidthHeight( + View view, + int width, + int height + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/WallpaperUtils.java b/lib/DevApp/src/main/java/dev/utils/app/WallpaperUtils.java new file mode 100644 index 0000000000..68e75c8d66 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/WallpaperUtils.java @@ -0,0 +1,540 @@ +package dev.utils.app; + +import android.Manifest; +import android.app.WallpaperColors; +import android.app.WallpaperInfo; +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; + +import androidx.annotation.RawRes; +import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresPermission; + +import java.io.InputStream; + +import dev.utils.LogPrintUtils; + +/** + * detail: 壁纸工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     

+ * 修改 ( 设置、清空 ) 壁纸成功都会触发 {@link Intent#ACTION_WALLPAPER_CHANGED} 广播 + *

+ * 桌面壁纸 + * {@link WallpaperManager#FLAG_SYSTEM} + * 锁屏壁纸 + * {@link WallpaperManager#FLAG_LOCK} + *

+ * 动态壁纸可参考 ( 支持 Gif、Camera、Video 动态壁纸 ) : + * @see
+ *
+ */ +public final class WallpaperUtils { + + private WallpaperUtils() { + } + + // 日志 TAG + private static final String TAG = WallpaperUtils.class.getSimpleName(); + + /** + * 是否支持壁纸 + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static boolean isWallpaperSupported() { + try { + return AppUtils.getWallpaperManager().isWallpaperSupported(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isWallpaperSupported"); + } + return false; + } + + /** + * 是否支持设置壁纸 + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static boolean isSetWallpaperAllowed() { + try { + return AppUtils.getWallpaperManager().isSetWallpaperAllowed(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isSetWallpaperAllowed"); + } + return false; + } + + /** + * 判断当前壁纸是否使用给定的资源 Id + *
+     *     前提是壁纸是通过 {@link WallpaperManager#setResource(int)} 进行设置的
+     * 
+ * @param resId resource identifier + * @return {@code true} yes, {@code false} no + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static boolean hasResourceWallpaper(@RawRes final int resId) { + try { + return AppUtils.getWallpaperManager().hasResourceWallpaper(resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "hasResourceWallpaper"); + } + return false; + } + + // ========== + // = 删除操作 = + // ========== + + /** + * 删除所有内部引用到最后加载的壁纸 + */ + public static void forgetLoadedWallpaper() { + AppUtils.getWallpaperManager().forgetLoadedWallpaper(); + } + + /** + * 删除壁纸 ( 恢复为系统内置桌面壁纸 ) + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean clear() { + try { + AppUtils.getWallpaperManager().clear(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "clear"); + } + return false; + } + + /** + * 删除壁纸 ( 恢复为系统内置壁纸 ) + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean clear(final int which) { + try { + AppUtils.getWallpaperManager().clear(which); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "clear which: %s", which); + } + return false; + } + + /** + * 删除壁纸 ( 恢复为系统内置壁纸 ) + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean clearWallpaper() { + try { + AppUtils.getWallpaperManager().clearWallpaper(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "clearWallpaper"); + } + return false; + } + + // ========== + // = 获取操作 = + // ========== + + /** + * 获取当前壁纸 Id + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return 当前壁纸 Id + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static int getWallpaperId(final int which) { + try { + return AppUtils.getWallpaperManager().getWallpaperId(which); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWallpaperId which: %s", which); + } + return 0; + } + + /** + * 获取动态壁纸信息 + *
+     *     如果非动态壁纸则返回 null
+     * 
+ * @return {@link WallpaperInfo} + */ + public static WallpaperInfo getWallpaperInfo() { + try { + return AppUtils.getWallpaperManager().getWallpaperInfo(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWallpaperInfo"); + } + return null; + } + + /** + * 获取壁纸颜色信息 + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@link WallpaperColors} + */ + @RequiresApi(api = Build.VERSION_CODES.O_MR1) + public static WallpaperColors getWallpaperColors(final int which) { + try { + return AppUtils.getWallpaperManager().getWallpaperColors(which); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWallpaperColors which: %s", which); + } + return null; + } + + /** + * 获取壁纸所需最小高度 + * @return 壁纸所需最小高度 + */ + public static int getDesiredMinimumHeight() { + try { + return AppUtils.getWallpaperManager().getDesiredMinimumHeight(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDesiredMinimumHeight"); + } + return 0; + } + + /** + * 获取壁纸所需最小宽度 + * @return 壁纸所需最小宽度 + */ + public static int getDesiredMinimumWidth() { + try { + return AppUtils.getWallpaperManager().getDesiredMinimumWidth(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDesiredMinimumWidth"); + } + return 0; + } + + /** + * 获取系统内置静态壁纸 ( 桌面壁纸 ) + * @return {@link Drawable} + */ + @RequiresApi(api = Build.VERSION_CODES.KITKAT) + public static Drawable getBuiltInDrawable() { + try { + return AppUtils.getWallpaperManager().getBuiltInDrawable(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBuiltInDrawable"); + } + return null; + } + + /** + * 获取系统内置静态壁纸 + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@link Drawable} + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public static Drawable getBuiltInDrawable(final int which) { + try { + return AppUtils.getWallpaperManager().getBuiltInDrawable(which); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBuiltInDrawable which: %s", which); + } + return null; + } + + /** + * 获取当前壁纸 ( 桌面壁纸 ) + *
+     *     如果未设置壁纸, 则返回系统内置的静态墙纸
+     * 
+ * @return {@link Drawable} + */ + public static Drawable getDrawable() { + try { + return AppUtils.getWallpaperManager().getDrawable(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDrawable"); + } + return null; + } + + /** + * 获取当前壁纸 ( 桌面壁纸 ) + *
+     *     与相似 {@link #getDrawable()} 但返回的 Drawable 具有许多限制, 以尽可能减少其开销
+     * 
+ * @return {@link Drawable} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + @RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + public static Drawable getFastDrawable() { + try { + return AppUtils.getWallpaperManager().getFastDrawable(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getFastDrawable"); + } + return null; + } + + /** + * 获取当前壁纸 ( 桌面壁纸 ) + *
+     *     如果未设置壁纸, 则返回 null
+     * 
+ * @return {@link Drawable} + */ + public static Drawable peekDrawable() { + try { + return AppUtils.getWallpaperManager().peekDrawable(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "peekDrawable"); + } + return null; + } + + /** + * 获取当前壁纸 ( 桌面壁纸 ) + *
+     *     与相似 {@link #peekDrawable()} 但返回的 Drawable 具有许多限制, 以尽可能减少其开销
+     * 
+ * @return {@link Drawable} + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) + @RequiresPermission(Manifest.permission.READ_EXTERNAL_STORAGE) + public static Drawable peekFastDrawable() { + try { + return AppUtils.getWallpaperManager().peekFastDrawable(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "peekFastDrawable"); + } + return null; + } + + // ========== + // = 设置操作 = + // ========== + + /** + * 通过 Bitmap 设置壁纸 ( 桌面壁纸 ) + * @param bitmap {@link Bitmap} + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setBitmap(final Bitmap bitmap) { + try { + AppUtils.getWallpaperManager().setBitmap(bitmap); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBitmap"); + } + return false; + } + + /** + * 通过 Bitmap 设置壁纸 + * @param bitmap {@link Bitmap} + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setBitmap( + final Bitmap bitmap, + final int which + ) { + try { + AppUtils.getWallpaperManager().setBitmap(bitmap, null, true, which); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBitmap which: %s", which); + } + return false; + } + + /** + * 通过 res 设置壁纸 + * @param resId resource identifier + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setResource(@RawRes final int resId) { + try { + AppUtils.getWallpaperManager().setResource(resId); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setResource"); + } + return false; + } + + /** + * 通过 res 设置壁纸 + * @param resId resource identifier + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setResource( + @RawRes final int resId, + final int which + ) { + try { + AppUtils.getWallpaperManager().setResource(resId, which); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setResource which: %s", which); + } + return false; + } + + /** + * 通过 InputStream 设置壁纸 + * @param inputStream bitmapData InputStream + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setStream(final InputStream inputStream) { + try { + AppUtils.getWallpaperManager().setStream(inputStream); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStream"); + } + return false; + } + + /** + * 通过 InputStream 设置壁纸 + * @param inputStream bitmapData InputStream + * @param which {@link WallpaperManager#FLAG_SYSTEM} or {@link WallpaperManager#FLAG_LOCK} + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setStream( + final InputStream inputStream, + final int which + ) { + try { + AppUtils.getWallpaperManager().setStream(inputStream, null, true, which); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStream which: %s", which); + } + return false; + } + + // ========== + // = 适配设置 = + // ========== + + /** + * detail: 其他回调 + * @author Ttt + */ + public interface OnOtherCallback { + + /** + * 非适配 ROM 则触发回调 + * @return {@code true} success, {@code false} fail + */ + boolean callback(); + } + + /** + * 通过 Uri 设置壁纸 ( 跳转到设置页 ) + * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setUri(final Uri uri) { + return setUri(uri, null); + } + + /** + * 通过 Uri 设置壁纸 ( 跳转到设置页 ) + * @param uri {@link Uri} + * @param callback {@link OnOtherCallback} + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.SET_WALLPAPER) + public static boolean setUri( + final Uri uri, + final OnOtherCallback callback + ) { + if (uri == null) return false; + Intent intent; + try { + if (ROMUtils.isXiaomi()) { + ComponentName componentName = new ComponentName( + "com.android.thememanager", + "com.android.thememanager.activity.WallpaperDetailActivity" + ); + intent = new Intent("miui.intent.action.START_WALLPAPER_DETAIL"); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(uri, "image/*"); + intent.putExtra("mimeType", "image/*"); + intent.setComponent(componentName); + return AppUtils.startActivity(intent); + } + + if (ROMUtils.isHuawei()) { + ComponentName componentName = new ComponentName( + "com.android.gallery3d", "com.android.gallery3d.app.Wallpaper" + ); + intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(uri, "image/*"); + intent.putExtra("mimeType", "image/*"); + intent.setComponent(componentName); + return AppUtils.startActivity(intent); + } + + if (ROMUtils.isOppo()) { + ComponentName componentName = new ComponentName( + "com.coloros.gallery3d", "com.oppo.gallery3d.app.Wallpaper" + ); + intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(uri, "image/*"); + intent.putExtra("mimeType", "image/*"); + intent.setComponent(componentName); + return AppUtils.startActivity(intent); + } + + if (ROMUtils.isVivo()) { + ComponentName componentName = new ComponentName( + "com.vivo.gallery", "com.android.gallery3d.app.Wallpaper" + ); + intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(uri, "image/*"); + intent.putExtra("mimeType", "image/*"); + intent.setComponent(componentName); + return AppUtils.startActivity(intent); + } + + if (callback != null) { + return callback.callback(); + } + return setStream(ResourceUtils.openInputStream(uri)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setUri"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/WidgetUtils.java b/lib/DevApp/src/main/java/dev/utils/app/WidgetUtils.java new file mode 100644 index 0000000000..5b2be63c04 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/WidgetUtils.java @@ -0,0 +1,311 @@ +package dev.utils.app; + +import android.os.Build; +import android.view.View; +import android.view.ViewGroup; + +import dev.utils.LogPrintUtils; + +/** + * detail: 控件工具类 + * @author Ttt + */ +public final class WidgetUtils { + + private WidgetUtils() { + } + + // 日志 TAG + private static final String TAG = WidgetUtils.class.getSimpleName(); + + /** + * View Measure + * @param view 待计算 View + * @param widthMeasureSpec horizontal space requirements as imposed by the parent + * @param heightMeasureSpec vertical space requirements as imposed by the parent + * @param maximumWidth maximum Width + * @param maximumHeight maximum Height + * @return measure space Array + */ + public static int[] viewMeasure( + final View view, + final int widthMeasureSpec, + final int heightMeasureSpec, + final int maximumWidth, + final int maximumHeight + ) { + return viewMeasure( + view, widthMeasureSpec, heightMeasureSpec, + maximumWidth, maximumHeight, + ViewUtils.MATCH_PARENT + ); + } + + /** + * View Measure + * @param view 待计算 View + * @param widthMeasureSpec horizontal space requirements as imposed by the parent + * @param heightMeasureSpec vertical space requirements as imposed by the parent + * @param maximumWidth maximum Width + * @param maximumHeight maximum Height + * @param defaultValue 默认值 + * @return measure space Array + */ + public static int[] viewMeasure( + final View view, + final int widthMeasureSpec, + final int heightMeasureSpec, + final int maximumWidth, + final int maximumHeight, + final int defaultValue + ) { + int minimumWidth = 0, minimumHeight = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + minimumWidth = view.getMinimumWidth(); + minimumHeight = view.getMinimumHeight(); + } + return viewMeasure( + widthMeasureSpec, heightMeasureSpec, + minimumWidth, maximumWidth, + minimumHeight, maximumHeight, defaultValue + ); + } + + /** + * View Measure + * @param widthMeasureSpec horizontal space requirements as imposed by the parent + * @param heightMeasureSpec vertical space requirements as imposed by the parent + * @param minimumWidth minimum Width + * @param maximumWidth maximum Width + * @param minimumHeight minimum Height + * @param maximumHeight maximum Height + * @return measure space Array + */ + public static int[] viewMeasure( + final int widthMeasureSpec, + final int heightMeasureSpec, + final int minimumWidth, + final int maximumWidth, + final int minimumHeight, + final int maximumHeight + ) { + return viewMeasure( + widthMeasureSpec, heightMeasureSpec, + minimumWidth, maximumWidth, + minimumHeight, maximumHeight, + ViewUtils.MATCH_PARENT + ); + } + + /** + * View Measure + * @param widthMeasureSpec horizontal space requirements as imposed by the parent + * @param heightMeasureSpec vertical space requirements as imposed by the parent + * @param minimumWidth minimum Width + * @param maximumWidth maximum Width + * @param minimumHeight minimum Height + * @param maximumHeight maximum Height + * @param defaultValue 默认值 + * @return measure space Array + */ + public static int[] viewMeasure( + final int widthMeasureSpec, + final int heightMeasureSpec, + final int minimumWidth, + final int maximumWidth, + final int minimumHeight, + final int maximumHeight, + final int defaultValue + ) { + // 获取原来宽、高 size + int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); + int widthMode = View.MeasureSpec.getMode(widthMeasureSpec); + + int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); + int heightMode = View.MeasureSpec.getMode(heightMeasureSpec); + + widthSize = calculateSize(widthSize, minimumWidth, maximumWidth, defaultValue); + heightSize = calculateSize(heightSize, minimumHeight, maximumHeight, defaultValue); + + int calcWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(widthSize, widthMode); + int calcHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(heightSize, heightMode); + + return new int[]{calcWidthMeasureSpec, calcHeightMeasureSpec}; + } + + /** + * 计算大小 + * @param size 待计算值 + * @param minimum 最小值 + * @param maximum 最大值 + * @return 计算后的大小 + */ + public static int calculateSize( + final int size, + final int minimum, + final int maximum + ) { + return calculateSize(size, minimum, maximum, ViewUtils.MATCH_PARENT); + } + + /** + * 计算大小 + * @param size 待计算值 + * @param minimum 最小值 + * @param maximum 最大值 + * @param defaultValue 默认值 + * @return 计算后的大小 + */ + public static int calculateSize( + final int size, + final int minimum, + final int maximum, + final int defaultValue + ) { + if (maximum == defaultValue) { + return Math.max(minimum, size); + } else { + if (size >= maximum) { + return maximum; + } else { + return Math.max(minimum, size); + } + } + } + + /** + * 从提供的测量规范中提取大小 + * @param measureSpec 测量规范 + * @return 测量大小 + */ + public static int getSize(final int measureSpec) { + return View.MeasureSpec.getSize(measureSpec); + } + + /** + * 从提供的测量规范中提取模式 + * @param measureSpec 测量规范 + * @return 测量模式 + */ + public static int getMode(final int measureSpec) { + return View.MeasureSpec.getMode(measureSpec); + } + + /** + * 测量 View + * @param view {@link View} + * @return int[] 0 = 宽度, 1 = 高度 + */ + public static int[] measureView(final View view) { + if (view != null) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams == null) { + layoutParams = new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + } + int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, layoutParams.width); + int height = layoutParams.height; + int heightSpec; + if (height > 0) { + heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + } else { + heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + } + view.measure(widthSpec, heightSpec); + return new int[]{view.getMeasuredWidth(), view.getMeasuredHeight()}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "measureView"); + } + } + return new int[]{0, 0}; + } + + /** + * 获取 View 的宽度 + * @param view {@link View} + * @return View 的宽度 + */ + public static int getMeasuredWidth(final View view) { + if (view != null) { + measureView(view); + return view.getMeasuredWidth(); + } + return 0; + } + + /** + * 获取 View 的高度 + * @param view {@link View} + * @return View 的高度 + */ + public static int getMeasuredHeight(final View view) { + if (view != null) { + measureView(view); + return view.getMeasuredHeight(); + } + return 0; + } + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @return {@code true} success, {@code false} fail + */ + public static boolean measureView( + final View view, + final int specifiedWidth + ) { + return measureView(view, specifiedWidth, 0); + } + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return {@code true} success, {@code false} fail + */ + public static boolean measureView( + final View view, + final int specifiedWidth, + final int specifiedHeight + ) { + try { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + // MeasureSpec + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + // 如果大于 0 + if (specifiedWidth > 0) { + widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(specifiedWidth, View.MeasureSpec.EXACTLY); + } + // 如果大于 0 + if (specifiedHeight > 0) { + heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(specifiedHeight, View.MeasureSpec.EXACTLY); + } + // 判断是否存在自定义宽高 + if (layoutParams != null) { + int width = layoutParams.width; + int height = layoutParams.height; + if (width > 0 && height > 0) { + widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + } else if (width > 0) { + widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY); + } else if (height > 0) { + heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY); + } + } + view.measure(widthMeasureSpec, heightMeasureSpec); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "measureView"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/WindowUtils.java b/lib/DevApp/src/main/java/dev/utils/app/WindowUtils.java new file mode 100644 index 0000000000..83366df22a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/WindowUtils.java @@ -0,0 +1,168 @@ +package dev.utils.app; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.view.Window; + +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; + +import dev.utils.app.assist.WindowAssist; + +/** + * detail: Window 工具类 + * @author Ttt + */ +public final class WindowUtils { + + private WindowUtils() { + } + + /** + * 获取 Window + * @param context {@link Context} + * @return {@link Window} + */ + public static Window getWindow(final Context context) { + return WindowAssist.getWindow(context); + } + + /** + * 获取 Window + * @param activity {@link Activity} + * @return {@link Window} + */ + public static Window getWindow(final Activity activity) { + return WindowAssist.getWindow(activity); + } + + /** + * 获取 Window + * @param fragment {@link Fragment} + * @return {@link Window} + */ + public static Window getWindow(final Fragment fragment) { + return WindowAssist.getWindow(fragment); + } + + /** + * 获取 Window + * @param fragment {@link android.app.Fragment} + * @return {@link Window} + */ + public static Window getWindow(final android.app.Fragment fragment) { + return WindowAssist.getWindow(fragment); + } + + /** + * 获取 Window + * @param dialog {@link Dialog} + * @return {@link Window} + */ + public static Window getWindow(final Dialog dialog) { + return WindowAssist.getWindow(dialog); + } + + /** + * 获取 Window + * @param dialog {@link DialogFragment} + * @return {@link Window} + */ + public static Window getWindow(final DialogFragment dialog) { + return WindowAssist.getWindow(dialog); + } + + /** + * 获取 Window + * @param dialog {@link android.app.DialogFragment} + * @return {@link Window} + */ + public static Window getWindow(final android.app.DialogFragment dialog) { + return WindowAssist.getWindow(dialog); + } + + // ======= + // = get = + // ======= + + /** + * 获取 WindowAssist + * @return {@link WindowAssist} + */ + public static WindowAssist get() { + return WindowAssist.get(); + } + + /** + * 获取 WindowAssist + * @param window {@link Window} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Window window) { + return WindowAssist.get(window); + } + + /** + * 获取 WindowAssist + * @param context {@link Context} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Context context) { + return WindowAssist.get(context); + } + + /** + * 获取 WindowAssist + * @param activity {@link Activity} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Activity activity) { + return WindowAssist.get(activity); + } + + /** + * 获取 WindowAssist + * @param fragment {@link Fragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Fragment fragment) { + return WindowAssist.get(fragment); + } + + /** + * 获取 WindowAssist + * @param fragment {@link android.app.Fragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final android.app.Fragment fragment) { + return WindowAssist.get(fragment); + } + + /** + * 获取 WindowAssist + * @param dialog {@link Dialog} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Dialog dialog) { + return WindowAssist.get(dialog); + } + + /** + * 获取 WindowAssist + * @param dialog {@link DialogFragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final DialogFragment dialog) { + return WindowAssist.get(dialog); + } + + /** + * 获取 WindowAssist + * @param dialog {@link android.app.DialogFragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final android.app.DialogFragment dialog) { + return WindowAssist.get(dialog); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/activity_result/ActivityResultAssist.java b/lib/DevApp/src/main/java/dev/utils/app/activity_result/ActivityResultAssist.java new file mode 100644 index 0000000000..bf2e11a8fa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/activity_result/ActivityResultAssist.java @@ -0,0 +1,334 @@ +package dev.utils.app.activity_result; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultCaller; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.ActivityResultRegistry; +import androidx.activity.result.contract.ActivityResultContract; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.core.app.ActivityOptionsCompat; +import androidx.lifecycle.LifecycleOwner; + +import dev.utils.DevFinal; +import dev.utils.app.ActivityResultUtils; + +/** + * detail: Activity Result 封装辅助类 + * @author Ttt + *
+ *     Activity Result API
+ *     @see 
+ *     

+ * 封装 {@link ActivityResultUtils} 无需考虑异常崩溃等各种情况 + * 注意事项: + * 虽然在 fragment 或 activity 创建完毕之前可安全地调用 registerForActivityResult() + * 但在 fragment 或 activity 的 Lifecycle 变为 CREATED 状态之前, 您无法启动 ActivityResultLauncher + * 并且必须在 STARTED 前调用 registerForActivityResult 可查看下方 register 方法 throw IllegalStateException + * {@link androidx.activity.result.ActivityResultRegistry#register} + * Activity Result API 有三个重要的类: + * ActivityResultContract: 协议, 这是一个抽象类, 定义如何传递数据和如何接收数据 + * ActivityResultLauncher: 启动器, 相当于以前的 startActivityForResult() + * ActivityResultCallback: 结果回调, 相当于以前的 onActivityResult() + *

+ * 系统内置常用 ActivityResultContract 具体点击 ActivityResultContracts 进行查看 + * {@link ActivityResultContracts.StartActivityForResult} 通用 Contract + * {@link ActivityResultContracts.RequestMultiplePermissions} 申请一组权限 + * {@link ActivityResultContracts.RequestPermission} 申请单个权限 + * {@link ActivityResultContracts.TakePicturePreview} 拍照 ( 返回 Bitmap ) + * {@link ActivityResultContracts.TakePicture} 拍照 ( 保存指定 Uri 地址, 返回 true 表示保存成功 ) + * {@link ActivityResultContracts.CaptureVideo} 拍视频 ( 保存指定 Uri 地址, 返回 true 表示保存成功 ) + * {@link ActivityResultContracts.PickContact} 从通讯录获取联系人 + * {@link ActivityResultContracts.CreateDocument} 选择一个文档 ( 返回 Uri ) + * {@link ActivityResultContracts.OpenDocumentTree} 选择一个目录 ( 返回 Uri ) + * {@link ActivityResultContracts.OpenMultipleDocuments} 选择多个文档 ( 返回多个 Uri ) + * {@link ActivityResultContracts.GetContent} 选择一条内容 ( 返回 Uri ) + *
+ */ +public final class ActivityResultAssist + extends ActivityResultLauncher { + + public static final int LAUNCH = 1; + public static final int LAUNCH_OPTIONS = 2; + public static final int UNREGISTER = 3; + + // 跳转回传值启动器 + private ActivityResultLauncher mLauncher; + // 操作回调 + private OperateCallback mCallback; + // 启动输入参数 + private I inputValue; + // Activity 启动选项 + private ActivityOptionsCompat optionsValue; + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断启动器是否为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isLauncherEmpty() { + return mLauncher == null; + } + + /** + * 判断启动器是否不为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isLauncherNotEmpty() { + return mLauncher != null; + } + + /** + * 设置操作回调 + * @param callback OperateCallback + * @return ActivityResultAssist + */ + public ActivityResultAssist setOperateCallback(final OperateCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * 获取启动输入参数值 + * @return 启动输入参数值 + */ + public I getInputValue() { + return inputValue; + } + + /** + * 获取 Activity 启动选项值 + * @return Activity 启动选项值 + */ + public ActivityOptionsCompat getOptionsValue() { + return optionsValue; + } + + /** + * 获取对应 Type 所属方法 + * @param type method Type + * @return Type 所属方法 + */ + public static String getMethodType(final int type) { + switch (type) { + case ActivityResultAssist.LAUNCH: + return "launch()"; + case ActivityResultAssist.LAUNCH_OPTIONS: + return "launch(options)"; + case ActivityResultAssist.UNREGISTER: + return "unregister()"; + default: + return DevFinal.STR.UNKNOWN; + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 内部设置值 + *
+     *     每次调用 launch 方法都会重置值
+     *     不能保证结果回调就是对应的 input value
+     *     可设置双击校验防止多次调用
+     * 
+ * @param input 启动输入参数 + * @param options Activity 启动选项 + */ + private void set( + final I input, + final ActivityOptionsCompat options + ) { + this.inputValue = input; + this.optionsValue = options; + } + + // ========================== + // = ActivityResultLauncher = + // ========================== + + @Override + public void launch(final I input) { + set(input, null); + + if (mCallback != null) { + mCallback.onStart(this, LAUNCH, input, null); + } + boolean result = ActivityResultUtils.launch(mLauncher, input); + if (mCallback != null) { + mCallback.onState(this, LAUNCH, input, null, result); + } + } + + @Override + public void launch( + final I input, + final ActivityOptionsCompat options + ) { + set(input, options); + + if (mCallback != null) { + mCallback.onStart(this, LAUNCH_OPTIONS, input, options); + } + boolean result = ActivityResultUtils.launch(mLauncher, input, options); + if (mCallback != null) { + mCallback.onState(this, LAUNCH_OPTIONS, input, options, result); + } + } + + @Override + public void unregister() { + if (mCallback != null) { + mCallback.onStart(this, UNREGISTER, null, null); + } + boolean result = ActivityResultUtils.unregister(mLauncher); + if (mCallback != null) { + mCallback.onState(this, UNREGISTER, null, null, result); + } + // 注销后调用 launch 将会失效 + mLauncher = null; + set(null, null); + } + + @NonNull + @Override + public ActivityResultContract getContract() { + return ActivityResultUtils.getContract(mLauncher); + } + + // ==================== + // = Operate Callback = + // ==================== + + /** + * detail: 操作回调 + * @author Ttt + */ + public static abstract class OperateCallback { + + /** + * 操作前回调 + * @param assist ActivityResultAssist + * @param type 类型 + * @param input 输入参数 + * @param options Activity 启动选项 + */ + public void onStart( + final ActivityResultAssist assist, + final int type, + final I input, + final ActivityOptionsCompat options + ) { + } + + /** + * 操作状态回调 + * @param assist ActivityResultAssist + * @param type 类型 + * @param input 输入参数 + * @param options Activity 启动选项 + * @param result 操作结果 + */ + public abstract void onState( + final ActivityResultAssist assist, + final int type, + final I input, + final ActivityOptionsCompat options, + final boolean result + ); + } + + // ========== + // = 构造函数 = + // ========== + + // ======================== + // = ActivityResultCaller = + // ======================== + + /** + * 注册创建跳转回传值启动器并返回 + * @param caller ActivityResultCaller ( 只要属于继承 Fragment、FragmentActivity 传入 this 即可 ) + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + */ + public ActivityResultAssist( + final ActivityResultCaller caller, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + mLauncher = ActivityResultUtils.registerForActivityResult( + caller, contract, callback + ); + } + + /** + * 注册创建跳转回传值启动器并返回 + * @param caller ActivityResultCaller ( 只要属于继承 Fragment、FragmentActivity 传入 this 即可 ) + * @param contract ActivityResultContract + * @param registry ActivityResultRegistry + * @param callback ActivityResultCallback 回传回调 + */ + public ActivityResultAssist( + final ActivityResultCaller caller, + final ActivityResultContract contract, + final ActivityResultRegistry registry, + final ActivityResultCallback callback + ) { + mLauncher = ActivityResultUtils.registerForActivityResult( + caller, contract, registry, callback + ); + } + + // ========================== + // = ActivityResultRegistry = + // ========================== + + /** + * 注册创建跳转回传值启动器并返回 + *
+     *     ActivityResultRegistry ( 可通过 ComponentActivity、Fragment getActivityResultRegistry() 获取 )
+     * 
+ * @param registry ActivityResultRegistry + * @param key 唯一值字符串 + * @param lifecycleOwner 生命周期监听 + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + */ + public ActivityResultAssist( + final ActivityResultRegistry registry, + final String key, + final LifecycleOwner lifecycleOwner, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + mLauncher = ActivityResultUtils.register( + registry, key, lifecycleOwner, contract, callback + ); + } + + /** + * 注册创建跳转回传值启动器并返回 + *
+     *     ActivityResultRegistry ( 可通过 ComponentActivity、Fragment getActivityResultRegistry() 获取 )
+     * 
+ * @param registry ActivityResultRegistry + * @param key 唯一值字符串 + * @param contract ActivityResultContract + * @param callback ActivityResultCallback 回传回调 + */ + public ActivityResultAssist( + final ActivityResultRegistry registry, + final String key, + final ActivityResultContract contract, + final ActivityResultCallback callback + ) { + mLauncher = ActivityResultUtils.register( + registry, key, contract, callback + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/activity_result/DefaultActivityResult.java b/lib/DevApp/src/main/java/dev/utils/app/activity_result/DefaultActivityResult.java new file mode 100644 index 0000000000..a492a62bf6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/activity_result/DefaultActivityResult.java @@ -0,0 +1,190 @@ +package dev.utils.app.activity_result; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; + +import java.util.HashMap; +import java.util.Map; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.DevCommonUtils; + +/** + * detail: Activity Result 封装辅助类 + * @author Ttt + *
+ *     默认 Activity onActivityResult(int, int, Intent) 实现方式封装
+ * 
+ */ +public final class DefaultActivityResult { + + private DefaultActivityResult() { + } + + // DefaultActivityResult 实例 + private static volatile DefaultActivityResult sInstance; + + /** + * 获取 DefaultActivityResult 实例 + * @return {@link DefaultActivityResult} + */ + public static DefaultActivityResult getInstance() { + if (sInstance == null) { + synchronized (DefaultActivityResult.class) { + if (sInstance == null) { + sInstance = new DefaultActivityResult(); + } + } + } + return sInstance; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * Activity 跳转回传 + * @param callback Activity 跳转回传回调 + * @return {@code true} success, {@code false} fail + */ + public boolean startActivityForResult(final ResultCallback callback) { + return DefaultActivityResult.ResultActivity.start(callback); + } + + // ========== + // = 跳转回传 = + // ========== + + // 跳转回传回调 Map + private static final Map sResultCallbackMaps = new HashMap<>(); + + /** + * detail: Activity 跳转回传回调 + * @author Ttt + */ + public interface ResultCallback { + + /** + * 跳转 Activity 操作 + *
+         *     跳转失败, 必须返回 false 内部会根据返回值关闭 ResultActivity
+         *     必须返回正确的值, 表示是否跳转成功
+         * 
+ * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + boolean onStartActivityForResult(Activity activity); + + /** + * 回传处理 + * @param result resultCode 是否等于 {@link Activity#RESULT_OK} + * @param resultCode resultCode + * @param intent 回传数据 + */ + void onActivityResult( + boolean result, + int resultCode, + Intent intent + ); + } + + // ============= + // = 内部封装逻辑 = + // ============= + + /** + * detail: 回传结果处理 Activity + * @author Ttt + */ + public static class ResultActivity + extends FragmentActivity { + + // 日志 TAG + private static final String TAG = ResultActivity.class.getSimpleName(); + + // 传参 UUID Key + private static final String EXTRA_UUID = DevFinal.STR.UUID; + // 跳转回传回调 + private ResultCallback mCallback; + // 跳转回传回调 + private Integer mUUIDHash; + + /** + * 跳转回传结果处理 Activity 内部方法 + * @param callback Activity 跳转回传回调 + * @return {@code true} success, {@code false} fail + */ + protected static boolean start(final ResultCallback callback) { + int uuid = -1; + boolean result = false; + if (callback != null) { + uuid = DevCommonUtils.randomUUIDToHashCode(); + while (sResultCallbackMaps.containsKey(uuid)) { + uuid = DevCommonUtils.randomUUIDToHashCode(); + } + sResultCallbackMaps.put(uuid, callback); + try { + Intent intent = new Intent(DevUtils.getContext(), ResultActivity.class); + intent.putExtra(EXTRA_UUID, uuid); + result = AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "start"); + } + } + if (!result && uuid != -1) { + sResultCallbackMaps.remove(uuid); + } + return result; + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + boolean result = false; // 跳转结果 + try { + mUUIDHash = getIntent().getIntExtra(EXTRA_UUID, -1); + mCallback = sResultCallbackMaps.get(mUUIDHash); + result = mCallback.onStartActivityForResult(this); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "onCreate"); + } + if (!result) { + if (mCallback != null) { + mCallback.onActivityResult(false, Activity.RESULT_CANCELED, null); + } + finish(); + } + } + + @Override + protected void onActivityResult( + int requestCode, + int resultCode, + Intent intent + ) { + super.onActivityResult(requestCode, resultCode, intent); + if (mCallback != null) { + mCallback.onActivityResult( + resultCode == Activity.RESULT_OK, + resultCode, intent + ); + } + finish(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // 移除操作 + sResultCallbackMaps.remove(mUUIDHash); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/anim/AnimationUtils.java b/lib/DevApp/src/main/java/dev/utils/app/anim/AnimationUtils.java new file mode 100644 index 0000000000..f2c57a9cac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/anim/AnimationUtils.java @@ -0,0 +1,950 @@ +package dev.utils.app.anim; + +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnticipateInterpolator; +import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.BounceInterpolator; +import android.view.animation.CycleInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.view.animation.RotateAnimation; +import android.view.animation.ScaleAnimation; +import android.view.animation.TranslateAnimation; + +import dev.utils.LogPrintUtils; + +/** + * detail: 动画工具类 + * @author Ttt + *
+ */ +public final class AnimationUtils { + + private AnimationUtils() { + } + + // 日志 TAG + private static final String TAG = AnimationUtils.class.getSimpleName(); + + // 默认动画持续时间 + public static final long DEFAULT_ANIMATION_DURATION = 400; + + /** + * 设置动画重复处理 + * @param animation {@link Animation} + * @param repeatCount 执行次数 + * @param repeatMode 重复模式 {@link Animation#RESTART} 重新从头开始执行、{@link Animation#REVERSE} 反方向执行 + * @param 泛型 + * @return 动画 + */ + public static T setAnimationRepeat( + final T animation, + final int repeatCount, + final int repeatMode + ) { + if (animation != null) { + animation.setRepeatCount(repeatCount); + animation.setRepeatMode(repeatMode); + } + return animation; + } + + /** + * 设置动画事件 + * @param animation {@link Animation} + * @param listener {@link AnimationListener} + * @return {@link Animation} + */ + public static Animation setAnimationListener( + final Animation animation, + final AnimationListener listener + ) { + if (animation != null) animation.setAnimationListener(listener); + return animation; + } + + /** + * 设置动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return {@link View} + */ + public static View setAnimation( + final View view, + final Animation animation + ) { + if (view != null) view.setAnimation(animation); + return view; + } + + /** + * 获取动画 + * @param view {@link View} + * @return {@link Animation} + */ + public static Animation getAnimation(final View view) { + return (view != null) ? view.getAnimation() : null; + } + + /** + * 清空动画 + * @param view {@link View} + * @return {@link View} + */ + public static View clearAnimation(final View view) { + if (view != null) { + try { + view.clearAnimation(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "clearAnimation"); + } + } + return view; + } + + /** + * 启动动画 + * @param view {@link View} + * @return {@link View} + */ + public static View startAnimation(final View view) { + return startAnimation(view, getAnimation(view)); + } + + /** + * 启动动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return {@link View} + */ + public static View startAnimation( + final View view, + final Animation animation + ) { + if (view != null && animation != null) { + try { + view.startAnimation(animation); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAnimation"); + } + } + return view; + } + + /** + * 启动动画 + * @param animation {@link Animation} + * @param 泛型 + * @return {@link Animation} + */ + public static T startAnimation(final T animation) { + if (animation != null) { + try { + animation.start(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startAnimation"); + } + } + return animation; + } + + /** + * 取消动画 + * @param view {@link View} + * @return {@link Animation} + */ + public static Animation cancelAnimation(final View view) { + return cancelAnimation(getAnimation(view)); + } + + /** + * 取消动画 + * @param animation {@link Animation} + * @param 泛型 + * @return {@link Animation} + */ + public static T cancelAnimation(final T animation) { + if (animation != null) { + try { + animation.cancel(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "cancelAnimation"); + } + } + return animation; + } + + // ============= + // = 视图旋转动画 = + // ============= + + /** + * 获取一个旋转动画 + * @param fromDegrees 开始角度 + * @param toDegrees 结束角度 + * @param pivotXType 旋转中心点 X 轴坐标相对类型 + * @param pivotXValue 旋转中心点 X 轴坐标 + * @param pivotYType 旋转中心点 Y 轴坐标相对类型 + * @param pivotYValue 旋转中心点 Y 轴坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个旋转动画 + */ + public static RotateAnimation getRotateAnimation( + final float fromDegrees, + final float toDegrees, + final int pivotXType, + final float pivotXValue, + final int pivotYType, + final float pivotYValue, + final long durationMillis, + final AnimationListener animationListener + ) { + RotateAnimation rotateAnimation = new RotateAnimation( + fromDegrees, toDegrees, pivotXType, + pivotXValue, pivotYType, pivotYValue + ); + rotateAnimation.setDuration(durationMillis); + if (animationListener != null) { + rotateAnimation.setAnimationListener(animationListener); + } + return rotateAnimation; + } + + /** + * 获取一个旋转动画 + * @param fromDegrees 开始角度 + * @param toDegrees 结束角度 + * @param pivotX 旋转中心点 X 轴坐标 + * @param pivotY 旋转中心点 Y 轴坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个旋转动画 + */ + public static RotateAnimation getRotateAnimation( + final float fromDegrees, + final float toDegrees, + final float pivotX, + final float pivotY, + final long durationMillis, + final AnimationListener animationListener + ) { + RotateAnimation rotateAnimation = new RotateAnimation( + fromDegrees, toDegrees, pivotX, pivotY + ); + rotateAnimation.setDuration(durationMillis); + if (animationListener != null) { + rotateAnimation.setAnimationListener(animationListener); + } + return rotateAnimation; + } + + /** + * 获取一个旋转动画 + * @param fromDegrees 开始角度 + * @param toDegrees 结束角度 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个旋转动画 + */ + public static RotateAnimation getRotateAnimation( + final float fromDegrees, + final float toDegrees, + final long durationMillis, + final AnimationListener animationListener + ) { + RotateAnimation rotateAnimation = new RotateAnimation(fromDegrees, toDegrees); + rotateAnimation.setDuration(durationMillis); + if (animationListener != null) { + rotateAnimation.setAnimationListener(animationListener); + } + return rotateAnimation; + } + + // = + + /** + * 获取一个根据视图自身中心点旋转的动画 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个根据中心点旋转的动画 + */ + public static RotateAnimation getRotateAnimationByCenter( + final long durationMillis, + final AnimationListener animationListener + ) { + return getRotateAnimation( + 0F, 359F, Animation.RELATIVE_TO_SELF, + 0.5F, Animation.RELATIVE_TO_SELF, 0.5F, + durationMillis, animationListener + ); + } + + /** + * 获取一个根据视图自身中心点旋转的动画 + * @param duration 动画持续时间 + * @return 一个根据中心点旋转的动画 + */ + public static RotateAnimation getRotateAnimationByCenter(final long duration) { + return getRotateAnimationByCenter(duration, null); + } + + /** + * 获取一个根据视图自身中心点旋转的动画 + * @param animationListener 动画监听器 + * @return 一个根据中心点旋转的动画 + */ + public static RotateAnimation getRotateAnimationByCenter(final AnimationListener animationListener) { + return getRotateAnimationByCenter(DEFAULT_ANIMATION_DURATION, animationListener); + } + + /** + * 获取一个根据视图自身中心点旋转的动画 + * @return 一个根据中心点旋转的动画 + */ + public static RotateAnimation getRotateAnimationByCenter() { + return getRotateAnimationByCenter(DEFAULT_ANIMATION_DURATION, null); + } + + // ============= + // = 视图渐变动画 = + // ============= + + /** + * 获取一个透明度渐变动画 + * @param fromAlpha 开始时的透明度 + * @param toAlpha 结束时的透明度 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个透明度渐变动画 + */ + public static AlphaAnimation getAlphaAnimation( + final float fromAlpha, + final float toAlpha, + final long durationMillis, + final AnimationListener animationListener + ) { + AlphaAnimation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha); + alphaAnimation.setDuration(durationMillis); + if (animationListener != null) { + alphaAnimation.setAnimationListener(animationListener); + } + return alphaAnimation; + } + + /** + * 获取一个透明度渐变动画 + * @param fromAlpha 开始时的透明度 + * @param toAlpha 结束时的透明度 + * @param durationMillis 动画持续时间 + * @return 一个透明度渐变动画 + */ + public static AlphaAnimation getAlphaAnimation( + final float fromAlpha, + final float toAlpha, + final long durationMillis + ) { + return getAlphaAnimation(fromAlpha, toAlpha, durationMillis, null); + } + + /** + * 获取一个透明度渐变动画 + * @param fromAlpha 开始时的透明度 + * @param toAlpha 结束时的透明度 + * @param animationListener 动画监听器 + * @return 一个透明度渐变动画 + */ + public static AlphaAnimation getAlphaAnimation( + final float fromAlpha, + final float toAlpha, + final AnimationListener animationListener + ) { + return getAlphaAnimation(fromAlpha, toAlpha, DEFAULT_ANIMATION_DURATION, animationListener); + } + + /** + * 获取一个透明度渐变动画 + * @param fromAlpha 开始时的透明度 + * @param toAlpha 结束时的透明度 + * @return 一个透明度渐变动画 + */ + public static AlphaAnimation getAlphaAnimation( + final float fromAlpha, + final float toAlpha + ) { + return getAlphaAnimation(fromAlpha, toAlpha, DEFAULT_ANIMATION_DURATION, null); + } + + // = + + /** + * 获取一个由完全显示变为不可见的透明度渐变动画 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个由完全显示变为不可见的透明度渐变动画 + */ + public static AlphaAnimation getHiddenAlphaAnimation( + final long durationMillis, + final AnimationListener animationListener + ) { + return getAlphaAnimation(1.0F, 0.0F, durationMillis, animationListener); + } + + /** + * 获取一个由完全显示变为不可见的透明度渐变动画 + * @param durationMillis 动画持续时间 + * @return 一个由完全显示变为不可见的透明度渐变动画 + */ + public static AlphaAnimation getHiddenAlphaAnimation(final long durationMillis) { + return getHiddenAlphaAnimation(durationMillis, null); + } + + /** + * 获取一个由完全显示变为不可见的透明度渐变动画 + * @param animationListener 动画监听器 + * @return 一个由完全显示变为不可见的透明度渐变动画 + */ + public static AlphaAnimation getHiddenAlphaAnimation(final AnimationListener animationListener) { + return getHiddenAlphaAnimation(DEFAULT_ANIMATION_DURATION, animationListener); + } + + /** + * 获取一个由完全显示变为不可见的透明度渐变动画 + * @return 一个由完全显示变为不可见的透明度渐变动画 + */ + public static AlphaAnimation getHiddenAlphaAnimation() { + return getHiddenAlphaAnimation(DEFAULT_ANIMATION_DURATION, null); + } + + // = + + /** + * 获取一个由不可见变为完全显示的透明度渐变动画 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个由不可见变为完全显示的透明度渐变动画 + */ + public static AlphaAnimation getShowAlphaAnimation( + final long durationMillis, + final AnimationListener animationListener + ) { + return getAlphaAnimation(0.0F, 1.0F, durationMillis, animationListener); + } + + /** + * 获取一个由不可见变为完全显示的透明度渐变动画 + * @param durationMillis 动画持续时间 + * @return 一个由不可见变为完全显示的透明度渐变动画 + */ + public static AlphaAnimation getShowAlphaAnimation(final long durationMillis) { + return getShowAlphaAnimation(durationMillis, null); + } + + /** + * 获取一个由不可见变为完全显示的透明度渐变动画 + * @param animationListener 动画监听器 + * @return 一个由不可见变为完全显示的透明度渐变动画 + */ + public static AlphaAnimation getShowAlphaAnimation(final AnimationListener animationListener) { + return getShowAlphaAnimation(DEFAULT_ANIMATION_DURATION, animationListener); + } + + /** + * 获取一个由不可见变为完全显示的透明度渐变动画 + * @return 一个由不可见变为完全显示的透明度渐变动画 + */ + public static AlphaAnimation getShowAlphaAnimation() { + return getShowAlphaAnimation(DEFAULT_ANIMATION_DURATION, null); + } + + // ============= + // = 视图缩放动画 = + // ============= + + /** + * 获取一个缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param pivotXType 缩放中心点的 X 坐标类型, 取值范围为 {@link Animation#ABSOLUTE}、 + * {@link Animation#RELATIVE_TO_SELF}、{@link Animation#RELATIVE_TO_PARENT} + * @param pivotXValue 缩放中心点的 X 坐标, 当 pivotXType == ABSOLUTE 时, 表示绝对位置, 否则表示相对位置, 1.0 表示 100% + * @param pivotYType 缩放中心点的 Y 坐标类型 + * @param pivotYValue 缩放中心点的 Y 坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个缩放动画 + */ + public static ScaleAnimation getScaleAnimation( + final float fromX, + final float toX, + final float fromY, + final float toY, + final int pivotXType, + final float pivotXValue, + final int pivotYType, + final float pivotYValue, + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation( + fromX, toX, fromY, toY, pivotXType, + pivotXValue, pivotYType, pivotYValue + ); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + /** + * 获取一个缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param pivotX 缩放中心点的 X 坐标 + * @param pivotY 缩放中心点的 Y 坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个缩放动画 + */ + public static ScaleAnimation getScaleAnimation( + final float fromX, + final float toX, + final float fromY, + final float toY, + final float pivotX, + final float pivotY, + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation( + fromX, toX, fromY, toY, pivotX, pivotY + ); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + /** + * 获取一个缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个缩放动画 + */ + public static ScaleAnimation getScaleAnimation( + final float fromX, + final float toX, + final float fromY, + final float toY, + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation(fromX, toX, fromY, toY); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + // = + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float toX, + final float fromY, + final float toY, + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation( + fromX, toX, fromY, toY, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F, + ScaleAnimation.RELATIVE_TO_SELF, 0.5F + ); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param durationMillis 动画持续时间 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float toX, + final float fromY, + final float toY, + final long durationMillis + ) { + return getScaleAnimationCenter( + fromX, toX, fromY, toY, durationMillis, null + ); + } + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param toX 动画结束后在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param toY 动画结束后在 Y 坐标 + * @param animationListener 动画监听器 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float toX, + final float fromY, + final float toY, + final AnimationListener animationListener + ) { + return getScaleAnimationCenter( + fromX, toX, fromY, toY, + DEFAULT_ANIMATION_DURATION, animationListener + ); + } + + // = + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float fromY, + final long durationMillis, + final AnimationListener animationListener + ) { + return getScaleAnimationCenter( + fromX, 1.0F, fromY, 1.0F, + durationMillis, animationListener + ); + } + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param durationMillis 动画持续时间 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float fromY, + final long durationMillis + ) { + return getScaleAnimationCenter(fromX, fromY, durationMillis, null); + } + + /** + * 获取一个中心点缩放动画 + * @param fromX 动画开始前在 X 坐标 + * @param fromY 动画开始前在 Y 坐标 + * @param animationListener 动画监听器 + * @return 一个中心点缩放动画 + */ + public static ScaleAnimation getScaleAnimationCenter( + final float fromX, + final float fromY, + final AnimationListener animationListener + ) { + return getScaleAnimationCenter(fromX, fromY, DEFAULT_ANIMATION_DURATION, animationListener); + } + + // = + + /** + * 获取一个缩小动画 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个缩小动画 + */ + public static ScaleAnimation getLessenScaleAnimation( + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation( + 1.0F, 0.0F, 1.0F, 0.0F, + ScaleAnimation.RELATIVE_TO_SELF, ScaleAnimation.RELATIVE_TO_SELF + ); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + /** + * 获取一个缩小动画 + * @param durationMillis 动画持续时间 + * @return 一个缩小动画 + */ + public static ScaleAnimation getLessenScaleAnimation(final long durationMillis) { + return getLessenScaleAnimation(durationMillis, null); + } + + /** + * 获取一个缩小动画 + * @param animationListener 动画监听器 + * @return 一个缩小动画 + */ + public static ScaleAnimation getLessenScaleAnimation(final AnimationListener animationListener) { + return getLessenScaleAnimation(DEFAULT_ANIMATION_DURATION, animationListener); + } + + // = + + /** + * 获取一个放大动画 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return 一个放大动画 + */ + public static ScaleAnimation getAmplificationAnimation( + final long durationMillis, + final AnimationListener animationListener + ) { + ScaleAnimation scaleAnimation = new ScaleAnimation( + 0.0F, 1.0F, 0.0F, 1.0F, + ScaleAnimation.RELATIVE_TO_SELF, ScaleAnimation.RELATIVE_TO_SELF + ); + scaleAnimation.setDuration(durationMillis); + if (animationListener != null) { + scaleAnimation.setAnimationListener(animationListener); + } + return scaleAnimation; + } + + /** + * 获取一个放大动画 + * @param durationMillis 动画持续时间 + * @return 一个放大动画 + */ + public static ScaleAnimation getAmplificationAnimation(final long durationMillis) { + return getAmplificationAnimation(durationMillis, null); + } + + /** + * 获取一个放大动画 + * @param animationListener 动画监听器 + * @return 一个放大动画 + */ + public static ScaleAnimation getAmplificationAnimation(final AnimationListener animationListener) { + return getAmplificationAnimation(DEFAULT_ANIMATION_DURATION, animationListener); + } + + // ============= + // = 视图移动动画 = + // ============= + + /** + * 获取一个视图移动动画 + * @param fromXType 动画开始前的 X 坐标类型, 取值范围为 {@link Animation#ABSOLUTE} 绝对位置、 + * {@link Animation#RELATIVE_TO_SELF} 以自身宽或高为参考、 + * {@link Animation#RELATIVE_TO_PARENT} 以父控件宽或高为参考 + * @param fromXValue 动画开始前的 X 坐标 + * @param toXType 动画结束后的 X 坐标类型 + * @param toXValue 动画结束后的 X 坐标 + * @param fromYType 动画开始前的 Y 坐标类型 + * @param fromYValue 动画开始前的 Y 坐标 + * @param toYType 动画结束后的 Y 坐标类型 + * @param toYValue 动画结束后的 Y 坐标 + * @param interpolator 动画周期 + * @param durationMillis 动画持续时间 + * @return 一个视图移动动画 + */ + public static TranslateAnimation getTranslateAnimation( + final int fromXType, + final float fromXValue, + final int toXType, + final float toXValue, + final int fromYType, + final float fromYValue, + final int toYType, + final float toYValue, + final Interpolator interpolator, + final long durationMillis + ) { + TranslateAnimation translateAnimation = new TranslateAnimation( + fromXType, fromXValue, toXType, toXValue, + fromYType, fromYValue, toYType, toYValue + ); + translateAnimation.setDuration(durationMillis); + if (interpolator != null) { + translateAnimation.setInterpolator(interpolator); + } + return translateAnimation; + } + + /** + * 获取一个视图移动动画 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param fromYDelta 动画开始的 Y 轴坐标 + * @param toYDelta 动画结束的 Y 轴坐标 + * @param interpolator 动画周期 + * @param durationMillis 动画持续时间 + * @return 一个视图移动动画 + */ + public static TranslateAnimation getTranslateAnimation( + final float fromXDelta, + final float toXDelta, + final float fromYDelta, + final float toYDelta, + final Interpolator interpolator, + final long durationMillis + ) { + TranslateAnimation translateAnimation = new TranslateAnimation( + fromXDelta, toXDelta, fromYDelta, toYDelta + ); + translateAnimation.setDuration(durationMillis); + if (interpolator != null) { + translateAnimation.setInterpolator(interpolator); + } + return translateAnimation; + } + + // = + + /** + * 获取一个视图摇晃动画 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @return 一个视图摇晃动画 + */ + public static TranslateAnimation getShakeAnimation( + final float fromXDelta, + final float toXDelta, + final float cycles, + final long durationMillis + ) { + Interpolator interpolator = (cycles > 0.0F) ? new CycleInterpolator(cycles) : null; + return getTranslateAnimation( + fromXDelta, toXDelta, 0.0F, 0.0F, + interpolator, durationMillis + ); + } + + /** + * 获取一个视图摇晃动画 ( 摇晃幅度为 10) + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @return 一个视图摇晃动画 + */ + public static TranslateAnimation getShakeAnimation( + final float cycles, + final long durationMillis + ) { + Interpolator interpolator = (cycles > 0.0F) ? new CycleInterpolator(cycles) : null; + return getTranslateAnimation( + 0.0F, 10.0F, 0.0F, 0.0F, + interpolator, durationMillis + ); + } + + /** + * 获取一个视图摇晃动画 ( 摇晃幅度为 10、持续 700 毫秒 ) + * @param cycles 动画周期 {@link CycleInterpolator} + * @return 一个视图摇晃动画 + */ + public static TranslateAnimation getShakeAnimation(final float cycles) { + Interpolator interpolator = (cycles > 0.0F) ? new CycleInterpolator(cycles) : null; + return getTranslateAnimation( + 0.0F, 10.0F, 0.0F, 0.0F, + interpolator, 700 + ); + } + + /** + * 获取一个视图摇晃动画 ( 摇晃幅度为 10、重复 7 次 ) + * @param durationMillis 动画持续时间 + * @return 一个视图摇晃动画 + */ + public static TranslateAnimation getShakeAnimation(final long durationMillis) { + return getTranslateAnimation( + 0.0F, 10.0F, 0.0F, 0.0F, + new CycleInterpolator(7), durationMillis + ); + } + + /** + * 获取一个视图摇晃动画 ( 摇晃幅度为 10、重复 7 次、持续 700 毫秒 ) + * @return 一个视图摇晃动画 + */ + public static TranslateAnimation getShakeAnimation() { + return getTranslateAnimation( + 0.0F, 10.0F, 0.0F, 0.0F, + new CycleInterpolator(7), 700 + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java b/lib/DevApp/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java new file mode 100644 index 0000000000..be52c3e4bd --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/anim/ViewAnimationUtils.java @@ -0,0 +1,800 @@ +package dev.utils.app.anim; + +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.CycleInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.TranslateAnimation; + +/** + * detail: View 动画工具类 + * @author Ttt + *
+ *     AnimationUtils 基础上封装, 提供简单的控制视图的动画的工具方法
+ * 
+ */ +public final class ViewAnimationUtils { + + private ViewAnimationUtils() { + } + + // ================== + // = 视图透明度渐变动画 = + // ================== + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick, + final AnimationListener animationListener + ) { + if (view != null && view.getVisibility() != View.INVISIBLE) { + view.setVisibility(View.INVISIBLE); + // 获取动画 + AlphaAnimation animation = AnimationUtils.getHiddenAlphaAnimation(durationMillis); + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (isBanClick) { + view.setClickable(false); + } + if (animationListener != null) { + animationListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + if (animationListener != null) { + animationListener.onAnimationRepeat(animation); + } + } + + @Override + public void onAnimationEnd(Animation animation) { + if (isBanClick) { + view.setClickable(true); + } + if (animationListener != null) { + animationListener.onAnimationEnd(animation); + } + } + }); + view.startAnimation(animation); + return true; + } + return false; + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final long durationMillis, + final AnimationListener animationListener + ) { + return invisibleViewByAlpha(view, durationMillis, false, animationListener); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick + ) { + return invisibleViewByAlpha(view, durationMillis, isBanClick, null); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final long durationMillis + ) { + return invisibleViewByAlpha(view, durationMillis, false, null); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final boolean isBanClick, + final AnimationListener animationListener + ) { + return invisibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, animationListener + ); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final AnimationListener animationListener + ) { + return invisibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, animationListener + ); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha( + final View view, + final boolean isBanClick + ) { + return invisibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, null + ); + } + + /** + * 将给定视图渐渐隐去 + * @param view 待处理的视图 + * @return {@code true} success, {@code false} fail + */ + public static boolean invisibleViewByAlpha(final View view) { + return invisibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, null + ); + } + + // = + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick, + final AnimationListener animationListener + ) { + if (view != null && view.getVisibility() != View.GONE) { + view.setVisibility(View.GONE); + // 获取动画 + AlphaAnimation animation = AnimationUtils.getHiddenAlphaAnimation(durationMillis); + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (isBanClick) { + view.setClickable(false); + } + if (animationListener != null) { + animationListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + if (animationListener != null) { + animationListener.onAnimationRepeat(animation); + } + } + + @Override + public void onAnimationEnd(Animation animation) { + if (isBanClick) { + view.setClickable(true); + } + if (animationListener != null) { + animationListener.onAnimationEnd(animation); + } + } + }); + view.startAnimation(animation); + return true; + } + return false; + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final long durationMillis, + final AnimationListener animationListener + ) { + return goneViewByAlpha(view, durationMillis, false, animationListener); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick + ) { + return goneViewByAlpha(view, durationMillis, isBanClick, null); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final long durationMillis + ) { + return goneViewByAlpha(view, durationMillis, false, null); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final boolean isBanClick, + final AnimationListener animationListener + ) { + return goneViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, animationListener + ); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final AnimationListener animationListener + ) { + return goneViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, animationListener + ); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha( + final View view, + final boolean isBanClick + ) { + return goneViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, null + ); + } + + /** + * 将给定视图渐渐隐去最后从界面中移除 + * @param view 待处理的视图 + * @return {@code true} success, {@code false} fail + */ + public static boolean goneViewByAlpha(final View view) { + return goneViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, null + ); + } + + // = + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick, + final AnimationListener animationListener + ) { + if (view != null && view.getVisibility() != View.VISIBLE) { + view.setVisibility(View.VISIBLE); + // 获取动画 + AlphaAnimation animation = AnimationUtils.getShowAlphaAnimation(durationMillis); + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (isBanClick) { + view.setClickable(false); + } + if (animationListener != null) { + animationListener.onAnimationStart(animation); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + if (animationListener != null) { + animationListener.onAnimationRepeat(animation); + } + } + + @Override + public void onAnimationEnd(Animation animation) { + if (isBanClick) { + view.setClickable(true); + } + if (animationListener != null) { + animationListener.onAnimationEnd(animation); + } + } + }); + view.startAnimation(animation); + return true; + } + return false; + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final long durationMillis, + final AnimationListener animationListener + ) { + return visibleViewByAlpha(view, durationMillis, false, animationListener); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final long durationMillis, + final boolean isBanClick + ) { + return visibleViewByAlpha(view, durationMillis, isBanClick, null); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final long durationMillis + ) { + return visibleViewByAlpha(view, durationMillis, false, null); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final boolean isBanClick, + final AnimationListener animationListener + ) { + return visibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, animationListener + ); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param animationListener 动画监听器 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final AnimationListener animationListener + ) { + return visibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, animationListener + ); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha( + final View view, + final boolean isBanClick + ) { + return visibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + isBanClick, null + ); + } + + /** + * 将给定视图渐渐显示出来 + * @param view 待处理的视图 + * @return {@code true} success, {@code false} fail + */ + public static boolean visibleViewByAlpha(final View view) { + return visibleViewByAlpha( + view, AnimationUtils.DEFAULT_ANIMATION_DURATION, + false, null + ); + } + + // ============= + // = 视图移动动画 = + // ============= + + /** + * 视图移动 + * @param view 待移动的视图 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param fromYDelta 动画开始的 Y 轴坐标 + * @param toYDelta 动画结束的 Y 轴坐标 + * @param interpolator 动画周期 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean translate( + final View view, + final float fromXDelta, + final float toXDelta, + final float fromYDelta, + final float toYDelta, + final Interpolator interpolator, + final long durationMillis, + final boolean isBanClick + ) { + if (view != null) { + TranslateAnimation animation = AnimationUtils.getTranslateAnimation( + fromXDelta, toXDelta, fromYDelta, toYDelta, + interpolator, durationMillis + ); + animation.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (isBanClick) { + view.setClickable(false); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + if (isBanClick) { + view.setClickable(true); + } + } + }); + view.startAnimation(animation); + return true; + } + return false; + } + + /** + * 视图移动 + * @param view 待移动的视图 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param fromYDelta 动画开始的 Y 轴坐标 + * @param toYDelta 动画结束的 Y 轴坐标 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean translate( + final View view, + final float fromXDelta, + final float toXDelta, + final float fromYDelta, + final float toYDelta, + final float cycles, + final long durationMillis, + final boolean isBanClick + ) { + if (view != null) { + Interpolator interpolator = (cycles > 0.0F) ? new CycleInterpolator(cycles) : null; + return translate( + view, fromXDelta, toXDelta, fromYDelta, toYDelta, + interpolator, durationMillis, isBanClick + ); + } + return false; + } + + /** + * 视图移动 + * @param view 待移动的视图 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param fromYDelta 动画开始的 Y 轴坐标 + * @param toYDelta 动画结束的 Y 轴坐标 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean translate( + final View view, + final float fromXDelta, + final float toXDelta, + final float fromYDelta, + final float toYDelta, + final float cycles, + final long durationMillis + ) { + return translate( + view, fromXDelta, toXDelta, fromYDelta, toYDelta, + cycles, durationMillis, false + ); + } + + // = + + /** + * 视图摇晃 + * @param view 待摇晃的视图 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float fromXDelta, + final float toXDelta, + final float cycles, + final long durationMillis, + final boolean isBanClick + ) { + return translate( + view, fromXDelta, toXDelta, 0.0F, 0.0F, + cycles, durationMillis, isBanClick + ); + } + + /** + * 视图摇晃 + * @param view 待摇晃的视图 + * @param fromXDelta 动画开始的 X 轴坐标 + * @param toXDelta 动画结束的 X 轴坐标 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float fromXDelta, + final float toXDelta, + final float cycles, + final long durationMillis + ) { + return translate( + view, fromXDelta, toXDelta, 0.0F, 0.0F, + cycles, durationMillis, false + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、重复 7 次 ) + * @param view 待摇晃的视图 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float cycles, + final long durationMillis, + final boolean isBanClick + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + cycles, durationMillis, isBanClick + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、持续 700 毫秒 ) + * @param view 待摇晃的视图 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float cycles, + final boolean isBanClick + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + cycles, 700, isBanClick + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10) + * @param view 待摇晃的视图 + * @param cycles 动画周期 {@link CycleInterpolator} + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float cycles, + final long durationMillis + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + cycles, durationMillis, false + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、重复 7 次 ) + * @param view 待摇晃的视图 + * @param durationMillis 动画持续时间 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final long durationMillis, + final boolean isBanClick + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + 7, durationMillis, isBanClick + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、持续 700 毫秒 ) + * @param view 待摇晃的视图 + * @param cycles 动画周期 {@link CycleInterpolator} + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final float cycles + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + cycles, 700, false + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、重复 7 次 ) + * @param view 待摇晃的视图 + * @param durationMillis 动画持续时间 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final long durationMillis + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + 7, durationMillis, false + ); + } + + // = + + /** + * 视图摇晃 ( 摇晃幅度为 10、重复 7 次、持续 700 毫秒 ) + * @param view 待摇晃的视图 + * @param isBanClick 在执行动画的过程中是否禁止点击 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake( + final View view, + final boolean isBanClick + ) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + 7, 700, isBanClick + ); + } + + /** + * 视图摇晃 ( 摇晃幅度为 10、重复 7 次、持续 700 毫秒 ) + * @param view 待摇晃的视图 + * @return {@code true} success, {@code false} fail + */ + public static boolean shake(final View view) { + return translate( + view, 0.0F, 10.0F, 0.0F, 0.0F, + 7, 700, false + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/ActivityManagerAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/ActivityManagerAssist.java new file mode 100644 index 0000000000..982c5bf4ef --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/ActivityManagerAssist.java @@ -0,0 +1,424 @@ +package dev.utils.app.assist; + +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; + +import java.util.Iterator; +import java.util.Stack; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; + +/** + * detail: Activity 栈管理辅助类 + * @author Ttt + */ +public final class ActivityManagerAssist { + + // 日志 TAG + private final String TAG = ActivityManagerAssist.class.getSimpleName(); + + // ================= + // = Activity 栈处理 = + // ================= + + // Activity 栈 ( 后进先出 ) + private final Stack mActivityStacks = new Stack<>(); + + /** + * 获取 Activity 栈 + * @return {@link Stack} + */ + public Stack getActivityStacks() { + return mActivityStacks; + } + + /** + * 添加 Activity + * @param activity {@link Activity} + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist addActivity(final Activity activity) { + if (activity != null) { + synchronized (mActivityStacks) { + if (mActivityStacks.contains(activity)) { + return this; + } + mActivityStacks.add(activity); + } + } + return this; + } + + /** + * 移除 Activity + * @param activity {@link Activity} + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist removeActivity(final Activity activity) { + if (activity != null) { + synchronized (mActivityStacks) { + int index = mActivityStacks.indexOf(activity); + if (index == -1) { + return this; + } + try { + mActivityStacks.remove(index); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeActivity"); + } + } + } + return this; + } + + /** + * 移除多个 Activity + * @param activitys Activity[] + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist removeActivity(final Activity... activitys) { + if (activitys != null && activitys.length != 0) { + for (Activity activity : activitys) { + removeActivity(activity); + } + } + return this; + } + + /** + * 获取最后一个 ( 当前 ) Activity + * @return 最后一个 ( 当前 ) {@link Activity} + */ + public Activity currentActivity() { + return mActivityStacks.lastElement(); + } + + /** + * 关闭最后一个 ( 当前 ) Activity + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishActivity() { + return finishActivity(mActivityStacks.lastElement()); + } + + /** + * 检测是否包含指定的 Activity + * @param clazzs Class(Activity)[] + * @return {@code true} yes, {@code false} no + */ + public boolean existActivitys(final Class... clazzs) { + if (clazzs != null && clazzs.length != 0) { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + try { + // 进行遍历判断 + for (Activity activity : stack) { + if (activity != null && !activity.isFinishing()) { + for (Class clazz : clazzs) { + if (clazz != null && clazz.getName().equals( + activity.getClass().getName()) + ) { + return true; + } + } + } + } + } finally { + // 移除数据, 并且清空内存 + stack.clear(); + } + } + } + return false; + } + + /** + * 关闭指定 Activity + * @param activity {@link Activity} + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishActivity(final Activity activity) { + // 先移除 Activity + removeActivity(activity); + // Activity 不为 null, 并且属于未销毁状态 + if (activity != null && !activity.isFinishing()) { + activity.finish(); + } + return this; + } + + /** + * 关闭多个 Activity + * @param activitys Activity[] + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishActivity(final Activity... activitys) { + if (activitys != null && activitys.length != 0) { + for (Activity activity : activitys) { + finishActivity(activity); + } + } + return this; + } + + /** + * 关闭指定类名 Activity + * @param clazz Activity.class + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishActivity(final Class clazz) { + if (clazz != null) { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + // 清空全部, 便于后续操作处理 + mActivityStacks.clear(); + // 进行遍历移除 + Iterator iterator = stack.iterator(); + while (iterator.hasNext()) { + Activity activity = iterator.next(); + // 判断是否想要关闭的 Activity + if (activity != null) { + if (activity.getClass() == clazz) { + // 如果 Activity 没有 finish 则进行 finish + if (!activity.isFinishing()) { + activity.finish(); + } + // 删除对应的 Item + iterator.remove(); + } + } else { + // 删除对应的 Item + iterator.remove(); + } + } + // 把不符合条件的保存回去 + mActivityStacks.addAll(stack); + // 移除数据, 并且清空内存 + stack.clear(); + } + } + return this; + } + + /** + * 结束多个类名 Activity + * @param clazzs Class(Activity)[] + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishActivity(final Class... clazzs) { + if (clazzs != null && clazzs.length != 0) { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + // 清空全部, 便于后续操作处理 + mActivityStacks.clear(); + // 判断是否销毁 + boolean isRemove; + // 进行遍历移除 + Iterator iterator = stack.iterator(); + while (iterator.hasNext()) { + Activity activity = iterator.next(); + // 判断是否想要关闭的 Activity + if (activity != null) { + // 默认不需要销毁 + isRemove = false; + // 循环判断 + for (Class clazz : clazzs) { + // 判断是否相同 + if (activity.getClass() == clazz) { + isRemove = true; + break; + } + } + // 判断是否销毁 + if (isRemove) { + // 如果 Activity 没有 finish 则进行 finish + if (!activity.isFinishing()) { + activity.finish(); + } + // 删除对应的 Item + iterator.remove(); + } + } else { + // 删除对应的 Item + iterator.remove(); + } + } + // 把不符合条件的保存回去 + mActivityStacks.addAll(stack); + // 移除数据, 并且清空内存 + stack.clear(); + } + } + return this; + } + + /** + * 结束全部 Activity 除忽略的 Activity 外 + * @param clazz Activity.class + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishAllActivityToIgnore(final Class clazz) { + if (clazz != null) { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + // 清空全部, 便于后续操作处理 + mActivityStacks.clear(); + // 进行遍历移除 + Iterator iterator = stack.iterator(); + while (iterator.hasNext()) { + Activity activity = iterator.next(); + // 判断是否想要关闭的 Activity + if (activity != null) { + if (!(activity.getClass() == clazz)) { + // 如果 Activity 没有 finish 则进行 finish + if (!activity.isFinishing()) { + activity.finish(); + } + // 删除对应的 Item + iterator.remove(); + } + } else { + // 删除对应的 Item + iterator.remove(); + } + } + // 把不符合条件的保存回去 + mActivityStacks.addAll(stack); + // 移除数据, 并且清空内存 + stack.clear(); + } + } + return this; + } + + /** + * 结束全部 Activity 除忽略的 Activity 外 + * @param clazzs Class(Activity)[] + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishAllActivityToIgnore(final Class... clazzs) { + if (clazzs != null && clazzs.length != 0) { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + // 清空全部, 便于后续操作处理 + mActivityStacks.clear(); + // 判断是否销毁 + boolean isRemove; + // 进行遍历移除 + Iterator iterator = stack.iterator(); + while (iterator.hasNext()) { + Activity activity = iterator.next(); + // 判断是否想要关闭的 Activity + if (activity != null) { + // 默认需要销毁 + isRemove = true; + // 循环判断 + for (Class clazz : clazzs) { + // 判断是否相同 + if (activity.getClass() == clazz) { + isRemove = false; + break; + } + } + // 判断是否销毁 + if (isRemove) { + // 如果 Activity 没有 finish 则进行 finish + if (!activity.isFinishing()) { + activity.finish(); + } + // 删除对应的 Item + iterator.remove(); + } + } else { + // 删除对应的 Item + iterator.remove(); + } + } + // 把不符合条件的保存回去 + mActivityStacks.addAll(stack); + // 移除数据, 并且清空内存 + stack.clear(); + } + } + return this; + } + + /** + * 结束所有 Activity + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist finishAllActivity() { + synchronized (mActivityStacks) { + // 保存新的堆栈, 防止出现同步问题 + Stack stack = new Stack<>(); + stack.addAll(mActivityStacks); + // 清空全部, 便于后续操作处理 + mActivityStacks.clear(); + // 进行遍历移除 + Iterator iterator = stack.iterator(); + while (iterator.hasNext()) { + Activity activity = iterator.next(); + if (activity != null && !activity.isFinishing()) { + activity.finish(); + // 删除对应的 Item + iterator.remove(); + } + } + // 移除数据, 并且清空内存 + stack.clear(); + } + return this; + } + + // = + + /** + * 退出应用程序 + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist exitApplication() { + try { + finishAllActivity(); + // 退出 JVM (Java 虚拟机 ) 释放所占内存资源, 0 表示正常退出、非 0 的都为异常退出 + System.exit(0); + // 从操作系统中结束掉当前程序的进程 + android.os.Process.killProcess(android.os.Process.myPid()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "exitApplication"); + // = + System.exit(-1); + } + return this; + } + + /** + * 重启 APP + * @return {@link ActivityManagerAssist} + */ + public ActivityManagerAssist restartApplication() { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return this; + try { + Intent intent = packageManager.getLaunchIntentForPackage( + AppUtils.getPackageName() + ); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + AppUtils.startActivity(intent); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "restartApplication"); + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java new file mode 100644 index 0000000000..fc6ca3a608 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/BeepVibrateAssist.java @@ -0,0 +1,313 @@ +package dev.utils.app.assist; + +import android.Manifest; +import android.app.Activity; +import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; +import android.media.MediaPlayer; + +import androidx.annotation.RawRes; +import androidx.annotation.RequiresPermission; + +import java.io.Closeable; +import java.lang.ref.WeakReference; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.app.VibrationUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: 播放「bee」的声音, 并且震动辅助类 + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public final class BeepVibrateAssist + implements Closeable { + + // 日志 TAG + private static final String TAG = BeepVibrateAssist.class.getSimpleName(); + + // Activity + private final WeakReference mActivity; + // 播放资源对象 + private MediaPlayer mMediaPlayer = null; + // 是否需要震动 + private boolean mVibrate = true; + // 震动时间 + private long mVibrateDuration = 200L; + + /** + * 构造函数 + * @param activity {@link Activity} + */ + public BeepVibrateAssist(final Activity activity) { + this.mActivity = new WeakReference<>(activity); + } + + /** + * 构造函数 + * @param activity {@link Activity} + * @param rawId R.raw.id + */ + public BeepVibrateAssist( + final Activity activity, + @RawRes final int rawId + ) { + this.mActivity = new WeakReference<>(activity); + this.mMediaPlayer = buildMediaPlayer(rawId); + } + + /** + * 构造函数 + * @param activity {@link Activity} + * @param filePath 文件路径 + */ + public BeepVibrateAssist( + final Activity activity, + final String filePath + ) { + this.mActivity = new WeakReference<>(activity); + this.mMediaPlayer = buildMediaPlayer(filePath); + } + + // ============= + // = 内部判断方法 = + // ============= + + /** + * 判断是否允许播放声音 + *
+     *     RINGER_MODE_NORMAL( 普通 )、RINGER_MODE_SILENT( 静音 )、RINGER_MODE_VIBRATE( 震动 )
+     * 
+ * @return {@code true} 允许, {@code false} 不允许 + */ + private boolean shouldBeep() { + AudioManager audioManager = AppUtils.getAudioManager(); + // 只有属于普通模式才播放 + return (audioManager != null && audioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL); + } + + /** + * 更新播放流处理 + */ + private synchronized void streamUpdate() { + if (shouldBeep() && mMediaPlayer != null) { + try { + // The volume on STREAM_SYSTEM is not adjustable, and users found it too loud, + // so we now play on the music stream. + mActivity.get().setVolumeControlStream(AudioManager.STREAM_MUSIC); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "streamUpdate"); + } + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断是否允许播放声音 + * @return {@code true} 允许, {@code false} 不允许 + */ + public boolean isPlayBeep() { + return shouldBeep(); + } + + /** + * 判断是否允许震动 + * @return {@code true} 允许, {@code false} 不允许 + */ + public boolean isVibrate() { + return mVibrate; + } + + /** + * 设置是否允许震动 + * @param vibrate 是否允许震动 + * @return {@link BeepVibrateAssist} + */ + public BeepVibrateAssist setVibrate(final boolean vibrate) { + return setVibrate(vibrate, 200L); + } + + /** + * 设置是否允许震动 + * @param vibrate 是否允许震动 + * @param vibrateDuration 震动时间 ( 毫秒 ) + * @return {@link BeepVibrateAssist} + */ + public BeepVibrateAssist setVibrate( + final boolean vibrate, + final long vibrateDuration + ) { + this.mVibrate = vibrate; + this.mVibrateDuration = vibrateDuration; + return this; + } + + /** + * 设置播放资源对象 + * @param mediaPlayer {@link MediaPlayer} + * @return {@link BeepVibrateAssist} + */ + public BeepVibrateAssist setMediaPlayer(final MediaPlayer mediaPlayer) { + this.mMediaPlayer = mediaPlayer; + // 更新播放流处理 + streamUpdate(); + return this; + } + + /** + * 进行播放声音, 并且震动 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.VIBRATE) + public synchronized boolean playBeepSoundAndVibrate() { + // 判断是否允许播放 + if (shouldBeep() && mMediaPlayer != null) { + // 判断是否允许震动 + if (mVibrate) { + VibrationUtils.vibrate(mVibrateDuration); + } + try { + // 播放 + mMediaPlayer.start(); + return true; + } catch (Exception ignored) { + } + } + return false; + } + + /** + * 关闭震动、提示声, 并释放资源 + */ + @Override + public synchronized void close() { + if (mMediaPlayer != null) { + mMediaPlayer.release(); + mMediaPlayer = null; + } + } + + // ======================= + // = 创建 MediaPlayer 处理 = + // ======================= + + /** + * 创建 MediaPlayer 对象 + * @param rawId R.raw.id + * @return {@link MediaPlayer} + */ + public static MediaPlayer buildMediaPlayer(@RawRes final int rawId) { + return buildMediaPlayer(rawId, 0.1F); + } + + /** + * 创建 MediaPlayer 对象 + * @param rawId R.raw.id + * @param beepVolume 音量 + * @return {@link MediaPlayer} + */ + public static MediaPlayer buildMediaPlayer( + @RawRes final int rawId, + final float beepVolume + ) { + final MediaPlayer mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setOnCompletionListener( + mp -> LogPrintUtils.dTag(TAG, "buildMediaPlayer - onCompletion") + ); + mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public synchronized boolean onError( + MediaPlayer mp, + int what, + int extra + ) { + LogPrintUtils.dTag( + TAG, "buildMediaPlayer onError what: %s, extra: %s", + what, extra + ); + // 播放异常, 直接不处理 + return true; + } + }); + try { + AssetFileDescriptor afd = ResourceUtils.openRawResourceFd(rawId); + try { + mediaPlayer.setDataSource( + afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength() + ); + } finally { + CloseUtils.closeIOQuietly(afd); + } + mediaPlayer.setVolume(beepVolume, beepVolume); + mediaPlayer.prepare(); + return mediaPlayer; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "buildMediaPlayer"); + mediaPlayer.release(); + return null; + } + } + + // = + + /** + * 创建 MediaPlayer 对象 + * @param filePath 文件路径 + * @return {@link MediaPlayer} + */ + public static MediaPlayer buildMediaPlayer(final String filePath) { + return buildMediaPlayer(filePath, 0.1F); + } + + /** + * 创建 MediaPlayer 对象 + * @param filePath 文件路径 + * @param beepVolume 音量 + * @return {@link MediaPlayer} + */ + public static MediaPlayer buildMediaPlayer( + final String filePath, + final float beepVolume + ) { + final MediaPlayer mediaPlayer = new MediaPlayer(); + mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mediaPlayer.setOnCompletionListener( + mp -> LogPrintUtils.dTag(TAG, "buildMediaPlayer - onCompletion") + ); + mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public synchronized boolean onError( + MediaPlayer mp, + int what, + int extra + ) { + LogPrintUtils.dTag( + TAG, "buildMediaPlayer onError what: %s, extra: %s", + what, extra + ); + // 播放异常, 直接不处理 + return true; + } + }); + try { + mediaPlayer.setDataSource(filePath); + mediaPlayer.setVolume(beepVolume, beepVolume); + mediaPlayer.prepare(); + return mediaPlayer; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "buildMediaPlayer"); + mediaPlayer.release(); + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/DelayAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/DelayAssist.java new file mode 100644 index 0000000000..cbe7946d66 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/DelayAssist.java @@ -0,0 +1,117 @@ +package dev.utils.app.assist; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +/** + * detail: 延迟触发辅助类 + * @author Ttt + */ +public class DelayAssist { + + // 默认延迟时间 + public static final long DELAY_MILLIS = 300L; + + // 主线程 Handler + private final Handler mMainHandler; + // 延迟时间 + private long mDelayMillis = DELAY_MILLIS; + // 搜索回调 + private Callback mCallback; + + // ========== + // = 构造函数 = + // ========== + + public DelayAssist() { + this(DELAY_MILLIS, null); + } + + public DelayAssist(long delayMillis) { + this(delayMillis, null); + } + + public DelayAssist(Callback callback) { + this(DELAY_MILLIS, callback); + } + + public DelayAssist( + long delayMillis, + Callback callback + ) { + setDelayMillis(delayMillis); + mCallback = callback; + mMainHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (mCallback != null) mCallback.callback(msg.obj); + } + }; + } + + /** + * detail: 回调接口 + * @author Ttt + */ + public interface Callback { + + /** + * 回调方法 + * @param object Object + */ + void callback(Object object); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 移除消息 + */ + public void remove() { + mMainHandler.removeCallbacksAndMessages(null); + } + + /** + * 发送消息 ( 功能由该方法实现 ) + */ + public void post() { + post(null); + } + + /** + * 发送消息 ( 功能由该方法实现 ) + * @param object Object + */ + public void post(final Object object) { + mMainHandler.removeCallbacksAndMessages(null); + Message message = mMainHandler.obtainMessage(); + message.obj = object; + mMainHandler.sendMessageDelayed(message, mDelayMillis); + } + + // = + + /** + * 设置搜索延迟时间 + * @param delayMillis 延迟时间 + * @return {@link DelayAssist} + */ + public DelayAssist setDelayMillis(final long delayMillis) { + mDelayMillis = (delayMillis > 0) ? delayMillis : DELAY_MILLIS; + return this; + } + + /** + * 设置搜索回调接口 + * @param callback {@link Callback} + * @return {@link DelayAssist} + */ + public DelayAssist setCallback(final Callback callback) { + mCallback = callback; + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java new file mode 100644 index 0000000000..68cc4cbf15 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/InactivityTimerAssist.java @@ -0,0 +1,183 @@ +package dev.utils.app.assist; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.os.BatteryManager; + +import java.lang.ref.WeakReference; + +/** + * detail: Activity 无操作定时辅助类 + * @author Ttt + *
+ *     需要在对应的生命周期内, 调用对应的 onPause/onResume/onDestroy 方法
+ * 
+ */ +public final class InactivityTimerAssist { + + // 无操作时间 ( 到时间自动关闭, 默认五分钟 ) + private final long mInactivityTime; + // 对应的页面 + private final WeakReference mActivity; + // 电池广播 ( 充电中, 则不处理, 主要是为了省电 ) + private final BroadcastReceiver mPowerStateReceiver; + // 检查任务 + private AsyncTask mInactivityTask; + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param activity {@link Activity} + */ + public InactivityTimerAssist(final Activity activity) { + this(activity, 300000L); // 5 * 60 * 1000L + } + + /** + * 构造函数 + * @param activity {@link Activity} + * @param inactivityTime 无操作时间间隔 ( 毫秒 ) + */ + public InactivityTimerAssist( + final Activity activity, + final long inactivityTime + ) { + this.mActivity = new WeakReference<>(activity); + this.mInactivityTime = inactivityTime; + // 电池广播监听 + mPowerStateReceiver = new PowerStateReceiver(); + // 关闭任务 + cancel(); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 暂停检测 + *
+     *     Activity 生命周期 onPause 调用
+     * 
+ */ + public synchronized void onPause() { + // 取消任务 + cancel(); + try { + // 取消注册广播 + mActivity.get().unregisterReceiver(mPowerStateReceiver); + } catch (Exception ignored) { + } + } + + /** + * 回到 Activity 处理 + *
+     *     Activity 生命周期 onResume 调用
+     * 
+ */ + public synchronized void onResume() { + try { + // 注册广播 + mActivity.get().registerReceiver( + mPowerStateReceiver, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ); + } catch (Exception ignored) { + } + // 开始检测 + start(); + } + + /** + * Activity 销毁处理 + *
+     *     Activity 生命周期 onDestroy 调用
+     * 
+ */ + public synchronized void onDestroy() { + cancel(); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 开始计时任务 + */ + private synchronized void start() { + // 取消任务 + cancel(); + // 注册任务 + mInactivityTask = new InactivityAsyncTask(); + // 开启任务 + mInactivityTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + /** + * 取消计时任务 + */ + private synchronized void cancel() { + AsyncTask task = mInactivityTask; + if (task != null) { + task.cancel(true); + // 重置为 null + mInactivityTask = null; + } + } + + // = + + /** + * detail: 电池监听广播 + * @author Ttt + */ + private class PowerStateReceiver + extends BroadcastReceiver { + @Override + public void onReceive( + Context context, + Intent intent + ) { + if (intent != null && Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + // 0 indicates that we're on battery + boolean isBatteryNow = intent.getIntExtra( + BatteryManager.EXTRA_PLUGGED, -1 + ) <= 0; + if (isBatteryNow) { // 属于非充电才进行记时 + InactivityTimerAssist.this.start(); + } else { // 充电中, 则不处理 + InactivityTimerAssist.this.cancel(); + } + } + } + } + + /** + * detail: 定时检测任务 ( 无操作检测 ) + * @author Ttt + */ + private class InactivityAsyncTask + extends AsyncTask { + @Override + protected Object doInBackground(Object... objects) { + try { + Thread.sleep(mInactivityTime); + // 关闭页面 + if (mActivity.get() != null) { + mActivity.get().finish(); + } + } catch (Exception ignored) { + } + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/ResourceAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/ResourceAssist.java new file mode 100644 index 0000000000..6e39efad90 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/ResourceAssist.java @@ -0,0 +1,1755 @@ +package dev.utils.app.assist; + +import android.annotation.SuppressLint; +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Menu; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; + +import androidx.annotation.AnimRes; +import androidx.annotation.AnimatorRes; +import androidx.annotation.AnyRes; +import androidx.annotation.ArrayRes; +import androidx.annotation.BoolRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.RawRes; +import androidx.annotation.StringRes; +import androidx.core.content.ContextCompat; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileIOUtils; + +/** + * detail: Resources 辅助类 + * @author Ttt + *
+ *     整合 DevApp Utils 代码, 最终统一通过该辅助工具类进行 Resources 获取
+ *     方便对 Resources 进行适配、统一控制、代码复用等
+ * 
+ */ +public final class ResourceAssist { + + // ResourceAssist 实例 + private static volatile ResourceAssist sInstance; + + /** + * 获取 ResourceAssist 实例 + * @return {@link ResourceAssist} + */ + private static ResourceAssist getInstance() { + if (sInstance == null) { + synchronized (ResourceAssist.class) { + if (sInstance == null) { + sInstance = new ResourceAssist(); + } + } + } + return sInstance; + } + + // 日志 TAG + private static final String TAG = ResourceAssist.class.getSimpleName(); + + // 空实现 ResourceAssist ( 用于 ResourcePluginUtils 简化判断代码 ) + public static final ResourceAssist EMPTY_IMPL = new ResourceAssist(null, null); + + // Resources + private Resources mResource; + // 应用包名 + private String mPackageName; + + // ========== + // = 构造函数 = + // ========== + + private ResourceAssist() { + this(staticResources(), AppUtils.getPackageName()); + } + + private ResourceAssist(final Context context) { + this(staticResources(context), AppUtils.getPackageName()); + } + + private ResourceAssist(final Resources resource) { + this(resource, AppUtils.getPackageName()); + } + + private ResourceAssist( + final Resources resource, + final String packageName + ) { + this.mResource = resource; + this.mPackageName = packageName; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取 ResourceAssist + * @return {@link ResourceAssist} + */ + public static ResourceAssist get() { + return getInstance(); + } + + /** + * 获取 ResourceAssist + * @param context {@link Context} + * @return {@link ResourceAssist} + */ + public static ResourceAssist get(final Context context) { + return new ResourceAssist(context); + } + + /** + * 获取 ResourceAssist + * @param resource {@link Resources} + * @return {@link ResourceAssist} + */ + public static ResourceAssist get(final Resources resource) { + return new ResourceAssist(resource); + } + + /** + * 获取 ResourceAssist + * @param resource {@link Resources} + * @param packageName 应用包名 + * @return {@link ResourceAssist} + */ + public static ResourceAssist get( + final Resources resource, + final String packageName + ) { + return new ResourceAssist(resource, packageName); + } + + // ========== + // = 静态方法 = + // ========== + + // ============= + // = Resources = + // ============= + + /** + * 获取 Resources + * @return {@link Resources} + */ + public static Resources staticResources() { + return staticResources(DevUtils.getContext()); + } + + /** + * 获取 Resources + * @param context {@link Context} + * @return {@link Resources} + */ + public static Resources staticResources(final Context context) { + if (context == null) return null; + try { + return context.getResources(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticResources"); + } + return null; + } + + // =================== + // = Resources.Theme = + // =================== + + /** + * 获取 Resources.Theme + * @return {@link Resources.Theme} + */ + public static Resources.Theme staticTheme() { + return staticTheme(DevUtils.getContext()); + } + + /** + * 获取 Resources.Theme + * @param context {@link Context} + * @return {@link Resources.Theme} + */ + public static Resources.Theme staticTheme(final Context context) { + if (context == null) return null; + try { + return context.getTheme(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticTheme"); + } + return null; + } + + // =================== + // = ContentResolver = + // =================== + + /** + * 获取 ContentResolver + * @return {@link ContentResolver} + */ + public static ContentResolver staticContentResolver() { + return staticContentResolver(DevUtils.getContext()); + } + + /** + * 获取 ContentResolver + * @param context {@link Context} + * @return {@link ContentResolver} + */ + public static ContentResolver staticContentResolver(final Context context) { + if (context == null) return null; + try { + return context.getContentResolver(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticContentResolver"); + } + return null; + } + + // ================== + // = DisplayMetrics = + // ================== + + /** + * 获取 DisplayMetrics + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics staticDisplayMetrics() { + return staticDisplayMetrics(staticResources()); + } + + /** + * 获取 DisplayMetrics + * @param context {@link Context} + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics staticDisplayMetrics(final Context context) { + return staticDisplayMetrics(staticResources(context)); + } + + /** + * 获取 DisplayMetrics + * @param resource {@link Resources} + * @return {@link DisplayMetrics} + */ + public static DisplayMetrics staticDisplayMetrics(final Resources resource) { + if (resource == null) return null; + try { + return resource.getDisplayMetrics(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticDisplayMetrics"); + } + return null; + } + + // ================= + // = Configuration = + // ================= + + /** + * 获取 Configuration + * @return {@link Configuration} + */ + public static Configuration staticConfiguration() { + return staticConfiguration(staticResources()); + } + + /** + * 获取 Configuration + * @param context {@link Context} + * @return {@link Configuration} + */ + public static Configuration staticConfiguration(final Context context) { + return staticConfiguration(staticResources(context)); + } + + /** + * 获取 Configuration + * @param resource {@link Resources} + * @return {@link Configuration} + */ + public static Configuration staticConfiguration(final Resources resource) { + if (resource == null) return null; + try { + return resource.getConfiguration(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticConfiguration"); + } + return null; + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager + * @return {@link AssetManager} + */ + public static AssetManager staticAssets() { + return staticAssets(staticResources()); + } + + /** + * 获取 AssetManager + * @param context {@link Context} + * @return {@link AssetManager} + */ + public static AssetManager staticAssets(final Context context) { + return staticAssets(staticResources(context)); + } + + /** + * 获取 AssetManager + * @param resource {@link Resources} + * @return {@link AssetManager} + */ + public static AssetManager staticAssets(final Resources resource) { + if (resource == null) return null; + try { + return resource.getAssets(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "staticAssets"); + } + return null; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 重置操作 + * @param resource {@link Resources} + * @param packageName 应用包名 + * @return {@link ResourceAssist} + */ + public ResourceAssist reset( + final Resources resource, + final String packageName + ) { + if (this == EMPTY_IMPL) return this; + this.mResource = resource; + this.mPackageName = packageName; + return this; + } + + /** + * 获取应用包名 + * @return 应用包名 + */ + public String getPackageName() { + return mPackageName; + } + + /** + * 获取 Resources + * @return {@link Resources} + */ + public Resources getResources() { + return mResource; + } + + /** + * 获取 Resources.Theme + * @param context {@link Context} + * @return {@link Resources.Theme} + */ + public Resources.Theme getTheme(final Context context) { + return staticTheme(context); + } + + /** + * 获取 ContentResolver + * @param context {@link Context} + * @return {@link ContentResolver} + */ + public ContentResolver getContentResolver(final Context context) { + return staticContentResolver(context); + } + + /** + * 获取 DisplayMetrics + * @return {@link DisplayMetrics} + */ + public DisplayMetrics getDisplayMetrics() { + try { + return mResource.getDisplayMetrics(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDisplayMetrics"); + } + return null; + } + + /** + * 获取 Configuration + * @return {@link Configuration} + */ + public Configuration getConfiguration() { + try { + return mResource.getConfiguration(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getConfiguration"); + } + return null; + } + + /** + * 获取 AssetManager + * @return {@link AssetManager} + */ + public AssetManager getAssets() { + try { + return mResource.getAssets(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAssets"); + } + return null; + } + + // ========== + // = 具体方法 = + // ========== + + /** + * 获取资源 id + * @param resName 资源名 + * @param defType 资源类型 + * @return 资源 id + */ + public int getIdentifier( + final String resName, + final String defType + ) { + try { + return mResource.getIdentifier(resName, defType, mPackageName); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "getIdentifier - %s %s: %s", + resName, defType, mPackageName + ); + } + return 0; + } + + /** + * 获取给定资源标识符的全名 + * @param id resource identifier + * @return Integer + */ + public String getResourceName(@AnyRes final int id) { + try { + return mResource.getResourceName(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getResourceName"); + } + return null; + } + + // ========== + // = 资源获取 = + // ========== + + /** + * 获取 String id + * @param resName resource name + * @return String id + */ + public int getStringId(final String resName) { + return getIdentifier(resName, "string"); + } + + /** + * 获取 String + * @param resName resource name + * @return String + */ + public String getString(final String resName) { + return getString(getStringId(resName)); + } + + /** + * 获取 String + * @param resName resource name + * @param formatArgs 格式化参数 + * @return String + */ + public String getString( + final String resName, + final Object... formatArgs + ) { + return getString(getStringId(resName), formatArgs); + } + + /** + * 获取 String + * @param id R.string.id + * @return String + */ + public String getString(@StringRes final int id) { + try { + return mResource.getString(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getString"); + } + return null; + } + + /** + * 获取 String + * @param id R.string.id + * @param formatArgs 格式化参数 + * @return String + */ + public String getString( + @StringRes final int id, + final Object... formatArgs + ) { + try { + return mResource.getString(id, formatArgs); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getString"); + } + return null; + } + + // = + + /** + * 获取 Dimension id + * @param resName resource name + * @return Dimension id + */ + public int getDimenId(final String resName) { + return getIdentifier(resName, "dimen"); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public float getDimension(final String resName) { + return getDimension(getDimenId(resName)); + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + public int getDimensionInt(final String resName) { + return getDimensionInt(getDimenId(resName)); + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public float getDimension(@DimenRes final int id) { + try { + return mResource.getDimension(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDimension"); + } + return 0F; + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + public int getDimensionInt(@DimenRes final int id) { + return (int) getDimension(id); + } + + // = + + /** + * 获取 Color id + * @param resName resource name + * @return Color id + */ + public int getColorId(final String resName) { + return getIdentifier(resName, "color"); + } + + /** + * 获取 Color + * @param resName resource name + * @return Color + */ + public int getColor(final String resName) { + return getColor(getColorId(resName)); + } + + /** + * 获取 Color + *
+     *     {@link ContextCompat#getColor(Context, int)}
+     * 
+ * @param colorId R.color.id + * @return Color + */ + public int getColor(@ColorRes final int colorId) { + try { + return mResource.getColor(colorId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getColor"); + } + return -1; + } + + // = + + /** + * 获取 Drawable id + * @param resName resource name + * @return Drawable id + */ + public int getDrawableId(final String resName) { + return getIdentifier(resName, "drawable"); + } + + /** + * 获取 Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public Drawable getDrawable(final String resName) { + return getDrawable(getDrawableId(resName)); + } + + /** + * 获取 .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawable(final String resName) { + return getNinePatchDrawable(getDrawableId(resName)); + } + + /** + * 获取 Drawable + *
+     *     {@link ContextCompat#getDrawable(Context, int)}
+     * 
+ * @param drawableId R.drawable.id + * @return {@link Drawable} + */ + @SuppressLint("UseCompatLoadingForDrawables") + public Drawable getDrawable(@DrawableRes final int drawableId) { + try { + return mResource.getDrawable(drawableId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDrawable"); + } + return null; + } + + /** + * 获取 .9 Drawable + * @param drawableId R.drawable.id + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawable(@DrawableRes final int drawableId) { + try { + return (NinePatchDrawable) getDrawable(drawableId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getNinePatchDrawable"); + } + return null; + } + + // = + + /** + * 获取 Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public Bitmap getBitmap(final String resName) { + return getBitmap(getDrawableId(resName)); + } + + /** + * 获取 Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmap( + final String resName, + final BitmapFactory.Options options + ) { + return getBitmap(getDrawableId(resName), options); + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @return {@link Bitmap} + */ + public Bitmap getBitmap(final int resId) { + try { + return BitmapFactory.decodeResource(mResource, resId); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + } + return null; + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmap( + final int resId, + final BitmapFactory.Options options + ) { + try { + return BitmapFactory.decodeResource(mResource, resId, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + } + return null; + } + + // = + + /** + * 获取 Mipmap id + * @param resName resource name + * @return Mipmap id + */ + public int getMipmapId(final String resName) { + return getIdentifier(resName, "mipmap"); + } + + /** + * 获取 Mipmap Drawable + * @param resName resource name + * @return {@link Drawable} + */ + public Drawable getDrawableMipmap(final String resName) { + return getDrawable(getMipmapId(resName)); + } + + /** + * 获取 Mipmap .9 Drawable + * @param resName resource name + * @return .9 {@link NinePatchDrawable} + */ + public NinePatchDrawable getNinePatchDrawableMipmap(final String resName) { + return getNinePatchDrawable(getMipmapId(resName)); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @return {@link Bitmap} + */ + public Bitmap getBitmapMipmap(final String resName) { + return getBitmap(getMipmapId(resName)); + } + + /** + * 获取 Mipmap Bitmap + * @param resName resource name + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public Bitmap getBitmapMipmap( + final String resName, + final BitmapFactory.Options options + ) { + return getBitmap(getMipmapId(resName), options); + } + + // = + + /** + * 获取 Anim id + * @param resName resource name + * @return Anim id + */ + public int getAnimId(final String resName) { + return getIdentifier(resName, "anim"); + } + + /** + * 获取 Animation Xml + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public XmlResourceParser getAnimationXml(final String resName) { + return getAnimationXml(getAnimId(resName)); + } + + /** + * 获取 Animation Xml + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public XmlResourceParser getAnimationXml(@AnimatorRes @AnimRes final int id) { + try { + return mResource.getAnimation(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAnimationXml"); + } + return null; + } + + /** + * 获取 Animation + * @param resName resource name + * @return {@link XmlResourceParser} + */ + public Animation getAnimation(final String resName) { + return getAnimation(getAnimId(resName)); + } + + /** + * 获取 Animation + * @param resName resource name + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public Animation getAnimation( + final String resName, + final Context context + ) { + return getAnimation(getAnimId(resName), context); + } + + /** + * 获取 Animation + * @param id resource identifier + * @return {@link XmlResourceParser} + */ + public Animation getAnimation(@AnimatorRes @AnimRes final int id) { + return getAnimation(id, DevUtils.getContext()); + } + + /** + * 获取 Animation + * @param id resource identifier + * @param context {@link Context} + * @return {@link XmlResourceParser} + */ + public Animation getAnimation( + @AnimatorRes @AnimRes final int id, + final Context context + ) { + try { + return AnimationUtils.loadAnimation(context, id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAnimation"); + } + return null; + } + + // = + + /** + * 获取 Boolean id + * @param resName resource name + * @return Boolean id + */ + public int getBooleanId(final String resName) { + return getIdentifier(resName, "bool"); + } + + /** + * 获取 Boolean + * @param resName resource name + * @return Boolean + */ + public boolean getBoolean(final String resName) { + return getBoolean(getBooleanId(resName)); + } + + /** + * 获取 Boolean + * @param id resource identifier + * @return Boolean + */ + public boolean getBoolean(@BoolRes final int id) { + try { + return mResource.getBoolean(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBoolean"); + } + return false; + } + + // = + + /** + * 获取 Integer id + * @param resName resource name + * @return Integer id + */ + public int getIntegerId(final String resName) { + return getIdentifier(resName, "integer"); + } + + /** + * 获取 Integer + * @param resName resource name + * @return Integer + */ + public int getInteger(final String resName) { + return getInteger(getIntegerId(resName)); + } + + /** + * 获取 Integer + * @param id resource identifier + * @return Integer + */ + public int getInteger(@IntegerRes final int id) { + try { + return mResource.getInteger(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getInteger"); + } + return -1; + } + + // = + + /** + * 获取 Array id + * @param resName resource name + * @return Array id + */ + public int getArrayId(final String resName) { + return getIdentifier(resName, "array"); + } + + /** + * 获取 int[] + * @param resName resource name + * @return int[] + */ + public int[] getIntArray(final String resName) { + return getIntArray(getArrayId(resName)); + } + + /** + * 获取 String[] + * @param resName resource name + * @return String[] + */ + public String[] getStringArray(final String resName) { + return getStringArray(getArrayId(resName)); + } + + /** + * 获取 CharSequence[] + * @param resName resource name + * @return CharSequence[] + */ + public CharSequence[] getTextArray(final String resName) { + return getTextArray(getArrayId(resName)); + } + + /** + * 获取 int[] + * @param id resource identifier + * @return int[] + */ + public int[] getIntArray(@ArrayRes final int id) { + try { + return mResource.getIntArray(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getIntArray"); + } + return null; + } + + /** + * 获取 String[] + * @param id resource identifier + * @return String[] + */ + public String[] getStringArray(@ArrayRes final int id) { + try { + return mResource.getStringArray(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getStringArray"); + } + return null; + } + + /** + * 获取 CharSequence[] + * @param id resource identifier + * @return CharSequence[] + */ + public CharSequence[] getTextArray(@ArrayRes final int id) { + try { + return mResource.getTextArray(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTextArray"); + } + return null; + } + + // = + + /** + * 获取 id ( view ) + * @param resName resource name + * @return id + */ + public int getId(final String resName) { + return getIdentifier(resName, "id"); + } + + /** + * 获取 Layout id + *
+     *     {@link android.view.LayoutInflater#inflate(int, ViewGroup)}
+     * 
+ * @param resName resource name + * @return Layout id + */ + public int getLayoutId(final String resName) { + return getIdentifier(resName, "layout"); + } + + /** + * 获取 Menu id + *
+     *     {@link android.view.MenuInflater#inflate(int, Menu)}
+     * 
+ * @param resName resource name + * @return Menu id + */ + public int getMenuId(final String resName) { + return getIdentifier(resName, "menu"); + } + + /** + * 获取 Raw id + * @param resName resource name + * @return Raw id + */ + public int getRawId(final String resName) { + return getIdentifier(resName, "raw"); + } + + /** + * 获取 Attr id + * @param resName resource name + * @return Attr id + */ + public int getAttrId(final String resName) { + return getIdentifier(resName, "attr"); + } + + /** + * 获取 Style id + * @param resName resource name + * @return Style id + */ + public int getStyleId(final String resName) { + return getIdentifier(resName, "style"); + } + + /** + * 获取 Styleable id + * @param resName resource name + * @return Styleable id + */ + public int getStyleableId(final String resName) { + return getIdentifier(resName, "styleable"); + } + + /** + * 获取 Animator id + * @param resName resource name + * @return Animator id + */ + public int getAnimatorId(final String resName) { + return getIdentifier(resName, "animator"); + } + + /** + * 获取 Xml id + * @param resName resource name + * @return Xml id + */ + public int getXmlId(final String resName) { + return getIdentifier(resName, "xml"); + } + + /** + * 获取 Interpolator id + * @param resName resource name + * @return Interpolator id + */ + public int getInterpolatorId(final String resName) { + return getIdentifier(resName, "interpolator"); + } + + /** + * 获取 Plurals id + * @param resName resource name + * @return Plurals id + */ + public int getPluralsId(final String resName) { + return getIdentifier(resName, "plurals"); + } + + // = + + /** + * 获取 ColorStateList + * @param resName resource Name + * @return {@link ColorStateList} + */ + public ColorStateList getColorStateList(final String resName) { + return getColorStateList(getColorId(resName)); + } + + /** + * 获取 ColorStateList + *
+     *     {@link ContextCompat#getColorStateList(Context, int)}
+     * 
+ * @param id resource identifier of a {@link ColorStateList} + * @return {@link ColorStateList} + */ + @SuppressLint("UseCompatLoadingForColorStateLists") + public ColorStateList getColorStateList(@ColorRes final int id) { + try { + return mResource.getColorStateList(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getColorStateList"); + } + return null; + } + + /** + * 获取十六进制颜色值 Drawable + * @param color 十六进制颜色值 + * @return 十六进制颜色值 Drawable + */ + public ColorDrawable getColorDrawable(final String color) { + try { + return new ColorDrawable(Color.parseColor(color)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getColorDrawable"); + } + return null; + } + + /** + * 获取指定颜色 Drawable + * @param color 颜色值 + * @return 指定颜色 Drawable + */ + public ColorDrawable getColorDrawable(@ColorInt final int color) { + try { + return new ColorDrawable(color); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getColorDrawable"); + } + return null; + } + + // =================== + // = ContentResolver = + // =================== + + /** + * 获取 Uri InputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri InputStream + */ + public InputStream openInputStream(final Uri uri) { + return openInputStream(uri, staticContentResolver()); + } + + /** + * 获取 Uri InputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param context {@link Context} + * @return Uri InputStream + */ + public InputStream openInputStream( + final Uri uri, + final Context context + ) { + return openInputStream(uri, staticContentResolver(context)); + } + + /** + * 获取 Uri InputStream + *
+     *     主要用于获取到分享的 FileProvider Uri 存储起来
+     *     {@link FileIOUtils#writeFileFromIS(File, InputStream)}
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri InputStream + */ + public InputStream openInputStream( + final Uri uri, + final ContentResolver resolver + ) { + if (uri == null) return null; + try { + return resolver.openInputStream(uri); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openInputStream %s", uri.toString()); + } + return null; + } + + // = + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @return Uri OutputStream + */ + public OutputStream openOutputStream(final Uri uri) { + return openOutputStream(uri, staticContentResolver()); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param context {@link Context} + * @return Uri OutputStream + */ + public OutputStream openOutputStream( + final Uri uri, + final Context context + ) { + return openOutputStream(uri, staticContentResolver(context)); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public OutputStream openOutputStream( + final Uri uri, + final ContentResolver resolver + ) { + if (uri == null) return null; + try { + return resolver.openOutputStream(uri); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openOutputStream %s", uri.toString()); + } + return null; + } + + // = + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri OutputStream + */ + public OutputStream openOutputStream( + final Uri uri, + final String mode + ) { + return openOutputStream(uri, mode, staticContentResolver()); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param context {@link Context} + * @return Uri OutputStream + */ + public OutputStream openOutputStream( + final Uri uri, + final String mode, + final Context context + ) { + return openOutputStream(uri, mode, staticContentResolver(context)); + } + + /** + * 获取 Uri OutputStream + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri OutputStream + */ + public OutputStream openOutputStream( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (uri == null) return null; + try { + return resolver.openOutputStream(uri, mode); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "openOutputStream mode: %s, %s", + mode, uri.toString() + ); + } + return null; + } + + // = + + /** + * 获取 Uri ParcelFileDescriptor + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri ParcelFileDescriptor + */ + public ParcelFileDescriptor openFileDescriptor( + final Uri uri, + final String mode + ) { + return openFileDescriptor(uri, mode, staticContentResolver()); + } + + /** + * 获取 Uri ParcelFileDescriptor + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param context {@link Context} + * @return Uri ParcelFileDescriptor + */ + public ParcelFileDescriptor openFileDescriptor( + final Uri uri, + final String mode, + final Context context + ) { + return openFileDescriptor(uri, mode, staticContentResolver(context)); + } + + /** + * 获取 Uri ParcelFileDescriptor + *
+     *     通过 new FileInputStream(openFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri ParcelFileDescriptor + */ + public ParcelFileDescriptor openFileDescriptor( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (uri == null || TextUtils.isEmpty(mode)) return null; + try { + return resolver.openFileDescriptor(uri, mode); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "openFileDescriptor mode: %s, %s", + mode, uri.toString() + ); + } + return null; + } + + // = + + /** + * 获取 Uri AssetFileDescriptor + * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @return Uri AssetFileDescriptor + */ + public AssetFileDescriptor openAssetFileDescriptor( + final Uri uri, + final String mode + ) { + return openAssetFileDescriptor(uri, mode, staticContentResolver()); + } + + /** + * 获取 Uri AssetFileDescriptor + *
+     *     通过 new FileInputStream(openAssetFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param context {@link Context} + * @return Uri AssetFileDescriptor + */ + public AssetFileDescriptor openAssetFileDescriptor( + final Uri uri, + final String mode, + final Context context + ) { + return openAssetFileDescriptor(uri, mode, staticContentResolver(context)); + } + + /** + * 获取 Uri AssetFileDescriptor + *
+     *     通过 new FileInputStream(openAssetFileDescriptor().getFileDescriptor()) 进行文件操作
+     * 
+ * @param uri {@link Uri} FileProvider Uri、Content Uri、File Uri + * @param mode 读写模式 + * @param resolver {@link ContentResolver} + * @return Uri AssetFileDescriptor + */ + public AssetFileDescriptor openAssetFileDescriptor( + final Uri uri, + final String mode, + final ContentResolver resolver + ) { + if (uri == null || TextUtils.isEmpty(mode)) return null; + try { + return resolver.openAssetFileDescriptor(uri, mode); + } catch (Exception e) { + LogPrintUtils.eTag( + TAG, e, "openAssetFileDescriptor mode: %s, %s", + mode, uri.toString() + ); + } + return null; + } + + // ================ + // = AssetManager = + // ================ + + /** + * 获取 AssetManager 指定资源 InputStream + * @param fileName 文件名 + * @return {@link InputStream} + */ + public InputStream open(final String fileName) { + try { + return getAssets().open(fileName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "open"); + } + return null; + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openFd(final String fileName) { + try { + return getAssets().openFd(fileName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openFd"); + } + return null; + } + + /** + * 获取 AssetManager 指定资源 AssetFileDescriptor + * @param fileName 文件名 + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openNonAssetFd(final String fileName) { + try { + return getAssets().openNonAssetFd(fileName); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openNonAssetFd"); + } + return null; + } + + /** + * 获取对应资源 InputStream + * @param id resource identifier + * @return {@link InputStream} + */ + public InputStream openRawResource(@RawRes final int id) { + try { + return mResource.openRawResource(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openRawResource"); + } + return null; + } + + /** + * 获取对应资源 AssetFileDescriptor + * @param id resource identifier + * @return {@link AssetFileDescriptor} + */ + public AssetFileDescriptor openRawResourceFd(@RawRes final int id) { + try { + return mResource.openRawResourceFd(id); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openRawResourceFd"); + } + return null; + } + + // ============= + // = 读取资源文件 = + // ============= + + /** + * 获取 Assets 资源文件数据 + *
+     *     直接传入文件名、文件夹 / 文件名 等
+     *     根目录 a.txt
+     *     子目录 /www/a.html
+     * 
+ * @param fileName 文件名 + * @return 文件 byte[] 数据 + */ + public byte[] readBytesFromAssets(final String fileName) { + InputStream is = open(fileName); + if (is == null) return null; + try { + int length = is.available(); + byte[] buffer = new byte[length]; + is.read(buffer); + return buffer; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "readBytesFromAssets"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } + + /** + * 获取 Assets 资源文件数据 + * @param fileName 文件名 + * @return 文件字符串内容 + */ + public String readStringFromAssets(final String fileName) { + try { + return new String(readBytesFromAssets(fileName), DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "readStringFromAssets"); + } + return null; + } + + // = + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件 byte[] 数据 + */ + public byte[] readBytesFromRaw(@RawRes final int resId) { + InputStream is = openRawResource(resId); + if (is == null) return null; + try { + int length = is.available(); + byte[] buffer = new byte[length]; + is.read(buffer); + return buffer; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "readBytesFromRaw"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } + + /** + * 获取 Raw 资源文件数据 + * @param resId 资源 id + * @return 文件字符串内容 + */ + public String readStringFromRaw(@RawRes final int resId) { + try { + return new String(readBytesFromRaw(resId), DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "readStringFromRaw"); + } + return null; + } + + // = + + /** + * 获取 Assets 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param fileName 文件名 + * @return {@link List } + */ + public List geFileToListFromAssets(final String fileName) { + InputStream is = null; + BufferedReader br = null; + try { + is = open(fileName); + br = new BufferedReader(new InputStreamReader(is)); + + List lists = new ArrayList<>(); + String line; + while ((line = br.readLine()) != null) { + lists.add(line); + } + return lists; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "geFileToListFromAssets"); + } finally { + CloseUtils.closeIOQuietly(is, br); + } + return null; + } + + /** + * 获取 Raw 资源文件数据 ( 返回 List 一行的全部内容属于一个索引 ) + * @param resId 资源 id + * @return {@link List} + */ + public List geFileToListFromRaw(@RawRes final int resId) { + InputStream is = null; + BufferedReader br = null; + try { + is = openRawResource(resId); + br = new BufferedReader(new InputStreamReader(is)); + + List lists = new ArrayList<>(); + String line; + while ((line = br.readLine()) != null) { + lists.add(line); + } + return lists; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "geFileToListFromRaw"); + } finally { + CloseUtils.closeIOQuietly(is, br); + } + return null; + } + + // = + + /** + * 获取 Assets 资源文件数据并保存到本地 + * @param fileName 文件名 + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public boolean saveAssetsFormFile( + final String fileName, + final File file + ) { + try { + // 获取 Assets 文件 + InputStream is = open(fileName); + // 存入 SDCard + FileOutputStream fos = new FileOutputStream(file); + // 设置数据缓冲 + byte[] buffer = new byte[1024]; + // 创建输入输出流 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + // 保存数据 + byte[] bytes = baos.toByteArray(); + // 写入保存的文件 + fos.write(bytes); + // 关闭流 + CloseUtils.closeIOQuietly(baos, is); + CloseUtils.flushCloseIOQuietly(fos); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saveAssetsFormFile"); + } + return false; + } + + /** + * 获取 Raw 资源文件数据并保存到本地 + * @param resId 资源 id + * @param file 文件存储地址 + * @return {@code true} success, {@code false} fail + */ + public boolean saveRawFormFile( + @RawRes final int resId, + final File file + ) { + try { + // 获取 raw 文件 + InputStream is = openRawResource(resId); + // 存入 SDCard + FileOutputStream fos = new FileOutputStream(file); + // 设置数据缓冲 + byte[] buffer = new byte[1024]; + // 创建输入输出流 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int len; + while ((len = is.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + // 保存数据 + byte[] bytes = baos.toByteArray(); + // 写入保存的文件 + fos.write(bytes); + // 关闭流 + CloseUtils.closeIOQuietly(baos, is); + CloseUtils.flushCloseIOQuietly(fos); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saveRawFormFile"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java new file mode 100644 index 0000000000..f5aa175503 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/ScreenSensorAssist.java @@ -0,0 +1,303 @@ +package dev.utils.app.assist; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.Message; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; + +/** + * detail: 屏幕传感器辅助类 ( 监听是否横竖屏 ) + * @author Ttt + */ +public final class ScreenSensorAssist { + + // 日志 TAG + private static final String TAG = ScreenSensorAssist.class.getSimpleName(); + + // ================== + // = 重力传感器监听对象 = + // ================== + + // 传感器管理对象 + private SensorManager mSensorManager; + // 重力传感器 + private Sensor mSensor; + // 重力传感器监听事件 + private OrientationSensorListener mListener; + + // =========================================== + // = 重力传感器监听对象 ( 改变方向后, 判断参数不同 ) = + // =========================================== + + // 传感器管理对象 ( 切屏后 ) + private SensorManager mSensorManagerChange; + // 重力传感器监听事件 ( 切屏后 ) + private OrientationSensorChangeListener mListenerChange; + + // ======= + // = 常量 = + // ======= + + // 方向未知常量 + private final int ORIENTATION_UNKNOWN = -1; + // 坐标索引常量 + private static final int DATA_X = 0; + private static final int DATA_Y = 1; + private static final int DATA_Z = 2; + // 触发屏幕方向改变回调 + public static final int CHANGE_ORIENTATION_WHAT = 9919; + + // ======= + // = 变量 = + // ======= + + // 是否允许切屏 + private boolean mAllowChange = false; + // 是否是竖屏 + private boolean mPortrait = true; + // 回调操作 + private Handler mHandler; + + /** + * 角度处理 Handler + */ + private final Handler mRotateHandler = new Handler() { + public void handleMessage(Message msg) { + switch (msg.what) { + case CHANGE_ORIENTATION_WHAT: + // 获取角度 + int rotation = msg.arg1; + // = + LogPrintUtils.dTag(TAG, "当前角度: %s", rotation); + // 判断角度 + if (rotation > 45 && rotation < 135) { + // 横屏 ( 屏幕对着别人 ) + LogPrintUtils.dTag(TAG, "切换成横屏 ( 屏幕对着自己 )"); + // = + if (mPortrait) { + mPortrait = false; + if (mHandler != null) { + Message vMsg = new Message(); + vMsg.what = CHANGE_ORIENTATION_WHAT; + vMsg.arg1 = 1; + mHandler.sendMessage(vMsg); + } + } + } else if (rotation > 135 && rotation < 225) { + // 竖屏 ( 屏幕对着别人 ) + LogPrintUtils.dTag(TAG, "切换成竖屏 ( 屏幕对着别人 )"); + // = + if (!mPortrait) { + mPortrait = true; + if (mHandler != null) { + Message vMsg = new Message(); + vMsg.what = CHANGE_ORIENTATION_WHAT; + vMsg.arg1 = 2; + mHandler.sendMessage(vMsg); + } + } + } else if (rotation > 225 && rotation < 315) { + // 横屏 ( 屏幕对着自己 ) + LogPrintUtils.dTag(TAG, "切换成横屏 ( 屏幕对着自己 )"); + // = + if (mPortrait) { + mPortrait = false; + if (mHandler != null) { + Message vMsg = new Message(); + vMsg.what = CHANGE_ORIENTATION_WHAT; + vMsg.arg1 = 1; + mHandler.sendMessage(vMsg); + } + } + } else if ((rotation > 315 && rotation < 360) || (rotation > 0 && rotation < 45)) { + // 竖屏 ( 屏幕对着自己 ) + LogPrintUtils.dTag(TAG, "切换成竖屏 ( 屏幕对着自己 )"); + // = + if (!mPortrait) { + mPortrait = true; + if (mHandler != null) { + Message vMsg = new Message(); + vMsg.what = CHANGE_ORIENTATION_WHAT; + vMsg.arg1 = 2; + mHandler.sendMessage(vMsg); + } + } + } else { + LogPrintUtils.dTag(TAG, "其他角度: %s", rotation); + } + break; + } + } + }; + + // = + + /** + * 初始化操作 + * @param handler 回调 {@link Handler} + */ + private void initialize(final Handler handler) { + this.mHandler = handler; + // 注册重力感应器, 监听屏幕旋转 + mSensorManager = AppUtils.getSensorManager(); + mListener = new OrientationSensorListener(); + // 根据 旋转之后、点击全屏之后 两者方向一致, 激活 SensorManager + mSensorManagerChange = AppUtils.getSensorManager(); + mListenerChange = new OrientationSensorChangeListener(); + // 设置传感器 + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + + /** + * 开始监听 + * @param handler 回调 {@link Handler} + * @return {@code true} success, {@code false} fail + */ + public boolean start(final Handler handler) { + mAllowChange = true; + try { + LogPrintUtils.dTag(TAG, "start orientation listener."); + // 初始化操作 + initialize(handler); + // 监听重力传感器 + mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "start"); + } + return false; + } + + /** + * 停止监听 + * @return {@code true} success, {@code false} fail + */ + public boolean stop() { + mAllowChange = false; + LogPrintUtils.dTag(TAG, "stop orientation listener."); + try { + mSensorManager.unregisterListener(mListener); + } catch (Exception ignored) { + } + try { + mSensorManagerChange.unregisterListener(mListenerChange); + } catch (Exception ignored) { + } + return true; + } + + /** + * 是否竖屏 + * @return {@code true} 竖屏, {@code false} 非竖屏 + */ + public boolean isPortrait() { + return this.mPortrait; + } + + /** + * 是否允许切屏 + * @return {@code true} 允许, {@code false} 不允许 + */ + public boolean isAllowChange() { + return this.mAllowChange; + } + + // = + + /** + * detail: 重力传感器监听事件 + * @author Ttt + */ + class OrientationSensorListener + implements SensorEventListener { + @Override + public void onAccuracyChanged( + Sensor sensor, + int accuracy + ) { + } + + @Override + public void onSensorChanged(SensorEvent event) { + float[] values = event.values; + int orientation = ORIENTATION_UNKNOWN; + float X = -values[DATA_X]; + float Y = -values[DATA_Y]; + float Z = -values[DATA_Z]; + float magnitude = X * X + Y * Y; + // Don't trust the angle if the magnitude is small compared to the y value + if (magnitude * 4 >= Z * Z) { + // 屏幕旋转时 + float OneEightyOverPi = 57.29577957855F; + float angle = (float) Math.atan2(-Y, X) * OneEightyOverPi; + orientation = 90 - Math.round(angle); + // normalize to 0 - 359 range + while (orientation >= 360) { + orientation -= 360; + } + while (orientation < 0) { + orientation += 360; + } + } + mRotateHandler.obtainMessage( + CHANGE_ORIENTATION_WHAT, orientation, 0 + ).sendToTarget(); + } + } + + /** + * detail: 重力传感器监听事件 ( 切屏后 ) + * @author Ttt + */ + class OrientationSensorChangeListener + implements SensorEventListener { + @Override + public void onAccuracyChanged( + Sensor sensor, + int accuracy + ) { + } + + @Override + public void onSensorChanged(SensorEvent event) { + float[] values = event.values; + int orientation = ORIENTATION_UNKNOWN; + float X = -values[DATA_X]; + float Y = -values[DATA_Y]; + float Z = -values[DATA_Z]; + float magnitude = X * X + Y * Y; + // Don't trust the angle if the magnitude is small compared to the y value + if (magnitude * 4 >= Z * Z) { + // 屏幕旋转时 + float OneEightyOverPi = 57.29577957855F; + float angle = (float) Math.atan2(-Y, X) * OneEightyOverPi; + orientation = 90 - Math.round(angle); + // normalize to 0 - 359 range + while (orientation >= 360) { + orientation -= 360; + } + while (orientation < 0) { + orientation += 360; + } + } + if (orientation > 225 && orientation < 315) { + // 检测到当前实际是横屏 + if (!mPortrait) { + mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); + mSensorManagerChange.unregisterListener(mListenerChange); + } + } else if ((orientation > 315 && orientation < 360) || (orientation > 0 && orientation < 45)) { + // 检测到当前实际是竖屏 + if (mPortrait) { + mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_UI); + mSensorManagerChange.unregisterListener(mListenerChange); + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/WindowAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/WindowAssist.java new file mode 100644 index 0000000000..7a44edf906 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/WindowAssist.java @@ -0,0 +1,1591 @@ +package dev.utils.app.assist; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.os.Build; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.RequiresApi; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; + +import java.lang.reflect.Field; + +import dev.utils.app.ActivityUtils; +import dev.utils.app.BrightnessUtils; +import dev.utils.common.assist.FlagsValue; + +/** + * detail: Window 辅助类 + * @author Ttt + */ +public final class WindowAssist { + + // ========== + // = 静态方法 = + // ========== + + /** + * 获取 Window + * @param context {@link Context} + * @return {@link Window} + */ + public static Window getWindow(final Context context) { + return getWindow(ActivityUtils.getActivity(context)); + } + + /** + * 获取 Window + * @param activity {@link Activity} + * @return {@link Window} + */ + public static Window getWindow(final Activity activity) { + return (activity != null) ? activity.getWindow() : null; + } + + /** + * 获取 Window + * @param fragment {@link Fragment} + * @return {@link Window} + */ + public static Window getWindow(final Fragment fragment) { + if (fragment == null) return null; + return getWindow(fragment.getActivity()); + } + + /** + * 获取 Window + * @param fragment {@link android.app.Fragment} + * @return {@link Window} + */ + public static Window getWindow(final android.app.Fragment fragment) { + if (fragment == null) return null; + return getWindow(fragment.getActivity()); + } + + /** + * 获取 Window + * @param dialog {@link Dialog} + * @return {@link Window} + */ + public static Window getWindow(final Dialog dialog) { + return (dialog != null) ? dialog.getWindow() : null; + } + + /** + * 获取 Window + * @param dialog {@link DialogFragment} + * @return {@link Window} + */ + public static Window getWindow(final DialogFragment dialog) { + if (dialog == null) return null; + return getWindow(dialog.getDialog()); + } + + /** + * 获取 Window + * @param dialog {@link android.app.DialogFragment} + * @return {@link Window} + */ + public static Window getWindow(final android.app.DialogFragment dialog) { + if (dialog == null) return null; + return getWindow(dialog.getDialog()); + } + + // ======= + // = get = + // ======= + + /** + * 获取 WindowAssist + * @return {@link WindowAssist} + */ + public static WindowAssist get() { + return getInstance(); + } + + /** + * 获取 WindowAssist + * @param window {@link Window} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Window window) { + return new WindowAssist(window); + } + + /** + * 获取 WindowAssist + * @param context {@link Context} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Context context) { + return new WindowAssist(context); + } + + /** + * 获取 WindowAssist + * @param activity {@link Activity} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Activity activity) { + return new WindowAssist(activity); + } + + /** + * 获取 WindowAssist + * @param fragment {@link Fragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Fragment fragment) { + return new WindowAssist(fragment); + } + + /** + * 获取 WindowAssist + * @param fragment {@link android.app.Fragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final android.app.Fragment fragment) { + return new WindowAssist(fragment); + } + + /** + * 获取 WindowAssist + * @param dialog {@link Dialog} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final Dialog dialog) { + return new WindowAssist(dialog); + } + + /** + * 获取 WindowAssist + * @param dialog {@link DialogFragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final DialogFragment dialog) { + return new WindowAssist(dialog); + } + + /** + * 获取 WindowAssist + * @param dialog {@link android.app.DialogFragment} + * @return {@link WindowAssist} + */ + public static WindowAssist get(final android.app.DialogFragment dialog) { + return new WindowAssist(dialog); + } + + // =================== + // = WindowUtils 使用 = + // =================== + + // WindowAssist 实例 + private static volatile WindowAssist sInstance; + + /** + * 获取 WindowAssist 实例 + * @return {@link WindowAssist} + */ + private static WindowAssist getInstance() { + if (sInstance == null) { + synchronized (WindowAssist.class) { + if (sInstance == null) { + sInstance = new WindowAssist((Window) null); + } + } + } + return sInstance; + } + + // =============== + // = 辅助类具体实现 = + // =============== + + // 内部 Window + private final Window mWindow; + + // ========== + // = 构造函数 = + // ========== + + public WindowAssist(final Window window) { + this.mWindow = window; + } + + public WindowAssist(final Context context) { + this(getWindow(context)); + } + + public WindowAssist(final Activity activity) { + this(getWindow(activity)); + } + + public WindowAssist(final Fragment fragment) { + this(getWindow(fragment)); + } + + public WindowAssist(final android.app.Fragment fragment) { + this(getWindow(fragment)); + } + + public WindowAssist(final Dialog dialog) { + this(getWindow(dialog)); + } + + public WindowAssist(final DialogFragment dialog) { + this(getWindow(dialog)); + } + + public WindowAssist(final android.app.DialogFragment dialog) { + this(getWindow(dialog)); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Window + * @return {@link Window} + */ + public Window getWindow() { + return mWindow; + } + + // ============== + // = Window 无参 = + // ============== + + /** + * 获取 Window DecorView + * @return DecorView + */ + public View getDecorView() { + return getDecorView(mWindow); + } + + /** + * 获取 Window DecorView + * @return DecorView + */ + public View peekDecorView() { + return peekDecorView(mWindow); + } + + /** + * 获取 Window 当前获取焦点 View + * @return 当前获取焦点 View + */ + public View getCurrentFocus() { + return getCurrentFocus(mWindow); + } + + /** + * 设置 Window System UI 可见性 + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibility(final int visibility) { + return setSystemUiVisibility(mWindow, visibility); + } + + /** + * 获取 Window System UI 可见性 + *
+     *     返回最后一次设置 {@link View#setSystemUiVisibility(int)} 值
+     * 
+ * @return Window System UI 可见性 + */ + public int getSystemUiVisibility() { + return getSystemUiVisibility(mWindow); + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行追加 ) + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibilityByAdd(final int visibility) { + return setSystemUiVisibilityByAdd(mWindow, visibility); + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行清除 ) + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibilityByClear(final int visibility) { + return setSystemUiVisibilityByClear(mWindow, visibility); + } + + /** + * 获取 Window LayoutParams + * @return Window LayoutParams + */ + public WindowManager.LayoutParams getAttributes() { + return getAttributes(mWindow); + } + + /** + * 设置 Window LayoutParams + * @param params WindowManager.LayoutParams + * @return {@code true} success, {@code false} fail + */ + public boolean setAttributes(final WindowManager.LayoutParams params) { + return setAttributes(mWindow, params); + } + + /** + * 刷新自身 Window LayoutParams + * @return {@code true} success, {@code false} fail + */ + public boolean refreshSelfAttributes() { + return refreshSelfAttributes(mWindow); + } + + /** + * 清除 Window flags + * @param flags 待清除 flags + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlags(final int flags) { + return clearFlags(mWindow, flags); + } + + /** + * 添加 Window flags + * @param flags 待添加 flags + * @return {@code true} success, {@code false} fail + */ + public boolean addFlags(final int flags) { + return addFlags(mWindow, flags); + } + + /** + * 设置 Window flags + * @param flags 待设置 flags + * @param mask 待设置 flags 位 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlags( + final int flags, + final int mask + ) { + return setFlags(mWindow, flags, mask); + } + + /** + * Window 是否设置指定 flags 值 + * @param flags 待校验 flags + * @return {@code true} yes, {@code false} no + */ + public boolean hasFlags(final int flags) { + return hasFlags(mWindow, flags); + } + + /** + * Window 是否没有设置指定 flags 值 + * @param flags 待校验 flags + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFlags(final int flags) { + return notHasFlags(mWindow, flags); + } + + /** + * 启用 Window Extended Feature + *
+     *     启用后无法关闭, 需要在 setContentView() 之前调用
+     * 
+ * @param featureId 待启用 feature + * @return {@code true} success, {@code false} fail + */ + public boolean requestFeature(final int featureId) { + return requestFeature(mWindow, featureId); + } + + /** + * Window 是否开启指定 Extended Feature + * @param featureId 待校验 feature + * @return {@code true} yes, {@code false} no + */ + public boolean hasFeature(final int featureId) { + return hasFeature(mWindow, featureId); + } + + /** + * Window 是否没有开启指定 Extended Feature + * @param featureId 待校验 feature + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFeature(final int featureId) { + return notHasFeature(mWindow, featureId); + } + + /** + * 设置 Window 输入模式 + * @param mode input mode + * @return {@code true} success, {@code false} fail + */ + public boolean setSoftInputMode(final int mode) { + return setSoftInputMode(mWindow, mode); + } + + /** + * 设置 StatusBar Color + * @param color StatusBar Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean setStatusBarColor(@ColorInt final int color) { + return setStatusBarColor(mWindow, color); + } + + /** + * 获取 StatusBar Color + * @return StatusBar Color + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public int getStatusBarColor() { + return getStatusBarColor(mWindow); + } + + /** + * 设置 NavigationBar Color + * @param color NavigationBar Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean setNavigationBarColor(@ColorInt final int color) { + return setNavigationBarColor(mWindow, color); + } + + /** + * 获取 NavigationBar Color + * @return NavigationBar Color + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public int getNavigationBarColor() { + return getNavigationBarColor(mWindow); + } + + /** + * 设置 NavigationBar Divider Color + * @param color NavigationBar Divider Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public boolean setNavigationBarDividerColor(@ColorInt final int color) { + return setNavigationBarDividerColor(mWindow, color); + } + + /** + * 获取 NavigationBar Divider Color + * @return NavigationBar Divider Color + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public int getNavigationBarDividerColor() { + return getNavigationBarDividerColor(mWindow); + } + + /** + * 设置 Dialog 宽度 + * @param width 宽度 + * @return {@code true} success, {@code false} fail + */ + public boolean setWidthByParams(final int width) { + return setWidthByParams(mWindow, width); + } + + /** + * 设置 Dialog 高度 + * @param height 高度 + * @return {@code true} success, {@code false} fail + */ + public boolean setHeightByParams(final int height) { + return setHeightByParams(mWindow, height); + } + + /** + * 设置 Dialog 宽度、高度 + * @param width 宽度 + * @param height 高度 + * @return {@code true} success, {@code false} fail + */ + public boolean setWidthHeightByParams( + final int width, + final int height + ) { + return setWidthHeightByParams(mWindow, width, height); + } + + /** + * 设置 Dialog X 轴坐标 + * @param x X 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setXByParams(final int x) { + return setXByParams(mWindow, x); + } + + /** + * 设置 Dialog Y 轴坐标 + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setYByParams(final int y) { + return setYByParams(mWindow, y); + } + + /** + * 设置 Dialog X、Y 轴坐标 + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setXYByParams( + final int x, + final int y + ) { + return setXYByParams(mWindow, x, y); + } + + /** + * 设置 Dialog Gravity + * @param gravity 重心 + * @return {@code true} success, {@code false} fail + */ + public boolean setGravityByParams(final int gravity) { + return setGravityByParams(mWindow, gravity); + } + + /** + * 设置 Dialog 透明度 + * @param dimAmount 透明度 + * @return {@code true} success, {@code false} fail + */ + public boolean setDimAmountByParams(final float dimAmount) { + return setDimAmountByParams(mWindow, dimAmount); + } + + // ========== + // = 具体功能 = + // ========== + + /** + * 设置窗口亮度 + * @param brightness 亮度值 + * @return {@code true} success, {@code false} fail + */ + public boolean setWindowBrightness(@IntRange(from = 0, to = 255) final int brightness) { + return setWindowBrightness(mWindow, brightness); + } + + /** + * 获取窗口亮度 + * @return 屏幕亮度 0-255 + */ + public int getWindowBrightness() { + return getWindowBrightness(mWindow); + } + + /** + * 设置 Window 软键盘是否显示 + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return {@code true} success, {@code false} fail + */ + public boolean setKeyBoardSoftInputMode( + final boolean inputVisible, + final boolean clearFlag + ) { + return setKeyBoardSoftInputMode(mWindow, inputVisible, clearFlag); + } + + /** + * 是否屏幕常亮 + * @return {@code true} yes, {@code false} no + */ + public boolean isKeepScreenOnFlag() { + return isKeepScreenOnFlag(mWindow); + } + + /** + * 设置屏幕常亮 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagKeepScreenOn() { + return setFlagKeepScreenOn(mWindow); + } + + /** + * 移除屏幕常亮 + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagKeepScreenOn() { + return clearFlagKeepScreenOn(mWindow); + } + + /** + * 是否禁止截屏 + * @return {@code true} yes, {@code false} no + */ + public boolean isSecureFlag() { + return isSecureFlag(mWindow); + } + + /** + * 设置禁止截屏 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagSecure() { + return setFlagSecure(mWindow); + } + + /** + * 移除禁止截屏 + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagSecure() { + return clearFlagSecure(mWindow); + } + + /** + * 是否屏幕为全屏 + * @return {@code true} yes, {@code false} no + */ + public boolean isFullScreenFlag() { + return isFullScreenFlag(mWindow); + } + + /** + * 设置屏幕为全屏 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagFullScreen() { + return setFlagFullScreen(mWindow); + } + + /** + * 移除屏幕全屏 + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagFullScreen() { + return clearFlagFullScreen(mWindow); + } + + /** + * 是否透明状态栏 + * @return {@code true} yes, {@code false} no + */ + public boolean isTranslucentStatusFlag() { + return isTranslucentStatusFlag(mWindow); + } + + /** + * 设置透明状态栏 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagTranslucentStatus() { + return setFlagTranslucentStatus(mWindow); + } + + /** + * 移除透明状态栏 + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagTranslucentStatus() { + return clearFlagTranslucentStatus(mWindow); + } + + /** + * 是否系统状态栏背景绘制 + * @return {@code true} yes, {@code false} no + */ + public boolean isDrawsSystemBarBackgroundsFlag() { + return isDrawsSystemBarBackgroundsFlag(mWindow); + } + + /** + * 设置系统状态栏背景绘制 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagDrawsSystemBarBackgrounds() { + return setFlagDrawsSystemBarBackgrounds(mWindow); + } + + /** + * 移除系统状态栏背景绘制 + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagDrawsSystemBarBackgrounds() { + return clearFlagDrawsSystemBarBackgrounds(mWindow); + } + + /** + * 是否屏幕页面为无标题 + * @return {@code true} yes, {@code false} no + */ + public boolean isNoTitleFeature() { + return isNoTitleFeature(mWindow); + } + + /** + * 设置屏幕页面无标题 + * @return {@code true} success, {@code false} fail + */ + public boolean setFeatureNoTitle() { + return setFeatureNoTitle(mWindow); + } + + /** + * 设置屏幕为全屏无标题 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagFullScreenAndNoTitle() { + return setFlagFullScreenAndNoTitle(mWindow); + } + + /** + * 设置高版本状态栏蒙层 + * @param color StatusBar Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean setSemiTransparentStatusBarColor(@ColorInt final int color) { + return setSemiTransparentStatusBarColor(mWindow, color); + } + + /** + * 设置状态栏颜色、高版本状态栏蒙层 + * @param color StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return {@code true} success, {@code false} fail + */ + public boolean setStatusBarColorAndFlag( + @ColorInt final int color, + final boolean addFlags + ) { + return setStatusBarColorAndFlag(mWindow, color, addFlags); + } + + // ============== + // = Window 传参 = + // ============== + + /** + * 获取 Window DecorView + * @param window {@link Window} + * @return DecorView + */ + public View getDecorView(final Window window) { + if (window == null) return null; + return window.getDecorView(); + } + + /** + * 获取 Window DecorView + * @param window {@link Window} + * @return DecorView + */ + public View peekDecorView(final Window window) { + if (window == null) return null; + return window.peekDecorView(); + } + + /** + * 获取 Window 当前获取焦点 View + * @param window {@link Window} + * @return 当前获取焦点 View + */ + public View getCurrentFocus(final Window window) { + if (window == null) return null; + return window.getCurrentFocus(); + } + + /** + * 设置 Window System UI 可见性 + *
+     *     可搭配 {@link FlagsValue} 使用
+     *     进行追加、清除、设置等最后调用 getFlags() 进行设置
+     *     

+ * FlagsValue mFlags = new FlagsValue(getSystemUiVisibility(window)); + * mFlags.addFlags(xxxx); + * mFlags.clearFlags(xxxx); + * int flags = mFlags.getFlags(); + * setSystemUiVisibility(window, flags) + *
+ * @param window {@link Window} + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibility( + final Window window, + final int visibility + ) { + View decorView = getDecorView(window); + if (decorView == null) return false; + decorView.setSystemUiVisibility(visibility); + return true; + } + + /** + * 获取 Window System UI 可见性 + *
+     *     返回最后一次设置 {@link View#setSystemUiVisibility(int)} 值
+     * 
+ * @param window {@link Window} + * @return Window System UI 可见性 + */ + public int getSystemUiVisibility(final Window window) { + View decorView = getDecorView(window); + if (decorView == null) return 0; + return decorView.getSystemUiVisibility(); + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行追加 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibilityByAdd( + final Window window, + final int visibility + ) { + View decorView = getDecorView(window); + if (decorView == null) return false; + int flags = new FlagsValue(decorView.getSystemUiVisibility()) + .addFlags(visibility).getFlags(); + decorView.setSystemUiVisibility(flags); + return true; + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行清除 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return {@code true} success, {@code false} fail + */ + public boolean setSystemUiVisibilityByClear( + final Window window, + final int visibility + ) { + View decorView = getDecorView(window); + if (decorView == null) return false; + int flags = new FlagsValue(decorView.getSystemUiVisibility()) + .clearFlags(visibility).getFlags(); + decorView.setSystemUiVisibility(flags); + return true; + } + + /** + * 获取 Window LayoutParams + * @param window {@link Window} + * @return Window LayoutParams + */ + public WindowManager.LayoutParams getAttributes(final Window window) { + if (window == null) return null; + return window.getAttributes(); + } + + /** + * 设置 Window LayoutParams + * @param window {@link Window} + * @param params WindowManager.LayoutParams + * @return {@code true} success, {@code false} fail + */ + public boolean setAttributes( + final Window window, + final WindowManager.LayoutParams params + ) { + if (window == null || params == null) return false; + window.setAttributes(params); + return true; + } + + /** + * 刷新自身 Window LayoutParams + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean refreshSelfAttributes(final Window window) { + return setAttributes(window, getAttributes(window)); + } + + /** + * 清除 Window flags + * @param window {@link Window} + * @param flags 待清除 flags + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlags( + final Window window, + final int flags + ) { + if (window == null) return false; + window.clearFlags(flags); + return true; + } + + /** + * 添加 Window flags + * @param window {@link Window} + * @param flags 待添加 flags + * @return {@code true} success, {@code false} fail + */ + public boolean addFlags( + final Window window, + final int flags + ) { + if (window == null) return false; + window.addFlags(flags); + return true; + } + + /** + * 设置 Window flags + * @param window {@link Window} + * @param flags 待设置 flags + * @param mask 待设置 flags 位 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlags( + final Window window, + final int flags, + final int mask + ) { + if (window == null) return false; + window.setFlags(flags, mask); + return true; + } + + /** + * Window 是否设置指定 flags 值 + * @param window {@link Window} + * @param flags 待校验 flags + * @return {@code true} yes, {@code false} no + */ + public boolean hasFlags( + final Window window, + final int flags + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + return (layoutParams.flags & flags) == flags; + } + + /** + * Window 是否没有设置指定 flags 值 + * @param window {@link Window} + * @param flags 待校验 flags + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFlags( + final Window window, + final int flags + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + return (layoutParams.flags & flags) != flags; + } + + /** + * 启用 Window Extended Feature + *
+     *     启用后无法关闭, 需要在 setContentView() 之前调用
+     * 
+ * @param window {@link Window} + * @param featureId 待启用 feature + * @return {@code true} success, {@code false} fail + */ + public boolean requestFeature( + final Window window, + final int featureId + ) { + if (window == null) return false; + window.requestFeature(featureId); + return true; + } + + /** + * Window 是否开启指定 Extended Feature + * @param window {@link Window} + * @param featureId 待校验 feature + * @return {@code true} yes, {@code false} no + */ + public boolean hasFeature( + final Window window, + final int featureId + ) { + if (window == null) return false; + return window.hasFeature(featureId); + } + + /** + * Window 是否没有开启指定 Extended Feature + * @param window {@link Window} + * @param featureId 待校验 feature + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFeature( + final Window window, + final int featureId + ) { + if (window == null) return false; + return !window.hasFeature(featureId); + } + + /** + * 设置 Window 输入模式 + * @param window {@link Window} + * @param mode input mode + * @return {@code true} success, {@code false} fail + */ + public boolean setSoftInputMode( + final Window window, + final int mode + ) { + if (window == null) return false; + window.setSoftInputMode(mode); + return true; + } + + /** + * 设置 StatusBar Color + * @param window {@link Window} + * @param color StatusBar Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean setStatusBarColor( + final Window window, + @ColorInt final int color + ) { + if (window == null) return false; + window.setStatusBarColor(color); + return true; + } + + /** + * 获取 StatusBar Color + * @param window {@link Window} + * @return StatusBar Color + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public int getStatusBarColor(final Window window) { + if (window == null) return 0; + return window.getStatusBarColor(); + } + + /** + * 设置 NavigationBar Color + * @param window {@link Window} + * @param color NavigationBar Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public boolean setNavigationBarColor( + final Window window, + @ColorInt final int color + ) { + if (window == null) return false; + window.setNavigationBarColor(color); + return true; + } + + /** + * 获取 NavigationBar Color + * @param window {@link Window} + * @return NavigationBar Color + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public int getNavigationBarColor(final Window window) { + if (window == null) return 0; + return window.getNavigationBarColor(); + } + + /** + * 设置 NavigationBar Divider Color + * @param window {@link Window} + * @param color NavigationBar Divider Color + * @return {@code true} success, {@code false} fail + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public boolean setNavigationBarDividerColor( + final Window window, + @ColorInt final int color + ) { + if (window == null) return false; + window.setNavigationBarDividerColor(color); + return true; + } + + /** + * 获取 NavigationBar Divider Color + * @param window {@link Window} + * @return NavigationBar Divider Color + */ + @RequiresApi(api = Build.VERSION_CODES.P) + public int getNavigationBarDividerColor(final Window window) { + if (window == null) return 0; + return window.getNavigationBarDividerColor(); + } + + /** + * 设置 Dialog 宽度 + * @param window {@link Window} + * @param width 宽度 + * @return {@code true} success, {@code false} fail + */ + public boolean setWidthByParams( + final Window window, + final int width + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.width = width; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog 高度 + * @param window {@link Window} + * @param height 高度 + * @return {@code true} success, {@code false} fail + */ + public boolean setHeightByParams( + final Window window, + final int height + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.height = height; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog 宽度、高度 + * @param window {@link Window} + * @param width 宽度 + * @param height 高度 + * @return {@code true} success, {@code false} fail + */ + public boolean setWidthHeightByParams( + final Window window, + final int width, + final int height + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.width = width; + layoutParams.height = height; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog X 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setXByParams( + final Window window, + final int x + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.x = x; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog Y 轴坐标 + * @param window {@link Window} + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setYByParams( + final Window window, + final int y + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.y = y; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog X、Y 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean setXYByParams( + final Window window, + final int x, + final int y + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.x = x; + layoutParams.y = y; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog Gravity + * @param window {@link Window} + * @param gravity 重心 + * @return {@code true} success, {@code false} fail + */ + public boolean setGravityByParams( + final Window window, + final int gravity + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.gravity = gravity; + return setAttributes(window, layoutParams); + } + + /** + * 设置 Dialog 透明度 + * @param window {@link Window} + * @param dimAmount 透明度 + * @return {@code true} success, {@code false} fail + */ + public boolean setDimAmountByParams( + final Window window, + final float dimAmount + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.dimAmount = dimAmount; + return setAttributes(window, layoutParams); + } + + // ========== + // = 具体功能 = + // ========== + + /** + * 设置窗口亮度 + * @param window {@link Window} + * @param brightness 亮度值 + * @return {@code true} success, {@code false} fail + */ + public boolean setWindowBrightness( + final Window window, + @IntRange(from = 0, to = 255) final int brightness + ) { + if (window == null) return false; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return false; + layoutParams.screenBrightness = brightness / 255F; + return setAttributes(window, layoutParams); + } + + /** + * 获取窗口亮度 + * @param window {@link Window} + * @return 屏幕亮度 0-255 + */ + public int getWindowBrightness(final Window window) { + if (window == null) return 0; + WindowManager.LayoutParams layoutParams = window.getAttributes(); + if (layoutParams == null) return 0; + float brightness = layoutParams.screenBrightness; + if (brightness < 0) return BrightnessUtils.getBrightness(); + return (int) (brightness * 255); + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return {@code true} success, {@code false} fail + */ + public boolean setKeyBoardSoftInputMode( + final Window window, + final boolean inputVisible, + final boolean clearFlag + ) { + if (window == null) return false; + if (inputVisible) { + if (clearFlag) { + window.clearFlags( + WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + ); + } + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + } + return true; + } + + /** + * 是否屏幕常亮 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public boolean isKeepScreenOnFlag(final Window window) { + return hasFlags(window, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagKeepScreenOn(final Window window) { + if (window == null) return false; + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + return true; + } + + /** + * 移除屏幕常亮 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagKeepScreenOn(final Window window) { + if (window == null) return false; + window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + return true; + } + + /** + * 是否禁止截屏 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public boolean isSecureFlag(final Window window) { + return hasFlags(window, WindowManager.LayoutParams.FLAG_SECURE); + } + + /** + * 设置禁止截屏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagSecure(final Window window) { + if (window == null) return false; + window.addFlags(WindowManager.LayoutParams.FLAG_SECURE); + return true; + } + + /** + * 移除禁止截屏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagSecure(final Window window) { + if (window == null) return false; + window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + return true; + } + + /** + * 是否屏幕为全屏 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public boolean isFullScreenFlag(final Window window) { + return hasFlags(window, WindowManager.LayoutParams.FLAG_FULLSCREEN); + } + + /** + * 设置屏幕为全屏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagFullScreen(final Window window) { + if (window == null) return false; + window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + return true; + } + + /** + * 移除屏幕全屏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean clearFlagFullScreen(final Window window) { + if (window == null) return false; + window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + return true; + } + + /** + * 是否透明状态栏 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + @SuppressLint("InlinedApi") + public boolean isTranslucentStatusFlag(final Window window) { + return hasFlags(window, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + + /** + * 设置透明状态栏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("InlinedApi") + public boolean setFlagTranslucentStatus(final Window window) { + if (window == null) return false; + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + return true; + } + + /** + * 移除透明状态栏 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("InlinedApi") + public boolean clearFlagTranslucentStatus(final Window window) { + if (window == null) return false; + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + return true; + } + + /** + * 是否系统状态栏背景绘制 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + @SuppressLint("InlinedApi") + public boolean isDrawsSystemBarBackgroundsFlag(final Window window) { + return hasFlags(window, WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + } + + /** + * 设置系统状态栏背景绘制 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("InlinedApi") + public boolean setFlagDrawsSystemBarBackgrounds(final Window window) { + if (window == null) return false; + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + return true; + } + + /** + * 移除系统状态栏背景绘制 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("InlinedApi") + public boolean clearFlagDrawsSystemBarBackgrounds(final Window window) { + if (window == null) return false; + window.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + return true; + } + + /** + * 是否屏幕页面为无标题 + * @param window {@link Window} + * @return {@code true} yes, {@code false} no + */ + public boolean isNoTitleFeature(final Window window) { + return hasFeature(window, Window.FEATURE_NO_TITLE); + } + + /** + * 设置屏幕页面无标题 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean setFeatureNoTitle(final Window window) { + return requestFeature(window, Window.FEATURE_NO_TITLE); + } + + /** + * 设置屏幕为全屏无标题 + * @param window {@link Window} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlagFullScreenAndNoTitle(final Window window) { + if (window == null) return false; + setFeatureNoTitle(window); + setFlagFullScreen(window); + return true; + } + + /** + * 设置高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @return {@code true} success, {@code false} fail + */ + @SuppressLint("PrivateApi") + @RequiresApi(api = Build.VERSION_CODES.N) + public boolean setSemiTransparentStatusBarColor( + final Window window, + @ColorInt final int color + ) { + if (window == null) return false; + try { + Class decorViewClazz = Class.forName("com.android.internal.policy.DecorView"); + Field field = decorViewClazz.getDeclaredField( + "mSemiTransparentStatusBarColor" + ); + field.setAccessible(true); + field.setInt(window, color); + return true; + } catch (Exception ignored) { + } + return false; + } + + /** + * 设置状态栏颜色、高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return {@code true} success, {@code false} fail + */ + public boolean setStatusBarColorAndFlag( + final Window window, + @ColorInt final int color, + final boolean addFlags + ) { + if (window == null) return false; + + if (addFlags) { + setFlagDrawsSystemBarBackgrounds(window); + setFlagTranslucentStatus(window); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setStatusBarColor(window, color); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + setSemiTransparentStatusBarColor(window, color); + } + return true; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifAssist.java new file mode 100644 index 0000000000..72b2e793af --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifAssist.java @@ -0,0 +1,919 @@ +package dev.utils.app.assist.exif; + +import android.Manifest; +import android.app.Activity; +import android.graphics.Bitmap; +import android.location.Location; +import android.net.Uri; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.provider.MediaStore; +import android.text.TextUtils; + +import androidx.exifinterface.media.ExifInterface; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import dev.utils.LogPrintUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.app.image.ImageUtils; +import dev.utils.app.permission.PermissionUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 图片 EXIF 读写辅助类 + * @author Ttt + *
+ *     Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF
+ *     Supported for writing: JPEG, PNG, WebP, DNG
+ *     

+ * 需要注意的是: + * 支持写入变更的只有上述 Supported for writing 类型, 如想要操作全部格式应使用其他库封装实现 + * 该辅助类使用 Android 官方 API 不依赖任何第三方库 + *

+ * 源码部分方法是不会抛出异常, 也统一进行 try-catch 防止后续库更新迭代代码变动 + *

+ * 如果需要获取敏感信息, 如图片位置信息则 + * 需要先申请权限 {@link Manifest.permission#ACCESS_MEDIA_LOCATION} 允许后 + * 再调用 {@link MediaStore#setRequireOriginal} 获取原始 Uri + * 最后使用 {@link ResourceUtils#openFileDescriptor(Uri, String)} 返回 ParcelFileDescriptor + * 再通过 ParcelFileDescriptor.getFileDescriptor() 进行创建 ExifAssist + *

+ * 注意事项: + * 为什么不通过 {@link ResourceUtils#openInputStream(Uri)} 返回 InputStream 进行创建 ExifAssist + * 是因为通过 InputStream 创建 ExifAssist 进行 saveAttributes 会抛出异常 + * throw new IOException("Failed to save new file. Original file is stored in") + * Write failed: EBADF (Bad file descriptor) + *

+ * 已提供 {@link ExifAssist#getByRequire(Uri)} 进行创建 ( 在申请权限成功后直接通过该方法创建即可 ) + * 或通过 {@link ExifAssist#requireOriginal(Uri)} 申请获取原始 Uri + *

+ * 申请权限方法已封装 {@link ExifAssist#requestPermission(Activity, PermissionUtils.PermissionCallback)} + * 在 Callback onGranted() 方法中调用 {@link ExifAssist#getByRequire(Uri)} 即可 + * 以上所有方法都已进行版本适配处理直接调用无需额外逻辑判断 + *
+ */ +public final class ExifAssist { + + // 日志 TAG + private static final String TAG = ExifAssist.class.getSimpleName(); + + // 图片 EXIF 操作接口 + private final ExifInterface mExif; + // 是否 EXIF 初始化异常 + private final Throwable mExifError; + + // ========== + // = 构造函数 = + // ========== + + private ExifAssist( + final ExifInterface exif, + final Throwable error + ) { + mExif = exif; + mExifError = error; + } + + private ExifAssist(final File file) { + ExifInterface exif = null; + Throwable error = null; + try { + exif = new ExifInterface(file); + } catch (Throwable e) { + LogPrintUtils.eTag( + TAG, e, "ExifAssist - File %s", + FileUtils.getAbsolutePath(file) + ); + error = e; + } + mExif = exif; + mExifError = error; + } + + private ExifAssist(final String filePath) { + ExifInterface exif = null; + Throwable error = null; + try { + exif = new ExifInterface(filePath); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "ExifAssist - FilePath %s", filePath); + error = e; + } + mExif = exif; + mExifError = error; + } + + private ExifAssist(final FileDescriptor fd) { + ExifInterface exif = null; + Throwable error = null; + try { + exif = new ExifInterface(fd); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "ExifAssist - FileDescriptor"); + error = e; + } + mExif = exif; + mExifError = error; + } + + private ExifAssist(final InputStream inputStream) { + ExifInterface exif = null; + Throwable error = null; + try { + exif = new ExifInterface(inputStream); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "ExifAssist - InputStream"); + error = e; + } + mExif = exif; + mExifError = error; + } + + private ExifAssist( + final InputStream inputStream, + @ExifInterface.ExifStreamType final int streamType + ) { + ExifInterface exif = null; + Throwable error = null; + try { + exif = new ExifInterface(inputStream, streamType); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "ExifAssist - InputStream, StreamType"); + error = e; + } + mExif = exif; + mExifError = error; + } + + // ========== + // = 静态方法 = + // ========== + + public static ExifAssist get(final ExifInterface exif) { + return new ExifAssist(exif, null); + } + + public static ExifAssist get(final File file) { + return new ExifAssist(file); + } + + public static ExifAssist get(final String filePath) { + return new ExifAssist(filePath); + } + + public static ExifAssist get(final FileDescriptor fd) { + return new ExifAssist(fd); + } + + public static ExifAssist get(final InputStream inputStream) { + return new ExifAssist(inputStream); + } + + public static ExifAssist get( + final InputStream inputStream, + @ExifInterface.ExifStreamType final int streamType + ) { + return new ExifAssist(inputStream, streamType); + } + + public static ExifAssist get(final Uri uri) { + ParcelFileDescriptor pfd = ResourceUtils.openFileDescriptor(uri, "rw"); + FileDescriptor fileDescriptor = null; + if (pfd != null) fileDescriptor = pfd.getFileDescriptor(); + return new ExifAssist(fileDescriptor); + } + + /** + * 创建可获取 EXIF 敏感信息辅助类 + * @param uri 待请求 Uri + * @return {@link ExifAssist} + */ + public static ExifAssist getByRequire(final Uri uri) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + try { + return get(MediaStore.setRequireOriginal(uri)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getByRequire"); + return new ExifAssist(null, e); + } + } + return get(uri); + } + + // = + + /** + * 获取 EXIF 敏感信息, 请求获取原始 Uri + * @param uri 待请求 Uri + * @return Uri + */ + public static Uri requireOriginal(final Uri uri) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + try { + return MediaStore.setRequireOriginal(uri); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "requireOriginal"); + return null; + } + } + return uri; + } + + /** + * 请求 ACCESS_MEDIA_LOCATION 权限并进行通知 + * @param activity {@link Activity} + * @param callback {@link PermissionUtils.PermissionCallback} + * @return {@code true} success, {@code false} fail + */ + public static boolean requestPermission( + final Activity activity, + final PermissionUtils.PermissionCallback callback + ) { + if (activity == null) return false; + if (callback == null) return false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + PermissionUtils.permission( + Manifest.permission.ACCESS_MEDIA_LOCATION + ).callback(callback).request(activity); + } else { + callback.onGranted(); + } + return true; + } + + /** + * 判断是否支持读取的资源类型 + * @param mimeType 资源类型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportedMimeType(final String mimeType) { + if (TextUtils.isEmpty(mimeType)) return false; + return ExifInterface.isSupportedMimeType(mimeType); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 克隆图片 EXIF 读写信息 + * @return {@link ExifAssist} + */ + public ExifAssist clone() { + return new ExifAssist(mExif, mExifError); + } + + /** + * 获取图片 EXIF 操作接口 + * @return {@link ExifInterface} + */ + public ExifInterface getExif() { + return mExif; + } + + /** + * 获取 EXIF 初始化异常信息 + * @return {@link Throwable} + */ + public Throwable getExifError() { + return mExifError; + } + + // = + + /** + * 是否图片 EXIF 为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isExifNull() { + return mExif == null; + } + + /** + * 是否图片 EXIF 不为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isExifNotNull() { + return mExif != null; + } + + /** + * 是否 EXIF 初始化异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isExifError() { + return mExifError != null; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 根据 TAG 获取对应值 + * @param tag TAG 标签 + * @param defaultValue 默认值 + * @return TAG 对应值 + */ + public int getAttributeInt( + final String tag, + final int defaultValue + ) { + if (TextUtils.isEmpty(tag)) return defaultValue; + if (isExifNull()) return defaultValue; + try { + return mExif.getAttributeInt(tag, defaultValue); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttributeInt - %s", tag); + return defaultValue; + } + } + + /** + * 根据 TAG 获取对应值 + * @param tag TAG 标签 + * @param defaultValue 默认值 + * @return TAG 对应值 + */ + public double getAttributeDouble( + final String tag, + final double defaultValue + ) { + if (TextUtils.isEmpty(tag)) return defaultValue; + if (isExifNull()) return defaultValue; + try { + return mExif.getAttributeDouble(tag, defaultValue); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttributeDouble - %s", tag); + return defaultValue; + } + } + + /** + * 根据 TAG 获取对应值 + * @param tag TAG 标签 + * @return TAG 对应值 + */ + public String getAttribute(final String tag) { + if (TextUtils.isEmpty(tag)) return null; + if (isExifNull()) return null; + try { + return mExif.getAttribute(tag); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttribute - %s", tag); + return null; + } + } + + /** + * 根据 TAG 获取对应值 + * @param tag TAG 标签 + * @return TAG 对应值 + */ + public byte[] getAttributeBytes(final String tag) { + if (TextUtils.isEmpty(tag)) return null; + if (isExifNull()) return null; + try { + return mExif.getAttributeBytes(tag); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttributeBytes - %s", tag); + return null; + } + } + + /** + * 根据 TAG 获取对应值 + * @param tag TAG 标签 + * @return TAG 对应值 + */ + public long[] getAttributeRange(final String tag) { + if (TextUtils.isEmpty(tag)) return null; + if (isExifNull()) return null; + try { + return mExif.getAttributeRange(tag); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttributeRange - %s", tag); + return null; + } + } + + /** + * 是否存在指定 TAG 值 + * @param tag TAG 标签 + * @return {@code true} yes, {@code false} no + */ + public boolean hasAttribute(final String tag) { + if (TextUtils.isEmpty(tag)) return false; + if (isExifNull()) return false; + try { + return mExif.hasAttribute(tag); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "hasAttribute - %s", tag); + return false; + } + } + + /** + * 设置对应 TAG 值 + * @param tag TAG 标签 + * @param value 待设置值 + * @return {@code true} success, {@code false} fail + */ + public boolean setAttribute( + final String tag, + final String value + ) { + if (TextUtils.isEmpty(tag)) return false; + if (isExifNull()) return false; + try { + mExif.setAttribute(tag, value); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAttribute - %s : %s", tag, value); + return false; + } + } + + /** + * 将标签数据存储到图片中 ( 最终必须调用 ) + *
+     *     Supported for writing: JPEG, PNG, WebP, DNG
+     *     正确使用是全部设置完成后统一保存, 避免设置一次值调用一次
+     *     因为每次调用 saveAttributes 都会创建一个临时文件写入成功后, 再写到原始文件
+     *     最后才删除临时文件
+     * 
+ * @return {@code true} success, {@code false} fail + */ + public boolean saveAttributes() { + if (isExifNull()) return false; + try { + mExif.saveAttributes(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saveAttributes"); + return false; + } + } + + // = + + /** + * 擦除图像 Exif 信息 ( 全部 ) + * @return {@code true} success, {@code false} fail + */ + public boolean eraseAllExif() { + return eraseExifByList(ExifTag.EXIF_TAGS_ALL); + } + + /** + * 擦除图像 Exif 信息 ( 指定集合 ) + * @param list 待擦除 TAG + * @return {@code true} success, {@code false} fail + */ + public boolean eraseExifByList(final List list) { + if (list == null) return false; + return eraseExifByArray(list.toArray(new String[0])); + } + + /** + * 擦除图像 Exif 信息 ( 指定数组 ) + *
+     *     可擦除指定 Group TAG Exif 信息
+     *     eraseExifByList(ExifTag.IFD_GPS_TAGS)
+     *     也可以擦除指定 TAG Exif 信息
+     *     eraseExifByArray(ExifInterface.TAG_GPS_LATITUDE)
+     * 
+ * @param tags 待擦除 TAG + * @return {@code true} success, {@code false} fail + */ + public boolean eraseExifByArray(final String... tags) { + if (tags != null && isExifNotNull()) { + for (String tag : tags) { + if (tag != null) { + try { + mExif.setAttribute(tag, null); + } catch (Exception ignored) { + } + } + } + return saveAttributes(); + } + return false; + } + + // = + + /** + * 擦除图像所有 GPS 位置信息 + * @return {@code true} success, {@code false} fail + */ + public boolean eraseExifLocation() { + return eraseExifLocation(true); + } + + /** + * 擦除图像所有 GPS 位置信息 + * @param check 是否校验存在定位信息再进行擦除 + * @return {@code true} success, {@code false} fail + */ + public boolean eraseExifLocation(final boolean check) { + if (check && !existLocation()) { + return false; + } + return eraseExifByList(ExifTag.IFD_GPS_TAGS); + } + + // =========== + // = get/set = + // =========== + + /** + * 是否存在 GPS 位置信息 + * @return {@code true} yes, {@code false} no + */ + public boolean existLocation() { + return getLatLong() != null; + } + + /** + * 获取经纬度信息 + * @return [0] = 纬度、[1] = 经度 + */ + public double[] getLatLong() { + if (isExifNull()) return null; + try { + return mExif.getLatLong(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getLatLong"); + return null; + } + } + + /** + * 设置经纬度信息 + * @param latitude 纬度 ( -90.0 - 90 之间 ) + * @param longitude 经度 ( -180.0 - 180.0 之间 ) + * @return {@code true} success, {@code false} fail + */ + public boolean setLatLong( + final double latitude, + final double longitude + ) { + if (isExifNull()) return false; + try { + mExif.setLatLong(latitude, longitude); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setLatLong"); + return false; + } + } + + /** + * 获取 GPS 信息 + *
+     *     API 没有该方法, 通过 {@link #setGpsInfo(Location)} 倒推
+     * 
+ * @return Location + */ + public Location getGpsInfo() { + if (isExifNull()) return null; + try { + String provider = mExif.getAttribute(ExifInterface.TAG_GPS_PROCESSING_METHOD); + double altitude = mExif.getAltitude(0D); + Long dateTime = mExif.getGpsDateTime(); + if (dateTime == null) return null; + + double[] latLong = mExif.getLatLong(); + if (latLong == null) return null; + + String gpsSpeed = mExif.getAttribute(ExifInterface.TAG_GPS_SPEED); + double speed = 0D; + if (gpsSpeed != null) { + String speedKMHR = gpsSpeed.substring(0, gpsSpeed.indexOf("/")); + double speedKMHRD = Double.parseDouble(speedKMHR); + speed = speedKMHRD / TimeUnit.HOURS.toSeconds(1) * 1000D; + } + + Location location = new Location(provider); + location.setAltitude(altitude); + location.setTime(dateTime); + location.setLatitude(latLong[0]); + location.setLongitude(latLong[1]); + location.setSpeed((float) speed); + return location; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getGpsInfo"); + return null; + } + } + + /** + * 设置 GPS 信息 + * @param location Location + * @return {@code true} success, {@code false} fail + */ + public boolean setGpsInfo(final Location location) { + if (location == null) return false; + if (isExifNull()) return false; + try { + mExif.setGpsInfo(location); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setGpsInfo"); + return false; + } + } + + /** + * 获取 GPS 定位时间信息 + * @return GPS 定位时间 + */ + public Long getGpsDateTime() { + if (isExifNull()) return null; + try { + return mExif.getGpsDateTime(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getGpsDateTime"); + return null; + } + } + + /** + * 获取海拔高度信息 ( 单位米 ) + * @param defaultValue 无数据时返回默认值 + * @return 海拔高度信息 ( 单位米 ) + */ + public double getAltitude(final double defaultValue) { + if (isExifNull()) return defaultValue; + try { + return mExif.getAltitude(defaultValue); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAltitude"); + return defaultValue; + } + } + + /** + * 设置海拔高度信息 + * @param altitude 海拔高度值 ( 单位米 ) + * @return {@code true} success, {@code false} fail + */ + public boolean setAltitude(final double altitude) { + if (isExifNull()) return false; + try { + mExif.setAltitude(altitude); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAltitude"); + return false; + } + } + + // = + + /** + * 是否存在缩略图 + * @return {@code true} yes, {@code false} no + */ + public boolean hasThumbnail() { + if (isExifNull()) return false; + try { + return mExif.hasThumbnail(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "hasThumbnail"); + return false; + } + } + + /** + * 是否存在 JPEG 压缩缩略图 + * @return {@code true} yes, {@code false} no + */ + public boolean isThumbnailCompressed() { + if (isExifNull()) return false; + try { + return mExif.isThumbnailCompressed(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isThumbnailCompressed"); + return false; + } + } + + /** + * 获取 JPEG 压缩缩略图 + *
+     *     如果非 JPEG 则返回 null
+     *     可通过 {@link ImageUtils#decodeByteArray(byte[])}
+     * 
+ * @return 压缩缩略图数据 + */ + public byte[] getThumbnail() { + if (isExifNull()) return null; + try { + return mExif.getThumbnail(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getThumbnail"); + return null; + } + } + + /** + * 获取 Exif 缩略图 + *
+     *     不管什么类型, 有的话则返回缩略图数据
+     * 
+ * @return 缩略图数据 + */ + public byte[] getThumbnailBytes() { + if (isExifNull()) return null; + try { + return mExif.getThumbnailBytes(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getThumbnailBytes"); + return null; + } + } + + /** + * 获取 Exif 缩略图 + * @return 缩略图 + */ + public Bitmap getThumbnailBitmap() { + if (isExifNull()) return null; + try { + return mExif.getThumbnailBitmap(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getThumbnailBitmap"); + return null; + } + } + + /** + * 获取缩略图数据偏移量位置和长度信息 + * @return offset and length of thumbnail inside the image file + */ + public long[] getThumbnailRange() { + if (isExifNull()) return null; + try { + return mExif.getThumbnailRange(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getThumbnailRange"); + return null; + } + } + + // = + + /** + * 当前图片是否翻转 + * @return {@code true} yes, {@code false} no + */ + public boolean isFlipped() { + if (isExifNull()) return false; + try { + return mExif.isFlipped(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isFlipped"); + return false; + } + } + + /** + * 进行水平翻转图片 + * @return {@code true} success, {@code false} fail + */ + public boolean flipHorizontally() { + if (isExifNull()) return false; + try { + mExif.flipHorizontally(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "flipHorizontally"); + return false; + } + } + + /** + * 进行垂直翻转图片 + * @return {@code true} success, {@code false} fail + */ + public boolean flipVertically() { + if (isExifNull()) return false; + try { + mExif.flipVertically(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "flipVertically"); + return false; + } + } + + /** + * 获取图片旋转角度 + * @return 图片旋转角度 + */ + public int getRotationDegrees() { + if (isExifNull()) return 0; + try { + return mExif.getRotationDegrees(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getRotationDegrees"); + return 0; + } + } + + /** + * 将图片顺时针旋转给定度数 + * @param degree 旋转角度 ( 必须是 90 整数倍 ) + * @return {@code true} success, {@code false} fail + */ + public boolean rotate(final int degree) { + if (isExifNull()) return false; + try { + mExif.rotate(degree); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "rotate"); + return false; + } + } + + /** + * 重置图片方向为默认方向 + * @return {@code true} success, {@code false} fail + */ + public boolean resetOrientation() { + if (isExifNull()) return false; + try { + mExif.resetOrientation(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "resetOrientation"); + return false; + } + } + + // ============ + // = 获取属性值 = + // ============ + + /** + * 获取 Exif 信息 ( ExifTag Group ) + * @return EXIF Tag Group Value Map + */ + public ExifTag.Group getAttributeByGroup() { + ExifTag.Group group = new ExifTag.Group(); + group.IFD_TIFF_TAGS.putAll(getAttributeByList(ExifTag.IFD_TIFF_TAGS)); + group.IFD_EXIF_TAGS.putAll(getAttributeByList(ExifTag.IFD_EXIF_TAGS)); + group.IFD_GPS_TAGS.putAll(getAttributeByList(ExifTag.IFD_GPS_TAGS)); + group.IFD_INTEROPERABILITY_TAGS.putAll(getAttributeByList(ExifTag.IFD_INTEROPERABILITY_TAGS)); + group.IFD_THUMBNAIL_TAGS.putAll(getAttributeByList(ExifTag.IFD_THUMBNAIL_TAGS)); + group.ORF_MAKER_NOTE_TAGS.putAll(getAttributeByList(ExifTag.ORF_MAKER_NOTE_TAGS)); + group.ORF_CAMERA_SETTINGS_TAGS.putAll(getAttributeByList(ExifTag.ORF_CAMERA_SETTINGS_TAGS)); + group.ORF_IMAGE_PROCESSING_TAGS.putAll(getAttributeByList(ExifTag.ORF_IMAGE_PROCESSING_TAGS)); + group.PEF_TAGS.putAll(getAttributeByList(ExifTag.PEF_TAGS)); + return group; + } + + /** + * 获取 Exif 信息 ( 指定集合 ) + * @param list 待获取 TAG + * @return Exif Value Map + */ + public Map getAttributeByList(final List list) { + if (list == null) return new LinkedHashMap<>(); + return getAttributeByArray(list.toArray(new String[0])); + } + + /** + * 获取 Exif 信息 ( 指定数组 ) + *
+     *     获取单个 TAG 使用
+     *     {@link ExifAssist#getAttribute(String)}
+     * 
+ * @param tags 待获取 TAG + * @return Exif Value Map + */ + public Map getAttributeByArray(final String... tags) { + Map maps = new LinkedHashMap<>(); + if (tags != null && isExifNotNull()) { + for (String tag : tags) { + if (tag != null) { + try { + maps.put(tag, mExif.getAttribute(tag)); + } catch (Exception ignored) { + } + } + } + } + return maps; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifTag.java b/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifTag.java new file mode 100644 index 0000000000..0ed02a10aa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/exif/ExifTag.java @@ -0,0 +1,376 @@ +package dev.utils.app.assist.exif; + +import androidx.exifinterface.media.ExifInterface; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * detail: 图片 EXIF Tag Group 常量类 + * @author Ttt + *
+ *     copy {@link androidx.exifinterface.media.ExifInterface}
+ *     static final ExifTag[][] EXIF_TAGS = new ExifTag[][]
+ *     修改为不可变 List 存储, 如后续库迭代导致 TAG 常量更新不及时
+ *     也可自行执行 new ArrayList<>(list) 再进行 add 使用
+ *     并提 issue 通知更新即可 ( 不定时进行更新全部库 )
+ *     

+ * 有需要设置单独的 TAG 直接使用 ExifInterface.TAG_XXX 即可 + *

+ * 快捷替换正则 + * , [0-9]+[, \w]+[, \w]+\), + *
+ */ +public final class ExifTag { + + private ExifTag() { + } + + /** + * 快捷创建 List 简化 add 操作 + * @param tags TAG 可变数组 + * @return List + */ + public static List asList(final String... tags) { + List list = new ArrayList<>(); + if (tags != null) Collections.addAll(list, tags); + return Collections.unmodifiableList(list); + } + + // List of Exif tag groups + public static final List> EXIF_TAGS; + public static final List EXIF_TAGS_ALL; + + // = + + // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + public static final List IFD_TIFF_TAGS; + + // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + public static final List IFD_EXIF_TAGS; + + // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.6 Tag Support Levels) + public static final List IFD_GPS_TAGS; + + // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + public static final List IFD_INTEROPERABILITY_TAGS; + + // IFD Thumbnail tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + public static final List IFD_THUMBNAIL_TAGS; + + // ORF file tags (See http://www.exiv2.org/tags-olympus.html) + public static final List ORF_MAKER_NOTE_TAGS; + + public static final List ORF_CAMERA_SETTINGS_TAGS; + + public static final List ORF_IMAGE_PROCESSING_TAGS; + + // PEF file tag (See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Pentax.html) + public static final List PEF_TAGS; + + static { + IFD_TIFF_TAGS = asList(INNER.IFD_TIFF_TAGS); + IFD_EXIF_TAGS = asList(INNER.IFD_EXIF_TAGS); + IFD_GPS_TAGS = asList(INNER.IFD_GPS_TAGS); + IFD_INTEROPERABILITY_TAGS = asList(INNER.IFD_INTEROPERABILITY_TAGS); + IFD_THUMBNAIL_TAGS = asList(INNER.IFD_THUMBNAIL_TAGS); + ORF_MAKER_NOTE_TAGS = asList(INNER.ORF_MAKER_NOTE_TAGS); + ORF_CAMERA_SETTINGS_TAGS = asList(INNER.ORF_CAMERA_SETTINGS_TAGS); + ORF_IMAGE_PROCESSING_TAGS = asList(INNER.ORF_IMAGE_PROCESSING_TAGS); + PEF_TAGS = asList(INNER.PEF_TAGS); + + List> lists = new ArrayList<>(); + lists.add(IFD_TIFF_TAGS); + lists.add(IFD_EXIF_TAGS); + lists.add(IFD_GPS_TAGS); + lists.add(IFD_INTEROPERABILITY_TAGS); + lists.add(IFD_THUMBNAIL_TAGS); + lists.add(ORF_MAKER_NOTE_TAGS); + lists.add(ORF_CAMERA_SETTINGS_TAGS); + lists.add(ORF_IMAGE_PROCESSING_TAGS); + lists.add(PEF_TAGS); + EXIF_TAGS = Collections.unmodifiableList(lists); + + List allList = new ArrayList<>(); + for (List list : lists) { + allList.addAll(list); + } + EXIF_TAGS_ALL = Collections.unmodifiableList(allList); + } + + // ============ + // = Exif Map = + // ============ + + /** + * detail: EXIF Tag Group Value Map + * @author Ttt + *
+     *     以 EXIF Tag Group 为分组进行获取各个 Group 值
+     * 
+ */ + public static final class Group { + + public final Map IFD_TIFF_TAGS = new LinkedHashMap<>(); + public final Map IFD_EXIF_TAGS = new LinkedHashMap<>(); + public final Map IFD_GPS_TAGS = new LinkedHashMap<>(); + public final Map IFD_INTEROPERABILITY_TAGS = new LinkedHashMap<>(); + public final Map IFD_THUMBNAIL_TAGS = new LinkedHashMap<>(); + public final Map ORF_MAKER_NOTE_TAGS = new LinkedHashMap<>(); + public final Map ORF_CAMERA_SETTINGS_TAGS = new LinkedHashMap<>(); + public final Map ORF_IMAGE_PROCESSING_TAGS = new LinkedHashMap<>(); + public final Map PEF_TAGS = new LinkedHashMap<>(); + } + + // ================= + // = ExifInterface = + // ================= + + /** + * detail: 内部常量 + * @author Android + */ + private static final class INNER { + + private static final String TAG_THUMBNAIL_ORIENTATION = "ThumbnailOrientation"; + private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer"; + private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer"; + private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer"; + private static final String TAG_SUB_IFD_POINTER = "SubIFDPointer"; + private static final String TAG_ORF_CAMERA_SETTINGS_IFD_POINTER = "CameraSettingsIFDPointer"; + private static final String TAG_ORF_IMAGE_PROCESSING_IFD_POINTER = "ImageProcessingIFDPointer"; + + // Primary image IFD TIFF tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + private static final String[] IFD_TIFF_TAGS = new String[]{ + // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images. + ExifInterface.TAG_NEW_SUBFILE_TYPE, + ExifInterface.TAG_SUBFILE_TYPE, + ExifInterface.TAG_IMAGE_WIDTH, + ExifInterface.TAG_IMAGE_LENGTH, + ExifInterface.TAG_BITS_PER_SAMPLE, + ExifInterface.TAG_COMPRESSION, + ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_STRIP_OFFSETS, + ExifInterface.TAG_ORIENTATION, + ExifInterface.TAG_SAMPLES_PER_PIXEL, + ExifInterface.TAG_ROWS_PER_STRIP, + ExifInterface.TAG_STRIP_BYTE_COUNTS, + ExifInterface.TAG_X_RESOLUTION, + ExifInterface.TAG_Y_RESOLUTION, + ExifInterface.TAG_PLANAR_CONFIGURATION, + ExifInterface.TAG_RESOLUTION_UNIT, + ExifInterface.TAG_TRANSFER_FUNCTION, + ExifInterface.TAG_SOFTWARE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_WHITE_POINT, + ExifInterface.TAG_PRIMARY_CHROMATICITIES, + // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1. + TAG_SUB_IFD_POINTER, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, + ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, + ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, + ExifInterface.TAG_Y_CB_CR_POSITIONING, + ExifInterface.TAG_REFERENCE_BLACK_WHITE, + ExifInterface.TAG_COPYRIGHT, + TAG_EXIF_IFD_POINTER, + TAG_GPS_INFO_IFD_POINTER, + // RW2 file tags + // See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html) + ExifInterface.TAG_RW2_SENSOR_TOP_BORDER, + ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER, + ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER, + ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER, + ExifInterface.TAG_RW2_ISO, + ExifInterface.TAG_RW2_JPG_FROM_RAW, + ExifInterface.TAG_XMP, + }; + + // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + private static final String[] IFD_EXIF_TAGS = new String[]{ + ExifInterface.TAG_EXPOSURE_TIME, + ExifInterface.TAG_F_NUMBER, + ExifInterface.TAG_EXPOSURE_PROGRAM, + ExifInterface.TAG_SPECTRAL_SENSITIVITY, + ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY, + ExifInterface.TAG_OECF, + ExifInterface.TAG_SENSITIVITY_TYPE, + ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY, + ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX, + ExifInterface.TAG_ISO_SPEED, + ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY, + ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ, + ExifInterface.TAG_EXIF_VERSION, + ExifInterface.TAG_DATETIME_ORIGINAL, + ExifInterface.TAG_DATETIME_DIGITIZED, + ExifInterface.TAG_OFFSET_TIME, + ExifInterface.TAG_OFFSET_TIME_ORIGINAL, + ExifInterface.TAG_OFFSET_TIME_DIGITIZED, + ExifInterface.TAG_COMPONENTS_CONFIGURATION, + ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, + ExifInterface.TAG_SHUTTER_SPEED_VALUE, + ExifInterface.TAG_APERTURE_VALUE, + ExifInterface.TAG_BRIGHTNESS_VALUE, + ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + ExifInterface.TAG_MAX_APERTURE_VALUE, + ExifInterface.TAG_SUBJECT_DISTANCE, + ExifInterface.TAG_METERING_MODE, + ExifInterface.TAG_LIGHT_SOURCE, + ExifInterface.TAG_FLASH, + ExifInterface.TAG_FOCAL_LENGTH, + ExifInterface.TAG_SUBJECT_AREA, + ExifInterface.TAG_MAKER_NOTE, + ExifInterface.TAG_USER_COMMENT, + ExifInterface.TAG_SUBSEC_TIME, + ExifInterface.TAG_SUBSEC_TIME_ORIGINAL, + ExifInterface.TAG_SUBSEC_TIME_DIGITIZED, + ExifInterface.TAG_FLASHPIX_VERSION, + ExifInterface.TAG_COLOR_SPACE, + ExifInterface.TAG_PIXEL_X_DIMENSION, + ExifInterface.TAG_PIXEL_Y_DIMENSION, + ExifInterface.TAG_RELATED_SOUND_FILE, + TAG_INTEROPERABILITY_IFD_POINTER, + ExifInterface.TAG_FLASH_ENERGY, + ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + ExifInterface.TAG_SUBJECT_LOCATION, + ExifInterface.TAG_EXPOSURE_INDEX, + ExifInterface.TAG_SENSING_METHOD, + ExifInterface.TAG_FILE_SOURCE, + ExifInterface.TAG_SCENE_TYPE, + ExifInterface.TAG_CFA_PATTERN, + ExifInterface.TAG_CUSTOM_RENDERED, + ExifInterface.TAG_EXPOSURE_MODE, + ExifInterface.TAG_WHITE_BALANCE, + ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM, + ExifInterface.TAG_SCENE_CAPTURE_TYPE, + ExifInterface.TAG_GAIN_CONTROL, + ExifInterface.TAG_CONTRAST, + ExifInterface.TAG_SATURATION, + ExifInterface.TAG_SHARPNESS, + ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + ExifInterface.TAG_IMAGE_UNIQUE_ID, + ExifInterface.TAG_CAMERA_OWNER_NAME, + ExifInterface.TAG_BODY_SERIAL_NUMBER, + ExifInterface.TAG_LENS_SPECIFICATION, + ExifInterface.TAG_LENS_MAKE, + ExifInterface.TAG_LENS_MODEL, + ExifInterface.TAG_GAMMA, + ExifInterface.TAG_DNG_VERSION, + ExifInterface.TAG_DEFAULT_CROP_SIZE + }; + + // Primary image IFD GPS Info tags (See JEITA CP-3451C Section 4.6.6 Tag Support Levels) + private static final String[] IFD_GPS_TAGS = new String[]{ + ExifInterface.TAG_GPS_VERSION_ID, + ExifInterface.TAG_GPS_LATITUDE_REF, + // Allow SRATIONAL to be compatible with apps using wrong format and + // even if it is negative, it may be valid latitude / longitude. + ExifInterface.TAG_GPS_LATITUDE, + ExifInterface.TAG_GPS_LONGITUDE_REF, + ExifInterface.TAG_GPS_LONGITUDE, + ExifInterface.TAG_GPS_ALTITUDE_REF, + ExifInterface.TAG_GPS_ALTITUDE, + ExifInterface.TAG_GPS_TIMESTAMP, + ExifInterface.TAG_GPS_SATELLITES, + ExifInterface.TAG_GPS_STATUS, + ExifInterface.TAG_GPS_MEASURE_MODE, + ExifInterface.TAG_GPS_DOP, + ExifInterface.TAG_GPS_SPEED_REF, + ExifInterface.TAG_GPS_SPEED, + ExifInterface.TAG_GPS_TRACK_REF, + ExifInterface.TAG_GPS_TRACK, + ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + ExifInterface.TAG_GPS_IMG_DIRECTION, + ExifInterface.TAG_GPS_MAP_DATUM, + ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + ExifInterface.TAG_GPS_DEST_LATITUDE, + ExifInterface.TAG_GPS_DEST_LONGITUDE_REF, + ExifInterface.TAG_GPS_DEST_LONGITUDE, + ExifInterface.TAG_GPS_DEST_BEARING_REF, + ExifInterface.TAG_GPS_DEST_BEARING, + ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + ExifInterface.TAG_GPS_DEST_DISTANCE, + ExifInterface.TAG_GPS_PROCESSING_METHOD, + ExifInterface.TAG_GPS_AREA_INFORMATION, + ExifInterface.TAG_GPS_DATESTAMP, + ExifInterface.TAG_GPS_DIFFERENTIAL, + ExifInterface.TAG_GPS_H_POSITIONING_ERROR + }; + // Primary image IFD Interoperability tag (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + private static final String[] IFD_INTEROPERABILITY_TAGS = new String[]{ + ExifInterface.TAG_INTEROPERABILITY_INDEX + }; + // IFD Thumbnail tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) + private static final String[] IFD_THUMBNAIL_TAGS = new String[]{ + // For below two, see TIFF 6.0 Spec Section 3: Bilevel Images. + ExifInterface.TAG_NEW_SUBFILE_TYPE, + ExifInterface.TAG_SUBFILE_TYPE, + ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH, + ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH, + ExifInterface.TAG_BITS_PER_SAMPLE, + ExifInterface.TAG_COMPRESSION, + ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, + ExifInterface.TAG_IMAGE_DESCRIPTION, + ExifInterface.TAG_MAKE, + ExifInterface.TAG_MODEL, + ExifInterface.TAG_STRIP_OFFSETS, + TAG_THUMBNAIL_ORIENTATION, + ExifInterface.TAG_SAMPLES_PER_PIXEL, + ExifInterface.TAG_ROWS_PER_STRIP, + ExifInterface.TAG_STRIP_BYTE_COUNTS, + ExifInterface.TAG_X_RESOLUTION, + ExifInterface.TAG_Y_RESOLUTION, + ExifInterface.TAG_PLANAR_CONFIGURATION, + ExifInterface.TAG_RESOLUTION_UNIT, + ExifInterface.TAG_TRANSFER_FUNCTION, + ExifInterface.TAG_SOFTWARE, + ExifInterface.TAG_DATETIME, + ExifInterface.TAG_ARTIST, + ExifInterface.TAG_WHITE_POINT, + ExifInterface.TAG_PRIMARY_CHROMATICITIES, + // See Adobe PageMaker® 6.0 TIFF Technical Notes, Note 1. + TAG_SUB_IFD_POINTER, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, + ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, + ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, + ExifInterface.TAG_Y_CB_CR_POSITIONING, + ExifInterface.TAG_REFERENCE_BLACK_WHITE, + ExifInterface.TAG_XMP, + ExifInterface.TAG_COPYRIGHT, + TAG_EXIF_IFD_POINTER, + TAG_GPS_INFO_IFD_POINTER, + ExifInterface.TAG_DNG_VERSION, + ExifInterface.TAG_DEFAULT_CROP_SIZE + }; + // ORF file tags (See http://www.exiv2.org/tags-olympus.html) + private static final String[] ORF_MAKER_NOTE_TAGS = new String[]{ + ExifInterface.TAG_ORF_THUMBNAIL_IMAGE, + TAG_ORF_CAMERA_SETTINGS_IFD_POINTER, + TAG_ORF_IMAGE_PROCESSING_IFD_POINTER + }; + private static final String[] ORF_CAMERA_SETTINGS_TAGS = new String[]{ + ExifInterface.TAG_ORF_PREVIEW_IMAGE_START, + ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH + }; + private static final String[] ORF_IMAGE_PROCESSING_TAGS = new String[]{ + ExifInterface.TAG_ORF_ASPECT_FRAME + }; + // PEF file tag (See http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Pentax.html) + private static final String[] PEF_TAGS = new String[]{ + ExifInterface.TAG_COLOR_SPACE + }; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingCommon.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingCommon.java new file mode 100644 index 0000000000..c55e569ab3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingCommon.java @@ -0,0 +1,294 @@ +package dev.utils.app.assist.floating; + +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; + +import dev.utils.app.ViewUtils; +import dev.utils.app.assist.DelayAssist; + +/** + * detail: 悬浮窗通用代码 + * @author Ttt + */ +public class DevFloatingCommon + implements DelayAssist.Callback { + + // 触摸 View + private View mView; + // 触摸事件 + private MotionEvent mEvent; + // 悬浮窗触摸事件接口 + private IFloatingListener mListener; + // 触摸时间 + private long mDownTime = 0L; + // 触摸点记录 + private final PointF mPoint = new PointF(); + // 首次触摸点记录 + private final PointF mFirstPoint = new PointF(); + // 延迟触发辅助类 + private final DelayAssist mDelayAssist = new DelayAssist(this); + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 实时更新方法 + *
+     *     通过 {@link IFloatingTouch#onTouchEvent(View, MotionEvent)} 方法回调
+     *     实时调用此方法进行更新
+     * 
+ * @param view 触摸 View + * @param event 触摸事件 + * @return DevFloatingCommon + */ + public DevFloatingCommon update( + final View view, + final MotionEvent event + ) { + this.mView = view; + this.mEvent = event; + return this; + } + + // =========== + // = get/set = + // =========== + + public View getView() { + return mView; + } + + public MotionEvent getEvent() { + return mEvent; + } + + public IFloatingListener getListener() { + return mListener; + } + + public long getDownTime() { + return mDownTime; + } + + public PointF getPoint() { + return mPoint; + } + + public PointF getFirstPoint() { + return mFirstPoint; + } + + public DelayAssist getDelayAssist() { + return mDelayAssist; + } + + // ============= + // = 事件相关方法 = + // ============= + + /** + * 手势按下 + * @param event 触摸事件 + */ + public void actionDown(final MotionEvent event) { + mPoint.x = event.getRawX(); + mPoint.y = event.getRawY(); + // 首次触摸点记录 + mFirstPoint.x = event.getRawX(); + mFirstPoint.y = event.getRawY(); + + mDownTime = System.currentTimeMillis(); + } + + /** + * 手势移动 + * @param event 触摸事件 + * @return 移动误差值 + */ + public int[] actionMove(final MotionEvent event) { + float x = event.getRawX(); + float y = event.getRawY(); + + int dx = (int) (x - mPoint.x); + int dy = (int) (y - mPoint.y); + + mPoint.x = x; + mPoint.y = y; + return new int[]{dx, dy}; + } + + /** + * 手势抬起 + * @param event 触摸事件 + */ + public void actionUp(final MotionEvent event) { + mDelayAssist.remove(); + } + + // = + + /** + * 悬浮窗 View 点击事件 + * @param view {@link View} + * @param event 触摸事件 + * @param listener 悬浮窗触摸事件 + * @return {@code true} 消费事件, {@code false} 不消费事件 + */ + public boolean onClick( + final View view, + final MotionEvent event, + final IFloatingListener listener + ) { + if (isValidClickByTime(listener)) { + return listener.onClick(view, event, mFirstPoint); + } + return false; + } + + /** + * 悬浮窗 View 长按事件 + * @param view {@link View} + * @param event 触摸事件 + * @param listener 悬浮窗触摸事件 + * @return {@code true} 消费事件, {@code false} 不消费事件 + */ + public boolean onLongClick( + final View view, + final MotionEvent event, + final IFloatingListener listener + ) { + if (isValidLongClickByTime(listener)) { + return listener.onLongClick(view, event, mFirstPoint); + } + return false; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取时间差 ( 当前时间 - 触摸时间 ) + * @return 时间差 + */ + public long getDiffTime() { + return System.currentTimeMillis() - mDownTime; + } + + /** + * 是否有效间隔时间 + * @param time 时间间隔 + * @return {@code true} yes, {@code false} no + */ + public boolean isValidTime(final long time) { + long diffTime = getDiffTime(); + return (time > 0 && diffTime <= time); + } + + /** + * 通过时间判断点击是否有效 + * @param listener 悬浮窗触摸事件 + * @return {@code true} yes, {@code false} no + */ + public boolean isValidClickByTime(final IFloatingListener listener) { + if (listener != null) { + return isValidTime(listener.getClickIntervalTime()); + } + return false; + } + + /** + * 通过时间判断长按是否有效 + * @param listener 悬浮窗触摸事件 + * @return {@code true} yes, {@code false} no + */ + public boolean isValidLongClickByTime(final IFloatingListener listener) { + if (listener != null) { + return isValidTime(listener.getLongClickIntervalTime()); + } + return false; + } + + /** + * 是否有效事件 ( 是否在小范围内移动 ) + * @param event 触摸事件 + * @param firstPoint 首次触摸点记录 + * @return {@code true} yes, {@code false} no + */ + public static boolean isValidEvent( + final MotionEvent event, + final PointF firstPoint + ) { + return isValidEvent(event, firstPoint, 5); + } + + /** + * 是否有效事件 ( 是否在小范围内移动 ) + *
+     *     判断触摸范围是否在误差值内
+     * 
+ * @param event 触摸事件 + * @param firstPoint 首次触摸点记录 + * @param value x、y 误差值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isValidEvent( + final MotionEvent event, + final PointF firstPoint, + final int value + ) { + if (event != null && firstPoint != null && value > 0) { + return ( + Math.abs(event.getRawX() - firstPoint.x) <= value && + Math.abs(event.getRawY() - firstPoint.y) <= value + ); + } + return false; + } + + /** + * 判断触点是否落在该 View 上 + *
+     *     可用于判断是否在悬浮窗内部子 View 上
+     * 
+ * @param event {@link MotionEvent} + * @param view 待判断 {@link View} + * @return {@code true} yes, {@code false} no + */ + public static boolean isTouchInView( + final MotionEvent event, + final View view + ) { + return ViewUtils.isTouchInView(event, view); + } + + // ================= + // = 延时长按校验相关 = + // ================= + + /** + * 开始校验长按 + * @param listener 悬浮窗触摸事件 + */ + public void postLongClick(final IFloatingListener listener) { + mDelayAssist.remove(); + if (listener != null) { + this.mListener = listener; + long time = listener.getLongClickIntervalTime(); + if (time > 0) mDelayAssist.post(); + } + } + + // ======================== + // = DelayAssist.Callback = + // ======================== + + @Override + public void callback(Object object) { + if (mListener != null && mView != null && mEvent != null) { + mListener.onLongClick(mView, mEvent, mFirstPoint); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingEdgeIMPL.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingEdgeIMPL.java new file mode 100644 index 0000000000..8929d16a94 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingEdgeIMPL.java @@ -0,0 +1,133 @@ +package dev.utils.app.assist.floating; + +import android.graphics.Point; +import android.view.View; + +import dev.utils.app.BarUtils; +import dev.utils.app.ScreenUtils; +import dev.utils.app.ViewUtils; + +/** + * detail: DevApp 悬浮窗边缘检测辅助类实现 + * @author Ttt + */ +public class DevFloatingEdgeIMPL + implements IFloatingEdge { + + // 最大显示高度 + private int mMaxHeight = 0; + // 向上边距 ( 可自行定义 ) + private int mMarginTop = 0; + // 向下边距 ( 可自行定义 ) + private int mMarginBottom = 0; + + @Override + public Point calculateEdge( + View view, + int x, + int y + ) { + if (x <= 0) x = 0; + if (y <= 0) y = 0; + + // 宽度 ( X 轴 ) 计算 + int viewWidth = ViewUtils.getWidth(view); + int screenWidth = ScreenUtils.getScreenWidth(); + + // 高度 ( Y 轴 ) 计算 + int viewHeight = ViewUtils.getHeight(view); + int screenHeight = ScreenUtils.getScreenHeight(); + // View 最大显示高度计算 + int viewMaxHeight = Math.max(mMaxHeight, viewHeight); + this.mMaxHeight = viewMaxHeight; + + int diffWidth = screenWidth - viewWidth; + int diffHeight = screenHeight - viewMaxHeight - mMarginTop - mMarginBottom; + + if (x >= diffWidth) x = diffWidth; + if (y >= diffHeight) y = diffHeight; + + return new Point(x, y); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mMaxHeight; + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link DevFloatingEdgeIMPL} + */ + public DevFloatingEdgeIMPL setMaxHeight(final int maxHeight) { + this.mMaxHeight = maxHeight; + return this; + } + + /** + * 获取向上边距 + * @return 向上边距 + */ + public int getMarginTop() { + return mMarginTop; + } + + /** + * 设置向上边距 + * @param margin 向上边距 + * @return {@link DevFloatingEdgeIMPL} + */ + public DevFloatingEdgeIMPL setMarginTop(final int margin) { + this.mMarginTop = margin; + return this; + } + + /** + * 获取向下边距 + * @return 向下边距 + */ + public int getMarginBottom() { + return mMarginBottom; + } + + /** + * 设置向下边距 + * @param margin 向下边距 + * @return {@link DevFloatingEdgeIMPL} + */ + public DevFloatingEdgeIMPL setMarginBottom(final int margin) { + this.mMarginBottom = margin; + return this; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 设置向上边距为状态栏高度 + * @return {@link DevFloatingEdgeIMPL} + */ + public DevFloatingEdgeIMPL setStatusBarHeightMargin() { + return setMarginTop(BarUtils.getStatusBarHeight2()); + } + + /** + * 设置向下边距为底部导航栏高度 + * @return {@link DevFloatingEdgeIMPL} + */ + public DevFloatingEdgeIMPL setNavigationBarHeightMargin() { + if (ScreenUtils.checkDeviceHasNavigationBar()) { + return setMarginBottom(ScreenUtils.getNavigationBarHeight()); + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingListener.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingListener.java new file mode 100644 index 0000000000..51e67c6698 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingListener.java @@ -0,0 +1,56 @@ +package dev.utils.app.assist.floating; + +/** + * detail: 悬浮窗触摸事件接口实现 + * @author Ttt + *
+ *     // 点击间隔时间
+ *     ViewConfiguration.HOVER_TAP_TIMEOUT
+ *     // 长按间隔时间
+ *     ViewConfiguration.DEFAULT_LONG_PRESS_TIMEOUT
+ * 
+ */ +public abstract class DevFloatingListener + implements IFloatingListener { + + // 点击事件间隔时间 + private long mClickIntervalTime = 150L; + // 长按事件间隔时间 + private long mLongClickIntervalTime = 600L; + + /** + * 获取点击事件间隔时间 + * @return 点击事件间隔时间 + */ + @Override + public long getClickIntervalTime() { + return mClickIntervalTime; + } + + /** + * 获取点击事件间隔时间 + * @param time 点击事件间隔时间 + */ + @Override + public void setClickIntervalTime(final long time) { + this.mClickIntervalTime = time; + } + + /** + * 获取长按事件间隔时间 + * @return 长按事件间隔时间 + */ + @Override + public long getLongClickIntervalTime() { + return mLongClickIntervalTime; + } + + /** + * 获取长按事件间隔时间 + * @param time 长按事件间隔时间 + */ + @Override + public void setLongClickIntervalTime(final long time) { + this.mLongClickIntervalTime = time; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL.java new file mode 100644 index 0000000000..66ab2b915b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL.java @@ -0,0 +1,86 @@ +package dev.utils.app.assist.floating; + +import android.view.MotionEvent; +import android.view.View; + +/** + * detail: DevApp 悬浮窗触摸辅助类实现 + * @author Ttt + */ +public abstract class DevFloatingTouchIMPL + implements IFloatingTouch { + + // ========== + // = 具体功能 = + // ========== + + // 悬浮窗通用代码 + private final DevFloatingCommon mCommon = new DevFloatingCommon(); + + @Override + public boolean onTouchEvent( + View view, + MotionEvent event + ) { + mCommon.update(view, event); + if (event != null) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mCommon.actionDown(event); + // 开始校验长按 + mCommon.postLongClick(mListener); + break; + case MotionEvent.ACTION_MOVE: + int[] points = mCommon.actionMove(event); + int dx = points[0]; + int dy = points[1]; + // 更新 View Layout + updateViewLayout(view, dx, dy); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mCommon.actionUp(event); + if (mCommon.onClick(view, event, mListener)) { + return true; + } + break; + } + } + return false; + } + + // ========== + // = 事件相关 = + // ========== + + // 悬浮窗触摸事件接口 + private IFloatingListener mListener; + + /** + * 获取悬浮窗触摸事件接口 + * @return 悬浮窗触摸事件接口 + */ + @Override + public IFloatingListener getFloatingListener() { + return mListener; + } + + /** + * 获取悬浮窗触摸事件接口 + * @param listener 悬浮窗触摸事件接口 + */ + @Override + public void setFloatingListener(final IFloatingListener listener) { + this.mListener = listener; + } + + // = + + /** + * 获取悬浮窗通用代码 + * @return 悬浮窗通用代码 + */ + public DevFloatingCommon getCommon() { + return mCommon; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL2.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL2.java new file mode 100644 index 0000000000..e4f1b95284 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/DevFloatingTouchIMPL2.java @@ -0,0 +1,191 @@ +package dev.utils.app.assist.floating; + +import android.graphics.Point; +import android.view.MotionEvent; +import android.view.View; + +import dev.utils.app.ViewUtils; + +/** + * detail: DevApp 悬浮窗触摸辅助类实现 + * @author Ttt + */ +public class DevFloatingTouchIMPL2 + implements IFloatingTouch { + + public DevFloatingTouchIMPL2() { + this(new DevFloatingEdgeIMPL()); + } + + public DevFloatingTouchIMPL2(final IFloatingEdge floatingEdge) { + this.mFloatingEdge = floatingEdge; + } + + // ========== + // = 具体功能 = + // ========== + + // 悬浮窗通用代码 + private final DevFloatingCommon mCommon = new DevFloatingCommon(); + + @Override + public boolean onTouchEvent( + View view, + MotionEvent event + ) { + mCommon.update(view, event); + if (event != null) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mCommon.actionDown(event); + // 开始校验长按 + mCommon.postLongClick(mListener); + break; + case MotionEvent.ACTION_MOVE: + int[] points = mCommon.actionMove(event); + int dx = points[0]; + int dy = points[1]; + // 更新 View Layout + updateViewLayout(view, dx, dy); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mCommon.actionUp(event); + if (mCommon.onClick(view, event, mListener)) { + return true; + } + break; + } + } + return false; + } + + /** + * 更新 View Layout + * @param view {@link View} + * @param dx 累加 X 轴坐标 + * @param dy 累加 Y 轴坐标 + */ + @Override + public void updateViewLayout( + View view, + int dx, + int dy + ) { + mX += dx; + mY += dy; + + if (mFloatingEdge != null) { + Point edgePoint = mFloatingEdge.calculateEdge(view, mX, mY); + if (edgePoint != null) { + mX = edgePoint.x; + mY = edgePoint.y; + } + } + // 设置边距 + ViewUtils.setMargin(view, mX, mY, 0, 0); + } + + // =========== + // = get/set = + // =========== + + // 当前 X 轴坐标 + private int mX = 0; + // 当前 Y 轴坐标 + private int mY = 0; + + /** + * 获取 X 轴坐标 + * @return X 轴坐标 + */ + public int getX() { + return mX; + } + + /** + * 设置 X 轴坐标 + * @param x X 轴坐标 + * @return DevFloatingTouchIMPL2 + */ + public DevFloatingTouchIMPL2 setX(final int x) { + this.mX = x; + return this; + } + + /** + * 获取 Y 轴坐标 + * @return Y 轴坐标 + */ + public int getY() { + return mY; + } + + /** + * 设置 Y 轴坐标 + * @param y Y 轴坐标 + * @return DevFloatingTouchIMPL2 + */ + public DevFloatingTouchIMPL2 setY(final int y) { + this.mY = y; + return this; + } + + // = + + // 悬浮窗边缘检测接口 + private IFloatingEdge mFloatingEdge; + + /** + * 获取悬浮窗边缘检测接口实现 + * @return IFloatingEdge + */ + public IFloatingEdge getFloatingEdge() { + return mFloatingEdge; + } + + /** + * 设置悬浮窗边缘检测接口实现 + * @param floatingEdge 悬浮窗边缘检测接口 + * @return DevFloatingTouchIMPL2 + */ + public DevFloatingTouchIMPL2 setFloatingEdge(final IFloatingEdge floatingEdge) { + this.mFloatingEdge = floatingEdge; + return this; + } + + // ========== + // = 事件相关 = + // ========== + + // 悬浮窗触摸事件接口 + private IFloatingListener mListener; + + /** + * 获取悬浮窗触摸事件接口 + * @return 悬浮窗触摸事件接口 + */ + @Override + public IFloatingListener getFloatingListener() { + return mListener; + } + + /** + * 获取悬浮窗触摸事件接口 + * @param listener 悬浮窗触摸事件接口 + */ + @Override + public void setFloatingListener(final IFloatingListener listener) { + this.mListener = listener; + } + + // = + + /** + * 获取悬浮窗通用代码 + * @return 悬浮窗通用代码 + */ + public DevFloatingCommon getCommon() { + return mCommon; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist.java new file mode 100644 index 0000000000..d9fb90919a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist.java @@ -0,0 +1,304 @@ +package dev.utils.app.assist.floating; + +import android.app.Activity; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Build; +import android.provider.Settings; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.app.IntentUtils; + +/** + * detail: 悬浮窗管理辅助类 ( 需权限 ) + * @author Ttt + *
+ *     所需权限
+ *     
+ * 
+ */ +public class FloatingWindowManagerAssist { + + // 日志 TAG + private final String TAG = FloatingWindowManagerAssist.class.getSimpleName(); + // 请求 Code + public static final int REQUEST_CODE = 112233; + + // 悬浮窗管理辅助类实现 + private final AssistIMPL IMPL; + + public FloatingWindowManagerAssist() { + this(new DevAssistIMPL()); + } + + public FloatingWindowManagerAssist(final AssistIMPL impl) { + this.IMPL = impl; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取悬浮窗管理辅助类实现 + * @return {@link AssistIMPL} + */ + public AssistIMPL getIMPL() { + return IMPL; + } + + /** + * 获取 WindowManager + * @return {@link WindowManager} + */ + public WindowManager getWindowManager() { + if (IMPL != null) return IMPL.getWindowManager(); + return null; + } + + /** + * 获取 Window LayoutParams + * @return {@link WindowManager.LayoutParams} + */ + public WindowManager.LayoutParams getLayoutParams() { + if (IMPL != null) return IMPL.getLayoutParams(); + return null; + } + + // = + + /** + * 添加悬浮 View + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public boolean addView(final View view) { + return addView(view, getLayoutParams()); + } + + /** + * 添加悬浮 View + * @param view {@link View} + * @param params Window LayoutParams + * @return {@code true} success, {@code false} fail + */ + public boolean addView( + final View view, + final WindowManager.LayoutParams params + ) { + if (view == null || params == null) return false; + WindowManager windowManager = getWindowManager(); + if (windowManager != null) { + try { + view.setLayoutParams(params); + windowManager.addView(view, params); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addView"); + } + } + return false; + } + + /** + * 移除悬浮 View + * @param view {@link View} + * @return {@code true} success, {@code false} fail + */ + public boolean removeView(final View view) { + if (view == null) return false; + WindowManager windowManager = getWindowManager(); + if (windowManager != null) { + try { + windowManager.removeView(view); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeView"); + } + } + return false; + } + + /** + * 更新 View Layout + * @param view {@link View} + * @param params Window LayoutParams + * @return {@code true} success, {@code false} fail + */ + public boolean updateViewLayout( + final View view, + final ViewGroup.LayoutParams params + ) { + if (view == null || params == null) return false; + WindowManager windowManager = getWindowManager(); + if (windowManager != null) { + try { + windowManager.updateViewLayout(view, params); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "updateViewLayout"); + } + } + return false; + } + + /** + * 更新 View Layout + * @param view {@link View} + * @param dx 累加 X 轴坐标 + * @param dy 累加 Y 轴坐标 + * @return {@code true} success, {@code false} fail + */ + public boolean updateViewLayout( + final View view, + final int dx, + final int dy + ) { + if (view != null) { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (layoutParams instanceof WindowManager.LayoutParams) { + WindowManager.LayoutParams params = (WindowManager.LayoutParams) layoutParams; + params.x += dx; + params.y += dy; + updateViewLayout(view, params); + return true; + } + } + return false; + } + + // ========== + // = 静态方法 = + // ========== + + /** + * 是否存在悬浮窗权限 + * @param context {@link Context} + * @return {@code true} yes, {@code false} no + */ + public static boolean canDrawOverlays(final Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (context == null) return false; + return Settings.canDrawOverlays(context); + } + return true; + } + + /** + * 检测是否存在悬浮窗权限并跳转 + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + public static boolean checkOverlayPermission(final Activity activity) { + return checkOverlayPermission(activity, false); + } + + /** + * 检测是否存在悬浮窗权限并跳转 + * @param activity {@link Activity} + * @param start 是否跳转权限开启页面 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkOverlayPermission( + final Activity activity, + final boolean start + ) { + if (canDrawOverlays(activity)) return true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (start) { + AppUtils.startActivityForResult( + activity, IntentUtils.getManageOverlayPermissionIntent(), + REQUEST_CODE + ); + } + } + return false; + } + + /** + * 是否悬浮窗请求回调 code + * @param requestCode 请求 code + * @return {@code true} yes, {@code false} no + */ + public static boolean isOverlayRequestCode(final int requestCode) { + return requestCode == REQUEST_CODE; + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 悬浮窗管理辅助类实现 + * @author Ttt + *
+     *     防止后续版本适配等差异化预留
+     * 
+ */ + public interface AssistIMPL { + + /** + * 获取悬浮窗管理类 + * @return {@link WindowManager} + */ + WindowManager getWindowManager(); + + /** + * 获取添加到悬浮窗 LayoutParams + * @return {@link WindowManager.LayoutParams} + */ + WindowManager.LayoutParams getLayoutParams(); + } + + // = + + /** + * detail: DevApp 悬浮窗管理辅助类实现 + * @author Ttt + */ + public static class DevAssistIMPL + implements AssistIMPL { + + // Window Manager + private WindowManager mWindowManager; + // Window Layout Params + private WindowManager.LayoutParams mLayoutParams; + + @Override + public WindowManager getWindowManager() { + if (mWindowManager == null) { + mWindowManager = AppUtils.getWindowManager(); + } + return mWindowManager; + } + + @Override + public WindowManager.LayoutParams getLayoutParams() { + if (mLayoutParams == null) { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.x = 0; + params.y = 0; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.gravity = Gravity.LEFT | Gravity.TOP; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + } else { + params.type = WindowManager.LayoutParams.TYPE_PHONE; + } + params.format = PixelFormat.RGBA_8888; + params.flags = ( + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + ); + mLayoutParams = params; + } + return mLayoutParams; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist2.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist2.java new file mode 100644 index 0000000000..42de6b49db --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/FloatingWindowManagerAssist2.java @@ -0,0 +1,212 @@ +package dev.utils.app.assist.floating; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import dev.utils.LogPrintUtils; +import dev.utils.app.ViewUtils; + +/** + * detail: 悬浮窗管理辅助类 ( 无需权限依赖 Activity ) + * @author Ttt + *
+ *     添加到 Activity content View 中
+ * 
+ */ +public class FloatingWindowManagerAssist2 + implements IFloatingOperate { + + // 日志 TAG + private final String TAG = FloatingWindowManagerAssist2.class.getSimpleName(); + // 悬浮窗 View Map + private final Map mViewMaps = new HashMap<>(); + // 是否处理悬浮 View 添加操作 + private boolean mNeedsAdd = true; + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 移除悬浮窗 View + * @param floatingActivity 悬浮窗辅助类接口 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean removeFloatingView(final IFloatingActivity floatingActivity) { + String key = getMapFloatingKey(floatingActivity); + if (key != null) { + View view = mViewMaps.remove(key); + ViewUtils.removeSelfFromParent(view); + return true; + } + return false; + } + + /** + * 添加悬浮窗 View + * @param floatingActivity 悬浮窗辅助类接口 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addFloatingView(final IFloatingActivity floatingActivity) { + if (!mNeedsAdd) return false; + String key = getMapFloatingKey(floatingActivity); + if (key != null) { + View view = mViewMaps.get(key); + if (view == null) { + Activity activity = getAttachActivity(floatingActivity); + View contentView = ViewUtils.getContentView(activity); + if (contentView instanceof ViewGroup) { + View floatingView = getMapFloatingView(floatingActivity); + ViewGroup.LayoutParams params = getMapFloatingViewLayoutParams(floatingActivity); + if (floatingView != null && params != null) { + // 添加 View + ViewUtils.addView((ViewGroup) contentView, floatingView, params); + // 存在父布局表示添加成功 + if (floatingView.getParent() != null) { + mViewMaps.put(key, floatingView); + return true; + } + } + } + return true; + } else { + // 更新悬浮窗 View Layout + updateViewLayout(floatingActivity, view); + } + } + return false; + } + + /** + * 移除所有悬浮窗 View + */ + @Override + public void removeAllFloatingView() { + List views = new ArrayList<>(mViewMaps.values()); + mViewMaps.clear(); + Iterator iterator = views.iterator(); + while (iterator.hasNext()) { + ViewUtils.removeSelfFromParent(iterator.next()); + iterator.remove(); + } + } + + /** + * 更新悬浮窗 View Layout + *
+     *     如需更新 View 坐标
+     *     则重写该方法, 搭配 {@link DevFloatingTouchIMPL2} 使用
+     * 
+ * @param floatingActivity 悬浮窗辅助类接口 + * @param view {@link View} + */ + @Override + public void updateViewLayout( + IFloatingActivity floatingActivity, + View view + ) { + } + + // =========== + // = get/set = + // =========== + + /** + * 是否处理悬浮 View 添加操作 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isNeedsAdd() { + return mNeedsAdd; + } + + /** + * 设置是否处理悬浮 View 添加操作 + * @param needsAdd {@code true} yes, {@code false} no + */ + @Override + public void setNeedsAdd(final boolean needsAdd) { + this.mNeedsAdd = needsAdd; + } + + // ========== + // = 内部方法 = + // ========== + + // ===================== + // = IFloatingActivity = + // ===================== + + /** + * 获取悬浮窗依附的 Activity + * @param floatingActivity 悬浮窗辅助类接口 + * @return {@link Activity} + */ + private Activity getAttachActivity(final IFloatingActivity floatingActivity) { + if (floatingActivity != null) { + try { + return floatingActivity.getAttachActivity(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAttachActivity"); + } + } + return null; + } + + /** + * 获取悬浮窗 Map Key + * @param floatingActivity 悬浮窗辅助类接口 + * @return 悬浮窗 Map Key + */ + private String getMapFloatingKey(final IFloatingActivity floatingActivity) { + if (floatingActivity != null) { + try { + return floatingActivity.getMapFloatingKey(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMapFloatingKey"); + } + } + return null; + } + + /** + * 获取悬浮窗 Map Value View + * @param floatingActivity 悬浮窗辅助类接口 + * @return 悬浮窗 Map Value View + */ + private View getMapFloatingView(final IFloatingActivity floatingActivity) { + if (floatingActivity != null) { + try { + return floatingActivity.getMapFloatingView(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMapFloatingView"); + } + } + return null; + } + + /** + * 获取悬浮窗 View LayoutParams + * @param floatingActivity 悬浮窗辅助类接口 + * @return 悬浮窗 View LayoutParams + */ + private ViewGroup.LayoutParams getMapFloatingViewLayoutParams(final IFloatingActivity floatingActivity) { + if (floatingActivity != null) { + try { + return floatingActivity.getMapFloatingViewLayoutParams(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getMapFloatingViewLayoutParams"); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingActivity.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingActivity.java new file mode 100644 index 0000000000..9db481ba01 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingActivity.java @@ -0,0 +1,39 @@ +package dev.utils.app.assist.floating; + +import android.app.Activity; +import android.view.View; +import android.view.ViewGroup; + +/** + * detail: 悬浮窗辅助类接口 + * @author Ttt + *
+ *     {@link FloatingWindowManagerAssist2} 所需接口方法
+ * 
+ */ +public interface IFloatingActivity { + + /** + * 获取悬浮窗依附的 Activity + * @return {@link Activity} + */ + Activity getAttachActivity(); + + /** + * 获取悬浮窗 Map Key + * @return 悬浮窗 Map Key + */ + String getMapFloatingKey(); + + /** + * 获取悬浮窗 Map Value View + * @return 悬浮窗 Map Value View + */ + View getMapFloatingView(); + + /** + * 获取悬浮窗 View LayoutParams + * @return 悬浮窗 View LayoutParams + */ + ViewGroup.LayoutParams getMapFloatingViewLayoutParams(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingEdge.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingEdge.java new file mode 100644 index 0000000000..b8157330f2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingEdge.java @@ -0,0 +1,29 @@ +package dev.utils.app.assist.floating; + +import android.graphics.Point; +import android.view.View; + +/** + * detail: 悬浮窗边缘检测接口 + * @author Ttt + *
+ *     {@link FloatingWindowManagerAssist2}
+ *     {@link DevFloatingTouchIMPL2}
+ *     悬浮窗触摸使用
+ * 
+ */ +public interface IFloatingEdge { + + /** + * 计算悬浮窗边缘检测坐标 + * @param view 待计算 View + * @param x 待变更 X 轴坐标 + * @param y 待变更 Y 轴坐标 + * @return 计算处理后的坐标值 + */ + Point calculateEdge( + View view, + int x, + int y + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingListener.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingListener.java new file mode 100644 index 0000000000..a3c326b5ad --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingListener.java @@ -0,0 +1,68 @@ +package dev.utils.app.assist.floating; + +import android.graphics.PointF; +import android.view.MotionEvent; +import android.view.View; + +/** + * detail: 悬浮窗触摸事件接口 + * @author Ttt + *
+ *     用于解决 onTouchEvent 与 click、longClick 冲突处理
+ *     可搭配 {@link dev.utils.app.ViewUtils#isTouchInView(MotionEvent, View)} 判断
+ * 
+ */ +public interface IFloatingListener { + + /** + * 悬浮窗 View 点击事件 + * @param view {@link View} + * @param event 触摸事件 + * @param firstPoint 首次触摸点记录 + * @return {@code true} 消费事件, {@code false} 不消费事件 + */ + boolean onClick( + View view, + MotionEvent event, + PointF firstPoint + ); + + /** + * 悬浮窗 View 长按事件 + * @param view {@link View} + * @param event 触摸事件 + * @param firstPoint 首次触摸点记录 + * @return {@code true} 消费事件, {@code false} 不消费事件 + */ + boolean onLongClick( + View view, + MotionEvent event, + PointF firstPoint + ); + + // = + + /** + * 获取点击事件间隔时间 + * @return 点击事件间隔时间 + */ + long getClickIntervalTime(); + + /** + * 获取点击事件间隔时间 + * @param time 点击事件间隔时间 + */ + void setClickIntervalTime(long time); + + /** + * 获取长按事件间隔时间 + * @return 长按事件间隔时间 + */ + long getLongClickIntervalTime(); + + /** + * 获取长按事件间隔时间 + * @param time 长按事件间隔时间 + */ + void setLongClickIntervalTime(long time); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingOperate.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingOperate.java new file mode 100644 index 0000000000..188c6d2034 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingOperate.java @@ -0,0 +1,54 @@ +package dev.utils.app.assist.floating; + +import android.view.View; + +/** + * detail: 悬浮窗操作辅助类接口 + * @author Ttt + *
+ *     {@link FloatingWindowManagerAssist2} 所需接口方法
+ * 
+ */ +public interface IFloatingOperate { + + /** + * 移除悬浮窗 View + * @param floatingActivity 悬浮窗辅助类接口 + * @return {@code true} success, {@code false} fail + */ + boolean removeFloatingView(IFloatingActivity floatingActivity); + + /** + * 添加悬浮窗 View + * @param floatingActivity 悬浮窗辅助类接口 + * @return {@code true} success, {@code false} fail + */ + boolean addFloatingView(IFloatingActivity floatingActivity); + + /** + * 移除所有悬浮窗 View + */ + void removeAllFloatingView(); + + /** + * 更新悬浮窗 View Layout + * @param floatingActivity 悬浮窗辅助类接口 + * @param view {@link View} + */ + void updateViewLayout( + IFloatingActivity floatingActivity, + View view + ); + + /** + * 是否处理悬浮 View 添加操作 + * @return {@code true} yes, {@code false} no + */ + boolean isNeedsAdd(); + + /** + * 设置是否处理悬浮 View 添加操作 + * @param needsAdd {@code true} yes, {@code false} no + */ + void setNeedsAdd(boolean needsAdd); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingTouch.java b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingTouch.java new file mode 100644 index 0000000000..65bdc89faf --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/floating/IFloatingTouch.java @@ -0,0 +1,50 @@ +package dev.utils.app.assist.floating; + +import android.view.MotionEvent; +import android.view.View; + +/** + * detail: 悬浮窗触摸辅助类接口 + * @author Ttt + */ +public interface IFloatingTouch { + + /** + * 悬浮窗 View 触摸事件 + * @param view {@link View} + * @param event 触摸事件 + * @return {@code true} 消费事件, {@code false} 不消费事件 + */ + boolean onTouchEvent( + View view, + MotionEvent event + ); + + /** + * 更新 View Layout + * @param view {@link View} + * @param dx 累加 X 轴坐标 + * @param dy 累加 Y 轴坐标 + */ + void updateViewLayout( + View view, + int dx, + int dy + ); + + // ========== + // = 事件相关 = + // ========== + + /** + * 获取悬浮窗触摸事件接口 + * @return 悬浮窗触摸事件接口 + */ + IFloatingListener getFloatingListener(); + + /** + * 获取悬浮窗触摸事件接口 + * @param listener 悬浮窗触摸事件接口 + */ + void setFloatingListener(IFloatingListener listener); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/AbstractActivityLifecycle.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/AbstractActivityLifecycle.java new file mode 100644 index 0000000000..00884108d5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/AbstractActivityLifecycle.java @@ -0,0 +1,47 @@ +package dev.utils.app.assist.lifecycle; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +/** + * detail: ActivityLifecycleCallbacks 抽象类 + * @author Ttt + */ +public abstract class AbstractActivityLifecycle + implements Application.ActivityLifecycleCallbacks { + + @Override + public void onActivityCreated( + Activity activity, + Bundle savedInstanceState + ) { + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState( + Activity activity, + Bundle outState + ) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleAssist.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleAssist.java new file mode 100644 index 0000000000..d29f8bd623 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleAssist.java @@ -0,0 +1,556 @@ +package dev.utils.app.assist.lifecycle; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.KeyBoardUtils; +import dev.utils.app.permission.PermissionUtils; + +/** + * detail: Activity 生命周期辅助类 + * @author Ttt + *
+ *     必须主动调用 {@link #registerActivityLifecycleCallbacks()} 方法进行注册监听
+ * 
+ */ +public final class ActivityLifecycleAssist { + + // 日志 TAG + private static final String TAG = ActivityLifecycleAssist.class.getSimpleName(); + + // Application 对象 + private final Application mApplication; + + // ========== + // = 构造函数 = + // ========== + + public ActivityLifecycleAssist() { + this(DevUtils.getContext()); + } + + public ActivityLifecycleAssist(final Context context) { + this(getApplication(context)); + } + + public ActivityLifecycleAssist(final Application application) { + this.mApplication = application; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Application + * @param context {@link Context} + * @return {@link Application} + */ + private static Application getApplication(final Context context) { + if (context == null) return null; + try { + return (Application) context.getApplicationContext(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApplication"); + } + return null; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Activity 生命周期 相关信息获取接口类 + * @return {@link ActivityLifecycleGet} + */ + public ActivityLifecycleGet getActivityLifecycleGet() { + return ACTIVITY_LIFECYCLE; + } + + /** + * 获取 Activity 生命周期 事件监听接口类 + * @return {@link ActivityLifecycleNotify} + */ + public ActivityLifecycleNotify getActivityLifecycleNotify() { + return ACTIVITY_LIFECYCLE; + } + + /** + * 获取 Top Activity + * @return {@link Activity} + */ + public Activity getTopActivity() { + return ACTIVITY_LIFECYCLE.getTopActivity(); + } + + /** + * 设置 Activity 生命周期 过滤判断接口 + * @param activityLifecycleFilter Activity 过滤判断接口 + * @return {@link ActivityLifecycleAssist} + */ + public ActivityLifecycleAssist setActivityLifecycleFilter(final ActivityLifecycleFilter activityLifecycleFilter) { + this.mActivityLifecycleFilter = activityLifecycleFilter; + return this; + } + + /** + * 设置 ActivityLifecycle 监听回调 + * @param abstractActivityLifecycle Activity 生命周期监听类 + * @return {@link ActivityLifecycleAssist} + */ + public ActivityLifecycleAssist setAbstractActivityLifecycle(final AbstractActivityLifecycle abstractActivityLifecycle) { + this.mAbstractActivityLifecycle = abstractActivityLifecycle; + return this; + } + + /** + * 注册绑定 Activity 生命周期事件处理 + * @return {@link ActivityLifecycleAssist} + */ + public ActivityLifecycleAssist registerActivityLifecycleCallbacks() { + // 先移除监听 + unregisterActivityLifecycleCallbacks(); + if (mApplication != null) { + try { + mApplication.registerActivityLifecycleCallbacks(ACTIVITY_LIFECYCLE); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "registerActivityLifecycleCallbacks"); + } + } + return this; + } + + /** + * 解除注册 Activity 生命周期事件处理 + * @return {@link ActivityLifecycleAssist} + */ + public ActivityLifecycleAssist unregisterActivityLifecycleCallbacks() { + if (mApplication != null) { + try { + mApplication.unregisterActivityLifecycleCallbacks(ACTIVITY_LIFECYCLE); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregisterActivityLifecycleCallbacks"); + } + } + return this; + } + + // ================ + // = Activity 监听 = + // ================ + + // Activity 过滤判断接口 + private ActivityLifecycleFilter mActivityLifecycleFilter; + // ActivityLifecycleCallbacks 抽象类 + private AbstractActivityLifecycle mAbstractActivityLifecycle; + // ActivityLifecycleCallbacks 实现类, 监听 Activity + private final ActivityLifecycleImpl ACTIVITY_LIFECYCLE = new ActivityLifecycleImpl(); + // 内部 Activity 生命周期过滤处理 + private final ActivityLifecycleFilter ACTIVITY_LIFECYCLE_FILTER = new ActivityLifecycleFilter() { + @Override + public boolean filter(Activity activity) { + if (activity != null) { + if (PermissionUtils.PERMISSION_ACTIVITY_CLASS_NAME.equals(activity.getClass().getName())) { + // 如果相同则不处理 ( 该页面为内部权限框架, 申请权限页面 ) + return true; + } else { + if (mActivityLifecycleFilter != null) { + return mActivityLifecycleFilter.filter(activity); + } + } + } + return false; + } + }; + + /** + * detail: 对 Activity 的生命周期事件进行集中处理, ActivityLifecycleCallbacks 实现方法 + * @author Ttt + */ + private class ActivityLifecycleImpl + implements Application.ActivityLifecycleCallbacks, + ActivityLifecycleGet, + ActivityLifecycleNotify { + + // 保存未销毁的 Activity + private final LinkedList mActivityLists = new LinkedList<>(); + // APP 状态改变事件 + private final Map mStatusListenerMaps = new ConcurrentHashMap<>(); + // Activity 销毁事件 + private final Map> mDestroyedListenerMaps = new ConcurrentHashMap<>(); + + // 前台 Activity 总数 + private int mForegroundCount = 0; + // Activity Configuration 改变次数 + private int mConfigCount = 0; + // 是否后台 Activity + private boolean mBackground = false; + + // ============================== + // = ActivityLifecycleCallbacks = + // ============================== + + @Override + public void onActivityCreated( + Activity activity, + Bundle savedInstanceState + ) { + setTopActivity(activity); + + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityCreated(activity, savedInstanceState); + } + } + + @Override + public void onActivityStarted(Activity activity) { + if (!mBackground) { + setTopActivity(activity); + } + if (mConfigCount < 0) { + ++mConfigCount; + } else { + ++mForegroundCount; + } + + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityStarted(activity); + } + } + + @Override + public void onActivityResumed(Activity activity) { + setTopActivity(activity); + // Activity 准备可见, 设置为非后台 Activity + if (mBackground) { + mBackground = false; + postStatus(true); + } + + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityResumed(activity); + } + } + + @Override + public void onActivityPaused(Activity activity) { + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityPaused(activity); + } + } + + @Override + public void onActivityStopped(Activity activity) { + // 检测当前的 Activity 是否因为 Configuration 的改变被销毁了 + if (activity.isChangingConfigurations()) { + --mConfigCount; + } else { + --mForegroundCount; + if (mForegroundCount <= 0) { + mBackground = true; + postStatus(false); + } + } + + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityStopped(activity); + } + } + + @Override + public void onActivitySaveInstanceState( + Activity activity, + Bundle outState + ) { + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivitySaveInstanceState(activity, outState); + } + } + + @Override + public void onActivityDestroyed(Activity activity) { + mActivityLists.remove(activity); + // 通知 Activity 销毁 + consumeOnActivityDestroyedListener(activity); + // 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 + KeyBoardUtils.fixSoftInputLeaks(activity); + + if (mAbstractActivityLifecycle != null) { + mAbstractActivityLifecycle.onActivityDestroyed(activity); + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 保存 Activity 栈顶 + * @param activity {@link Activity} + */ + private void setTopActivity(final Activity activity) { + if (activity == null) return; + // 判断是否过滤 Activity + if (ACTIVITY_LIFECYCLE_FILTER.filter(activity)) return; + // 判断是否已经包含该 Activity + if (mActivityLists.contains(activity)) { + if (!activity.equals(mActivityLists.getLast())) { + mActivityLists.remove(activity); + mActivityLists.addLast(activity); + } + } else { + mActivityLists.addLast(activity); + } + } + + /** + * 反射获取栈顶 Activity + * @return {@link Activity} + */ + private Activity getTopActivityByReflect() { + try { + @SuppressLint("PrivateApi") + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Object activityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null); + Field activitiesField = activityThreadClass.getDeclaredField("mActivityLists"); + activitiesField.setAccessible(true); + Map activities = (Map) activitiesField.get(activityThread); + if (activities == null) return null; + for (Object activityRecord : activities.values()) { + Class activityRecordClass = activityRecord.getClass(); + Field pausedField = activityRecordClass.getDeclaredField("paused"); + pausedField.setAccessible(true); + if (!pausedField.getBoolean(activityRecord)) { + Field activityField = activityRecordClass.getDeclaredField("activity"); + activityField.setAccessible(true); + return (Activity) activityField.get(activityRecord); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTopActivityByReflect"); + } + return null; + } + + // ============================ + // = ActivityLifecycleGet 方法 = + // ============================ + + /** + * 获取最顶部 ( 当前或最后一个显示 ) Activity + * @return {@link Activity} + */ + @Override + public Activity getTopActivity() { + if (!mActivityLists.isEmpty()) { + final Activity topActivity = mActivityLists.getLast(); + if (topActivity != null) { + return topActivity; + } + } + Activity topActivityByReflect = getTopActivityByReflect(); + if (topActivityByReflect != null) { + setTopActivity(topActivityByReflect); + } + return topActivityByReflect; + } + + /** + * 判断某个 Activity 是否 Top Activity + * @param activityClassName Activity.class.getCanonicalName() + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTopActivity(final String activityClassName) { + if (!TextUtils.isEmpty(activityClassName)) { + try { + Activity activity = getTopActivity(); + if (activity == null) return false; + // 判断是否类是否一致 + return activityClassName.equals( + activity.getClass().getCanonicalName() + ); + } catch (Exception ignored) { + } + } + return false; + } + + /** + * 判断某个 Class(Activity) 是否 Top Activity + * @param clazz Activity.class or this.getClass() + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTopActivity(final Class clazz) { + if (clazz != null) { + try { + Activity activity = getTopActivity(); + if (activity == null || activity.getClass().getCanonicalName() == null) { + return false; + } + // 判断是否类是否一致 + return clazz.getCanonicalName().equals( + activity.getClass().getCanonicalName() + ); + } catch (Exception ignored) { + } + } + return false; + } + + /** + * 判断应用是否在后台 ( 不可见 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isBackground() { + return mBackground; + } + + /** + * 获取 Activity 总数 + * @return 已打开 Activity 总数 + */ + @Override + public int getActivityCount() { + return mActivityLists.size(); + } + + // =========================== + // = ActivityLifecycleNotify = + // =========================== + + /** + * 添加 APP 状态改变事件监听 + * @param object key + * @param listener APP 状态改变监听事件 + */ + @Override + public void addOnAppStatusChangedListener( + final Object object, + final OnAppStatusChangedListener listener + ) { + mStatusListenerMaps.put(object, listener); + } + + /** + * 移除 APP 状态改变事件监听 + * @param object key + */ + @Override + public void removeOnAppStatusChangedListener(final Object object) { + mStatusListenerMaps.remove(object); + } + + /** + * 移除全部 APP 状态改变事件监听 + */ + @Override + public void removeAllOnAppStatusChangedListener() { + mStatusListenerMaps.clear(); + } + + // = + + /** + * 添加 Activity 销毁通知事件 + * @param activity {@link Activity} + * @param listener Activity 销毁通知事件 + */ + @Override + public void addOnActivityDestroyedListener( + final Activity activity, + final OnActivityDestroyedListener listener + ) { + if (activity == null || listener == null) return; + Set listeners; + if (!mDestroyedListenerMaps.containsKey(activity)) { + listeners = new HashSet<>(); + mDestroyedListenerMaps.put(activity, listeners); + } else { + listeners = mDestroyedListenerMaps.get(activity); + if (listeners.contains(listener)) return; + } + listeners.add(listener); + } + + /** + * 移除 Activity 销毁通知事件 + * @param activity {@link Activity} + */ + @Override + public void removeOnActivityDestroyedListener(final Activity activity) { + if (activity == null) return; + mDestroyedListenerMaps.remove(activity); + } + + /** + * 移除全部 Activity 销毁通知事件 + */ + @Override + public void removeAllOnActivityDestroyedListener() { + mDestroyedListenerMaps.clear(); + } + + // ============= + // = 事件通知相关 = + // ============= + + /** + * 发送状态改变通知 + * @param isForeground 是否在前台 + */ + private void postStatus(final boolean isForeground) { + if (mStatusListenerMaps.isEmpty()) return; + // 保存到新的集合, 防止 ConcurrentModificationException + List lists = new ArrayList<>(mStatusListenerMaps.values()); + // 遍历通知 + for (OnAppStatusChangedListener listener : lists) { + if (listener != null) { + if (isForeground) { + listener.onForeground(); + } else { + listener.onBackground(); + } + } + } + } + + /** + * 通知 Activity 销毁, 并且消费 ( 移除 ) 监听事件 + * @param activity {@link Activity} + */ + private void consumeOnActivityDestroyedListener(final Activity activity) { + try { + // 保存到新的集合, 防止 ConcurrentModificationException + Set sets = new HashSet<>(mDestroyedListenerMaps.get(activity)); + // 遍历通知 + for (OnActivityDestroyedListener listener : sets) { + if (listener != null) { + listener.onActivityDestroyed(activity); + } + } + } catch (Exception ignored) { + } + // 移除已消费的事件 + removeOnActivityDestroyedListener(activity); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleFilter.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleFilter.java new file mode 100644 index 0000000000..0d9de5b2ca --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleFilter.java @@ -0,0 +1,17 @@ +package dev.utils.app.assist.lifecycle; + +import android.app.Activity; + +/** + * detail: Activity 生命周期 过滤判断接口 + * @author Ttt + */ +public interface ActivityLifecycleFilter { + + /** + * 判断是否过滤该类 ( 不进行添加等操作 ) + * @param activity {@link Activity} + * @return {@code true} yes, {@code false} no + */ + boolean filter(Activity activity); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleGet.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleGet.java new file mode 100644 index 0000000000..8a029b9552 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleGet.java @@ -0,0 +1,42 @@ +package dev.utils.app.assist.lifecycle; + +import android.app.Activity; + +/** + * detail: Activity 生命周期 相关信息获取接口 + * @author Ttt + */ +public interface ActivityLifecycleGet { + + /** + * 获取最顶部 ( 当前或最后一个显示 ) Activity + * @return {@link Activity} + */ + Activity getTopActivity(); + + /** + * 判断某个 Activity 是否 Top Activity + * @param activityClassName Activity.class.getCanonicalName() + * @return {@code true} yes, {@code false} no + */ + boolean isTopActivity(String activityClassName); + + /** + * 判断某个 Class(Activity) 是否 Top Activity + * @param clazz Activity.class or this.getClass() + * @return {@code true} yes, {@code false} no + */ + boolean isTopActivity(Class clazz); + + /** + * 判断应用是否在后台 ( 不可见 ) + * @return {@code true} yes, {@code false} no + */ + boolean isBackground(); + + /** + * 获取 Activity 总数 + * @return 已打开 Activity 总数 + */ + int getActivityCount(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleNotify.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleNotify.java new file mode 100644 index 0000000000..f53703d4ce --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/ActivityLifecycleNotify.java @@ -0,0 +1,54 @@ +package dev.utils.app.assist.lifecycle; + +import android.app.Activity; + +/** + * detail: Activity 生命周期 通知接口 + * @author Ttt + */ +public interface ActivityLifecycleNotify { + + /** + * 添加 APP 状态改变事件监听 + * @param object key + * @param listener APP 状态改变监听事件 + */ + void addOnAppStatusChangedListener( + Object object, + OnAppStatusChangedListener listener + ); + + /** + * 移除 APP 状态改变事件监听 + * @param object key + */ + void removeOnAppStatusChangedListener(Object object); + + /** + * 移除全部 APP 状态改变事件监听 + */ + void removeAllOnAppStatusChangedListener(); + + // = + + /** + * 添加 Activity 销毁通知事件 + * @param activity {@link Activity} + * @param listener Activity 销毁通知事件 + */ + void addOnActivityDestroyedListener( + Activity activity, + OnActivityDestroyedListener listener + ); + + /** + * 移除 Activity 销毁通知事件 + * @param activity {@link Activity} + */ + void removeOnActivityDestroyedListener(Activity activity); + + /** + * 移除全部 Activity 销毁通知事件 + */ + void removeAllOnActivityDestroyedListener(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnActivityDestroyedListener.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnActivityDestroyedListener.java new file mode 100644 index 0000000000..4e130926f6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnActivityDestroyedListener.java @@ -0,0 +1,16 @@ +package dev.utils.app.assist.lifecycle; + +import android.app.Activity; + +/** + * detail: Activity 销毁事件 + * @author Ttt + */ +public interface OnActivityDestroyedListener { + + /** + * Activity 销毁通知 + * @param activity {@link Activity} + */ + void onActivityDestroyed(Activity activity); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnAppStatusChangedListener.java b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnAppStatusChangedListener.java new file mode 100644 index 0000000000..b8e9b2466b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/lifecycle/OnAppStatusChangedListener.java @@ -0,0 +1,18 @@ +package dev.utils.app.assist.lifecycle; + +/** + * detail: APP 状态改变事件 + * @author Ttt + */ +public interface OnAppStatusChangedListener { + + /** + * 切换到前台 + */ + void onForeground(); + + /** + * 切换到后台 + */ + void onBackground(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/record/AppRecordInsert.java b/lib/DevApp/src/main/java/dev/utils/app/assist/record/AppRecordInsert.java new file mode 100644 index 0000000000..1c8351eb75 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/record/AppRecordInsert.java @@ -0,0 +1,122 @@ +package dev.utils.app.assist.record; + +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.app.AppUtils; +import dev.utils.app.DeviceUtils; +import dev.utils.app.ManifestUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.assist.record.RecordInsert; + +/** + * detail: App 日志记录插入信息 + * @author Ttt + */ +public class AppRecordInsert + extends RecordInsert { + + // 是否每次都创建新的 FileInfo + private final boolean mEveryCreate; + + public AppRecordInsert(boolean everyCreate) { + super(null); + this.mEveryCreate = everyCreate; + } + + // = + + @Override + public final RecordInsert setFileInfo(String fileInfo) { + return this; + } + + @Override + public String getFileInfo() { + if (mEveryCreate) return createFileInfo(); + if (StringUtils.isNotEmpty(mFileInfo)) return mFileInfo; + mFileInfo = createFileInfo(); + return mFileInfo; + } + + // ========== + // = 配置信息 = + // ========== + + // APP 版本名 ( 主要用于对用户显示版本信息 ) + private String APP_VERSION_NAME = ""; + // APP 版本号 + private String APP_VERSION_CODE = ""; + // 应用包名 + private String PACKAGE_NAME = ""; + // 设备信息 + private String DEVICE_INFO_STR = null; + // 设备信息存储 Map + private final Map DEVICE_INFO_MAPS = new HashMap<>(); + + private String createFileInfo() { + // 如果版本信息为 null, 才进行处理 + if (TextUtils.isEmpty(APP_VERSION_NAME) || TextUtils.isEmpty(APP_VERSION_CODE)) { + // 获取 APP 版本信息 + String[] versions = ManifestUtils.getAppVersion(); + // 防止为 null + if (versions != null && versions.length == 2) { + // 保存 APP 版本信息 + APP_VERSION_NAME = versions[0]; + APP_VERSION_CODE = versions[1]; + } + } + + // 获取包名 + if (TextUtils.isEmpty(PACKAGE_NAME)) { + PACKAGE_NAME = AppUtils.getPackageName(); + } + + int deviceInfoSize = DEVICE_INFO_MAPS.size(); + // 判断是否存在设备信息 + if (TextUtils.isEmpty(DEVICE_INFO_STR) || deviceInfoSize == 0) { + if (deviceInfoSize == 0) { + // 获取设备信息 + DeviceUtils.getDeviceInfo(DEVICE_INFO_MAPS); + } + if (TextUtils.isEmpty(DEVICE_INFO_STR)) { + // 拼接设备信息 + DEVICE_INFO_STR = DeviceUtils.handlerDeviceInfo( + DEVICE_INFO_MAPS, "failed to get device information" + ); + } + } + + // ========== + // = 拼接数据 = + // ========== + + return DevFinal.SYMBOL.NEW_LINE_X2 + + "[设备信息]" + + DevFinal.SYMBOL.NEW_LINE_X2 + + "===========================" + + DevFinal.SYMBOL.NEW_LINE_X2 + + DEVICE_INFO_STR + + DevFinal.SYMBOL.NEW_LINE + + "===========================" + + DevFinal.SYMBOL.NEW_LINE_X4 + + "[版本信息]" + + DevFinal.SYMBOL.NEW_LINE_X2 + + "===========================" + + DevFinal.SYMBOL.NEW_LINE_X2 + + "versionName: " + APP_VERSION_NAME + + DevFinal.SYMBOL.NEW_LINE + + "versionCode: " + APP_VERSION_CODE + + DevFinal.SYMBOL.NEW_LINE + + "package: " + PACKAGE_NAME + + DevFinal.SYMBOL.NEW_LINE_X2 + + "===========================" + + DevFinal.SYMBOL.NEW_LINE_X4 + + "[日志内容]" + + DevFinal.SYMBOL.NEW_LINE_X2 + + "==========================="; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/assist/url/AndroidUrlParser.java b/lib/DevApp/src/main/java/dev/utils/app/assist/url/AndroidUrlParser.java new file mode 100644 index 0000000000..26f3a645f2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/assist/url/AndroidUrlParser.java @@ -0,0 +1,146 @@ +package dev.utils.app.assist.url; + +import android.net.Uri; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import dev.utils.LogPrintUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.assist.url.UrlExtras; + +/** + * detail: Android Api 实现 Url 解析器 + * @author Ttt + */ +public class AndroidUrlParser + implements UrlExtras.Parser { + + // 日志 TAG + private static final String TAG = AndroidUrlParser.class.getSimpleName(); + + // 完整 Url + private String mUrl; + // Url 前缀 ( 去除参数部分 ) + private String mUrlPrefix; + // Url 参数部分字符串 + private String mUrlParams; + // Url Params Map + private Map mUrlParamsMap; + // Url Params Map ( 参数值进行 UrlDecode ) + private Map mUrlParamsDecodeMap; + // 是否解析、转换 Param Map + private boolean mConvertMap = true; + + // ==================== + // = UrlExtras.Parser = + // ==================== + + @Override + public UrlExtras.Parser reset(final String url) { + return new AndroidUrlParser().setUrl(url); + } + + @Override + public UrlExtras.Parser setUrl(final String url) { + initialize(url); + return this; + } + + @Override + public String getUrl() { + return this.mUrl; + } + + @Override + public String getUrlByPrefix() { + return this.mUrlPrefix; + } + + @Override + public String getUrlByParams() { + return this.mUrlParams; + } + + @Override + public Map getUrlParams() { + return this.mUrlParamsMap; + } + + @Override + public Map getUrlParamsDecode() { + return this.mUrlParamsDecodeMap; + } + + @Override + public boolean isConvertMap() { + return this.mConvertMap; + } + + @Override + public UrlExtras.Parser setConvertMap(final boolean convertMap) { + this.mConvertMap = convertMap; + return this; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化方法 + *
+     *     会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义
+     * 
+ * @param url 待处理完整 Url + */ + private void initialize(final String url) { + this.mUrl = StringUtils.clearSpaceTabLine(url); + this.mUrlPrefix = null; + this.mUrlParams = null; + this.mUrlParamsMap = null; + this.mUrlParamsDecodeMap = null; + + if (StringUtils.isNotEmpty(mUrl)) { + try { + Uri uri = Uri.parse(mUrl); + this.mUrlPrefix = uriToUrlPrefix(uri); + this.mUrlParams = uri.getEncodedQuery(); + + if (mConvertMap && StringUtils.isNotEmpty(mUrlParams)) { + this.mUrlParamsMap = new LinkedHashMap<>(); + this.mUrlParamsDecodeMap = new LinkedHashMap<>(); + + Set keys = uri.getQueryParameterNames(); + for (String key : keys) { + String value = uri.getQueryParameter(key); + String decode = StringUtils.urlDecodeWhile(value, 10); + + this.mUrlParamsMap.put(key, value); + this.mUrlParamsDecodeMap.put( + key, StringUtils.checkValue(value, decode) + ); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "initialize"); + } + } + } + + /** + * 通过 Uri 拼接 Url 前缀 ( 去除参数部分 ) + *
+     *     参照部分 Uri#toSafeString() 代码
+     * 
+ * @param uri Uri.parse + * @return Url 前缀 + */ + private String uriToUrlPrefix(final Uri uri) { + String scheme = StringUtils.checkValue(uri.getScheme()); + String authority = StringUtils.checkValue(uri.getAuthority()); + String path = StringUtils.checkValue(uri.getPath()); + return scheme + "://" + authority + path; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java b/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java new file mode 100644 index 0000000000..4b14f77f56 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java @@ -0,0 +1,879 @@ +package dev.utils.app.cache; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.Serializable; +import java.util.List; + +import dev.utils.app.PathUtils; +import dev.utils.common.cipher.Cipher; + +/** + * detail: 缓存类 + * @author Ttt + */ +public final class DevCache { + + // 默认缓存文件名 + private static final String DEFAULT_NAME = DevCache.class.getSimpleName(); + // 缓存管理类 + private final DevCacheManager mManager; + + /** + * 获取 DevCache + * @param cachePath 缓存文件夹路径 + * @param cipher 通用加解密中间层 + */ + private DevCache( + final String cachePath, + final Cipher cipher + ) { + mManager = new DevCacheManager(cachePath, cipher); + } + + // 数据类型 + public static final int INT = 1; + public static final int LONG = 2; + public static final int FLOAT = 3; + public static final int DOUBLE = 4; + public static final int BOOLEAN = 5; + public static final int STRING = 6; + public static final int BYTES = 7; + public static final int BITMAP = 8; + public static final int DRAWABLE = 9; + public static final int SERIALIZABLE = 10; + public static final int PARCELABLE = 11; + public static final int JSON_OBJECT = 12; + public static final int JSON_ARRAY = 13; + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 DevCache + * @return {@link DevCache} + */ + public static DevCache newCache() { + DevCache cache = DevCacheManager.sInstanceMaps.get(""); + if (cache == null) { + String cachePath = PathUtils.getAppExternal().getAppCachePath(DEFAULT_NAME); + cache = new DevCache(cachePath, null); + DevCacheManager.sInstanceMaps.put("", cache); + DevCacheManager.sInstanceMaps.put(cachePath, cache); + } + return cache; + } + + /** + * 获取 DevCache + * @param cachePath 缓存文件夹路径 + * @return {@link DevCache} + */ + public static DevCache newCache(final String cachePath) { + return newCache(cachePath, null); + } + + /** + * 获取 DevCache + * @param cachePath 缓存文件夹路径 + * @param cipher 通用加解密中间层 + * @return {@link DevCache} + */ + public static DevCache newCache( + final String cachePath, + final Cipher cipher + ) { + if (TextUtils.isEmpty(cachePath)) { + return newCache(); + } + DevCache cache = DevCacheManager.sInstanceMaps.get(cachePath); + if (cache == null) { + cache = new DevCache(cachePath, cipher); + DevCacheManager.sInstanceMaps.put(cachePath, cache); + } + return cache; + } + + /** + * 获取缓存地址 + * @return 缓存地址 + */ + public String getCachePath() { + return mManager.getCachePath(); + } + + // = + + /** + * 移除数据 + * @param key 保存的 key + */ + public void remove(String key) { + mManager.remove(key); + } + + /** + * 删除 Key[] 配置、数据文件 + * @param keys 存储 key[] + */ + public void removeForKeys(String[] keys) { + mManager.removeForKeys(keys); + } + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + public boolean contains(String key) { + return mManager.contains(key); + } + + /** + * 判断某个 key 是否过期 + *
+     *     如果不存在该 key 也返回过期
+     * 
+ * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + public boolean isDue(String key) { + return mManager.isDue(key); + } + + /** + * 清除全部数据 + */ + public void clear() { + mManager.clear(); + } + + /** + * 清除过期数据 + */ + public void clearDue() { + mManager.clearDue(); + } + + /** + * 清除某个类型的全部数据 + * @param type 类型 + */ + public void clearType(int type) { + mManager.clearType(type); + } + + /** + * 通过 Key 获取 Item + * @param key 保存的 key + * @return Item + */ + public Data getItemByKey(String key) { + return mManager.getItemByKey(key); + } + + /** + * 获取有效 Key 集合 + * @return 有效 Key 集合 + */ + public List getKeys() { + return mManager.getKeys(); + } + + /** + * 获取永久有效 Key 集合 + * @return 永久有效 Key 集合 + */ + public List getPermanentKeys() { + return mManager.getPermanentKeys(); + } + + /** + * 获取有效 Key 数量 + * @return 有效 Key 数量 + */ + public int getCount() { + return mManager.getCount(); + } + + /** + * 获取有效 Key 占用总大小 + * @return 有效 Key 占用总大小 + */ + public long getSize() { + return mManager.getSize(); + } + + // ======= + // = 存储 = + // ======= + + /** + * 保存 int 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + int value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 long 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + long value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 float 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + float value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 double 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + double value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 boolean 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + boolean value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 String 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + String value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 byte[] 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + byte[] value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 Bitmap 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + Bitmap value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 Drawable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + Drawable value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 Serializable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + Serializable value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 Parcelable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + Parcelable value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 JSONObject 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + JSONObject value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + /** + * 保存 JSONArray 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + public boolean put( + String key, + JSONArray value, + long validTime + ) { + return mManager.put(key, value, validTime); + } + + // ======= + // = 获取 = + // ======= + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public int getInt(String key) { + return mManager.getInt(key); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public long getLong(String key) { + return mManager.getLong(key); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public float getFloat(String key) { + return mManager.getFloat(key); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public double getDouble(String key) { + return mManager.getDouble(key); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public boolean getBoolean(String key) { + return mManager.getBoolean(key); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public String getString(String key) { + return mManager.getString(key); + } + + /** + * 获取 byte[] 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public byte[] getBytes(String key) { + return mManager.getBytes(key); + } + + /** + * 获取 Bitmap 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public Bitmap getBitmap(String key) { + return mManager.getBitmap(key); + } + + /** + * 获取 Drawable 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public Drawable getDrawable(String key) { + return mManager.getDrawable(key); + } + + /** + * 获取 Serializable 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public Object getSerializable(String key) { + return mManager.getSerializable(key); + } + + /** + * 获取 Parcelable 类型的数据 + * @param key 保存的 key + * @param creator {@link Parcelable.Creator} + * @return 存储的数据 + */ + public T getParcelable( + String key, + Parcelable.Creator creator + ) { + return mManager.getParcelable(key, creator); + } + + /** + * 获取 JSONObject 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public JSONObject getJSONObject(String key) { + return mManager.getJSONObject(key); + } + + /** + * 获取 JSONArray 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public JSONArray getJSONArray(String key) { + return mManager.getJSONArray(key); + } + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public int getInt( + String key, + int defaultValue + ) { + return mManager.getInt(key, defaultValue); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public long getLong( + String key, + long defaultValue + ) { + return mManager.getLong(key, defaultValue); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public float getFloat( + String key, + float defaultValue + ) { + return mManager.getFloat(key, defaultValue); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public double getDouble( + String key, + double defaultValue + ) { + return mManager.getDouble(key, defaultValue); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public boolean getBoolean( + String key, + boolean defaultValue + ) { + return mManager.getBoolean(key, defaultValue); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public String getString( + String key, + String defaultValue + ) { + return mManager.getString(key, defaultValue); + } + + /** + * 获取 byte[] 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public byte[] getBytes( + String key, + byte[] defaultValue + ) { + return mManager.getBytes(key, defaultValue); + } + + /** + * 获取 Bitmap 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public Bitmap getBitmap( + String key, + Bitmap defaultValue + ) { + return mManager.getBitmap(key, defaultValue); + } + + /** + * 获取 Drawable 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public Drawable getDrawable( + String key, + Drawable defaultValue + ) { + return mManager.getDrawable(key, defaultValue); + } + + /** + * 获取 Serializable 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public Object getSerializable( + String key, + Object defaultValue + ) { + return mManager.getSerializable(key, defaultValue); + } + + /** + * 获取 Parcelable 类型的数据 + * @param key 保存的 key + * @param creator {@link Parcelable.Creator} + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public T getParcelable( + String key, + Parcelable.Creator creator, + T defaultValue + ) { + return mManager.getParcelable(key, creator, defaultValue); + } + + /** + * 获取 JSONObject 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public JSONObject getJSONObject( + String key, + JSONObject defaultValue + ) { + return mManager.getJSONObject(key, defaultValue); + } + + /** + * 获取 JSONArray 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public JSONArray getJSONArray( + String key, + JSONArray defaultValue + ) { + return mManager.getJSONArray(key, defaultValue); + } + + // ======== + // = 实体类 = + // ======== + + /** + * detail: 数据源 + * @author Ttt + */ + public static final class Data { + + // 缓存地址 + private final String mPath; + // 存储 Key + private final String mKey; + // 存储类型 + private int mType; + // 保存时间 ( 毫秒 ) + private long mSaveTime; + // 有效期 ( 毫秒 ) + private long mValidTime; + + protected Data( + String path, + String key, + int type, + long saveTime, + long validTime + ) { + this.mPath = path; + this.mKey = key; + this.mType = type; + this.mSaveTime = saveTime; + this.mValidTime = validTime; + } + + /** + * 获取存储 Key + * @return 存储 Key + */ + public String getKey() { + return mKey; + } + + /** + * 是否永久有效 + * @return {@code true} yes, {@code false} no + */ + public boolean isPermanent() { + return mValidTime <= 0; + } + + /** + * 是否过期 + * @return {@code true} yes, {@code false} no + */ + public boolean isDue() { + if (mValidTime <= 0) return false; + long time = mSaveTime + mValidTime; + return System.currentTimeMillis() - time >= 0; + } + + /** + * 获取文件大小 + * @return 文件大小 + */ + public long getSize() { + return DevCacheManager.getDataFileSize(mPath, mKey); + } + + // = + + /** + * 获取数据存储类型 + * @return 数据存储类型 + */ + public int getType() { + return mType; + } + + /** + * 获取保存时间 ( 毫秒 ) + * @return 保存时间 ( 毫秒 ) + */ + public long getSaveTime() { + return mSaveTime; + } + + /** + * 获取有效期 ( 毫秒 ) + * @return 有效期 ( 毫秒 ) + */ + public long getValidTime() { + return mValidTime; + } + + // = + + protected Data setType(int type) { + this.mType = type; + return this; + } + + protected Data setSaveTime(long saveTime) { + this.mSaveTime = saveTime; + return this; + } + + protected Data setValidTime(long validTime) { + this.mValidTime = validTime; + return this; + } + + // ========== + // = 判断方法 = + // ========== + + public boolean isInt() { + return mType == INT; + } + + public boolean isLong() { + return mType == LONG; + } + + public boolean isFloat() { + return mType == FLOAT; + } + + public boolean isDouble() { + return mType == DOUBLE; + } + + public boolean isBoolean() { + return mType == BOOLEAN; + } + + public boolean isString() { + return mType == STRING; + } + + public boolean isBytes() { + return mType == BYTES; + } + + public boolean isBitmap() { + return mType == BITMAP; + } + + public boolean isDrawable() { + return mType == DRAWABLE; + } + + public boolean isSerializable() { + return mType == SERIALIZABLE; + } + + public boolean isParcelable() { + return mType == PARCELABLE; + } + + public boolean isJSONObject() { + return mType == JSON_OBJECT; + } + + public boolean isJSONArray() { + return mType == JSON_ARRAY; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/cache/DevCacheManager.java b/lib/DevApp/src/main/java/dev/utils/app/cache/DevCacheManager.java new file mode 100644 index 0000000000..cc777cea30 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/cache/DevCacheManager.java @@ -0,0 +1,836 @@ +package dev.utils.app.cache; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.image.ImageUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.cipher.Cipher; + +/** + * detail: 缓存管理类 + * @author Ttt + */ +final class DevCacheManager { + + // 不同地址配置缓存对象 + protected static final Map sInstanceMaps = new HashMap<>(); + // 日志 TAG + private final String TAG = DevCacheManager.class.getSimpleName(); + // 文件后缀 + private static final String DATA_EXTENSION = ".data"; + private static final String CONFIG_EXTENSION = ".config"; + // 缓存地址 + private final String mCachePath; + // 通用加解密中间层 + private final Cipher mCipher; + // 总缓存大小 + private final AtomicLong mCacheSize = new AtomicLong(); + // 总缓存的文件总数 + private final AtomicInteger mCacheCount = new AtomicInteger(); + + public DevCacheManager( + String cachePath, + Cipher cipher + ) { + this.mCachePath = cachePath; + this.mCipher = cipher; + // 计算文件信息 + calculateCacheSizeAndCacheCount(); + } + + /** + * 计算 cacheSize 和 cacheCount + */ + private void calculateCacheSizeAndCacheCount() { + new Thread(() -> { + long size = 0, count = 0; + if (mCachePath != null) { + File[] cachedFiles = new File(mCachePath).listFiles(); + if (cachedFiles != null) { + for (File file : cachedFiles) { + if (file != null && file.isFile()) { + String fileName = file.getName(); + if (fileName.endsWith(CONFIG_EXTENSION)) { + String key = FileUtils.getFileNotSuffix(fileName); + DevCache.Data data = _mapGetData(key); + if (data != null) { + size += data.getSize(); + count += 1; + } + } + } + } + mCacheSize.set(size); + mCacheCount.set((int) count); + } + } + }).start(); + } + + // ============= + // = 对外公开方法 = + // ============= + + public String getCachePath() { + return mCachePath; + } + + // = + + public void remove(String key) { + if (TextUtils.isEmpty(key)) return; + File dataFile = _getKeyDataFile(key); + File configFile = _getKeyConfigFile(key); + long size = getDataFileSize(mCachePath, key); + if (FileUtils.deleteFile(dataFile) + && FileUtils.deleteFile(configFile)) { + mCacheSize.addAndGet(-size); + mCacheCount.addAndGet(-1); + mDataMaps.remove(key); // 移除缓存 + } + } + + public void removeForKeys(String[] keys) { + if (keys == null) return; + for (String key : keys) { + remove(key); + } + } + + public boolean contains(String key) { + return _isExistKeyFile(key); + } + + public boolean isDue(String key) { + DevCache.Data data = _mapGetData(key); + if (data != null) return data.isDue(); + return true; + } + + public void clear() { + new Thread(() -> { + HashSet keys = new HashSet<>(mDataMaps.keySet()); + for (String key : keys) { + remove(key); + } + }).start(); + } + + public void clearDue() { + new Thread(() -> { + HashSet keys = new HashSet<>(mDataMaps.keySet()); + for (String key : keys) { + if (isDue(key)) remove(key); + } + }).start(); + } + + public void clearType(int type) { + new Thread(() -> { + HashSet keys = new HashSet<>(mDataMaps.keySet()); + for (String key : keys) { + DevCache.Data data = _mapGetData(key); + if (data != null && data.getType() == type) { + remove(key); + } + } + }).start(); + } + + public DevCache.Data getItemByKey(String key) { + return _mapGetData(key); + } + + public List getKeys() { + return new ArrayList<>(mDataMaps.values()); + } + + public List getPermanentKeys() { + List lists = new ArrayList<>(); + HashSet keys = new HashSet<>(mDataMaps.keySet()); + for (String key : keys) { + DevCache.Data data = _mapGetData(key); + if (data != null && data.isPermanent()) { + lists.add(data); + } + } + return lists; + } + + public int getCount() { + return mCacheCount.get(); + } + + public long getSize() { + return mCacheSize.get(); + } + + // ======= + // = 存储 = + // ======= + + public boolean put( + String key, + int value, + long validTime + ) { + return _put(key, DevCache.INT, String.valueOf(value).getBytes(), validTime); + } + + public boolean put( + String key, + long value, + long validTime + ) { + return _put(key, DevCache.LONG, String.valueOf(value).getBytes(), validTime); + } + + public boolean put( + String key, + float value, + long validTime + ) { + return _put(key, DevCache.FLOAT, String.valueOf(value).getBytes(), validTime); + } + + public boolean put( + String key, + double value, + long validTime + ) { + return _put(key, DevCache.DOUBLE, String.valueOf(value).getBytes(), validTime); + } + + public boolean put( + String key, + boolean value, + long validTime + ) { + return _put(key, DevCache.BOOLEAN, String.valueOf(value).getBytes(), validTime); + } + + public boolean put( + String key, + String value, + long validTime + ) { + if (value == null) return false; + return _put(key, DevCache.STRING, value.getBytes(), validTime); + } + + public boolean put( + String key, + byte[] value, + long validTime + ) { + return _put(key, DevCache.BYTES, value, validTime); + } + + public boolean put( + String key, + Bitmap value, + long validTime + ) { + return _put(key, DevCache.BITMAP, ImageUtils.bitmapToByte(value), validTime); + } + + public boolean put( + String key, + Drawable value, + long validTime + ) { + return _put(key, DevCache.DRAWABLE, ImageUtils.bitmapToByte( + ImageUtils.drawableToBitmap(value) + ), validTime); + } + + public boolean put( + String key, + Serializable value, + long validTime + ) { + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(value); + byte[] bytes = baos.toByteArray(); + return _put(key, DevCache.SERIALIZABLE, bytes, validTime); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "put - Serializable"); + } finally { + CloseUtils.closeIOQuietly(oos); + } + return false; + } + + public boolean put( + String key, + Parcelable value, + long validTime + ) { + try { + Parcel parcel = Parcel.obtain(); + value.writeToParcel(parcel, 0); + byte[] bytes = parcel.marshall(); + parcel.recycle(); + return _put(key, DevCache.PARCELABLE, bytes, validTime); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "put - Parcelable"); + } + return false; + } + + public boolean put( + String key, + JSONObject value, + long validTime + ) { + if (value == null) return false; + return _put(key, DevCache.JSON_OBJECT, value.toString().getBytes(), validTime); + } + + public boolean put( + String key, + JSONArray value, + long validTime + ) { + if (value == null) return false; + return _put(key, DevCache.JSON_ARRAY, value.toString().getBytes(), validTime); + } + + // ======= + // = 获取 = + // ======= + + public int getInt(String key) { + return getInt(key, DevFinal.DEFAULT.INT); + } + + public long getLong(String key) { + return getLong(key, DevFinal.DEFAULT.LONG); + } + + public float getFloat(String key) { + return getFloat(key, DevFinal.DEFAULT.FLOAT); + } + + public double getDouble(String key) { + return getDouble(key, DevFinal.DEFAULT.DOUBLE); + } + + public boolean getBoolean(String key) { + return getBoolean(key, DevFinal.DEFAULT.BOOLEAN); + } + + public String getString(String key) { + return getString(key, null); + } + + public byte[] getBytes(String key) { + return getBytes(key, null); + } + + public Bitmap getBitmap(String key) { + return getBitmap(key, null); + } + + public Drawable getDrawable(String key) { + return getDrawable(key, null); + } + + public Object getSerializable(String key) { + return getSerializable(key, null); + } + + public T getParcelable( + String key, + Parcelable.Creator creator + ) { + return getParcelable(key, creator, null); + } + + public JSONObject getJSONObject(String key) { + return getJSONObject(key, null); + } + + public JSONArray getJSONArray(String key) { + return getJSONArray(key, null); + } + + // = + + public int getInt( + String key, + int defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return Integer.parseInt(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getInt"); + } + } + } + return defaultValue; + } + + public long getLong( + String key, + long defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return Long.parseLong(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getLong"); + } + } + } + return defaultValue; + } + + public float getFloat( + String key, + float defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return Float.parseFloat(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getFloat"); + } + } + } + return defaultValue; + } + + public double getDouble( + String key, + double defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return Double.parseDouble(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getDouble"); + } + } + } + return defaultValue; + } + + public boolean getBoolean( + String key, + boolean defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return Boolean.parseBoolean(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getBoolean"); + } + } + } + return defaultValue; + } + + public String getString( + String key, + String defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return new String(bytes); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getString"); + } + } + } + return defaultValue; + } + + public byte[] getBytes( + String key, + byte[] defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + return _get(key); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getBytes"); + } + } + } + return defaultValue; + } + + public Bitmap getBitmap( + String key, + Bitmap defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return ImageUtils.decodeByteArray(bytes); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getBitmap"); + } + } + } + return defaultValue; + } + + public Drawable getDrawable( + String key, + Drawable defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + Bitmap bitmap = ImageUtils.decodeByteArray(bytes); + return ImageUtils.bitmapToDrawable(bitmap); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getDrawable"); + } + } + } + return defaultValue; + } + + public Object getSerializable( + String key, + Object defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + ObjectInputStream ois = null; + try { + byte[] bytes = _get(key); + ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return ois.readObject(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getSerializable"); + } finally { + CloseUtils.closeIOQuietly(ois); + } + } + } + return defaultValue; + } + + public T getParcelable( + String key, + Parcelable.Creator creator, + T defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + return creator.createFromParcel(parcel); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getParcelable"); + } + } + } + return defaultValue; + } + + public JSONObject getJSONObject( + String key, + JSONObject defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return new JSONObject(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getJSONObject"); + } + } + } + return defaultValue; + } + + public JSONArray getJSONArray( + String key, + JSONArray defaultValue + ) { + DevCache.Data data = _mapGetData(key); + if (data != null) { + if (data.isDue()) { + remove(key); + } else { + try { + byte[] bytes = _get(key); + return new JSONArray(new String(bytes)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, "getJSONArray"); + } + } + } + return defaultValue; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Key 数据文件 + * @param key 存储 key + * @return Key 数据文件 + */ + private File _getKeyDataFile(final String key) { + if (TextUtils.isEmpty(key)) return null; + return FileUtils.getFile(mCachePath, key + DATA_EXTENSION); + } + + /** + * 获取 Key 配置文件 + * @param key 存储 key + * @return Key 配置文件 + */ + private File _getKeyConfigFile(final String key) { + if (TextUtils.isEmpty(key)) return null; + return FileUtils.getFile(mCachePath, key + CONFIG_EXTENSION); + } + + /** + * 获取存储数据大小 + * @param path 文件地址 + * @param key 存储 key + * @return 存储数据大小 + */ + public static long getDataFileSize( + final String path, + final String key + ) { + if (TextUtils.isEmpty(key)) return 0L; + return FileUtils.getFileLength( + FileUtils.getFile(path, key + DATA_EXTENSION) + ); + } + + /** + * 判断是否存在 Key 配置、数据文件 + * @param key 存储 key + * @return {@code true} yes, {@code false} no + */ + private boolean _isExistKeyFile(final String key) { + if (TextUtils.isEmpty(key)) return false; + return FileUtils.isFileExists(_getKeyDataFile(key)) + && FileUtils.isFileExists(_getKeyConfigFile(key)); + } + + // ======== + // = Data = + // ======== + + // 缓存 Data + private final HashMap mDataMaps = new HashMap<>(); + + private DevCache.Data _mapGetData(final String key) { + if (TextUtils.isEmpty(key)) return null; + DevCache.Data data = mDataMaps.get(key); + if (data == null) { + data = _getData(key); + if (data != null) { + mDataMaps.put(key, data); + } + } + return data; + } + + // = + + /** + * Data Format JSON String + * @param data 数据源 + * @return JSON String + */ + private String _toDataString(final DevCache.Data data) { + // Data JSON Format + return String.format( + "{\"key\":\"%s\",\"type\":%d,\"saveTime\":%d,\"validTime\":%d}", + data.getKey(), data.getType(), + data.getSaveTime(), data.getValidTime() + ); + } + + /** + * 读取配置初始化 Data + * @param key 存储 key + * @return {@link DevCache.Data} + */ + private DevCache.Data _getData(final String key) { + if (!_isExistKeyFile(key)) return null; + try { + File configFile = _getKeyConfigFile(key); + String config = new String(FileUtils.readFileBytes(configFile)); + JSONObject jsonObject = new JSONObject(config); + if (jsonObject.has("key") + && jsonObject.has("type") + && jsonObject.has("saveTime") + && jsonObject.has("validTime") + ) { + String _key = jsonObject.getString("key"); + int type = jsonObject.getInt("type"); + long saveTime = jsonObject.getLong("saveTime"); + long validTime = jsonObject.getLong("validTime"); + return new DevCache.Data(mCachePath, _key, + type, saveTime, validTime + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "_getData"); + } + return null; + } + + // = + + /** + * 保存方法 ( 最终调用 ) + * @param key 保存的 key + * @param type 保存类型 + * @param bytes 保存数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + private boolean _put( + String key, + int type, + byte[] bytes, + long validTime + ) { + if (TextUtils.isEmpty(key)) return false; + if (bytes != null && mCipher != null) { + try { + bytes = mCipher.encrypt(bytes); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "_put - encrypt"); + bytes = null; + } + } + if (bytes == null) return false; + DevCache.Data data = _mapGetData(key); + long size = getDataFileSize(mCachePath, key); + boolean result = FileUtils.saveFile(_getKeyDataFile(key), bytes); + if (result) { + if (data != null) { + data.setSaveTime(System.currentTimeMillis()) + .setType(type).setValidTime(validTime); + mCacheSize.addAndGet(-size); + mCacheSize.addAndGet(getDataFileSize(mCachePath, key)); + } else { + data = new DevCache.Data(mCachePath, key, type, + System.currentTimeMillis(), validTime + ); + mCacheSize.addAndGet(getDataFileSize(mCachePath, key)); + mCacheCount.incrementAndGet(); + mDataMaps.put(key, data); + } + FileUtils.saveFile(_getKeyConfigFile(key), _toDataString(data).getBytes()); + } + return result; + } + + /** + * 获取方法 ( 最终调用 ) + * @param key 保存的 key + * @return 保存的数据 + */ + private byte[] _get(String key) { + byte[] bytes = FileUtils.readFileBytes(_getKeyDataFile(key)); + if (bytes != null && mCipher != null) { + try { + bytes = mCipher.decrypt(bytes); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "_get - decrypt"); + bytes = null; + } + } + return bytes; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/AutoFocusAssist.java b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/AutoFocusAssist.java new file mode 100644 index 0000000000..2a0b0da381 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/AutoFocusAssist.java @@ -0,0 +1,237 @@ +package dev.utils.app.camera.camera1; + +import android.hardware.Camera; +import android.os.AsyncTask; + +import java.util.ArrayList; +import java.util.Collection; + +import dev.utils.LogPrintUtils; + +/** + * detail: 摄像头自动获取焦点辅助类 + * @author Ttt + *
+ *     对焦模式
+ *     @see 
+ * 
+ */ +public final class AutoFocusAssist + implements Camera.AutoFocusCallback { + + // 日志 TAG + private final String TAG = AutoFocusAssist.class.getSimpleName(); + + // 设置对焦模式 + public static final Collection FOCUS_MODES; + + static { + FOCUS_MODES = new ArrayList<>(); + FOCUS_MODES.add(Camera.Parameters.FOCUS_MODE_AUTO); // 自动对焦 + FOCUS_MODES.add(Camera.Parameters.FOCUS_MODE_MACRO); // 微距 + } + + // ======= + // = 变量 = + // ======= + + // 自动对焦时间间隔 + private final long mInterval; + // 摄像头对象 + private final Camera mCamera; + // 判断摄像头是否使用对焦 + private final boolean mUseAutoFocus; + // 判断是否停止对焦 + private boolean mStopped; + // 判断是否对焦中 + private boolean mFocusing; + // 对焦任务 + private AsyncTask mOutstandingTask; + // 判断是否需要自动对焦 + private boolean mAutoFocus = true; + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param camera {@link android.hardware.Camera} + */ + public AutoFocusAssist(final Camera camera) { + this(camera, 2000L); + } + + /** + * 构造函数 + * @param camera {@link android.hardware.Camera} + * @param interval 自动对焦时间间隔 + */ + public AutoFocusAssist( + final Camera camera, + final long interval + ) { + this.mCamera = camera; + this.mInterval = interval; + // 防止为 null + if (camera != null) { + // 获取对象对焦模式 + String currentFocusMode = camera.getParameters().getFocusMode(); + // 判断是否支持对焦 + mUseAutoFocus = FOCUS_MODES.contains(currentFocusMode); + } else { + // 不支持对焦 + mUseAutoFocus = false; + } + // 开始任务 + start(); + } + + /** + * 设置对焦模式 + * @param collection 对焦模式集合 + */ + public static void setFocusModes(final Collection collection) { + if (collection != null) { + FOCUS_MODES.clear(); + // 保存对焦模式集合 + FOCUS_MODES.addAll(collection); + } + } + + /** + * 是否允许自动对焦 + * @return {@code true} 自动对焦, {@code false} 非自动对焦 + */ + public boolean isAutoFocus() { + return mAutoFocus; + } + + /** + * 设置是否开启自动对焦 + * @param autoFocus 是否自动对焦 + */ + public void setAutoFocus(final boolean autoFocus) { + this.mAutoFocus = autoFocus; + // 判断是否开启自动对焦 + if (autoFocus) { + start(); + } else { + stop(); + } + } + + /** + * 对焦回调 {@link Camera.AutoFocusCallback} 重写方法 + * @param success 是否对焦成功 + * @param theCamera 对焦的 {@link android.hardware.Camera} + */ + @Override + public synchronized void onAutoFocus( + boolean success, + Camera theCamera + ) { + // 对焦结束, 设置非对焦中 + mFocusing = false; + // 再次自动对焦 + autoFocusAgainLater(); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 再次自动对焦 + */ + private synchronized void autoFocusAgainLater() { + // 不属于停止, 并且任务等于 null 才处理 + if (!mStopped && mOutstandingTask == null) { + // 初始化任务 + AutoFocusTask newTask = new AutoFocusTask(); + try { + // 默认使用异步任务 + newTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + mOutstandingTask = newTask; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "autoFocusAgainLater"); + } + } + } + + /** + * 开始对焦 + */ + public synchronized void start() { + // 如果不使用自动对焦, 则不处理 + if (!mAutoFocus) return; + // 支持对焦才处理 + if (mUseAutoFocus) { + // 重置任务为 null + mOutstandingTask = null; + // 不属于停止 并且 非对焦中 + if (!mStopped && !mFocusing) { + try { + // 设置自动对焦回调 + mCamera.autoFocus(this); + // 表示对焦中 + mFocusing = true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "start"); + // Try again later to keep cycle going + autoFocusAgainLater(); + } + } + } + } + + /** + * 停止对焦 + */ + public synchronized void stop() { + // 表示属于停止 + mStopped = true; + // 判断是否支持对焦 + if (mUseAutoFocus) { + try { + // 关闭任务 + cancelOutstandingTask(); + // 取消对焦 + mCamera.cancelAutoFocus(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "stop"); + } + } + } + + /** + * 取消对焦任务 + */ + private synchronized void cancelOutstandingTask() { + if (mOutstandingTask != null) { + if (mOutstandingTask.getStatus() != AsyncTask.Status.FINISHED) { + mOutstandingTask.cancel(true); + } + mOutstandingTask = null; + } + } + + /** + * detail: 自动对焦任务 + * @author Ttt + */ + private final class AutoFocusTask + extends AsyncTask { + @Override + protected Object doInBackground(Object... voids) { + try { + // 堵塞时间 + Thread.sleep(mInterval); + } catch (Exception ignored) { + } + // 开启定时 + start(); + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraAssist.java b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraAssist.java new file mode 100644 index 0000000000..d65d29ac0a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraAssist.java @@ -0,0 +1,297 @@ +package dev.utils.app.camera.camera1; + +import android.hardware.Camera; +import android.view.SurfaceHolder; + +import dev.utils.LogPrintUtils; + +/** + * detail: 摄像头辅助类 + * @author Ttt + */ +public final class CameraAssist { + + // 日志 TAG + private static final String TAG = CameraAssist.class.getSimpleName(); + + // 摄像头对象 + private Camera mCamera; + // 是否预览中 + private boolean mPreviewing; + // 自动对焦时间间隔 + private long mAutoInterval = 2000L; + // 预览通知 + private PreviewNotify mPreviewNotify; + + // ============ + // = 内部工具类 = + // ============ + + // 摄像头大小计算 + private CameraSizeAssist mCameraSizeAssist; + // 自动获取焦点辅助类 + private AutoFocusAssist mAutoFocusAssist; + + public CameraAssist() { + } + + /** + * 构造函数 + * @param camera {@link android.hardware.Camera} + */ + public CameraAssist(final Camera camera) { + setCamera(camera); + } + + /** + * 构造函数 + * @param camera {@link android.hardware.Camera} + * @param interval 自动对焦时间间隔 + */ + public CameraAssist( + final Camera camera, + final long interval + ) { + this.mAutoInterval = interval; + setCamera(camera); + } + + // ========== + // = 操作方法 = + // ========== + + /** + * 打开摄像头程序 + * @param holder {@link SurfaceHolder} + * @return {@link CameraAssist} + * @throws Exception 设置预览画面, 异常时抛出 + */ + public synchronized CameraAssist openDriver(final SurfaceHolder holder) + throws Exception { + Camera theCamera = mCamera; + // 设置预览 Holder + theCamera.setPreviewDisplay(holder); + return this; + } + + /** + * 关闭摄像头程序 + */ + public synchronized void closeDriver() { + // 释放摄像头资源 + freeCameraResource(); + } + + // ========== + // = 预览相关 = + // ========== + + /** + * 开始将 Camera 画面预览到手机上 + */ + public synchronized void startPreview() { + Camera theCamera = mCamera; + if (theCamera != null && !mPreviewing) { + // 开始预览 + theCamera.startPreview(); + // 表示预览中 + mPreviewing = true; + // 初始化自动获取焦点 + mAutoFocusAssist = new AutoFocusAssist(mCamera, mAutoInterval); + // 开始预览通知 + if (mPreviewNotify != null) { + mPreviewNotify.startPreviewNotify(); + } + } + } + + /** + * 停止 Camera 画面预览 + */ + public synchronized void stopPreview() { + if (mAutoFocusAssist != null) { + mAutoFocusAssist.stop(); + mAutoFocusAssist = null; + } + if (mCamera != null && mPreviewing) { + // 停止预览 + mCamera.stopPreview(); + // 表示非预览中 + mPreviewing = false; + // 停止预览通知 + if (mPreviewNotify != null) { + mPreviewNotify.stopPreviewNotify(); + } + } + } + + /** + * 释放摄像头资源 + */ + private void freeCameraResource() { + try { + if (mCamera != null) { + mCamera.setPreviewCallback(null); + mCamera.stopPreview(); + mCamera.lock(); + mCamera.release(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "freeCameraResource"); + } finally { + mCamera = null; + } + } + + // ============ + // = 摄像头相关 = + // ============ + + // 预览尺寸大小 + private Camera.Size mPreviewSize = null; + + /** + * 获取相机分辨率 + * @return {@link Camera.Size} 相机分辨率 + */ + public Camera.Size getCameraResolution() { + if (mPreviewSize == null) { // 获取预览大小 + mPreviewSize = mCameraSizeAssist.getPreviewSize(); + } + return mPreviewSize; + } + + /** + * 获取预览分辨率 + * @return {@link Camera.Size} 预览分辨率 + */ + public Camera.Size getPreviewSize() { + if (mCamera != null && mCamera.getParameters() != null) { + return mCamera.getParameters().getPreviewSize(); + } + return null; + } + + /** + * 获取 Camera.Size 计算辅助类 + * @return {@link CameraSizeAssist} + */ + public CameraSizeAssist getCameraSizeAssist() { + return mCameraSizeAssist; + } + + /** + * 获取摄像头 + * @return {@link android.hardware.Camera} + */ + public Camera getCamera() { + return mCamera; + } + + /** + * 设置摄像头 + * @param camera {@link android.hardware.Camera} + * @return {@link CameraAssist} + */ + public CameraAssist setCamera(final Camera camera) { + this.mCamera = camera; + // 初始化 Camera 大小 + this.mCameraSizeAssist = new CameraSizeAssist(mCamera); + return this; + } + + /** + * 设置预览回调 + * @param previewNotify 预览通知接口 + * @return {@link CameraAssist} + */ + public CameraAssist setPreviewNotify(final PreviewNotify previewNotify) { + this.mPreviewNotify = previewNotify; + return this; + } + + /** + * 设置是否开启自动对焦 + * @param autoFocus 是否自动对焦 + * @return {@link CameraAssist} + */ + public CameraAssist setAutoFocus(final boolean autoFocus) { + if (mAutoFocusAssist != null) { + mAutoFocusAssist.setAutoFocus(autoFocus); + } + return this; + } + + /** + * 是否预览中 + * @return {@code true} 预览中, {@code false} 非预览 + */ + public boolean isPreviewing() { + return mPreviewing; + } + + /** + * 设置自动对焦时间间隔 + * @param autoInterval 自动对焦时间间隔 + * @return {@link CameraAssist} + */ + public CameraAssist setAutoInterval(final long autoInterval) { + this.mAutoInterval = autoInterval; + return this; + } + + // = + + /** + * 是否支持手机闪光灯 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFlashlightEnable() { + return FlashlightUtils.isFlashlightEnable(); + } + + /** + * 打开闪光灯 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOn() { + return FlashlightUtils.getInstance().setFlashlightOn(mCamera); + } + + /** + * 关闭闪光灯 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOff() { + return FlashlightUtils.getInstance().setFlashlightOff(mCamera); + } + + /** + * 是否打开闪光灯 + * @return {@code true} yes, {@code false} no + */ + public boolean isFlashlightOn() { + return FlashlightUtils.getInstance().isFlashlightOn(mCamera); + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 预览通知接口 + * @author Ttt + */ + public interface PreviewNotify { + + /** + * 停止预览通知 + */ + void stopPreviewNotify(); + + /** + * 开始预览通知 + */ + void startPreviewNotify(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraSizeAssist.java b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraSizeAssist.java new file mode 100644 index 0000000000..3ef4082fd9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraSizeAssist.java @@ -0,0 +1,747 @@ +package dev.utils.app.camera.camera1; + +import android.graphics.Point; +import android.hardware.Camera; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.ScreenUtils; + +/** + * detail: 摄像头 ( 预览、输出大小 ) 辅助类 + * @author Ttt + *
+ *     正常只需要摄像头权限
+ *     所需权限
+ *      摄像头
+ *      震动
+ *      手电筒
+ * 
+ */ +public final class CameraSizeAssist { + + // 日志 TAG + private static final String TAG = CameraSizeAssist.class.getSimpleName(); + + // 摄像头对象 + private final Camera mCamera; + // 默认最大的偏差 + private static final double MAX_ASPECT_DISTORTION = 0.15D; + // 最小尺寸, 小于该尺寸则不处理 + private static final int MIN_PREVIEW_PIXELS = 153600; // 480 * 320; + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param camera {@link android.hardware.Camera} + */ + public CameraSizeAssist(final Camera camera) { + this.mCamera = camera; + } + + /** + * 获取摄像头 + * @return {@link android.hardware.Camera} + */ + public Camera getCamera() { + return mCamera; + } + + // ============= + // = 预览大小相关 = + // ============= + + /** + * 设置预览大小 + * @param previewSize 预览 {@link Camera.Size} + * @return {@link Camera.Parameters} + */ + public Camera.Parameters setPreviewSize(final Camera.Size previewSize) { + return setPreviewSize(null, previewSize); + } + + /** + * 设置预览大小 + * @param parameters {@link Camera.Parameters} + * @param previewSize 预览 {@link Camera.Size} + * @return {@link Camera.Parameters} + */ + public Camera.Parameters setPreviewSize( + Camera.Parameters parameters, + final Camera.Size previewSize + ) { + if (mCamera != null && previewSize != null) { + try { + if (parameters == null) { + parameters = mCamera.getParameters(); + } + // 设置预览大小 + parameters.setPreviewSize(previewSize.width, previewSize.height); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setPreviewSize"); + } + } + return parameters; + } + + // = + + /** + * 根据手机支持的预览分辨率计算, 设置预览尺寸 + * @return {@link Camera.Size} 预览分辨率 + */ + public Camera.Size getPreviewSize() { + return getPreviewSize(null, -1D); + } + + /** + * 根据手机支持的预览分辨率计算, 设置预览尺寸 + * @param point {@link Point} point.x = 宽, point.y = 高 + * @return {@link Camera.Size} 预览分辨率 + */ + public Camera.Size getPreviewSize(final Point point) { + return getPreviewSize(point, -1D); + } + + /** + * 根据手机支持的预览分辨率计算, 设置预览尺寸 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 预览分辨率 + */ + public Camera.Size getPreviewSize(final double distortion) { + return getPreviewSize(null, distortion); + } + + /** + * 根据手机支持的预览分辨率计算, 设置预览尺寸 ( 无任何操作, 单独把 Camera 显示到 SurfaceView 预览尺寸 ) + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 预览分辨率 + */ + public Camera.Size getPreviewSize( + final Point point, + final double distortion + ) { + if (mCamera == null) { + LogPrintUtils.dTag(TAG, "camera is null"); + return null; + } + try { + // 计算大小并返回 + return calcPreviewSize(point, distortion); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPreviewSize"); + } + return null; + } + + // ============= + // = 拍照大小相关 = + // ============= + + /** + * 设置拍照图片大小 + * @param pictureSize 拍照图片 {@link Camera.Size} + * @return {@link Camera.Parameters} + */ + public Camera.Parameters setPictureSize(final Camera.Size pictureSize) { + return setPictureSize(null, pictureSize); + } + + /** + * 设置拍照图片大小 + * @param parameters {@link Camera.Parameters} + * @param pictureSize 拍照图片 {@link Camera.Size} + * @return {@link Camera.Parameters} + */ + public Camera.Parameters setPictureSize( + Camera.Parameters parameters, + final Camera.Size pictureSize + ) { + if (mCamera != null && pictureSize != null) { + try { + if (parameters == null) { + parameters = mCamera.getParameters(); + } + // 设置拍照大小 + parameters.setPictureSize(pictureSize.width, pictureSize.height); +// // 设置拍照输出格式 +// parameters.setPictureFormat(PixelFormat.JPEG); +// // 照片质量 +// parameters.set("jpeg-quality", 70); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setPictureSize"); + } + } + return parameters; + } + + // = + + /** + * 根据手机支持的拍照分辨率计算 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize() { + return getPictureSize(false, null, -1D); + } + + /** + * 根据手机支持的拍照分辨率计算 + * @param max 是否使用最大的尺寸 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize(final boolean max) { + return getPictureSize(max, null, -1D); + } + + /** + * 根据手机支持的拍照分辨率计算 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize(final Point point) { + return getPictureSize(false, point, -1D); + } + + /** + * 根据手机支持的拍照分辨率计算 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize(final double distortion) { + return getPictureSize(false, null, distortion); + } + + /** + * 根据手机支持的拍照分辨率计算 + * @param point {@link Point} point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize( + final Point point, + final double distortion + ) { + return getPictureSize(false, point, distortion); + } + + /** + * 根据手机支持的拍照分辨率计算, 设置预览尺寸 + * @param max 是否使用最大的尺寸 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 拍照分辨率 + */ + public Camera.Size getPictureSize( + final boolean max, + final Point point, + final double distortion + ) { + if (mCamera == null) { + LogPrintUtils.dTag(TAG, "camera is null"); + return null; + } + try { + // 计算大小并返回 + return calcPictureSize(max, point, distortion); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPictureSize"); + } + return null; + } + + // ================ + // = 视频录制大小相关 = + // ================ + + /** + * 根据手机支持的视频录制分辨率计算 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize() { + return getVideoSize(false, null, -1D, false); + } + + /** + * 根据手机支持的视频录制分辨率计算 + * @param max 是否使用最大的尺寸 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize(final boolean max) { + return getVideoSize(max, null, -1D, false); + } + + /** + * 根据手机支持的视频录制分辨率计算 + * @param point {@link Point} point.x = 宽, point.y = 高 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize(final Point point) { + return getVideoSize(false, point, -1D, false); + } + + /** + * 根据手机支持的视频录制分辨率计算 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize(final double distortion) { + return getVideoSize(false, null, distortion, false); + } + + /** + * 根据手机支持的视频录制分辨率计算 + * @param point {@link Point} point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize( + final Point point, + final double distortion + ) { + return getVideoSize(false, point, distortion, false); + } + + /** + * 根据手机支持的视频录制分辨率计算, 设置预览尺寸 + * @param max 是否使用最大的尺寸 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @param minAccord 是否存在最小使用最小 + * @return {@link Camera.Size} 视频分辨率 + */ + public Camera.Size getVideoSize( + final boolean max, + final Point point, + final double distortion, + final boolean minAccord + ) { + if (mCamera == null) { + LogPrintUtils.dTag(TAG, "camera is null"); + return null; + } + try { + // 计算大小并返回 + return calcVideoSize(max, point, distortion, minAccord); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoSize"); + } + return null; + } + + // ========== + // = 预览大小 = + // ========== + + /** + * 根据对应的尺寸, 计算相应最符合的大小 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 预览分辨率 + */ + private Camera.Size calcPreviewSize( + Point point, + double distortion + ) { + // 判断是否为 null + if (point == null) { + point = ScreenUtils.getScreenWidthHeightToPoint(); + } + // 如果误差为负数, 则使用默认值 + if (distortion < 0) { + distortion = MAX_ASPECT_DISTORTION; + } + // 获取 Camera 参数 + Camera.Parameters params = mCamera.getParameters(); + // 获取手机支持的分辨率集合, 并以宽度为基准降序排序 + List listPreviewSizes = params.getSupportedPreviewSizes(); + // 防止数据为 null + if (listPreviewSizes == null) { + // 获取默认预览大小 + return params.getPreviewSize(); + } + + // 进行排序处理, 并以宽度 * 高度 为基准降序排序 + Collections.sort(listPreviewSizes, (lhs, rhs) -> { + int leftPixels = lhs.height * lhs.width; + int rightPixels = rhs.height * rhs.width; + return Integer.compare(rightPixels, leftPixels); + }); + + // = 打印信息 = + if (LogPrintUtils.isPrintLog()) { + StringBuilder builder = new StringBuilder(); + builder.append("预览支持尺寸: ").append(DevFinal.SYMBOL.NEW_LINE); + // 打印信息 + for (Camera.Size previewSize : listPreviewSizes) { + // 例: 1080 x 1920 + builder.append(previewSize.width) + .append("x").append(previewSize.height) + .append(DevFinal.SYMBOL.NEW_LINE); + } + // 打印尺寸信息 + LogPrintUtils.dTag(TAG, builder.toString()); + } + + // 判断是否竖屏 point.x = 宽, point.y = 高 + if (point.y > point.x) { + int tempY = point.y; + int tempX = point.x; + // 进行转换 + point.x = tempY; + point.y = tempX; + } + + // 计算比例值 宽 / 高 + double screenAspectRatio = (double) point.x / (double) point.y; + + // 循环遍历判断 + Iterator iterator = listPreviewSizes.iterator(); + while (iterator.hasNext()) { + // 获取预览大小 + Camera.Size previewSize = iterator.next(); + // 获取宽、高 + int realWidth = previewSize.width; + int realHeight = previewSize.height; + // 小于最小尺寸, 则不处理 + if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { + iterator.remove(); + continue; + } + + // 判断预选的尺寸是否竖屏 + boolean isCandidatePortrait = realWidth < realHeight; + // 翻转宽、高 + int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; + int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; + + // 计算比例 + double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; + double calcDistortion = Math.abs(aspectRatio - screenAspectRatio); + + // 如果大于指定的尺寸比例差, 则跳过 + if (calcDistortion > distortion) { + iterator.remove(); + continue; + } + + // 如果相符, 则直接跳过 + if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { + return previewSize; + } + } + + // 如果没有精确匹配, 则使用最大预览大小 + if (!listPreviewSizes.isEmpty()) { + // 获取最大的尺寸 + return listPreviewSizes.get(0); + } + + // = 都不匹配, 则用默认分辨率 = + // 获取默认预览大小 + return params.getPreviewSize(); + } + + // ========== + // = 拍照大小 = + // ========== + + /** + * 根据对应的尺寸, 计算相应最符合的大小 + * @param max 是否使用最大的尺寸 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @return {@link Camera.Size} 拍照分辨率 + */ + private Camera.Size calcPictureSize( + final boolean max, + Point point, + double distortion + ) { + // 判断是否为 null + if (point == null) { + point = ScreenUtils.getScreenWidthHeightToPoint(); + } + // 如果误差为负数, 则使用默认值 + if (distortion < 0) { + distortion = MAX_ASPECT_DISTORTION; + } + // 获取 Camera 参数 + Camera.Parameters params = mCamera.getParameters(); + // 获取手机支持的分辨率集合, 并以宽度为基准降序排序 + List listPictureSizes = params.getSupportedPictureSizes(); + // 防止数据为 null + if (listPictureSizes == null) { + // 获取默认拍照大小 + return params.getPictureSize(); + } + + // 进行排序处理, 并以宽度 * 高度 为基准降序排序 + Collections.sort(listPictureSizes, (lhs, rhs) -> { + int leftPixels = lhs.height * lhs.width; + int rightPixels = rhs.height * rhs.width; + return Integer.compare(rightPixels, leftPixels); + }); + + // = 打印信息 = + if (LogPrintUtils.isPrintLog()) { + StringBuilder builder = new StringBuilder(); + builder.append("拍照支持尺寸: ").append(DevFinal.SYMBOL.NEW_LINE); + // 打印信息 + for (Camera.Size pictureSize : listPictureSizes) { + // 例: 1080 x 1920 + builder.append(pictureSize.width) + .append("x").append(pictureSize.height) + .append(DevFinal.SYMBOL.NEW_LINE); + } + // 打印尺寸信息 + LogPrintUtils.dTag(TAG, builder.toString()); + } + + // 判断是否拿最大支持的尺寸 + if (max) { + if (!listPictureSizes.isEmpty()) { + // 获取最大的尺寸 + return listPictureSizes.get(0); + } else { + // 获取默认拍照大小 + return params.getPictureSize(); + } + } + + // 判断是否竖屏 point.x = 宽, point.y = 高 + if (point.y > point.x) { + int tempY = point.y; + int tempX = point.x; + // 进行转换 + point.x = tempY; + point.y = tempX; + } + + // 计算比例值 宽 / 高 + double pictureAspectRatio = (double) point.x / (double) point.y; + + // 判断最大符合 + Camera.Size maxAccordSize = null; + + // 循环遍历判断 + Iterator iterator = listPictureSizes.iterator(); + while (iterator.hasNext()) { + // 获取拍照大小 + Camera.Size pictureSize = iterator.next(); + // 获取宽、高 + int realWidth = pictureSize.width; + int realHeight = pictureSize.height; + // 小于最小尺寸, 则不处理 + if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { + iterator.remove(); + continue; + } + + // 判断预选的尺寸是否竖屏 + boolean isCandidatePortrait = realWidth < realHeight; + // 翻转宽、高 + int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; + int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; + + // 计算比例 + double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; + double calcDistortion = Math.abs(aspectRatio - pictureAspectRatio); + + // 如果大于指定的尺寸比例差, 则跳过 + if (calcDistortion > distortion) { + iterator.remove(); + continue; + } + + // 如果相符, 则直接跳过 + if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { + return pictureSize; + } + + // 保存最大相符的尺寸 + if (maxAccordSize == null) { + maxAccordSize = pictureSize; + } + } + + // 如果存在最相符的则返回 + if (maxAccordSize != null) { + return maxAccordSize; + } + + // 如果没有精确匹配, 则使用最大尺寸大小 + if (!listPictureSizes.isEmpty()) { + // 获取最大的尺寸 + return listPictureSizes.get(0); + } + + // = 都不匹配, 则用默认分辨率 = + // 获取默认拍照大小 + return params.getPictureSize(); + } + + // ============= + // = 视频录制尺寸 = + // ============= + + /** + * 根据对应的尺寸, 计算相应最符合的大小 + * @param max 是否使用最大的尺寸 + * @param point 指定的尺寸 ( 为 null, 则使用屏幕尺寸 ) + * ( 从指定的宽高, 开始往下 ( 超过的不处理 ) 选择最接近尺寸 ) point.x = 宽, point.y = 高 + * @param distortion 偏差比例值 + * @param minAccord 是否判断存在最小使用最小 + * @return {@link Camera.Size} 视频分辨率 + */ + private Camera.Size calcVideoSize( + final boolean max, + Point point, + double distortion, + final boolean minAccord + ) { + // 判断是否为 null + if (point == null) { + point = ScreenUtils.getScreenWidthHeightToPoint(); + } + // 如果误差为负数, 则使用默认值 + if (distortion < 0) { + distortion = MAX_ASPECT_DISTORTION; + } + // 获取 Camera 参数 + Camera.Parameters params = mCamera.getParameters(); + // 获取手机支持的分辨率集合, 并以宽度为基准降序排序 + List listVideoSizes = params.getSupportedVideoSizes(); + // 防止数据为 null + if (listVideoSizes == null) { + // 获取默认拍照大小 + return params.getPreferredPreviewSizeForVideo(); + } + + // 进行排序处理, 并以宽度 * 高度 为基准降序排序 + Collections.sort(listVideoSizes, (lhs, rhs) -> { + int leftPixels = lhs.height * lhs.width; + int rightPixels = rhs.height * rhs.width; + return Integer.compare(rightPixels, leftPixels); + }); + + // = 打印信息 = + if (LogPrintUtils.isPrintLog()) { + StringBuilder builder = new StringBuilder(); + builder.append("视频录制支持尺寸: ").append(DevFinal.SYMBOL.NEW_LINE); + // 打印信息 + for (Camera.Size videoSize : listVideoSizes) { + // 例: 1080 x 1920 + builder.append(videoSize.width) + .append("x").append(videoSize.height) + .append(DevFinal.SYMBOL.NEW_LINE); + } + // 打印尺寸信息 + LogPrintUtils.dTag(TAG, builder.toString()); + } + + // 判断是否拿最大支持的尺寸 + if (max) { + if (!listVideoSizes.isEmpty()) { + // 获取最大的尺寸 + return listVideoSizes.get(0); + } else { + // 获取默认视频大小 + return params.getPreferredPreviewSizeForVideo(); + } + } + + // 判断是否竖屏 point.x = 宽, point.y = 高 + if (point.y > point.x) { + int tempY = point.y; + int tempX = point.x; + // 进行转换 + point.x = tempY; + point.y = tempX; + } + + // 计算比例值 宽 / 高 + double videoAspectRatio = (double) point.x / (double) point.y; + + // 判断最大符合 + Camera.Size maxAccordSize = null; + // 判断最小符合 + Camera.Size minAccordSize = null; + + // 循环遍历判断 + Iterator iterator = listVideoSizes.iterator(); + while (iterator.hasNext()) { + // 获取视频大小 + Camera.Size videoSize = iterator.next(); + // 获取宽、高 + int realWidth = videoSize.width; + int realHeight = videoSize.height; + // 小于最小尺寸, 则不处理 + if (realWidth * realHeight < MIN_PREVIEW_PIXELS) { + iterator.remove(); + continue; + } + + // 判断预选的尺寸是否竖屏 + boolean isCandidatePortrait = realWidth < realHeight; + // 翻转宽、高 + int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth; + int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight; + + // 计算比例 + double aspectRatio = (double) maybeFlippedWidth / (double) maybeFlippedHeight; + double calcDistortion = Math.abs(aspectRatio - videoAspectRatio); + + // 如果大于指定的尺寸比例差, 则跳过 + if (calcDistortion > distortion) { + iterator.remove(); + continue; + } + + // 如果相符, 则直接跳过 + if (maybeFlippedWidth == point.x && maybeFlippedHeight == point.y) { + return videoSize; + } + + // 保存最大相符的尺寸 + if (maxAccordSize == null) { + maxAccordSize = videoSize; + } + // 保存最小符合的 + minAccordSize = videoSize; + } + + if (minAccord && minAccordSize != null) { + return minAccordSize; + } + + // 如果存在最相符的则返回 + if (maxAccordSize != null) { + return maxAccordSize; + } + + // 如果没有精确匹配, 则使用最大尺寸大小 + if (!listVideoSizes.isEmpty()) { + // 获取最大的尺寸 + return listVideoSizes.get(0); + } + + // = 都不匹配, 则用默认分辨率 = + // 获取默认视频大小 + return params.getPreferredPreviewSizeForVideo(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraUtils.java b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraUtils.java new file mode 100644 index 0000000000..cead81764a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/CameraUtils.java @@ -0,0 +1,223 @@ +package dev.utils.app.camera.camera1; + +import android.hardware.Camera; + +import dev.utils.LogPrintUtils; + +/** + * detail: 摄像头相关工具类 + * @author Ttt + */ +public final class CameraUtils { + + private CameraUtils() { + } + + // 日志 TAG + private static final String TAG = CameraUtils.class.getSimpleName(); + + // =============== + // = 摄像头快速处理 = + // =============== + + /** + * 判断是否支持反转摄像头 ( 是否存在前置摄像头 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportReverse() { + try { + // 默认是不支持 + int isSupportReverse = 0; + // 判断是否支持前置, 支持则使用前置 + if (checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)) { + isSupportReverse += 1; + // = + LogPrintUtils.dTag(TAG, "支持前置摄像头 ( 手机屏幕 )"); + } + // 判断是否支持后置, 是则使用后置 + if (checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_BACK)) { + isSupportReverse += 1; + // = + LogPrintUtils.dTag(TAG, "支持后置摄像头 ( 手机背面 )"); + } + // 如果都支持才表示支持反转 + return isSupportReverse == 2; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isSupportReverse"); + } + // 默认不支持反转摄像头 + return false; + } + + /** + * 检查是否有指定的摄像头 + * @param facing 摄像头标识 id + * @return {@code true} yes, {@code false} no + */ + public static boolean checkCameraFacing(final int facing) { + try { + int cameraCount = Camera.getNumberOfCameras(); + Camera.CameraInfo info = new Camera.CameraInfo(); + for (int i = 0; i < cameraCount; i++) { + Camera.getCameraInfo(i, info); + if (facing == info.facing) { + return true; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "checkCameraFacing"); + } + return false; + } + + /** + * 判断是否使用前置摄像头 + * @param facing 摄像头标识 id + * @return {@code true} yes, {@code false} no + */ + public static boolean isFrontCamera(final int facing) { + return facing == Camera.CameraInfo.CAMERA_FACING_FRONT; + } + + /** + * 判断是否使用后置摄像头 + * @param facing 摄像头标识 id + * @return {@code true} yes, {@code false} no + */ + public static boolean isBackCamera(final int facing) { + return facing == Camera.CameraInfo.CAMERA_FACING_BACK; + } + + /** + * 判断使用的摄像头 + * @param isFrontCamera 是否前置摄像头 + * @return 摄像头标识 id + */ + public static int isUseCameraFacing(final boolean isFrontCamera) { + // 默认使用后置摄像头 + int cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; + try { + // 支持的摄像头 ( 前置, 后置 ) + boolean[] cameraFacings = new boolean[]{false, false}; + // 判断是否支持前置 + cameraFacings[0] = checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT); + // 判断是否支持后置 + cameraFacings[1] = checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_BACK); + // 进行判断想要使用的是前置, 还是后置 + if (isFrontCamera && cameraFacings[0]) { // 使用前置, 必须也支持前置 + // 表示使用前置摄像头 + cameraFacing = Camera.CameraInfo.CAMERA_FACING_FRONT; + } else { + // 表示使用后置摄像头 + cameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isUseCameraFacing"); + } + return cameraFacing; + } + + // = + + /** + * 释放摄像头资源 + * @param camera {@link android.hardware.Camera} + */ + public static void freeCameraResource(final Camera camera) { + try { + if (camera != null) { + camera.setPreviewCallback(null); + camera.stopPreview(); + camera.lock(); + camera.release(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "freeCameraResource"); + } + } + + /** + * 初始化摄像头 + * @param camera {@link android.hardware.Camera} + * @param isFrontCamera {@code true} 前置 ( 屏幕面 ), {@code false} 后置 ( 手机背面 ) + * @return {@link android.hardware.Camera} + */ + public static Camera initCamera( + Camera camera, + final boolean isFrontCamera + ) { + // 如果之前存在摄像头数据, 则释放资源 + if (camera != null) { + freeCameraResource(camera); + } + try { + // 进行判断想要使用的是前置, 还是后置 + if (isFrontCamera && checkCameraFacing(Camera.CameraInfo.CAMERA_FACING_FRONT)) { + // 初始化前置摄像头 + camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT); + } else { + // 初始化后置摄像头 + camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "initCamera"); + // 释放资源 + freeCameraResource(camera); + } + return camera; + } + + /** + * 打开摄像头 + * @param cameraId {@link Camera.CameraInfo} CAMERA_FACING_FRONT( 前置 ), CAMERA_FACING_BACK( 后置 ) + * @return {@link android.hardware.Camera} + */ + public static Camera open(int cameraId) { + // 判断支持的摄像头数量 + int numCameras = Camera.getNumberOfCameras(); + if (numCameras == 0) { + return null; + } + try { + // 判断是否指定哪个摄像头 + boolean explicitRequest = cameraId >= 0; + // 如果没指定, 则进行判断处理 + if (!explicitRequest) { // 默认使用 后置摄像头, 没有后置才用其他 ( 前置 ) + int index = 0; + while (index < numCameras) { + Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); + Camera.getCameraInfo(index, cameraInfo); + if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) { + break; + } + index++; + } + cameraId = index; + } + + Camera camera; + if (cameraId < numCameras) { + camera = Camera.open(cameraId); + } else { + if (explicitRequest) { + camera = null; + } else { + // 默认使用后置 + camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK); + } + } + return camera; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "open - cameraId: %s", cameraId); + } + return null; + } + + /** + * 打开摄像头 ( 默认后置摄像头 ) + * @return {@link android.hardware.Camera} + */ + public static Camera open() { + return open(-1); // Camera.CameraInfo.CAMERA_FACING_BACK + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/FlashlightUtils.java b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/FlashlightUtils.java new file mode 100644 index 0000000000..9680c72ef6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/camera/camera1/FlashlightUtils.java @@ -0,0 +1,194 @@ +package dev.utils.app.camera.camera1; + +import android.content.pm.PackageManager; +import android.graphics.SurfaceTexture; +import android.hardware.Camera; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; + +/** + * detail: 手电筒工具类 + * @author Ttt + *
+ *     在非 Camera 预览页面使用的话, 需要先调用 register(), 不使用的时候 unregister();
+ * 
+ */ +public final class FlashlightUtils { + + private FlashlightUtils() { + } + + // 日志 TAG + private final String TAG = FlashlightUtils.class.getSimpleName(); + + // FlashlightUtils 实例 + private static volatile FlashlightUtils sInstance; + + /** + * 获取 FlashlightUtils 实例 + * @return {@link FlashlightUtils} + */ + public static FlashlightUtils getInstance() { + if (sInstance == null) { + synchronized (FlashlightUtils.class) { + if (sInstance == null) { + sInstance = new FlashlightUtils(); + } + } + } + return sInstance; + } + + // Camera 对象 + private Camera mCamera; + + /** + * 注册摄像头 + * @return {@code true} success, {@code false} fail + */ + public boolean register() { + try { + mCamera = Camera.open(0); + } catch (Throwable ignore) { + return false; + } + if (mCamera == null) return false; + try { + mCamera.setPreviewTexture(new SurfaceTexture(0)); + mCamera.startPreview(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "register"); + return false; + } + } + + /** + * 注销摄像头 + * @return {@code true} success, {@code false} fail + */ + public boolean unregister() { + if (mCamera == null) return false; + try { + mCamera.stopPreview(); + mCamera.release(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "unregister"); + } + return false; + } + + // = + + /** + * 是否支持手机闪光灯 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFlashlightEnable() { + PackageManager packageManager = AppUtils.getPackageManager(); + return (packageManager != null) && + packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); + } + + // = + + /** + * 打开闪光灯 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOn() { + return setFlashlightOn(mCamera); + } + + /** + * 打开闪光灯 + * @param camera {@link android.graphics.Camera} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOn(final Camera camera) { + if (camera != null) { + try { + Camera.Parameters parameters = camera.getParameters(); + if (parameters != null && + !Camera.Parameters.FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { + try { + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); + camera.setParameters(parameters); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setFlashlightOn"); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setFlashlightOn - getParameters"); + } + } + return false; + } + + // = + + /** + * 关闭闪光灯 + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOff() { + return setFlashlightOff(mCamera); + } + + /** + * 关闭闪光灯 + * @param camera {@link android.graphics.Camera} + * @return {@code true} success, {@code false} fail + */ + public boolean setFlashlightOff(final Camera camera) { + if (camera != null) { + try { + Camera.Parameters parameters = camera.getParameters(); + if (parameters != null + && Camera.Parameters.FLASH_MODE_TORCH.equals(parameters.getFlashMode())) { + try { + parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); + camera.setParameters(parameters); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setFlashlightOff"); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setFlashlightOff - getParameters"); + } + } + return false; + } + + // = + + /** + * 是否打开闪光灯 + * @return {@code true} yes, {@code false} no + */ + public boolean isFlashlightOn() { + return isFlashlightOn(mCamera); + } + + /** + * 是否打开闪光灯 + * @param camera {@link android.graphics.Camera} + * @return {@code true} yes, {@code false} no + */ + public boolean isFlashlightOn(final Camera camera) { + if (camera != null) { + try { + Camera.Parameters parameters = camera.getParameters(); + return parameters != null + && Camera.Parameters.FLASH_MODE_TORCH.equals(parameters.getFlashMode()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isFlashlightOn"); + } + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/BaseHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/BaseHelper.java new file mode 100644 index 0000000000..de7e3c50b5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/BaseHelper.java @@ -0,0 +1,150 @@ +package dev.utils.app.helper; + +import android.view.View; + +import dev.utils.app.HandlerUtils; +import dev.utils.app.helper.dev.DevHelper; +import dev.utils.app.helper.flow.FlowHelper; +import dev.utils.app.helper.quick.QuickHelper; +import dev.utils.app.helper.view.ViewHelper; + +/** + * detail: 基础 Helper 通用实现类 + * @author Ttt + */ +public abstract class BaseHelper { + + /** + * 获取 DevHelper + * @return {@link DevHelper} + */ + public DevHelper devHelper() { + return DevHelper.get(); + } + + /** + * 获取 QuickHelper + * @param target 目标 View + * @return {@link QuickHelper} + */ + public QuickHelper quickHelper(View target) { + return QuickHelper.get(target); + } + + /** + * 获取 ViewHelper + * @return {@link ViewHelper} + */ + public ViewHelper viewHelper() { + return ViewHelper.get(); + } + + /** + * 获取 FlowHelper + * @return {@link FlowHelper} + */ + public FlowHelper flowHelper() { + return FlowHelper.get(); + } + + // ======== + // = Flow = + // ======== + + /** + * 执行 Action 流方法 + * @param action Action + * @return Helper + */ + public abstract Helper flow(FlowHelper.Action action); + + // = + + /** + * 流式返回传入值 + * @param value 泛型值 + * @param 泛型 + * @return 泛型值 + */ + public T flowValue(T value) { + return value; + } + + /** + * 流式返回传入值 + * @param value 泛型值 + * @param action Action + * @param 泛型 + * @return 泛型值 + */ + public T flowValue( + T value, + FlowHelper.Action action + ) { + if (action != null) { + action.action(); + } + return value; + } + + // ================ + // = HandlerUtils = + // ================ + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + * @return Helper + */ + public abstract Helper postRunnable(Runnable runnable); + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @return Helper + */ + public abstract Helper postRunnable( + Runnable runnable, + long delayMillis + ); + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @return Helper + */ + public abstract Helper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval + ); + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param listener 结束通知 + * @return Helper + */ + public abstract Helper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval, + HandlerUtils.OnEndListener listener + ); + + /** + * 在主线程 Handler 中清除任务 + * @param runnable 需要清除的任务 + * @return Helper + */ + public abstract Helper removeRunnable(Runnable runnable); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/dev/DevHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/dev/DevHelper.java new file mode 100644 index 0000000000..6f30ec76fd --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/dev/DevHelper.java @@ -0,0 +1,2469 @@ +package dev.utils.app.helper.dev; + +import android.Manifest; +import android.app.Activity; +import android.app.Dialog; +import android.app.Notification; +import android.app.NotificationChannel; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.PopupWindow; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.annotation.RequiresPermission; +import androidx.fragment.app.DialogFragment; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.OutputStream; +import java.io.Writer; +import java.util.Locale; + +import dev.utils.app.ClickUtils; +import dev.utils.app.ClipboardUtils; +import dev.utils.app.DialogUtils; +import dev.utils.app.HandlerUtils; +import dev.utils.app.KeyBoardUtils; +import dev.utils.app.LanguageUtils; +import dev.utils.app.NotificationUtils; +import dev.utils.app.PhoneUtils; +import dev.utils.app.PowerManagerUtils; +import dev.utils.app.ScreenUtils; +import dev.utils.app.SizeUtils; +import dev.utils.app.VibrationUtils; +import dev.utils.app.ViewUtils; +import dev.utils.app.WidgetUtils; +import dev.utils.app.WindowUtils; +import dev.utils.app.anim.AnimationUtils; +import dev.utils.app.helper.BaseHelper; +import dev.utils.app.helper.flow.FlowHelper; +import dev.utils.app.image.BitmapUtils; +import dev.utils.app.timer.DevTimer; +import dev.utils.app.timer.TimerManager; +import dev.utils.common.CloseUtils; +import dev.utils.common.ForUtils; + +/** + * detail: Dev 工具类链式调用 Helper 类 + * @author Ttt + *
+ *     通过 DevApp 工具类快捷实现
+ *     

+ * DevApp Api + * @see
+ *
+ */ +public final class DevHelper + extends BaseHelper + implements IHelperByDev { + + private DevHelper() { + } + + // DevHelper + private static final DevHelper HELPER = new DevHelper(); + + /** + * 获取单例 DevHelper + * @return {@link DevHelper} + */ + public static DevHelper get() { + return HELPER; + } + + // ======== + // = Flow = + // ======== + + /** + * 执行 Action 流方法 + * @param action Action + * @return Helper + */ + @Override + public DevHelper flow(FlowHelper.Action action) { + if (action != null) action.action(); + return this; + } + + // ================ + // = HandlerUtils = + // ================ + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + * @return Helper + */ + @Override + public DevHelper postRunnable(Runnable runnable) { + HandlerUtils.postRunnable(runnable); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @return Helper + */ + @Override + public DevHelper postRunnable( + Runnable runnable, + long delayMillis + ) { + HandlerUtils.postRunnable(runnable, delayMillis); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @return Helper + */ + @Override + public DevHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param listener 结束通知 + * @return Helper + */ + @Override + public DevHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval, + HandlerUtils.OnEndListener listener + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval, listener); + return this; + } + + /** + * 在主线程 Handler 中清除任务 + * @param runnable 需要清除的任务 + * @return Helper + */ + @Override + public DevHelper removeRunnable(Runnable runnable) { + HandlerUtils.removeRunnable(runnable); + return this; + } + + // ================ + // = IHelperByDev = + // ================ + + // ================== + // = AnimationUtils = + // ================== + + /** + * 设置动画重复处理 + * @param repeatCount 执行次数 + * @param repeatMode 重复模式 {@link Animation#RESTART} 重新从头开始执行、{@link Animation#REVERSE} 反方向执行 + * @param animations Animation[] + * @return Helper + */ + @Override + public DevHelper setAnimationRepeat( + int repeatCount, + int repeatMode, + Animation... animations + ) { + ForUtils.forSimpleArgs( + value -> AnimationUtils.setAnimationRepeat( + value, repeatCount, repeatMode + ), animations + ); + return this; + } + + /** + * 设置动画事件 + * @param listener {@link Animation.AnimationListener} + * @param animations Animation[] + * @return Helper + */ + @Override + public DevHelper setAnimationListener( + Animation.AnimationListener listener, + Animation... animations + ) { + ForUtils.forSimpleArgs( + value -> AnimationUtils.setAnimationListener( + value, listener + ), animations + ); + return this; + } + + /** + * 启动动画 + * @param animations Animation[] + * @return Helper + */ + @Override + public DevHelper startAnimation(Animation... animations) { + ForUtils.forSimpleArgs( + value -> AnimationUtils.startAnimation(value), animations + ); + return this; + } + + /** + * 取消动画 + * @param animations Animation[] + * @return Helper + */ + @Override + public DevHelper cancelAnimation(Animation... animations) { + ForUtils.forSimpleArgs( + value -> AnimationUtils.cancelAnimation(value), animations + ); + return this; + } + + // =============== + // = BitmapUtils = + // =============== + + /** + * Bitmap 通知回收 + * @param bitmaps Bitmap[] + * @return Helper + */ + @Override + public DevHelper recycle(Bitmap... bitmaps) { + ForUtils.forSimpleArgs( + value -> BitmapUtils.recycle(value), bitmaps + ); + return this; + } + + // ================ + // = TimerManager = + // ================ + + /** + * 运行定时器 + * @param timers DevTimer[] + * @return Helper + */ + @Override + public DevHelper startTimer(DevTimer... timers) { + ForUtils.forSimpleArgs( + value -> TimerManager.startTimer(value), timers + ); + return this; + } + + /** + * 关闭定时器 + * @param timers DevTimer[] + * @return Helper + */ + @Override + public DevHelper stopTimer(DevTimer... timers) { + ForUtils.forSimpleArgs( + value -> TimerManager.stopTimer(value), timers + ); + return this; + } + + /** + * 回收定时器资源 + * @return Helper + */ + @Override + public DevHelper recycleTimer() { + TimerManager.recycle(); + return this; + } + + /** + * 关闭全部定时器 + * @return Helper + */ + @Override + public DevHelper closeAllTimer() { + TimerManager.closeAll(); + return this; + } + + /** + * 关闭所有未运行的定时器 + * @return Helper + */ + @Override + public DevHelper closeAllNotRunningTimer() { + TimerManager.closeAllNotRunning(); + return this; + } + + /** + * 关闭所有无限循环的定时器 + * @return Helper + */ + @Override + public DevHelper closeAllInfiniteTimer() { + TimerManager.closeAllInfinite(); + return this; + } + + /** + * 关闭所有对应 TAG 定时器 + * @param tags 判断 {@link DevTimer#getTag()} + * @return Helper + */ + @Override + public DevHelper closeAllTagTimer(String... tags) { + ForUtils.forSimpleArgs( + value -> TimerManager.closeAllTag(value), tags + ); + return this; + } + + /** + * 关闭所有对应 UUID 定时器 + * @param uuids 判断 {@link DevTimer#getUUID()} + * @return Helper + */ + @Override + public DevHelper closeAllUUIDTimer(int... uuids) { + ForUtils.forInts( + (index, value) -> TimerManager.closeAllUUID(value), uuids + ); + return this; + } + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @param views View[] + * @return Helper + */ + @Override + public DevHelper addTouchArea( + int range, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.addTouchArea(value, range), views + ); + return this; + } + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @param views View[] + * @return Helper + */ + @Override + public DevHelper addTouchArea( + int left, + int top, + int right, + int bottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.addTouchArea(value, left, top, right, bottom), views + ); + return this; + } + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @param views View[] + * @return Helper + */ + @Override + public DevHelper setOnClick( + View.OnClickListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnClick(value, listener), views + ); + return this; + } + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @param views View[] + * @return Helper + */ + @Override + public DevHelper setOnLongClick( + View.OnLongClickListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnLongClick(value, listener), views + ); + return this; + } + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @param views View[] + * @return Helper + */ + @Override + public DevHelper setOnTouch( + View.OnTouchListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnTouch(value, listener), views + ); + return this; + } + + // ================== + // = ClipboardUtils = + // ================== + + /** + * 复制文本到剪贴板 + * @param text 文本 + * @return Helper + */ + @Override + public DevHelper copyText(CharSequence text) { + ClipboardUtils.copyText(text); + return this; + } + + /** + * 复制 URI 到剪贴板 + * @param uri {@link Uri} + * @return Helper + */ + @Override + public DevHelper copyUri(Uri uri) { + ClipboardUtils.copyUri(uri); + return this; + } + + /** + * 复制意图到剪贴板 + * @param intent {@link Intent} + * @return Helper + */ + @Override + public DevHelper copyIntent(Intent intent) { + ClipboardUtils.copyIntent(intent); + return this; + } + + // =============== + // = DialogUtils = + // =============== + + /** + * 设置 Dialog 状态栏颜色 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return Helper + */ + @Override + public DevHelper setDialogStatusBarColor( + Dialog dialog, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + DialogUtils.setStatusBarColor( + dialog, color + ); + } + return this; + } + + /** + * 设置 Dialog 高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return Helper + */ + @Override + public DevHelper setDialogSemiTransparentStatusBarColor( + Dialog dialog, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + DialogUtils.setSemiTransparentStatusBarColor( + dialog, color + ); + } + return this; + } + + /** + * 设置 Dialog 状态栏颜色、高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return Helper + */ + @Override + public DevHelper setDialogStatusBarColorAndFlag( + Dialog dialog, + @ColorInt int color, + boolean addFlags + ) { + DialogUtils.setStatusBarColorAndFlag( + dialog, color, addFlags + ); + return this; + } + + /** + * 设置 Dialog Window LayoutParams + * @param dialog {@link Dialog} + * @param params {@link WindowManager.LayoutParams} + * @return Helper + */ + @Override + public DevHelper setDialogAttributes( + Dialog dialog, + WindowManager.LayoutParams params + ) { + DialogUtils.setAttributes( + dialog, params + ); + return this; + } + + /** + * 设置 Dialog 宽度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @return Helper + */ + @Override + public DevHelper setDialogWidth( + Dialog dialog, + int width + ) { + DialogUtils.setWidth( + dialog, width + ); + return this; + } + + /** + * 设置 Dialog 高度 + * @param dialog {@link Dialog} + * @param height 高度 + * @return Helper + */ + @Override + public DevHelper setDialogHeight( + Dialog dialog, + int height + ) { + DialogUtils.setHeight( + dialog, height + ); + return this; + } + + /** + * 设置 Dialog 宽度、高度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @param height 高度 + * @return Helper + */ + @Override + public DevHelper setDialogWidthHeight( + Dialog dialog, + int width, + int height + ) { + DialogUtils.setWidthHeight( + dialog, width, height + ); + return this; + } + + /** + * 设置 Dialog X 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @return Helper + */ + @Override + public DevHelper setDialogX( + Dialog dialog, + int x + ) { + DialogUtils.setX( + dialog, x + ); + return this; + } + + /** + * 设置 Dialog Y 轴坐标 + * @param dialog {@link Dialog} + * @param y Y 轴坐标 + * @return Helper + */ + @Override + public DevHelper setDialogY( + Dialog dialog, + int y + ) { + DialogUtils.setY( + dialog, y + ); + return this; + } + + /** + * 设置 Dialog X、Y 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return Helper + */ + @Override + public DevHelper setDialogXY( + Dialog dialog, + int x, + int y + ) { + DialogUtils.setXY( + dialog, x, y + ); + return this; + } + + /** + * 设置 Dialog Gravity + * @param dialog {@link Dialog} + * @param gravity 重心 + * @return Helper + */ + @Override + public DevHelper setDialogGravity( + Dialog dialog, + int gravity + ) { + DialogUtils.setGravity( + dialog, gravity + ); + return this; + } + + /** + * 设置 Dialog 透明度 + * @param dialog {@link Dialog} + * @param dimAmount 透明度 + * @return Helper + */ + @Override + public DevHelper setDialogDimAmount( + Dialog dialog, + float dimAmount + ) { + DialogUtils.setDimAmount( + dialog, dimAmount + ); + return this; + } + + /** + * 设置是否允许返回键关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + @Override + public DevHelper setDialogCancelable( + Dialog dialog, + boolean cancel + ) { + DialogUtils.setCancelable( + dialog, cancel + ); + return this; + } + + /** + * 设置是否允许点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + @Override + public DevHelper setDialogCanceledOnTouchOutside( + Dialog dialog, + boolean cancel + ) { + DialogUtils.setCanceledOnTouchOutside( + dialog, cancel + ); + return this; + } + + /** + * 设置是否允许 返回键关闭、点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + @Override + public DevHelper setDialogCancelableAndTouchOutside( + Dialog dialog, + boolean cancel + ) { + DialogUtils.setCancelableAndTouchOutside( + dialog, cancel + ); + return this; + } + + // ============== + // = Dialog 操作 = + // ============== + + /** + * 显示 Dialog + * @param dialog {@link Dialog} + * @return Helper + */ + @Override + public DevHelper showDialog(Dialog dialog) { + DialogUtils.showDialog(dialog); + return this; + } + + /** + * 关闭多个 Dialog + * @param dialogs Dialog[] + * @return Helper + */ + @Override + public DevHelper closeDialogs(Dialog... dialogs) { + DialogUtils.closeDialogs(dialogs); + return this; + } + + /** + * 关闭多个 DialogFragment + * @param dialogs DialogFragment[] + * @return Helper + */ + @Override + public DevHelper closeDialogs(DialogFragment... dialogs) { + DialogUtils.closeDialogs(dialogs); + return this; + } + + /** + * 关闭多个 PopupWindow + * @param popupWindows PopupWindow[] + * @return Helper + */ + @Override + public DevHelper closePopupWindows(PopupWindow... popupWindows) { + DialogUtils.closePopupWindows(popupWindows); + return this; + } + + /** + * 自动关闭 dialog + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param dialogs Dialog[] + * @return Helper + */ + @Override + public DevHelper autoCloseDialog( + long delayMillis, + Handler handler, + Dialog... dialogs + ) { + ForUtils.forSimpleArgs( + value -> DialogUtils.autoCloseDialog( + value, delayMillis, handler + ), dialogs + ); + return this; + } + + /** + * 自动关闭 DialogFragment + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param dialogs DialogFragment[] + * @return Helper + */ + @Override + public DevHelper autoCloseDialog( + long delayMillis, + Handler handler, + DialogFragment... dialogs + ) { + ForUtils.forSimpleArgs( + value -> DialogUtils.autoCloseDialog( + value, delayMillis, handler + ), dialogs + ); + return this; + } + + /** + * 自动关闭 PopupWindow + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param popupWindows PopupWindow[] + * @return Helper + */ + @Override + public DevHelper autoClosePopupWindow( + long delayMillis, + Handler handler, + PopupWindow... popupWindows + ) { + ForUtils.forSimpleArgs( + value -> DialogUtils.autoClosePopupWindow( + value, delayMillis, handler + ), popupWindows + ); + return this; + } + + // ================= + // = KeyBoardUtils = + // ================= + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @return Helper + */ + @Override + public DevHelper setSoftInputMode( + Activity activity, + boolean inputVisible + ) { + KeyBoardUtils.setSoftInputMode( + activity, inputVisible + ); + return this; + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @return Helper + */ + @Override + public DevHelper setSoftInputMode( + Window window, + boolean inputVisible + ) { + KeyBoardUtils.setSoftInputMode( + window, inputVisible + ); + return this; + } + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + @Override + public DevHelper setSoftInputMode( + Activity activity, + boolean inputVisible, + boolean clearFlag + ) { + KeyBoardUtils.setSoftInputMode( + activity, inputVisible, clearFlag + ); + return this; + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + @Override + public DevHelper setSoftInputMode( + Window window, + boolean inputVisible, + boolean clearFlag + ) { + KeyBoardUtils.setSoftInputMode( + window, inputVisible, clearFlag + ); + return this; + } + + // ============================ + // = 点击非 EditText 则隐藏软键盘 = + // ============================ + + /** + * 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 + * @param view {@link View} + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper judgeView( + View view, + Activity activity + ) { + KeyBoardUtils.judgeView( + view, activity + ); + return this; + } + + // =============== + // = 软键盘隐藏显示 = + // =============== + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link KeyBoardUtils.OnSoftInputChangedListener} + * @return Helper + */ + @Override + public DevHelper registerSoftInputChangedListener( + Activity activity, + KeyBoardUtils.OnSoftInputChangedListener listener + ) { + KeyBoardUtils.registerSoftInputChangedListener( + activity, listener + ); + return this; + } + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link KeyBoardUtils.OnSoftInputChangedListener} + * @return Helper + */ + @Override + public DevHelper registerSoftInputChangedListener2( + Activity activity, + KeyBoardUtils.OnSoftInputChangedListener listener + ) { + KeyBoardUtils.registerSoftInputChangedListener2( + activity, listener + ); + return this; + } + + /** + * 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 + * @param context {@link Context} + * @return Helper + */ + @Override + public DevHelper fixSoftInputLeaks(Context context) { + KeyBoardUtils.fixSoftInputLeaks(context); + return this; + } + + /** + * 自动切换键盘状态, 如果键盘显示则隐藏反之显示 + *
+     *     // 无法获取键盘是否打开 ( 不准确 )
+     *     InputMethodManager.isActive()
+     *     // 获取状态有些版本可以, 不适用
+     *     Activity.getWindow().getAttributes().softInputMode
+     *     

+ * 可以配合 {@link KeyBoardUtils#isSoftInputVisible(Activity)} 判断是否显示输入法 + *
+ * @return Helper + */ + @Override + public DevHelper toggleKeyboard() { + KeyBoardUtils.toggleKeyboard(); + return this; + } + + // =========== + // = 打开软键盘 = + // =========== + + /** + * 打开软键盘 + * @return Helper + */ + @Override + public DevHelper openKeyboard() { + KeyBoardUtils.openKeyboard(); + return this; + } + + /** + * 延时打开软键盘 + * @return Helper + */ + @Override + public DevHelper openKeyboardDelay() { + KeyBoardUtils.openKeyboardDelay(); + return this; + } + + /** + * 延时打开软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper openKeyboardDelay(long delayMillis) { + KeyBoardUtils.openKeyboardDelay(delayMillis); + return this; + } + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + @Override + public DevHelper openKeyboard(EditText editText) { + KeyBoardUtils.openKeyboard(editText); + return this; + } + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + @Override + public DevHelper openKeyboardDelay(EditText editText) { + KeyBoardUtils.openKeyboardDelay(editText); + return this; + } + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper openKeyboardDelay( + EditText editText, + long delayMillis + ) { + KeyBoardUtils.openKeyboardDelay( + editText, delayMillis + ); + return this; + } + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + @Override + public DevHelper openKeyboardByFocus(EditText editText) { + KeyBoardUtils.openKeyboardByFocus(editText); + return this; + } + + // =========== + // = 关闭软键盘 = + // =========== + + /** + * 关闭软键盘 + * @return Helper + */ + @Override + public DevHelper closeKeyboard() { + KeyBoardUtils.closeKeyboard(); + return this; + } + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @return Helper + */ + @Override + public DevHelper closeKeyboard(EditText editText) { + KeyBoardUtils.closeKeyboard(editText); + return this; + } + + /** + * 关闭软键盘 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper closeKeyboard(Activity activity) { + KeyBoardUtils.closeKeyboard(activity); + return this; + } + + /** + * 关闭 dialog 中打开的键盘 + * @param dialog {@link Dialog} + * @return Helper + */ + @Override + public DevHelper closeKeyboard(Dialog dialog) { + KeyBoardUtils.closeKeyboard(dialog); + return this; + } + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return Helper + */ + @Override + public DevHelper closeKeyBoardSpecial( + EditText editText, + Dialog dialog + ) { + KeyBoardUtils.closeKeyBoardSpecial( + editText, dialog + ); + return this; + } + + // ========== + // = 延时关闭 = + // ========== + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return Helper + */ + @Override + public DevHelper closeKeyBoardSpecialDelay( + EditText editText, + Dialog dialog + ) { + KeyBoardUtils.closeKeyBoardSpecialDelay( + editText, dialog + ); + return this; + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper closeKeyBoardSpecialDelay( + EditText editText, + Dialog dialog, + long delayMillis + ) { + KeyBoardUtils.closeKeyBoardSpecialDelay( + editText, dialog, delayMillis + ); + return this; + } + + /** + * 延时关闭软键盘 + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay() { + KeyBoardUtils.closeKeyboardDelay(); + return this; + } + + /** + * 延时关闭软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay(long delayMillis) { + KeyBoardUtils.closeKeyboardDelay(delayMillis); + return this; + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay(EditText editText) { + KeyBoardUtils.closeKeyboardDelay(editText); + return this; + } + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay( + EditText editText, + long delayMillis + ) { + KeyBoardUtils.closeKeyboardDelay( + editText, delayMillis + ); + return this; + } + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay(Activity activity) { + KeyBoardUtils.closeKeyboardDelay(activity); + return this; + } + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay( + Activity activity, + long delayMillis + ) { + KeyBoardUtils.closeKeyboardDelay( + activity, delayMillis + ); + return this; + } + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay(Dialog dialog) { + KeyBoardUtils.closeKeyboardDelay(dialog); + return this; + } + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + @Override + public DevHelper closeKeyboardDelay( + Dialog dialog, + long delayMillis + ) { + KeyBoardUtils.closeKeyboardDelay( + dialog, delayMillis + ); + return this; + } + + // ================= + // = LanguageUtils = + // ================= + + /** + * 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} - Activity + * @param locale {@link Locale} + * @return Helper + */ + @Override + public DevHelper applyLanguage( + Context context, + Locale locale + ) { + LanguageUtils.applyLanguage( + context, locale + ); + return this; + } + + /** + * 修改系统语言 (APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} + * @param language 语言 + * @return Helper + */ + @Override + public DevHelper applyLanguage( + Context context, + String language + ) { + LanguageUtils.applyLanguage( + context, language + ); + return this; + } + + // ===================== + // = NotificationUtils = + // ===================== + + /** + * 移除通知 ( 移除所有通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @return Helper + */ + @Override + public DevHelper cancelAllNotification() { + NotificationUtils.cancelAll(); + return this; + } + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param args 消息 id 集合 + * @return Helper + */ + @Override + public DevHelper cancelNotification(int... args) { + NotificationUtils.cancel(args); + return this; + } + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param tag 标记 TAG + * @param id 消息 id + * @return Helper + */ + @Override + public DevHelper cancelNotification( + String tag, + int id + ) { + NotificationUtils.cancel(tag, id); + return this; + } + + /** + * 进行通知 + * @param id 消息 id + * @param notification {@link Notification} + * @return Helper + */ + @Override + public DevHelper notifyNotification( + int id, + Notification notification + ) { + NotificationUtils.notify(id, notification); + return this; + } + + /** + * 进行通知 + * @param tag 标记 TAG + * @param id 消息 id + * @param notification {@link Notification} + * @return Helper + */ + @Override + public DevHelper notifyNotification( + String tag, + int id, + Notification notification + ) { + NotificationUtils.notify( + tag, id, notification + ); + return this; + } + + /** + * 创建 NotificationChannel + * @param channel {@link NotificationChannel} + * @return Helper + */ + @Override + public DevHelper createNotificationChannel(NotificationChannel channel) { + NotificationUtils.createNotificationChannel(channel); + return this; + } + + // ============== + // = PhoneUtils = + // ============== + + /** + * 跳至拨号界面 + * @param phoneNumber 电话号码 + * @return Helper + */ + @Override + public DevHelper dial(String phoneNumber) { + PhoneUtils.dial(phoneNumber); + return this; + } + + /** + * 拨打电话 + * @param phoneNumber 电话号码 + * @return Helper + */ + @Override + public DevHelper call(String phoneNumber) { + PhoneUtils.call(phoneNumber); + return this; + } + + /** + * 跳至发送短信界面 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return Helper + */ + @Override + public DevHelper sendSms( + String phoneNumber, + String content + ) { + PhoneUtils.sendSms(phoneNumber, content); + return this; + } + + /** + * 发送短信 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return Helper + */ + @Override + public DevHelper sendSmsSilent( + String phoneNumber, + String content + ) { + PhoneUtils.sendSmsSilent(phoneNumber, content); + return this; + } + + // ===================== + // = PowerManagerUtils = + // ===================== + + /** + * 设置屏幕常亮 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setBright(Activity activity) { + PowerManagerUtils.setBright(activity); + return this; + } + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setBright(Window window) { + PowerManagerUtils.setBright(window); + return this; + } + + // =============== + // = ScreenUtils = + // =============== + + /** + * 设置禁止截屏 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setWindowSecure(Activity activity) { + ScreenUtils.setWindowSecure(activity); + return this; + } + + /** + * 设置屏幕为全屏 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setFullScreen(Activity activity) { + ScreenUtils.setFullScreen(activity); + return this; + } + + /** + * 设置屏幕为全屏无标题 + *
+     *     需要在 setContentView 之前调用
+     * 
+ * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setFullScreenNoTitle(Activity activity) { + ScreenUtils.setFullScreenNoTitle(activity); + return this; + } + + /** + * 设置屏幕为横屏 + *
+     *     还有一种就是在 Activity 中加属性 android:screenOrientation="landscape"
+     *     不设置 Activity 的 android:configChanges 时
+     *     切屏会重新调用各个生命周期, 切横屏时会执行一次, 切竖屏时会执行两次
+     *     设置 Activity 的 android:configChanges="orientation" 时
+     *     切屏还是会重新调用各个生命周期, 切横、竖屏时只会执行一次
+     *     设置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize"
+     *     4.0 以上必须带最后一个参数时
+     *     切屏不会重新调用各个生命周期, 只会执行 onConfigurationChanged 方法
+     * 
+ * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setLandscape(Activity activity) { + ScreenUtils.setLandscape(activity); + return this; + } + + /** + * 设置屏幕为竖屏 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper setPortrait(Activity activity) { + ScreenUtils.setPortrait(activity); + return this; + } + + /** + * 切换屏幕方向 + * @param activity {@link Activity} + * @return Helper + */ + @Override + public DevHelper toggleScreenOrientation(Activity activity) { + ScreenUtils.toggleScreenOrientation(activity); + return this; + } + + /** + * 设置进入休眠时长 + * @param duration 时长 + * @return Helper + */ + @Override + public DevHelper setSleepDuration(int duration) { + ScreenUtils.setSleepDuration(duration); + return this; + } + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + *
+     *     用法示例如下所示
+     *     

+ * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() { + * Override + * public void onGetSize(View view) { + * view.getWidth() { + * return this; + * } + * } + * }) { + * return this; + * } + *
+ * @param view {@link View} + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + @Override + public DevHelper forceGetViewSize( + View view, + SizeUtils.OnGetSizeListener listener + ) { + SizeUtils.forceGetViewSize( + view, listener + ); + return this; + } + + // ================== + // = VibrationUtils = + // ================== + + /** + * 震动 + * @param millis 震动时长 ( 毫秒 ) + * @return Helper + */ + @RequiresPermission(Manifest.permission.VIBRATE) + @Override + public DevHelper vibrate(long millis) { + VibrationUtils.vibrate(millis); + return this; + } + + /** + * pattern 模式震动 + * @param pattern new long[]{400, 800, 1200, 1600}, 就是指定在 400ms、800ms、1200ms、1600ms 这些时间点交替启动、关闭手机震动器 + * @param repeat 指定 pattern 数组的索引, 指定 pattern 数组中从 repeat 索引开始的震动进行循环, + * -1 表示只震动一次, 非 -1 表示从 pattern 数组指定下标开始重复震动 + * @return Helper + */ + @RequiresPermission(Manifest.permission.VIBRATE) + @Override + public DevHelper vibrate( + long[] pattern, + int repeat + ) { + VibrationUtils.vibrate(pattern, repeat); + return this; + } + + /** + * 取消震动 + * @return Helper + */ + @RequiresPermission(Manifest.permission.VIBRATE) + @Override + public DevHelper cancelVibrate() { + VibrationUtils.cancel(); + return this; + } + + // ============= + // = ViewUtils = + // ============= + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return Helper + */ + @Override + public DevHelper getWidthHeightExact( + View view, + ViewUtils.OnWHListener listener + ) { + ViewUtils.getWidthHeightExact( + view, listener + ); + return this; + } + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return Helper + */ + @Override + public DevHelper getWidthHeightExact2( + View view, + ViewUtils.OnWHListener listener + ) { + ViewUtils.getWidthHeightExact2( + view, listener + ); + return this; + } + + // =============== + // = WidgetUtils = + // =============== + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @return Helper + */ + @Override + public DevHelper measureView( + View view, + int specifiedWidth + ) { + WidgetUtils.measureView( + view, specifiedWidth + ); + return this; + } + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return Helper + */ + @Override + public DevHelper measureView( + View view, + int specifiedWidth, + int specifiedHeight + ) { + WidgetUtils.measureView( + view, specifiedWidth, specifiedHeight + ); + return this; + } + + // ============== + // = CloseUtils = + // ============== + + /** + * 关闭 IO + * @param closeables Closeable[] + * @return Helper + */ + @Override + public DevHelper closeIO(Closeable... closeables) { + CloseUtils.closeIO(closeables); + return this; + } + + /** + * 安静关闭 IO + * @param closeables Closeable[] + * @return Helper + */ + @Override + public DevHelper closeIOQuietly(Closeable... closeables) { + CloseUtils.closeIOQuietly(closeables); + return this; + } + + /** + * 将缓冲区数据输出 + * @param flushables Flushable[] + * @return Helper + */ + @Override + public DevHelper flush(Flushable... flushables) { + CloseUtils.flush(flushables); + return this; + } + + /** + * 安静将缓冲区数据输出 + * @param flushables Flushable[] + * @return Helper + */ + @Override + public DevHelper flushQuietly(Flushable... flushables) { + CloseUtils.flushQuietly(flushables); + return this; + } + + /** + * 将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + * @return Helper + */ + @Override + public DevHelper flushCloseIO(OutputStream outputStream) { + CloseUtils.flushCloseIO(outputStream); + return this; + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + * @return Helper + */ + @Override + public DevHelper flushCloseIOQuietly(OutputStream outputStream) { + CloseUtils.flushCloseIOQuietly(outputStream); + return this; + } + + /** + * 将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + * @return Helper + */ + @Override + public DevHelper flushCloseIO(Writer writer) { + CloseUtils.flushCloseIO(writer); + return this; + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + * @return Helper + */ + @Override + public DevHelper flushCloseIOQuietly(Writer writer) { + CloseUtils.flushCloseIOQuietly(writer); + return this; + } + + // ================ + // = WindowAssist = + // ================ + + /** + * 设置 Window System UI 可见性 + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + @Override + public DevHelper setSystemUiVisibility( + Window window, + int visibility + ) { + WindowUtils.get().setSystemUiVisibility( + window, visibility + ); + return this; + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行追加 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + @Override + public DevHelper setSystemUiVisibilityByAdd( + Window window, + int visibility + ) { + WindowUtils.get().setSystemUiVisibilityByAdd( + window, visibility + ); + return this; + } + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行清除 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + @Override + public DevHelper setSystemUiVisibilityByClear( + Window window, + int visibility + ) { + WindowUtils.get().setSystemUiVisibilityByClear( + window, visibility + ); + return this; + } + + /** + * 设置 Window LayoutParams + * @param window {@link Window} + * @param params WindowManager.LayoutParams + * @return Helper + */ + @Override + public DevHelper setAttributes( + Window window, + WindowManager.LayoutParams params + ) { + WindowUtils.get().setAttributes( + window, params + ); + return this; + } + + /** + * 刷新自身 Window LayoutParams + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper refreshSelfAttributes(Window window) { + WindowUtils.get().refreshSelfAttributes(window); + return this; + } + + /** + * 清除 Window flags + * @param window {@link Window} + * @param flags 待清除 flags + * @return Helper + */ + @Override + public DevHelper clearFlags( + Window window, + int flags + ) { + WindowUtils.get().clearFlags( + window, flags + ); + return this; + } + + /** + * 添加 Window flags + * @param window {@link Window} + * @param flags 待添加 flags + * @return Helper + */ + @Override + public DevHelper addFlags( + Window window, + int flags + ) { + WindowUtils.get().addFlags( + window, flags + ); + return this; + } + + /** + * 设置 Window flags + * @param window {@link Window} + * @param flags 待设置 flags + * @param mask 待设置 flags 位 + * @return Helper + */ + @Override + public DevHelper setFlags( + Window window, + int flags, + int mask + ) { + WindowUtils.get().setFlags( + window, flags, mask + ); + return this; + } + + /** + * 启用 Window Extended Feature + *
+     *     启用后无法关闭, 需要在 setContentView() 之前调用
+     * 
+ * @param window {@link Window} + * @param featureId 待启用 feature + * @return Helper + */ + @Override + public DevHelper requestFeature( + Window window, + int featureId + ) { + WindowUtils.get().requestFeature( + window, featureId + ); + return this; + } + + /** + * 设置 Window 输入模式 + * @param window {@link Window} + * @param mode input mode + * @return Helper + */ + @Override + public DevHelper setSoftInputMode( + Window window, + int mode + ) { + WindowUtils.get().setSoftInputMode( + window, mode + ); + return this; + } + + /** + * 设置 StatusBar Color + * @param window {@link Window} + * @param color StatusBar Color + * @return Helper + */ + @Override + public DevHelper setStatusBarColor( + Window window, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + WindowUtils.get().setStatusBarColor( + window, color + ); + } + return this; + } + + /** + * 设置 NavigationBar Color + * @param window {@link Window} + * @param color NavigationBar Color + * @return Helper + */ + @Override + public DevHelper setNavigationBarColor( + Window window, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + WindowUtils.get().setNavigationBarColor( + window, color + ); + } + return this; + } + + /** + * 设置 NavigationBar Divider Color + * @param window {@link Window} + * @param color NavigationBar Divider Color + * @return Helper + */ + @Override + public DevHelper setNavigationBarDividerColor( + Window window, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowUtils.get().setNavigationBarDividerColor( + window, color + ); + } + return this; + } + + /** + * 设置 Dialog 宽度 + * @param window {@link Window} + * @param width 宽度 + * @return Helper + */ + @Override + public DevHelper setWidthByParams( + Window window, + int width + ) { + WindowUtils.get().setWidthByParams( + window, width + ); + return this; + } + + /** + * 设置 Dialog 高度 + * @param window {@link Window} + * @param height 高度 + * @return Helper + */ + @Override + public DevHelper setHeightByParams( + Window window, + int height + ) { + WindowUtils.get().setHeightByParams( + window, height + ); + return this; + } + + /** + * 设置 Dialog 宽度、高度 + * @param window {@link Window} + * @param width 宽度 + * @param height 高度 + * @return Helper + */ + @Override + public DevHelper setWidthHeightByParams( + Window window, + int width, + int height + ) { + WindowUtils.get().setWidthHeightByParams( + window, width, height + ); + return this; + } + + /** + * 设置 Dialog X 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @return Helper + */ + @Override + public DevHelper setXByParams( + Window window, + int x + ) { + WindowUtils.get().setXByParams( + window, x + ); + return this; + } + + /** + * 设置 Dialog Y 轴坐标 + * @param window {@link Window} + * @param y Y 轴坐标 + * @return Helper + */ + @Override + public DevHelper setYByParams( + Window window, + int y + ) { + WindowUtils.get().setYByParams( + window, y + ); + return this; + } + + /** + * 设置 Dialog X、Y 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return Helper + */ + @Override + public DevHelper setXYByParams( + Window window, + int x, + int y + ) { + WindowUtils.get().setXYByParams( + window, x, y + ); + return this; + } + + /** + * 设置 Dialog Gravity + * @param window {@link Window} + * @param gravity 重心 + * @return Helper + */ + @Override + public DevHelper setGravityByParams( + Window window, + int gravity + ) { + WindowUtils.get().setGravityByParams( + window, gravity + ); + return this; + } + + /** + * 设置 Dialog 透明度 + * @param window {@link Window} + * @param dimAmount 透明度 + * @return Helper + */ + @Override + public DevHelper setDimAmountByParams( + Window window, + float dimAmount + ) { + WindowUtils.get().setDimAmountByParams( + window, dimAmount + ); + return this; + } + + /** + * 设置窗口亮度 + * @param window {@link Window} + * @param brightness 亮度值 + * @return Helper + */ + @Override + public DevHelper setWindowBrightness( + Window window, + @IntRange(from = 0, to = 255) int brightness + ) { + WindowUtils.get().setWindowBrightness( + window, brightness + ); + return this; + } + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + @Override + public DevHelper setKeyBoardSoftInputMode( + Window window, + boolean inputVisible, + boolean clearFlag + ) { + WindowUtils.get().setKeyBoardSoftInputMode( + window, inputVisible, clearFlag + ); + return this; + } + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagKeepScreenOn(Window window) { + WindowUtils.get().setFlagKeepScreenOn(window); + return this; + } + + /** + * 移除屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper clearFlagKeepScreenOn(Window window) { + WindowUtils.get().clearFlagKeepScreenOn(window); + return this; + } + + /** + * 设置禁止截屏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagSecure(Window window) { + WindowUtils.get().setFlagSecure(window); + return this; + } + + /** + * 移除禁止截屏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper clearFlagSecure(Window window) { + WindowUtils.get().clearFlagSecure(window); + return this; + } + + /** + * 设置屏幕为全屏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagFullScreen(Window window) { + WindowUtils.get().setFlagFullScreen(window); + return this; + } + + /** + * 移除屏幕全屏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper clearFlagFullScreen(Window window) { + WindowUtils.get().clearFlagFullScreen(window); + return this; + } + + /** + * 设置透明状态栏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagTranslucentStatus(Window window) { + WindowUtils.get().setFlagTranslucentStatus(window); + return this; + } + + /** + * 移除透明状态栏 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper clearFlagTranslucentStatus(Window window) { + WindowUtils.get().clearFlagTranslucentStatus(window); + return this; + } + + /** + * 设置系统状态栏背景绘制 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagDrawsSystemBarBackgrounds(Window window) { + WindowUtils.get().setFlagDrawsSystemBarBackgrounds(window); + return this; + } + + /** + * 移除系统状态栏背景绘制 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper clearFlagDrawsSystemBarBackgrounds(Window window) { + WindowUtils.get().clearFlagDrawsSystemBarBackgrounds(window); + return this; + } + + /** + * 设置屏幕页面无标题 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFeatureNoTitle(Window window) { + WindowUtils.get().setFeatureNoTitle(window); + return this; + } + + /** + * 设置屏幕为全屏无标题 + * @param window {@link Window} + * @return Helper + */ + @Override + public DevHelper setFlagFullScreenAndNoTitle(Window window) { + WindowUtils.get().setFlagFullScreenAndNoTitle(window); + return this; + } + + /** + * 设置高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @return Helper + */ + @Override + public DevHelper setSemiTransparentStatusBarColor( + Window window, + @ColorInt int color + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + WindowUtils.get().setSemiTransparentStatusBarColor( + window, color + ); + } + return this; + } + + /** + * 设置状态栏颜色、高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return Helper + */ + @Override + public DevHelper setStatusBarColorAndFlag( + Window window, + @ColorInt int color, + boolean addFlags + ) { + WindowUtils.get().setStatusBarColorAndFlag( + window, color, addFlags + ); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/dev/IHelperByDev.java b/lib/DevApp/src/main/java/dev/utils/app/helper/dev/IHelperByDev.java new file mode 100644 index 0000000000..9b38ac812a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/dev/IHelperByDev.java @@ -0,0 +1,1548 @@ +package dev.utils.app.helper.dev; + +import android.app.Activity; +import android.app.Dialog; +import android.app.Notification; +import android.app.NotificationChannel; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Handler; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.PopupWindow; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; +import androidx.fragment.app.DialogFragment; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.OutputStream; +import java.io.Writer; +import java.util.Locale; + +import dev.utils.app.KeyBoardUtils; +import dev.utils.app.SizeUtils; +import dev.utils.app.ViewUtils; +import dev.utils.app.timer.DevTimer; + +/** + * detail: DevHelper 接口 + * @author Ttt + */ +public interface IHelperByDev { + + // ================== + // = AnimationUtils = + // ================== + + /** + * 设置动画重复处理 + * @param repeatCount 执行次数 + * @param repeatMode 重复模式 {@link Animation#RESTART} 重新从头开始执行、{@link Animation#REVERSE} 反方向执行 + * @param animations Animation[] + * @return Helper + */ + T setAnimationRepeat( + int repeatCount, + int repeatMode, + Animation... animations + ); + + /** + * 设置动画事件 + * @param listener {@link Animation.AnimationListener} + * @param animations Animation[] + * @return Helper + */ + T setAnimationListener( + Animation.AnimationListener listener, + Animation... animations + ); + + /** + * 启动动画 + * @param animations Animation[] + * @return Helper + */ + T startAnimation(Animation... animations); + + /** + * 取消动画 + * @param animations Animation[] + * @return Helper + */ + T cancelAnimation(Animation... animations); + + // =============== + // = BitmapUtils = + // =============== + + /** + * Bitmap 通知回收 + * @param bitmaps Bitmap[] + * @return Helper + */ + T recycle(Bitmap... bitmaps); + + // ================ + // = TimerManager = + // ================ + + /** + * 运行定时器 + * @param timers DevTimer[] + * @return Helper + */ + T startTimer(DevTimer... timers); + + /** + * 关闭定时器 + * @param timers DevTimer[] + * @return Helper + */ + T stopTimer(DevTimer... timers); + + /** + * 回收定时器资源 + * @return Helper + */ + T recycleTimer(); + + /** + * 关闭全部定时器 + * @return Helper + */ + T closeAllTimer(); + + /** + * 关闭所有未运行的定时器 + * @return Helper + */ + T closeAllNotRunningTimer(); + + /** + * 关闭所有无限循环的定时器 + * @return Helper + */ + T closeAllInfiniteTimer(); + + /** + * 关闭所有对应 TAG 定时器 + * @param tags 判断 {@link DevTimer#getTag()} + * @return Helper + */ + T closeAllTagTimer(String... tags); + + /** + * 关闭所有对应 UUID 定时器 + * @param uuids 判断 {@link DevTimer#getUUID()} + * @return Helper + */ + T closeAllUUIDTimer(int... uuids); + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @param views View[] + * @return Helper + */ + T addTouchArea( + int range, + View... views + ); + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @param views View[] + * @return Helper + */ + T addTouchArea( + int left, + int top, + int right, + int bottom, + View... views + ); + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @param views View[] + * @return Helper + */ + T setOnClick( + View.OnClickListener listener, + View... views + ); + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @param views View[] + * @return Helper + */ + T setOnLongClick( + View.OnLongClickListener listener, + View... views + ); + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @param views View[] + * @return Helper + */ + T setOnTouch( + View.OnTouchListener listener, + View... views + ); + + // ================== + // = ClipboardUtils = + // ================== + + /** + * 复制文本到剪贴板 + * @param text 文本 + * @return Helper + */ + T copyText(CharSequence text); + + /** + * 复制 URI 到剪贴板 + * @param uri {@link Uri} + * @return Helper + */ + T copyUri(Uri uri); + + /** + * 复制意图到剪贴板 + * @param intent {@link Intent} + * @return Helper + */ + T copyIntent(Intent intent); + + // =============== + // = DialogUtils = + // =============== + + /** + * 设置 Dialog 状态栏颜色 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return Helper + */ + T setDialogStatusBarColor( + Dialog dialog, + @ColorInt int color + ); + + /** + * 设置 Dialog 高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @return Helper + */ + T setDialogSemiTransparentStatusBarColor( + Dialog dialog, + @ColorInt int color + ); + + /** + * 设置 Dialog 状态栏颜色、高版本状态栏蒙层 + * @param dialog {@link Dialog} + * @param color Dialog StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return Helper + */ + T setDialogStatusBarColorAndFlag( + Dialog dialog, + @ColorInt int color, + boolean addFlags + ); + + /** + * 设置 Dialog Window LayoutParams + * @param dialog {@link Dialog} + * @param params {@link WindowManager.LayoutParams} + * @return Helper + */ + T setDialogAttributes( + Dialog dialog, + WindowManager.LayoutParams params + ); + + /** + * 设置 Dialog 宽度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @return Helper + */ + T setDialogWidth( + Dialog dialog, + int width + ); + + /** + * 设置 Dialog 高度 + * @param dialog {@link Dialog} + * @param height 高度 + * @return Helper + */ + T setDialogHeight( + Dialog dialog, + int height + ); + + /** + * 设置 Dialog 宽度、高度 + * @param dialog {@link Dialog} + * @param width 宽度 + * @param height 高度 + * @return Helper + */ + T setDialogWidthHeight( + Dialog dialog, + int width, + int height + ); + + /** + * 设置 Dialog X 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @return Helper + */ + T setDialogX( + Dialog dialog, + int x + ); + + /** + * 设置 Dialog Y 轴坐标 + * @param dialog {@link Dialog} + * @param y Y 轴坐标 + * @return Helper + */ + T setDialogY( + Dialog dialog, + int y + ); + + /** + * 设置 Dialog X、Y 轴坐标 + * @param dialog {@link Dialog} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return Helper + */ + T setDialogXY( + Dialog dialog, + int x, + int y + ); + + /** + * 设置 Dialog Gravity + * @param dialog {@link Dialog} + * @param gravity 重心 + * @return Helper + */ + T setDialogGravity( + Dialog dialog, + int gravity + ); + + /** + * 设置 Dialog 透明度 + * @param dialog {@link Dialog} + * @param dimAmount 透明度 + * @return Helper + */ + T setDialogDimAmount( + Dialog dialog, + float dimAmount + ); + + /** + * 设置是否允许返回键关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + T setDialogCancelable( + Dialog dialog, + boolean cancel + ); + + /** + * 设置是否允许点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + T setDialogCanceledOnTouchOutside( + Dialog dialog, + boolean cancel + ); + + /** + * 设置是否允许 返回键关闭、点击其他地方自动关闭 + * @param dialog {@link Dialog} + * @param cancel {@code true} 允许, {@code false} 不允许 + * @return Helper + */ + T setDialogCancelableAndTouchOutside( + Dialog dialog, + boolean cancel + ); + + // ============== + // = Dialog 操作 = + // ============== + + /** + * 显示 Dialog + * @param dialog {@link Dialog} + * @return Helper + */ + T showDialog(Dialog dialog); + + /** + * 关闭多个 Dialog + * @param dialogs Dialog[] + * @return Helper + */ + T closeDialogs(Dialog... dialogs); + + /** + * 关闭多个 DialogFragment + * @param dialogs DialogFragment[] + * @return Helper + */ + T closeDialogs(DialogFragment... dialogs); + + /** + * 关闭多个 PopupWindow + * @param popupWindows PopupWindow[] + * @return Helper + */ + T closePopupWindows(PopupWindow... popupWindows); + + /** + * 自动关闭 dialog + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param dialogs Dialog[] + * @return Helper + */ + T autoCloseDialog( + long delayMillis, + Handler handler, + Dialog... dialogs + ); + + /** + * 自动关闭 DialogFragment + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param dialogs DialogFragment[] + * @return Helper + */ + T autoCloseDialog( + long delayMillis, + Handler handler, + DialogFragment... dialogs + ); + + /** + * 自动关闭 PopupWindow + * @param delayMillis 延迟关闭时间 + * @param handler {@link Handler} + * @param popupWindows PopupWindow[] + * @return Helper + */ + T autoClosePopupWindow( + long delayMillis, + Handler handler, + PopupWindow... popupWindows + ); + + // ================= + // = KeyBoardUtils = + // ================= + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @return Helper + */ + T setSoftInputMode( + Activity activity, + boolean inputVisible + ); + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @return Helper + */ + T setSoftInputMode( + Window window, + boolean inputVisible + ); + + /** + * 设置 Window 软键盘是否显示 + * @param activity {@link Activity} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + T setSoftInputMode( + Activity activity, + boolean inputVisible, + boolean clearFlag + ); + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + T setSoftInputMode( + Window window, + boolean inputVisible, + boolean clearFlag + ); + + // ============================ + // = 点击非 EditText 则隐藏软键盘 = + // ============================ + + /** + * 设置某个 View 内所有非 EditText 的子 View OnTouchListener 事件 + * @param view {@link View} + * @param activity {@link Activity} + * @return Helper + */ + T judgeView( + View view, + Activity activity + ); + + // =============== + // = 软键盘隐藏显示 = + // =============== + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link KeyBoardUtils.OnSoftInputChangedListener} + * @return Helper + */ + T registerSoftInputChangedListener( + Activity activity, + KeyBoardUtils.OnSoftInputChangedListener listener + ); + + /** + * 注册软键盘改变监听 + * @param activity {@link Activity} + * @param listener {@link KeyBoardUtils.OnSoftInputChangedListener} + * @return Helper + */ + T registerSoftInputChangedListener2( + Activity activity, + KeyBoardUtils.OnSoftInputChangedListener listener + ); + + /** + * 修复软键盘内存泄漏 在 Activity.onDestroy() 中使用 + * @param context {@link Context} + * @return Helper + */ + T fixSoftInputLeaks(Context context); + + /** + * 自动切换键盘状态, 如果键盘显示则隐藏反之显示 + *
+     *     // 无法获取键盘是否打开 ( 不准确 )
+     *     InputMethodManager.isActive()
+     *     // 获取状态有些版本可以, 不适用
+     *     Activity.getWindow().getAttributes().softInputMode
+     *     

+ * 可以配合 {@link KeyBoardUtils#isSoftInputVisible(Activity)} 判断是否显示输入法 + *
+ * @return Helper + */ + T toggleKeyboard(); + + // =========== + // = 打开软键盘 = + // =========== + + /** + * 打开软键盘 + * @return Helper + */ + T openKeyboard(); + + /** + * 延时打开软键盘 + * @return Helper + */ + T openKeyboardDelay(); + + /** + * 延时打开软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T openKeyboardDelay(long delayMillis); + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + T openKeyboard(EditText editText); + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + T openKeyboardDelay(EditText editText); + + /** + * 延时打开软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T openKeyboardDelay( + EditText editText, + long delayMillis + ); + + /** + * 打开软键盘 + * @param editText {@link EditText} + * @return Helper + */ + T openKeyboardByFocus(EditText editText); + + // =========== + // = 关闭软键盘 = + // =========== + + /** + * 关闭软键盘 + * @return Helper + */ + T closeKeyboard(); + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @return Helper + */ + T closeKeyboard(EditText editText); + + /** + * 关闭软键盘 + * @param activity {@link Activity} + * @return Helper + */ + T closeKeyboard(Activity activity); + + /** + * 关闭 dialog 中打开的键盘 + * @param dialog {@link Dialog} + * @return Helper + */ + T closeKeyboard(Dialog dialog); + + /** + * 关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return Helper + */ + T closeKeyBoardSpecial( + EditText editText, + Dialog dialog + ); + + // ========== + // = 延时关闭 = + // ========== + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @return Helper + */ + T closeKeyBoardSpecialDelay( + EditText editText, + Dialog dialog + ); + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T closeKeyBoardSpecialDelay( + EditText editText, + Dialog dialog, + long delayMillis + ); + + /** + * 延时关闭软键盘 + * @return Helper + */ + T closeKeyboardDelay(); + + /** + * 延时关闭软键盘 + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T closeKeyboardDelay(long delayMillis); + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @return Helper + */ + T closeKeyboardDelay(EditText editText); + + /** + * 延时关闭软键盘 + * @param editText {@link EditText} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T closeKeyboardDelay( + EditText editText, + long delayMillis + ); + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @return Helper + */ + T closeKeyboardDelay(Activity activity); + + /** + * 延时关闭软键盘 + * @param activity {@link Activity} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T closeKeyboardDelay( + Activity activity, + long delayMillis + ); + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @return Helper + */ + T closeKeyboardDelay(Dialog dialog); + + /** + * 延时关闭软键盘 + * @param dialog {@link Dialog} + * @param delayMillis 延迟时间 ( 毫秒 ) + * @return Helper + */ + T closeKeyboardDelay( + Dialog dialog, + long delayMillis + ); + + // ================= + // = LanguageUtils = + // ================= + + /** + * 修改系统语言 ( APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} - Activity + * @param locale {@link Locale} + * @return Helper + */ + T applyLanguage( + Context context, + Locale locale + ); + + /** + * 修改系统语言 (APP 多语言, 单独改变 APP 语言 ) + * @param context {@link Context} + * @param language 语言 + * @return Helper + */ + T applyLanguage( + Context context, + String language + ); + + // ===================== + // = NotificationUtils = + // ===================== + + /** + * 移除通知 ( 移除所有通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @return Helper + */ + T cancelAllNotification(); + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param args 消息 id 集合 + * @return Helper + */ + T cancelNotification(int... args); + + /** + * 移除通知 ( 移除标记为 id 的通知 ) + *
+     *     只是针对当前 Context 下的所有 Notification
+     * 
+ * @param tag 标记 TAG + * @param id 消息 id + * @return Helper + */ + T cancelNotification( + String tag, + int id + ); + + /** + * 进行通知 + * @param id 消息 id + * @param notification {@link Notification} + * @return Helper + */ + T notifyNotification( + int id, + Notification notification + ); + + /** + * 进行通知 + * @param tag 标记 TAG + * @param id 消息 id + * @param notification {@link Notification} + * @return Helper + */ + T notifyNotification( + String tag, + int id, + Notification notification + ); + + /** + * 创建 NotificationChannel + * @param channel {@link NotificationChannel} + * @return Helper + */ + T createNotificationChannel(NotificationChannel channel); + + // ============== + // = PhoneUtils = + // ============== + + /** + * 跳至拨号界面 + * @param phoneNumber 电话号码 + * @return Helper + */ + T dial(String phoneNumber); + + /** + * 拨打电话 + * @param phoneNumber 电话号码 + * @return Helper + */ + T call(String phoneNumber); + + /** + * 跳至发送短信界面 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return Helper + */ + T sendSms( + String phoneNumber, + String content + ); + + /** + * 发送短信 + * @param phoneNumber 接收号码 + * @param content 短信内容 + * @return Helper + */ + T sendSmsSilent( + String phoneNumber, + String content + ); + + // ===================== + // = PowerManagerUtils = + // ===================== + + /** + * 设置屏幕常亮 + * @param activity {@link Activity} + * @return Helper + */ + T setBright(Activity activity); + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + T setBright(Window window); + + // =============== + // = ScreenUtils = + // =============== + + /** + * 设置禁止截屏 + * @param activity {@link Activity} + * @return Helper + */ + T setWindowSecure(Activity activity); + + /** + * 设置屏幕为全屏 + * @param activity {@link Activity} + * @return Helper + */ + T setFullScreen(Activity activity); + + /** + * 设置屏幕为全屏无标题 + *
+     *     需要在 setContentView 之前调用
+     * 
+ * @param activity {@link Activity} + * @return Helper + */ + T setFullScreenNoTitle(Activity activity); + + /** + * 设置屏幕为横屏 + *
+     *     还有一种就是在 Activity 中加属性 android:screenOrientation="landscape"
+     *     不设置 Activity 的 android:configChanges 时
+     *     切屏会重新调用各个生命周期, 切横屏时会执行一次, 切竖屏时会执行两次
+     *     设置 Activity 的 android:configChanges="orientation" 时
+     *     切屏还是会重新调用各个生命周期, 切横、竖屏时只会执行一次
+     *     设置 Activity 的 android:configChanges="orientation|keyboardHidden|screenSize"
+     *     4.0 以上必须带最后一个参数时
+     *     切屏不会重新调用各个生命周期, 只会执行 onConfigurationChanged 方法
+     * 
+ * @param activity {@link Activity} + * @return Helper + */ + T setLandscape(Activity activity); + + /** + * 设置屏幕为竖屏 + * @param activity {@link Activity} + * @return Helper + */ + T setPortrait(Activity activity); + + /** + * 切换屏幕方向 + * @param activity {@link Activity} + * @return Helper + */ + T toggleScreenOrientation(Activity activity); + + /** + * 设置进入休眠时长 + * @param duration 时长 + * @return Helper + */ + T setSleepDuration(int duration); + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + *
+     *     用法示例如下所示
+     *     

+ * SizeUtils.forceGetViewSize(view, new SizeUtils.onGetSizeListener() { + * Override + * public void onGetSize(View view) { + * view.getWidth(); + * } + * }); + *
+ * @param view {@link View} + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + T forceGetViewSize( + View view, + SizeUtils.OnGetSizeListener listener + ); + + // ================== + // = VibrationUtils = + // ================== + + /** + * 震动 + * @param millis 震动时长 ( 毫秒 ) + * @return Helper + */ + T vibrate(long millis); + + /** + * pattern 模式震动 + * @param pattern new long[]{400, 800, 1200, 1600}, 就是指定在 400ms、800ms、1200ms、1600ms 这些时间点交替启动、关闭手机震动器 + * @param repeat 指定 pattern 数组的索引, 指定 pattern 数组中从 repeat 索引开始的震动进行循环, + * -1 表示只震动一次, 非 -1 表示从 pattern 数组指定下标开始重复震动 + * @return Helper + */ + T vibrate( + long[] pattern, + int repeat + ); + + /** + * 取消震动 + * @return Helper + */ + T cancelVibrate(); + + // ============= + // = ViewUtils = + // ============= + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return Helper + */ + T getWidthHeightExact( + View view, + ViewUtils.OnWHListener listener + ); + + /** + * 获取 View 宽高 ( 准确 ) + * @param view {@link View} + * @param listener 回调事件 + * @return Helper + */ + T getWidthHeightExact2( + View view, + ViewUtils.OnWHListener listener + ); + + // =============== + // = WidgetUtils = + // =============== + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @return Helper + */ + T measureView( + View view, + int specifiedWidth + ); + + /** + * 测量 View + * @param view {@link View} + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return Helper + */ + T measureView( + View view, + int specifiedWidth, + int specifiedHeight + ); + + // ============== + // = CloseUtils = + // ============== + + /** + * 关闭 IO + * @param closeables Closeable[] + * @return Helper + */ + T closeIO(Closeable... closeables); + + /** + * 安静关闭 IO + * @param closeables Closeable[] + * @return Helper + */ + T closeIOQuietly(Closeable... closeables); + + /** + * 将缓冲区数据输出 + * @param flushables Flushable[] + * @return Helper + */ + T flush(Flushable... flushables); + + /** + * 安静将缓冲区数据输出 + * @param flushables Flushable[] + * @return Helper + */ + T flushQuietly(Flushable... flushables); + + /** + * 将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + * @return Helper + */ + T flushCloseIO(OutputStream outputStream); + + /** + * 安静将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + * @return Helper + */ + T flushCloseIOQuietly(OutputStream outputStream); + + /** + * 将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + * @return Helper + */ + T flushCloseIO(Writer writer); + + /** + * 安静将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + * @return Helper + */ + T flushCloseIOQuietly(Writer writer); + + // ================ + // = WindowAssist = + // ================ + + /** + * 设置 Window System UI 可见性 + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + T setSystemUiVisibility( + Window window, + int visibility + ); + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行追加 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + T setSystemUiVisibilityByAdd( + Window window, + int visibility + ); + + /** + * 设置 Window System UI 可见性 ( 原来基础上进行清除 ) + * @param window {@link Window} + * @param visibility 待操作 flags + * @return Helper + */ + T setSystemUiVisibilityByClear( + Window window, + int visibility + ); + + /** + * 设置 Window LayoutParams + * @param window {@link Window} + * @param params WindowManager.LayoutParams + * @return Helper + */ + T setAttributes( + Window window, + WindowManager.LayoutParams params + ); + + /** + * 刷新自身 Window LayoutParams + * @param window {@link Window} + * @return Helper + */ + T refreshSelfAttributes(Window window); + + /** + * 清除 Window flags + * @param window {@link Window} + * @param flags 待清除 flags + * @return Helper + */ + T clearFlags( + Window window, + int flags + ); + + /** + * 添加 Window flags + * @param window {@link Window} + * @param flags 待添加 flags + * @return Helper + */ + T addFlags( + Window window, + int flags + ); + + /** + * 设置 Window flags + * @param window {@link Window} + * @param flags 待设置 flags + * @param mask 待设置 flags 位 + * @return Helper + */ + T setFlags( + Window window, + int flags, + int mask + ); + + /** + * 启用 Window Extended Feature + *
+     *     启用后无法关闭, 需要在 setContentView() 之前调用
+     * 
+ * @param window {@link Window} + * @param featureId 待启用 feature + * @return Helper + */ + T requestFeature( + Window window, + int featureId + ); + + /** + * 设置 Window 输入模式 + * @param window {@link Window} + * @param mode input mode + * @return Helper + */ + T setSoftInputMode( + Window window, + int mode + ); + + /** + * 设置 StatusBar Color + * @param window {@link Window} + * @param color StatusBar Color + * @return Helper + */ + T setStatusBarColor( + Window window, + @ColorInt int color + ); + + /** + * 设置 NavigationBar Color + * @param window {@link Window} + * @param color NavigationBar Color + * @return Helper + */ + T setNavigationBarColor( + Window window, + @ColorInt int color + ); + + /** + * 设置 NavigationBar Divider Color + * @param window {@link Window} + * @param color NavigationBar Divider Color + * @return Helper + */ + T setNavigationBarDividerColor( + Window window, + @ColorInt int color + ); + + /** + * 设置 Dialog 宽度 + * @param window {@link Window} + * @param width 宽度 + * @return Helper + */ + T setWidthByParams( + Window window, + int width + ); + + /** + * 设置 Dialog 高度 + * @param window {@link Window} + * @param height 高度 + * @return Helper + */ + T setHeightByParams( + Window window, + int height + ); + + /** + * 设置 Dialog 宽度、高度 + * @param window {@link Window} + * @param width 宽度 + * @param height 高度 + * @return Helper + */ + T setWidthHeightByParams( + Window window, + int width, + int height + ); + + /** + * 设置 Dialog X 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @return Helper + */ + T setXByParams( + Window window, + int x + ); + + /** + * 设置 Dialog Y 轴坐标 + * @param window {@link Window} + * @param y Y 轴坐标 + * @return Helper + */ + T setYByParams( + Window window, + int y + ); + + /** + * 设置 Dialog X、Y 轴坐标 + * @param window {@link Window} + * @param x X 轴坐标 + * @param y Y 轴坐标 + * @return Helper + */ + T setXYByParams( + Window window, + int x, + int y + ); + + /** + * 设置 Dialog Gravity + * @param window {@link Window} + * @param gravity 重心 + * @return Helper + */ + T setGravityByParams( + Window window, + int gravity + ); + + /** + * 设置 Dialog 透明度 + * @param window {@link Window} + * @param dimAmount 透明度 + * @return Helper + */ + T setDimAmountByParams( + Window window, + float dimAmount + ); + + /** + * 设置窗口亮度 + * @param window {@link Window} + * @param brightness 亮度值 + * @return Helper + */ + T setWindowBrightness( + Window window, + @IntRange(from = 0, to = 255) int brightness + ); + + /** + * 设置 Window 软键盘是否显示 + * @param window {@link Window} + * @param inputVisible 是否显示软键盘 + * @param clearFlag 是否清空 Flag ( FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE ) + * @return Helper + */ + T setKeyBoardSoftInputMode( + Window window, + boolean inputVisible, + boolean clearFlag + ); + + /** + * 设置屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + T setFlagKeepScreenOn(Window window); + + /** + * 移除屏幕常亮 + * @param window {@link Window} + * @return Helper + */ + T clearFlagKeepScreenOn(Window window); + + /** + * 设置禁止截屏 + * @param window {@link Window} + * @return Helper + */ + T setFlagSecure(Window window); + + /** + * 移除禁止截屏 + * @param window {@link Window} + * @return Helper + */ + T clearFlagSecure(Window window); + + /** + * 设置屏幕为全屏 + * @param window {@link Window} + * @return Helper + */ + T setFlagFullScreen(Window window); + + /** + * 移除屏幕全屏 + * @param window {@link Window} + * @return Helper + */ + T clearFlagFullScreen(Window window); + + /** + * 设置透明状态栏 + * @param window {@link Window} + * @return Helper + */ + T setFlagTranslucentStatus(Window window); + + /** + * 移除透明状态栏 + * @param window {@link Window} + * @return Helper + */ + T clearFlagTranslucentStatus(Window window); + + /** + * 设置系统状态栏背景绘制 + * @param window {@link Window} + * @return Helper + */ + T setFlagDrawsSystemBarBackgrounds(Window window); + + /** + * 移除系统状态栏背景绘制 + * @param window {@link Window} + * @return Helper + */ + T clearFlagDrawsSystemBarBackgrounds(Window window); + + /** + * 设置屏幕页面无标题 + * @param window {@link Window} + * @return Helper + */ + T setFeatureNoTitle(Window window); + + /** + * 设置屏幕为全屏无标题 + * @param window {@link Window} + * @return Helper + */ + T setFlagFullScreenAndNoTitle(Window window); + + /** + * 设置高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @return Helper + */ + T setSemiTransparentStatusBarColor( + Window window, + @ColorInt int color + ); + + /** + * 设置状态栏颜色、高版本状态栏蒙层 + * @param window {@link Window} + * @param color StatusBar Color + * @param addFlags 是否添加 Windows flags + * @return Helper + */ + T setStatusBarColorAndFlag( + Window window, + @ColorInt int color, + boolean addFlags + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/flow/FlowHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/flow/FlowHelper.java new file mode 100644 index 0000000000..3bc5955221 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/flow/FlowHelper.java @@ -0,0 +1,141 @@ +package dev.utils.app.helper.flow; + +import dev.utils.app.HandlerUtils; +import dev.utils.app.helper.BaseHelper; +import dev.utils.app.helper.dev.DevHelper; +import dev.utils.app.helper.quick.QuickHelper; +import dev.utils.app.helper.view.ViewHelper; + +/** + * detail: 流式 ( 链式 ) 连接 Helper 类 + * @author Ttt + *
+ *     支持连接 {@link DevHelper}、{@link QuickHelper}、{@link ViewHelper} 等类使用
+ *     且不局限于上述类, 只需要调用 {@link BaseHelper#flowValue(Object, Action)} 即可使用
+ * 
+ */ +public final class FlowHelper + extends BaseHelper { + + private FlowHelper() { + } + + // FlowHelper + private static final FlowHelper HELPER = new FlowHelper(); + + /** + * 获取单例 FlowHelper + * @return {@link FlowHelper} + */ + public static FlowHelper get() { + return HELPER; + } + + /** + * detail: 操作方法 + * @author Ttt + */ + public interface Action { + + /** + * 操作方法 + */ + void action(); + } + + // ======== + // = Flow = + // ======== + + /** + * 执行 Action 流方法 + * @param action Action + * @return Helper + */ + @Override + public FlowHelper flow(FlowHelper.Action action) { + if (action != null) action.action(); + return this; + } + + // ================ + // = HandlerUtils = + // ================ + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + * @return Helper + */ + @Override + public FlowHelper postRunnable(Runnable runnable) { + HandlerUtils.postRunnable(runnable); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @return Helper + */ + @Override + public FlowHelper postRunnable( + Runnable runnable, + long delayMillis + ) { + HandlerUtils.postRunnable(runnable, delayMillis); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @return Helper + */ + @Override + public FlowHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param listener 结束通知 + * @return Helper + */ + @Override + public FlowHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval, + HandlerUtils.OnEndListener listener + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval, listener); + return this; + } + + /** + * 在主线程 Handler 中清除任务 + * @param runnable 需要清除的任务 + * @return Helper + */ + @Override + public FlowHelper removeRunnable(Runnable runnable) { + HandlerUtils.removeRunnable(runnable); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/quick/IHelperByQuick.java b/lib/DevApp/src/main/java/dev/utils/app/helper/quick/IHelperByQuick.java new file mode 100644 index 0000000000..817ce4ad85 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/quick/IHelperByQuick.java @@ -0,0 +1,2101 @@ +package dev.utils.app.helper.quick; + +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.text.method.TransformationMethod; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.ImageView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IdRes; +import androidx.core.widget.TextViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import dev.utils.app.SizeUtils; + +/** + * detail: QuickHelper 接口 + * @author Ttt + */ +public interface IHelperByQuick { + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @return Helper + */ + T addTouchArea(int range); + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @return Helper + */ + T addTouchArea( + int left, + int top, + int right, + int bottom + ); + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @return Helper + */ + T setOnClick(View.OnClickListener listener); + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @return Helper + */ + T setOnLongClick(View.OnLongClickListener listener); + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @return Helper + */ + T setOnTouch(View.OnTouchListener listener); + + // ============= + // = ViewUtils = + // ============= + + /** + * 设置 View Id + * @param id View Id + * @return Helper + */ + T setId(int id); + + /** + * 设置是否限制子 View 在其边界内绘制 + * @param clipChildren {@code true} yes, {@code false} no + * @return Helper + */ + T setClipChildren(boolean clipChildren); + + /** + * 移除全部子 View + * @return Helper + */ + T removeAllViews(); + + /** + * 添加 View + * @param child 待添加 View + * @return Helper + */ + T addView(View child); + + /** + * 添加 View + * @param child 待添加 View + * @param index 添加位置索引 + * @return Helper + */ + T addView( + View child, + int index + ); + + /** + * 添加 View + * @param child 待添加 View + * @param index 添加位置索引 + * @param params LayoutParams + * @return Helper + */ + T addView( + View child, + int index, + ViewGroup.LayoutParams params + ); + + /** + * 添加 View + * @param child 待添加 View + * @param params LayoutParams + * @return Helper + */ + T addView( + View child, + ViewGroup.LayoutParams params + ); + + /** + * 添加 View + * @param child 待添加 View + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + T addView( + View child, + int width, + int height + ); + + /** + * 设置 View LayoutParams + * @param params LayoutParams + * @return Helper + */ + T setLayoutParams(ViewGroup.LayoutParams params); + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + T setWidthHeight( + int width, + int height + ); + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + T setWidthHeight( + int width, + int height, + boolean nullNewLP + ); + + /** + * 设置 View weight 权重 + * @param weight 权重比例 + * @return Helper + */ + T setWeight(float weight); + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @return Helper + */ + T setWidth(int width); + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + T setWidth( + int width, + boolean nullNewLP + ); + + /** + * 设置 View 高度 + * @param height View 高度 + * @return Helper + */ + T setHeight(int height); + + /** + * 设置 View 高度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + T setHeight( + int height, + boolean nullNewLP + ); + + /** + * 设置 View 最小宽度 + * @param minWidth 最小宽度 + * @return Helper + */ + T setMinimumWidth(int minWidth); + + /** + * 设置 View 最小高度 + * @param minHeight 最小高度 + * @return Helper + */ + T setMinimumHeight(int minHeight); + + /** + * 设置 View 透明度 + * @param alpha 透明度 + * @return Helper + */ + T setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha); + + /** + * 设置 View TAG + * @param object TAG + * @return Helper + */ + T setTag(Object object); + + /** + * 设置 View 滑动的 X 轴坐标 + * @param value X 轴坐标 + * @return Helper + */ + T setScrollX(int value); + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param value Y 轴坐标 + * @return Helper + */ + T setScrollY(int value); + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @return Helper + */ + T setDescendantFocusability(int focusability); + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @return Helper + */ + T setOverScrollMode(int overScrollMode); + + /** + * 设置是否绘制横向滚动条 + * @param horizontalScrollBarEnabled {@code true} yes, {@code false} no + * @return Helper + */ + T setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled); + + /** + * 设置是否绘制垂直滚动条 + * @param verticalScrollBarEnabled {@code true} yes, {@code false} no + * @return Helper + */ + T setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled); + + /** + * 设置 View 滚动效应 + * @param isScrollContainer 是否需要滚动效应 + * @return Helper + */ + T setScrollContainer(boolean isScrollContainer); + + /** + * 设置下一个获取焦点的 View id + * @param nextFocusForwardId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusForwardId(@IdRes int nextFocusForwardId); + + /** + * 设置向下移动焦点时, 下一个获取焦点的 View id + * @param nextFocusDownId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusDownId(@IdRes int nextFocusDownId); + + /** + * 设置向左移动焦点时, 下一个获取焦点的 View id + * @param nextFocusLeftId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusLeftId(@IdRes int nextFocusLeftId); + + /** + * 设置向右移动焦点时, 下一个获取焦点的 View id + * @param nextFocusRightId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusRightId(@IdRes int nextFocusRightId); + + /** + * 设置向上移动焦点时, 下一个获取焦点的 View id + * @param nextFocusUpId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusUpId(@IdRes int nextFocusUpId); + + /** + * 设置 View 旋转度数 + * @param rotation 旋转度数 + * @return Helper + */ + T setRotation(float rotation); + + /** + * 设置 View 水平旋转度数 + * @param rotationX 水平旋转度数 + * @return Helper + */ + T setRotationX(float rotationX); + + /** + * 设置 View 竖直旋转度数 + * @param rotationY 竖直旋转度数 + * @return Helper + */ + T setRotationY(float rotationY); + + /** + * 设置 View 水平方向缩放比例 + * @param scaleX 水平方向缩放比例 + * @return Helper + */ + T setScaleX(float scaleX); + + /** + * 设置 View 竖直方向缩放比例 + * @param scaleY 竖直方向缩放比例 + * @return Helper + */ + T setScaleY(float scaleY); + + /** + * 设置文本的显示方式 + * @param textAlignment 文本的显示方式 + * @return Helper + */ + T setTextAlignment(int textAlignment); + + /** + * 设置文本的显示方向 + * @param textDirection 文本的显示方向 + * @return Helper + */ + T setTextDirection(int textDirection); + + /** + * 设置水平方向偏转量 + * @param pivotX 水平方向偏转量 + * @return Helper + */ + T setPivotX(float pivotX); + + /** + * 设置竖直方向偏转量 + * @param pivotY 竖直方向偏转量 + * @return Helper + */ + T setPivotY(float pivotY); + + /** + * 设置水平方向的移动距离 + * @param translationX 水平方向的移动距离 + * @return Helper + */ + T setTranslationX(float translationX); + + /** + * 设置竖直方向的移动距离 + * @param translationY 竖直方向的移动距离 + * @return Helper + */ + T setTranslationY(float translationY); + + /** + * 设置 X 轴位置 + * @param x X 轴位置 + * @return Helper + */ + T setX(float x); + + /** + * 设置 Y 轴位置 + * @param y Y 轴位置 + * @return Helper + */ + T setY(float y); + + /** + * 设置 View 硬件加速类型 + * @param layerType 硬件加速类型 + * @param paint {@link Paint} + * @return Helper + */ + T setLayerType( + int layerType, + Paint paint + ); + + /** + * 请求重新对 View 布局 + * @return Helper + */ + T requestLayout(); + + /** + * View 请求获取焦点 + * @return Helper + */ + T requestFocus(); + + /** + * View 清除焦点 + * @return Helper + */ + T clearFocus(); + + /** + * 设置 View 是否在触摸模式下获得焦点 + * @param focusableInTouchMode {@code true} 可获取, {@code false} 不可获取 + * @return Helper + */ + T setFocusableInTouchMode(boolean focusableInTouchMode); + + /** + * 设置 View 是否可以获取焦点 + * @param focusable {@code true} 可获取, {@code false} 不可获取 + * @return Helper + */ + T setFocusable(boolean focusable); + + /** + * 切换获取焦点状态 + * @return Helper + */ + T toggleFocusable(); + + /** + * 设置 View 是否选中 + * @param selected {@code true} 选中, {@code false} 非选中 + * @return Helper + */ + T setSelected(boolean selected); + + /** + * 切换选中状态 + * @return Helper + */ + T toggleSelected(); + + /** + * 设置 View 是否启用 + * @param enabled {@code true} 启用, {@code false} 禁用 + * @return Helper + */ + T setEnabled(boolean enabled); + + /** + * 切换 View 是否启用状态 + * @return Helper + */ + T toggleEnabled(); + + /** + * 设置 View 是否可以点击 + * @param clickable {@code true} 可点击, {@code false} 不可点击 + * @return Helper + */ + T setClickable(boolean clickable); + + /** + * 切换 View 是否可以点击状态 + * @return Helper + */ + T toggleClickable(); + + /** + * 设置 View 是否可以长按 + * @param longClickable {@code true} 可长按, {@code false} 不可长按 + * @return Helper + */ + T setLongClickable(boolean longClickable); + + /** + * 切换 View 是否可以长按状态 + * @return Helper + */ + T toggleLongClickable(); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @return Helper + */ + T setVisibilitys(boolean isVisibility); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setVisibilitys(int isVisibility); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @return Helper + */ + T setVisibilityINs(boolean isVisibility); + + /** + * 切换 View 显示的状态 + * @param views View[] + * @return Helper + */ + T toggleVisibilitys(View... views); + + /** + * 切换 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T toggleVisibilitys( + int state, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + int state, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + boolean isVisibility, + View... views + ); + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T toggleViews( + boolean isChange, + int isVisibility + ); + + /** + * 把自身从父 View 中移除 + * @return Helper + */ + T removeSelfFromParent(); + + /** + * View 请求更新 + * @param allParent 是否全部父布局 View 都请求 + * @return Helper + */ + T requestLayoutParent(boolean allParent); + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @return Helper + */ + T measureView(int specifiedWidth); + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return Helper + */ + T measureView( + int specifiedWidth, + int specifiedHeight + ); + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @return Helper + */ + T setLayoutGravity(int gravity); + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param isReflection 是否使用反射 + * @return Helper + */ + T setLayoutGravity( + int gravity, + boolean isReflection + ); + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @return Helper + */ + T setMarginLeft(int leftMargin); + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + T setMarginLeft( + int leftMargin, + boolean reset + ); + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @return Helper + */ + T setMarginTop(int topMargin); + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + T setMarginTop( + int topMargin, + boolean reset + ); + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @return Helper + */ + T setMarginRight(int rightMargin); + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + T setMarginRight( + int rightMargin, + boolean reset + ); + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @return Helper + */ + T setMarginBottom(int bottomMargin); + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + T setMarginBottom( + int bottomMargin, + boolean reset + ); + + /** + * 设置 Margin 边距 + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @return Helper + */ + T setMargin( + int leftRight, + int topBottom + ); + + /** + * 设置 Margin 边距 + * @param margin Margin + * @return Helper + */ + T setMargin(int margin); + + /** + * 设置 Margin 边距 + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @return Helper + */ + T setMargin( + int left, + int top, + int right, + int bottom + ); + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @return Helper + */ + T setPaddingLeft(int leftPadding); + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + T setPaddingLeft( + int leftPadding, + boolean reset + ); + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @return Helper + */ + T setPaddingTop(int topPadding); + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + T setPaddingTop( + int topPadding, + boolean reset + ); + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @return Helper + */ + T setPaddingRight(int rightPadding); + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + T setPaddingRight( + int rightPadding, + boolean reset + ); + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @return Helper + */ + T setPaddingBottom(int bottomPadding); + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + T setPaddingBottom( + int bottomPadding, + boolean reset + ); + + /** + * 设置 Padding 边距 + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @return Helper + */ + T setPadding( + int leftRight, + int topBottom + ); + + /** + * 设置 Padding 边距 + * @param padding Padding + * @return Helper + */ + T setPadding(int padding); + + /** + * 设置 Padding 边距 + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @return Helper + */ + T setPadding( + int left, + int top, + int right, + int bottom + ); + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @return Helper + */ + T addRules(int verb); + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param subject 关联 View id + * @return Helper + */ + T addRules( + int verb, + int subject + ); + + /** + * 移除多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @return Helper + */ + T removeRules(int verb); + + /** + * 设置动画 + * @param animation {@link Animation} + * @return Helper + */ + T setAnimation(Animation animation); + + /** + * 清空动画 + * @return Helper + */ + T clearAnimation(); + + /** + * 启动动画 + * @return Helper + */ + T startAnimation(); + + /** + * 启动动画 + * @param animation {@link Animation} + * @return Helper + */ + T startAnimation(Animation animation); + + /** + * 取消动画 + * @return Helper + */ + T cancelAnimation(); + + /** + * 设置背景图片 + * @param background 背景图片 + * @return Helper + */ + T setBackground(Drawable background); + + /** + * 设置背景颜色 + * @param color 背景颜色 + * @return Helper + */ + T setBackgroundColor(@ColorInt int color); + + /** + * 设置背景资源 + * @param resId resource identifier + * @return Helper + */ + T setBackgroundResource(@DrawableRes int resId); + + /** + * 设置背景着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + T setBackgroundTintList(ColorStateList tint); + + /** + * 设置背景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + T setBackgroundTintMode(PorterDuff.Mode tintMode); + + /** + * 设置前景图片 + * @param foreground 前景图片 + * @return Helper + */ + T setForeground(Drawable foreground); + + /** + * 设置前景重心 + * @param gravity 重心 + * @return Helper + */ + T setForegroundGravity(int gravity); + + /** + * 设置前景着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + T setForegroundTintList(ColorStateList tint); + + /** + * 设置前景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + T setForegroundTintMode(PorterDuff.Mode tintMode); + + /** + * View 着色处理 + * @param color 颜色值 + * @return Helper + */ + T setColorFilter(@ColorInt int color); + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param color 颜色值 + * @return Helper + */ + T setColorFilter( + Drawable drawable, + @ColorInt int color + ); + + /** + * View 着色处理 + * @param colorFilter 颜色过滤 ( 效果 ) + * @return Helper + */ + T setColorFilter(ColorFilter colorFilter); + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @return Helper + */ + T setColorFilter( + Drawable drawable, + ColorFilter colorFilter + ); + + /** + * 设置 ProgressBar 进度条样式 + * @param drawable {@link Drawable} + * @return Helper + */ + T setProgressDrawable(Drawable drawable); + + /** + * 设置 ProgressBar 进度值 + * @param progress 当前进度 + * @return Helper + */ + T setBarProgress(int progress); + + /** + * 设置 ProgressBar 最大值 + * @param max 最大值 + * @return Helper + */ + T setBarMax(int max); + + /** + * 设置 ProgressBar 最大值 + * @param progress 当前进度 + * @param max 最大值 + * @return Helper + */ + T setBarValue( + int progress, + int max + ); + + // ================= + // = ListViewUtils = + // ================= + + /** + * 滑动到指定索引 ( 有滚动过程 ) + * @param position 索引 + * @return Helper + */ + T smoothScrollToPosition(int position); + + /** + * 滑动到指定索引 ( 无滚动过程 ) + * @param position 索引 + * @return Helper + */ + T scrollToPosition(int position); + + /** + * 滑动到顶部 ( 有滚动过程 ) + * @return Helper + */ + T smoothScrollToTop(); + + /** + * 滑动到顶部 ( 无滚动过程 ) + * @return Helper + */ + T scrollToTop(); + + /** + * 滑动到底部 ( 有滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 smoothScrollBy 搭配到底部 )
+     *     smoothScrollToBottom(view)
+     *     smoothScrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @return Helper + */ + T smoothScrollToBottom(); + + /** + * 滑动到底部 ( 无滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 scrollBy 搭配到底部 )
+     *     scrollToBottom(view)
+     *     scrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @return Helper + */ + T scrollToBottom(); + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + T smoothScrollTo( + int x, + int y + ); + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + T smoothScrollBy( + int x, + int y + ); + + /** + * 滚动方向 ( 有滚动过程 ) + * @param direction 滚动方向 如: View.FOCUS_UP、View.FOCUS_DOWN + * @return Helper + */ + T fullScroll(int direction); + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + T scrollTo( + int x, + int y + ); + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + T scrollBy( + int x, + int y + ); + + // ================== + // = ImageViewUtils = + // ================== + + /** + * 设置 ImageView 是否保持宽高比 + * @param adjustViewBounds 是否调整此视图的边界以保持可绘制的原始纵横比 + * @return Helper + */ + T setAdjustViewBounds(boolean adjustViewBounds); + + /** + * 设置 ImageView 最大高度 + * @param maxHeight 最大高度 + * @return Helper + */ + T setMaxHeight(int maxHeight); + + /** + * 设置 ImageView 最大宽度 + * @param maxWidth 最大宽度 + * @return Helper + */ + T setMaxWidth(int maxWidth); + + /** + * 设置 ImageView Level + * @param level level Image + * @return Helper + */ + T setImageLevel(int level); + + /** + * 设置 ImageView Bitmap + * @param bitmap {@link Bitmap} + * @return Helper + */ + T setImageBitmap(Bitmap bitmap); + + /** + * 设置 ImageView Drawable + * @param drawable {@link Bitmap} + * @return Helper + */ + T setImageDrawable(Drawable drawable); + + /** + * 设置 ImageView 资源 + * @param resId resource identifier + * @return Helper + */ + T setImageResource(@DrawableRes int resId); + + /** + * 设置 ImageView Matrix + * @param matrix {@link Matrix} + * @return Helper + */ + T setImageMatrix(Matrix matrix); + + /** + * 设置 ImageView 着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + T setImageTintList(ColorStateList tint); + + /** + * 设置 ImageView 着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + T setImageTintMode(PorterDuff.Mode tintMode); + + /** + * 设置 ImageView 缩放类型 + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @return Helper + */ + T setScaleType(ImageView.ScaleType scaleType); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @return Helper + */ + T setBackgroundResources(@DrawableRes int resId); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setBackgroundResources( + @DrawableRes int resId, + int isVisibility + ); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @return Helper + */ + T setImageResources(@DrawableRes int resId); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setImageResources( + @DrawableRes int resId, + int isVisibility + ); + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @return Helper + */ + T setImageBitmaps(Bitmap bitmap); + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setImageBitmaps( + Bitmap bitmap, + int isVisibility + ); + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @return Helper + */ + T setImageDrawables(Drawable drawable); + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setImageDrawables( + Drawable drawable, + int isVisibility + ); + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @return Helper + */ + T setScaleTypes(ImageView.ScaleType scaleType); + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + T setScaleTypes( + ImageView.ScaleType scaleType, + int isVisibility + ); + + // =============================== + // = EditTextUtils、TextViewUtils = + // =============================== + + /** + * 设置文本 + * @param text TextView text + * @return Helper + */ + T setText(CharSequence text); + + /** + * 设置长度限制 + * @param maxLength 长度限制 + * @return Helper + */ + T setMaxLength(int maxLength); + + /** + * 设置长度限制, 并且设置内容 + * @param content 文本内容 + * @param maxLength 长度限制 + * @return Helper + */ + T setMaxLengthAndText( + CharSequence content, + int maxLength + ); + + /** + * 设置输入类型 + * @param type 类型 + * @return Helper + */ + T setInputType(int type); + + /** + * 设置软键盘右下角按钮类型 + * @param imeOptions 软键盘按钮类型 + * @return Helper + */ + T setImeOptions(int imeOptions); + + /** + * 设置文本视图显示转换 + * @param method {@link TransformationMethod} + * @return Helper + */ + T setTransformationMethod(TransformationMethod method); + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @return Helper + */ + T setTransformationMethod(boolean isDisplayPassword); + + // ================= + // = EditTextUtils = + // ================= + + /** + * 设置内容 + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @return Helper + */ + T setText( + CharSequence content, + boolean isSelect + ); + + /** + * 追加内容 ( 当前光标位置追加 ) + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @return Helper + */ + T insert( + CharSequence content, + boolean isSelect + ); + + /** + * 追加内容 + * @param content 文本内容 + * @param start 开始添加的位置 + * @param isSelect 是否设置光标 + * @return Helper + */ + T insert( + CharSequence content, + int start, + boolean isSelect + ); + + /** + * 设置是否显示光标 + * @param visible 是否显示光标 + * @return Helper + */ + T setCursorVisible(boolean visible); + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @return Helper + */ + T setTextCursorDrawable(@DrawableRes int textCursorDrawable); + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @return Helper + */ + T setTextCursorDrawable(Drawable textCursorDrawable); + + /** + * 设置光标在第一位 + * @return Helper + */ + T setSelectionToTop(); + + /** + * 设置光标在最后一位 + * @return Helper + */ + T setSelectionToBottom(); + + /** + * 设置光标位置 + * @param index 光标位置 + * @return Helper + */ + T setSelection(int index); + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param isSelectBottom 是否设置光标到最后 + * @return Helper + */ + T setTransformationMethod( + boolean isDisplayPassword, + boolean isSelectBottom + ); + + /** + * 添加输入监听事件 + * @param watcher 输入监听 + * @return Helper + */ + T addTextChangedListener(TextWatcher watcher); + + /** + * 移除输入监听事件 + * @param watcher 输入监听 + * @return Helper + */ + T removeTextChangedListener(TextWatcher watcher); + + /** + * 设置 KeyListener + * @param listener {@link KeyListener} + * @return Helper + */ + T setKeyListener(KeyListener listener); + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容, 如: 0123456789 + * @return Helper + */ + T setKeyListener(String accepted); + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容 + * @return Helper + */ + T setKeyListener(char[] accepted); + + // ================= + // = TextViewUtils = + // ================= + + /** + * 设置 Hint 文本 + * @param text Hint text + * @return Helper + */ + T setHint(CharSequence text); + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @return Helper + */ + T setHintTextColors(@ColorInt int color); + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @return Helper + */ + T setHintTextColors(ColorStateList colors); + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @return Helper + */ + T setTextColors(@ColorInt int color); + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @return Helper + */ + T setTextColors(ColorStateList colors); + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @return Helper + */ + T setHtmlTexts(String content); + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @return Helper + */ + T setTypeface(Typeface typeface); + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @return Helper + */ + T setTypeface( + Typeface typeface, + int style + ); + + /** + * 设置字体大小 ( px 像素 ) + * @param size 字体大小 + * @return Helper + */ + T setTextSizeByPx(float size); + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param size 字体大小 + * @return Helper + */ + T setTextSizeBySp(float size); + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param size 字体大小 + * @return Helper + */ + T setTextSizeByDp(float size); + + /** + * 设置字体大小 ( inches 英寸 ) + * @param size 字体大小 + * @return Helper + */ + T setTextSizeByIn(float size); + + /** + * 设置字体大小 + * @param unit 字体参数类型 + * @param size 字体大小 + * @return Helper + */ + T setTextSize( + int unit, + float size + ); + + /** + * 清空 flags + * @return Helper + */ + T clearFlags(); + + /** + * 设置 TextView flags + * @param flags flags + * @return Helper + */ + T setPaintFlags(int flags); + + /** + * 设置 TextView 抗锯齿 flags + * @return Helper + */ + T setAntiAliasFlag(); + + /** + * 设置 TextView 是否加粗 + * @return Helper + */ + T setBold(); + + /** + * 设置 TextView 是否加粗 + * @param isBold {@code true} yes, {@code false} no + * @return Helper + */ + T setBold(boolean isBold); + + /** + * 设置 TextView 是否加粗 + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @return Helper + */ + T setBold( + Typeface typeface, + boolean isBold + ); + + /** + * 设置下划线 + * @return Helper + */ + T setUnderlineText(); + + /** + * 设置下划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @return Helper + */ + T setUnderlineText(boolean isAntiAlias); + + /** + * 设置中划线 + * @return Helper + */ + T setStrikeThruText(); + + /** + * 设置中划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @return Helper + */ + T setStrikeThruText(boolean isAntiAlias); + + /** + * 设置文字水平间距 + * @param letterSpacing 文字水平间距 + * @return Helper + */ + T setLetterSpacing(float letterSpacing); + + /** + * 设置文字行间距 ( 行高 ) + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @return Helper + */ + T setLineSpacing(float lineSpacing); + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @return Helper + */ + T setLineSpacingAndMultiplier( + float lineSpacing, + float multiplier + ); + + /** + * 设置字体水平方向的缩放 + * @param size 缩放比例 + * @return Helper + */ + T setTextScaleX(float size); + + /** + * 设置是否保留字体留白间隙区域 + * @param includePadding 是否保留字体留白间隙区域 + * @return Helper + */ + T setIncludeFontPadding(boolean includePadding); + + /** + * 设置行数 + * @param lines 行数 + * @return Helper + */ + T setLines(int lines); + + /** + * 设置最大行数 + * @param maxLines 最大行数 + * @return Helper + */ + T setMaxLines(int maxLines); + + /** + * 设置最小行数 + * @param minLines 最小行数 + * @return Helper + */ + T setMinLines(int minLines); + + /** + * 设置最大字符宽度限制 + * @param maxEms 最大字符 + * @return Helper + */ + T setMaxEms(int maxEms); + + /** + * 设置最小字符宽度限制 + * @param minEms 最小字符 + * @return Helper + */ + T setMinEms(int minEms); + + /** + * 设置指定字符宽度 + * @param ems 字符 + * @return Helper + */ + T setEms(int ems); + + /** + * 设置 Ellipsize 效果 + * @param where {@link TextUtils.TruncateAt} + * @return Helper + */ + T setEllipsize(TextUtils.TruncateAt where); + + /** + * 设置自动识别文本链接 + * @param mask {@link android.text.util.Linkify} + * @return Helper + */ + T setAutoLinkMask(int mask); + + /** + * 设置文本全为大写 + * @param allCaps 是否全部大写 + * @return Helper + */ + T setAllCaps(boolean allCaps); + + /** + * 设置 Gravity + * @param gravity {@link android.view.Gravity} + * @return Helper + */ + T setGravity(int gravity); + + /** + * 设置 CompoundDrawables Padding + * @param padding CompoundDrawables Padding + * @return Helper + */ + T setCompoundDrawablePadding(int padding); + + /** + * 设置 Left CompoundDrawables + * @param left left Drawable + * @return Helper + */ + T setCompoundDrawablesByLeft(Drawable left); + + /** + * 设置 Top CompoundDrawables + * @param top top Drawable + * @return Helper + */ + T setCompoundDrawablesByTop(Drawable top); + + /** + * 设置 Right CompoundDrawables + * @param right right Drawable + * @return Helper + */ + T setCompoundDrawablesByRight(Drawable right); + + /** + * 设置 Bottom CompoundDrawables + * @param bottom bottom Drawable + * @return Helper + */ + T setCompoundDrawablesByBottom(Drawable bottom); + + /** + * 设置 CompoundDrawables + *
+     *     CompoundDrawable 的大小控制是通过 drawable.setBounds() 控制
+     *     需要先设置 Drawable 的 setBounds
+     *     {@link dev.utils.app.image.ImageUtils#setBounds}
+     * 
+ * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @return Helper + */ + T setCompoundDrawables( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom + ); + + /** + * 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByLeft(Drawable left); + + /** + * 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param top top Drawable + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByTop(Drawable top); + + /** + * 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param right right Drawable + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByRight(Drawable right); + + /** + * 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param bottom bottom Drawable + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByBottom(Drawable bottom); + + /** + * 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBounds( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom + ); + + /** + * 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 + * @param autoSizeTextType 自动调整大小类型 + * @return Helper + */ + T setAutoSizeTextTypeWithDefaults( + @TextViewCompat.AutoSizeTextType int autoSizeTextType + ); + + /** + * 设置 TextView 自动调整字体大小配置 + * @param autoSizeMinTextSize 自动调整最小字体大小 + * @param autoSizeMaxTextSize 自动调整最大字体大小 + * @param autoSizeStepGranularity 自动调整大小变动粒度 ( 跨度区间值 ) + * @param unit 字体参数类型 + * @return Helper + */ + T setAutoSizeTextTypeUniformWithConfiguration( + int autoSizeMinTextSize, + int autoSizeMaxTextSize, + int autoSizeStepGranularity, + int unit + ); + + /** + * 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM + * @param presetSizes 预设字体大小范围像素为单位 + * @param unit 字体参数类型 + * @return Helper + */ + T setAutoSizeTextTypeUniformWithPresetSizes( + int[] presetSizes, + int unit + ); + + // ===================== + // = RecyclerViewUtils = + // ===================== + + /** + * 设置 RecyclerView LayoutManager + * @param layoutManager LayoutManager + * @return Helper + */ + T setLayoutManager(RecyclerView.LayoutManager layoutManager); + + /** + * 设置 GridLayoutManager SpanCount + * @param spanCount Span Count + * @return Helper + */ + T setSpanCount(int spanCount); + + /** + * 设置 RecyclerView Orientation + * @param orientation 方向 + * @return Helper + */ + T setOrientation(@RecyclerView.Orientation int orientation); + + /** + * 设置 RecyclerView Adapter + * @param adapter Adapter + * @return Helper + */ + T setAdapter(RecyclerView.Adapter adapter); + + /** + * RecyclerView notifyItemRemoved + * @param position 索引 + * @return Helper + */ + T notifyItemRemoved(int position); + + /** + * RecyclerView notifyItemInserted + * @param position 索引 + * @return Helper + */ + T notifyItemInserted(int position); + + /** + * RecyclerView notifyItemMoved + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return Helper + */ + T notifyItemMoved( + int fromPosition, + int toPosition + ); + + /** + * RecyclerView notifyDataSetChanged + * @return Helper + */ + T notifyDataSetChanged(); + + /** + * 设置 RecyclerView LinearSnapHelper + * @return Helper + */ + T attachLinearSnapHelper(); + + /** + * 设置 RecyclerView PagerSnapHelper + * @return Helper + */ + T attachPagerSnapHelper(); + + /** + * 添加 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + T addItemDecoration(RecyclerView.ItemDecoration decor); + + /** + * 添加 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return Helper + */ + T addItemDecoration( + RecyclerView.ItemDecoration decor, + int index + ); + + /** + * 移除 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + T removeItemDecoration(RecyclerView.ItemDecoration decor); + + /** + * 移除 RecyclerView ItemDecoration + * @param index RecyclerView ItemDecoration 索引 + * @return Helper + */ + T removeItemDecorationAt(int index); + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @return Helper + */ + T removeAllItemDecoration(); + + /** + * 设置 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + T setOnScrollListener(RecyclerView.OnScrollListener listener); + + /** + * 添加 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + T addOnScrollListener(RecyclerView.OnScrollListener listener); + + /** + * 移除 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + T removeOnScrollListener(RecyclerView.OnScrollListener listener); + + /** + * 清空 RecyclerView ScrollListener + * @return Helper + */ + T clearOnScrollListeners(); + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param enabled 嵌套滚动开关 + * @return Helper + */ + T setNestedScrollingEnabled(boolean enabled); + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + T forceGetViewSize(SizeUtils.OnGetSizeListener listener); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/quick/QuickHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/quick/QuickHelper.java new file mode 100644 index 0000000000..4dfd8dcfd0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/quick/QuickHelper.java @@ -0,0 +1,3336 @@ +package dev.utils.app.helper.quick; + +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.text.method.TransformationMethod; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IdRes; +import androidx.core.widget.TextViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import java.lang.ref.WeakReference; + +import dev.utils.app.HandlerUtils; +import dev.utils.app.SizeUtils; +import dev.utils.app.ViewUtils; +import dev.utils.app.helper.BaseHelper; +import dev.utils.app.helper.flow.FlowHelper; +import dev.utils.app.helper.view.ViewHelper; + +/** + * detail: 简化链式设置 View Quick Helper 类 + * @author Ttt + *
+ *     整合 {@link ViewHelper} 代码
+ *     针对单个 View 设置处理, 无需多次传入 View
+ * 
+ */ +public final class QuickHelper + extends BaseHelper + implements IHelperByQuick { + + // 持有 View + private final WeakReference mViewRef; + + /** + * 构造函数 + * @param target 目标 View + */ + private QuickHelper(final View target) { + this.mViewRef = new WeakReference<>(target); + } + + // = + + /** + * 获取 QuickHelper + * @param target 目标 View + * @return {@link QuickHelper} + */ + public static QuickHelper get(final View target) { + return new QuickHelper(target); + } + + /** + * 获取 View + * @param 泛型 + * @return {@link View} + */ + public T getView() { + return ViewUtils.convertView(targetView()); + } + + /** + * 获取目标 View + * @return {@link View} + */ + public View targetView() { + return mViewRef.get(); + } + + /** + * 获取目标 View ( 转 ViewGroup ) + * @return {@link ViewGroup} + */ + public ViewGroup targetViewGroup() { + View view = targetView(); + if (view instanceof ViewGroup) { + return (ViewGroup) view; + } + return null; + } + + /** + * 获取目标 View ( 转 ImageView ) + * @return {@link ImageView} + */ + public ImageView targetImageView() { + View view = targetView(); + if (view instanceof ImageView) { + return (ImageView) view; + } + return null; + } + + /** + * 获取目标 View ( 转 TextView ) + * @return {@link TextView} + */ + public TextView targetTextView() { + View view = targetView(); + if (view instanceof TextView) { + return (TextView) view; + } + return null; + } + + /** + * 获取目标 View ( 转 EditText ) + * @return {@link EditText} + */ + public EditText targetEditText() { + View view = targetView(); + if (view instanceof EditText) { + return (EditText) view; + } + return null; + } + + /** + * 获取目标 View ( 转 RecyclerView ) + * @return {@link RecyclerView} + */ + public RecyclerView targetRecyclerView() { + View view = targetView(); + if (view instanceof RecyclerView) { + return (RecyclerView) view; + } + return null; + } + + // ======== + // = Flow = + // ======== + + /** + * 执行 Action 流方法 + * @param action Action + * @return Helper + */ + @Override + public QuickHelper flow(FlowHelper.Action action) { + if (action != null) action.action(); + return this; + } + + // ================ + // = HandlerUtils = + // ================ + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + * @return Helper + */ + @Override + public QuickHelper postRunnable(Runnable runnable) { + HandlerUtils.postRunnable(runnable); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @return Helper + */ + @Override + public QuickHelper postRunnable( + Runnable runnable, + long delayMillis + ) { + HandlerUtils.postRunnable(runnable, delayMillis); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @return Helper + */ + @Override + public QuickHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param listener 结束通知 + * @return Helper + */ + @Override + public QuickHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval, + HandlerUtils.OnEndListener listener + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval, listener); + return this; + } + + /** + * 在主线程 Handler 中清除任务 + * @param runnable 需要清除的任务 + * @return Helper + */ + @Override + public QuickHelper removeRunnable(Runnable runnable) { + HandlerUtils.removeRunnable(runnable); + return this; + } + + // ================= + // = IHelperByView = + // ================= + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @return Helper + */ + @Override + public QuickHelper addTouchArea(int range) { + ViewHelper.get().addTouchArea(range, targetView()); + return this; + } + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @return Helper + */ + @Override + public QuickHelper addTouchArea( + int left, + int top, + int right, + int bottom + ) { + ViewHelper.get().addTouchArea(left, top, right, bottom, targetView()); + return this; + } + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @return Helper + */ + @Override + public QuickHelper setOnClick(View.OnClickListener listener) { + ViewHelper.get().setOnClick(listener, targetView()); + return this; + } + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @return Helper + */ + @Override + public QuickHelper setOnLongClick(View.OnLongClickListener listener) { + ViewHelper.get().setOnLongClick(listener, targetView()); + return this; + } + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @return Helper + */ + @Override + public QuickHelper setOnTouch(View.OnTouchListener listener) { + ViewHelper.get().setOnTouch(listener, targetView()); + return this; + } + + // ============= + // = ViewUtils = + // ============= + + /** + * 设置 View Id + * @param id View Id + * @return Helper + */ + @Override + public QuickHelper setId(int id) { + ViewHelper.get().setId(targetView(), id); + return this; + } + + /** + * 设置是否限制子 View 在其边界内绘制 + * @param clipChildren {@code true} yes, {@code false} no + * @return Helper + */ + @Override + public QuickHelper setClipChildren(boolean clipChildren) { + ViewHelper.get().setClipChildren(clipChildren, targetViewGroup()); + return this; + } + + /** + * 移除全部子 View + * @return Helper + */ + @Override + public QuickHelper removeAllViews() { + ViewHelper.get().removeAllViews(targetViewGroup()); + return this; + } + + /** + * 添加 View + * @param child 待添加 View + * @return Helper + */ + @Override + public QuickHelper addView(View child) { + ViewHelper.get().addView(targetViewGroup(), child); + return this; + } + + /** + * 添加 View + * @param child 待添加 View + * @param index 添加位置索引 + * @return Helper + */ + @Override + public QuickHelper addView( + View child, + int index + ) { + ViewHelper.get().addView(targetViewGroup(), child, index); + return this; + } + + /** + * 添加 View + * @param child 待添加 View + * @param index 添加位置索引 + * @param params LayoutParams + * @return Helper + */ + @Override + public QuickHelper addView( + View child, + int index, + ViewGroup.LayoutParams params + ) { + ViewHelper.get().addView(targetViewGroup(), child, index, params); + return this; + } + + /** + * 添加 View + * @param child 待添加 View + * @param params LayoutParams + * @return Helper + */ + @Override + public QuickHelper addView( + View child, + ViewGroup.LayoutParams params + ) { + ViewHelper.get().addView(targetViewGroup(), child, params); + return this; + } + + /** + * 添加 View + * @param child 待添加 View + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + @Override + public QuickHelper addView( + View child, + int width, + int height + ) { + ViewHelper.get().addView(targetViewGroup(), child, width, height); + return this; + } + + /** + * 设置 View LayoutParams + * @param params LayoutParams + * @return Helper + */ + @Override + public QuickHelper setLayoutParams(ViewGroup.LayoutParams params) { + ViewHelper.get().setLayoutParams(targetView(), params); + return this; + } + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + @Override + public QuickHelper setWidthHeight( + int width, + int height + ) { + ViewHelper.get().setWidthHeight(width, height, targetView()); + return this; + } + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + @Override + public QuickHelper setWidthHeight( + int width, + int height, + boolean nullNewLP + ) { + ViewHelper.get().setWidthHeight(width, height, nullNewLP, targetView()); + return this; + } + + /** + * 设置 View weight 权重 + * @param weight 权重比例 + * @return Helper + */ + @Override + public QuickHelper setWeight(float weight) { + ViewHelper.get().setWeight(weight, targetView()); + return this; + } + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @return Helper + */ + @Override + public QuickHelper setWidth(int width) { + ViewHelper.get().setWidth(width, targetView()); + return this; + } + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + @Override + public QuickHelper setWidth( + int width, + boolean nullNewLP + ) { + ViewHelper.get().setWidth(width, nullNewLP, targetView()); + return this; + } + + /** + * 设置 View 高度 + * @param height View 高度 + * @return Helper + */ + @Override + public QuickHelper setHeight(int height) { + ViewHelper.get().setHeight(height, targetView()); + return this; + } + + /** + * 设置 View 高度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @return Helper + */ + @Override + public QuickHelper setHeight( + int height, + boolean nullNewLP + ) { + ViewHelper.get().setHeight(height, nullNewLP, targetView()); + return this; + } + + /** + * 设置 View 最小宽度 + * @param minWidth 最小宽度 + * @return Helper + */ + @Override + public QuickHelper setMinimumWidth(int minWidth) { + ViewHelper.get().setMinimumWidth(minWidth, targetView()); + return this; + } + + /** + * 设置 View 最小高度 + * @param minHeight 最小高度 + * @return Helper + */ + @Override + public QuickHelper setMinimumHeight(int minHeight) { + ViewHelper.get().setMinimumHeight(minHeight, targetView()); + return this; + } + + /** + * 设置 View 透明度 + * @param alpha 透明度 + * @return Helper + */ + @Override + public QuickHelper setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) { + ViewHelper.get().setAlpha(alpha, targetView()); + return this; + } + + /** + * 设置 View TAG + * @param object TAG + * @return Helper + */ + @Override + public QuickHelper setTag(Object object) { + ViewHelper.get().setTag(targetView(), object); + return this; + } + + /** + * 设置 View 滑动的 X 轴坐标 + * @param value X 轴坐标 + * @return Helper + */ + @Override + public QuickHelper setScrollX(int value) { + ViewHelper.get().setScrollX(value, targetView()); + return this; + } + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param value Y 轴坐标 + * @return Helper + */ + @Override + public QuickHelper setScrollY(int value) { + ViewHelper.get().setScrollY(value, targetView()); + return this; + } + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @return Helper + */ + @Override + public QuickHelper setDescendantFocusability(int focusability) { + ViewHelper.get().setDescendantFocusability(focusability, targetViewGroup()); + return this; + } + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @return Helper + */ + @Override + public QuickHelper setOverScrollMode(int overScrollMode) { + ViewHelper.get().setOverScrollMode(overScrollMode, targetView()); + return this; + } + + /** + * 设置是否绘制横向滚动条 + * @param horizontalScrollBarEnabled {@code true} yes, {@code false} no + * @return Helper + */ + @Override + public QuickHelper setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) { + ViewHelper.get().setHorizontalScrollBarEnabled(horizontalScrollBarEnabled, targetView()); + return this; + } + + /** + * 设置是否绘制垂直滚动条 + * @param verticalScrollBarEnabled {@code true} yes, {@code false} no + * @return Helper + */ + @Override + public QuickHelper setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { + ViewHelper.get().setVerticalScrollBarEnabled(verticalScrollBarEnabled, targetView()); + return this; + } + + /** + * 设置 View 滚动效应 + * @param isScrollContainer 是否需要滚动效应 + * @return Helper + */ + @Override + public QuickHelper setScrollContainer(boolean isScrollContainer) { + ViewHelper.get().setScrollContainer(isScrollContainer, targetView()); + return this; + } + + /** + * 设置下一个获取焦点的 View id + * @param nextFocusForwardId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public QuickHelper setNextFocusForwardId(@IdRes int nextFocusForwardId) { + ViewHelper.get().setNextFocusForwardId(targetView(), nextFocusForwardId); + return this; + } + + /** + * 设置向下移动焦点时, 下一个获取焦点的 View id + * @param nextFocusDownId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public QuickHelper setNextFocusDownId(@IdRes int nextFocusDownId) { + ViewHelper.get().setNextFocusDownId(targetView(), nextFocusDownId); + return this; + } + + /** + * 设置向左移动焦点时, 下一个获取焦点的 View id + * @param nextFocusLeftId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public QuickHelper setNextFocusLeftId(@IdRes int nextFocusLeftId) { + ViewHelper.get().setNextFocusLeftId(targetView(), nextFocusLeftId); + return this; + } + + /** + * 设置向右移动焦点时, 下一个获取焦点的 View id + * @param nextFocusRightId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public QuickHelper setNextFocusRightId(@IdRes int nextFocusRightId) { + ViewHelper.get().setNextFocusRightId(targetView(), nextFocusRightId); + return this; + } + + /** + * 设置向上移动焦点时, 下一个获取焦点的 View id + * @param nextFocusUpId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public QuickHelper setNextFocusUpId(@IdRes int nextFocusUpId) { + ViewHelper.get().setNextFocusUpId(targetView(), nextFocusUpId); + return this; + } + + /** + * 设置 View 旋转度数 + * @param rotation 旋转度数 + * @return Helper + */ + @Override + public QuickHelper setRotation(float rotation) { + ViewHelper.get().setRotation(rotation, targetView()); + return this; + } + + /** + * 设置 View 水平旋转度数 + * @param rotationX 水平旋转度数 + * @return Helper + */ + @Override + public QuickHelper setRotationX(float rotationX) { + ViewHelper.get().setRotationX(rotationX, targetView()); + return this; + } + + /** + * 设置 View 竖直旋转度数 + * @param rotationY 竖直旋转度数 + * @return Helper + */ + @Override + public QuickHelper setRotationY(float rotationY) { + ViewHelper.get().setRotationY(rotationY, targetView()); + return this; + } + + /** + * 设置 View 水平方向缩放比例 + * @param scaleX 水平方向缩放比例 + * @return Helper + */ + @Override + public QuickHelper setScaleX(float scaleX) { + ViewHelper.get().setScaleX(scaleX, targetView()); + return this; + } + + /** + * 设置 View 竖直方向缩放比例 + * @param scaleY 竖直方向缩放比例 + * @return Helper + */ + @Override + public QuickHelper setScaleY(float scaleY) { + ViewHelper.get().setScaleY(scaleY, targetView()); + return this; + } + + /** + * 设置文本的显示方式 + * @param textAlignment 文本的显示方式 + * @return Helper + */ + @Override + public QuickHelper setTextAlignment(int textAlignment) { + ViewHelper.get().setTextAlignment(textAlignment, targetView()); + return this; + } + + /** + * 设置文本的显示方向 + * @param textDirection 文本的显示方向 + * @return Helper + */ + @Override + public QuickHelper setTextDirection(int textDirection) { + ViewHelper.get().setTextDirection(textDirection, targetView()); + return this; + } + + /** + * 设置水平方向偏转量 + * @param pivotX 水平方向偏转量 + * @return Helper + */ + @Override + public QuickHelper setPivotX(float pivotX) { + ViewHelper.get().setPivotX(pivotX, targetView()); + return this; + } + + /** + * 设置竖直方向偏转量 + * @param pivotY 竖直方向偏转量 + * @return Helper + */ + @Override + public QuickHelper setPivotY(float pivotY) { + ViewHelper.get().setPivotY(pivotY, targetView()); + return this; + } + + /** + * 设置水平方向的移动距离 + * @param translationX 水平方向的移动距离 + * @return Helper + */ + @Override + public QuickHelper setTranslationX(float translationX) { + ViewHelper.get().setTranslationX(translationX, targetView()); + return this; + } + + /** + * 设置竖直方向的移动距离 + * @param translationY 竖直方向的移动距离 + * @return Helper + */ + @Override + public QuickHelper setTranslationY(float translationY) { + ViewHelper.get().setTranslationY(translationY, targetView()); + return this; + } + + /** + * 设置 X 轴位置 + * @param x X 轴位置 + * @return Helper + */ + @Override + public QuickHelper setX(float x) { + ViewHelper.get().setX(x, targetView()); + return this; + } + + /** + * 设置 Y 轴位置 + * @param y Y 轴位置 + * @return Helper + */ + @Override + public QuickHelper setY(float y) { + ViewHelper.get().setY(y, targetView()); + return this; + } + + /** + * 设置 View 硬件加速类型 + * @param layerType 硬件加速类型 + * @param paint {@link Paint} + * @return Helper + */ + @Override + public QuickHelper setLayerType( + int layerType, + Paint paint + ) { + ViewHelper.get().setLayerType(layerType, paint, targetView()); + return this; + } + + /** + * 请求重新对 View 布局 + * @return Helper + */ + @Override + public QuickHelper requestLayout() { + ViewHelper.get().requestLayout(targetView()); + return this; + } + + /** + * View 请求获取焦点 + * @return Helper + */ + @Override + public QuickHelper requestFocus() { + ViewHelper.get().requestFocus(targetView()); + return this; + } + + /** + * View 清除焦点 + * @return Helper + */ + @Override + public QuickHelper clearFocus() { + ViewHelper.get().clearFocus(targetView()); + return this; + } + + /** + * 设置 View 是否在触摸模式下获得焦点 + * @param focusableInTouchMode {@code true} 可获取, {@code false} 不可获取 + * @return Helper + */ + @Override + public QuickHelper setFocusableInTouchMode(boolean focusableInTouchMode) { + ViewHelper.get().setFocusableInTouchMode(focusableInTouchMode, targetView()); + return this; + } + + /** + * 设置 View 是否可以获取焦点 + * @param focusable {@code true} 可获取, {@code false} 不可获取 + * @return Helper + */ + @Override + public QuickHelper setFocusable(boolean focusable) { + ViewHelper.get().setFocusable(focusable, targetView()); + return this; + } + + /** + * 切换获取焦点状态 + * @return Helper + */ + @Override + public QuickHelper toggleFocusable() { + ViewHelper.get().toggleFocusable(targetView()); + return this; + } + + /** + * 设置 View 是否选中 + * @param selected {@code true} 选中, {@code false} 非选中 + * @return Helper + */ + @Override + public QuickHelper setSelected(boolean selected) { + ViewHelper.get().setSelected(selected, targetView()); + return this; + } + + /** + * 切换选中状态 + * @return Helper + */ + @Override + public QuickHelper toggleSelected() { + ViewHelper.get().toggleSelected(targetView()); + return this; + } + + /** + * 设置 View 是否启用 + * @param enabled {@code true} 启用, {@code false} 禁用 + * @return Helper + */ + @Override + public QuickHelper setEnabled(boolean enabled) { + ViewHelper.get().setEnabled(enabled, targetView()); + return this; + } + + /** + * 切换 View 是否启用状态 + * @return Helper + */ + @Override + public QuickHelper toggleEnabled() { + ViewHelper.get().toggleEnabled(targetView()); + return this; + } + + /** + * 设置 View 是否可以点击 + * @param clickable {@code true} 可点击, {@code false} 不可点击 + * @return Helper + */ + @Override + public QuickHelper setClickable(boolean clickable) { + ViewHelper.get().setClickable(clickable, targetView()); + return this; + } + + /** + * 切换 View 是否可以点击状态 + * @return Helper + */ + @Override + public QuickHelper toggleClickable() { + ViewHelper.get().toggleClickable(targetView()); + return this; + } + + /** + * 设置 View 是否可以长按 + * @param longClickable {@code true} 可长按, {@code false} 不可长按 + * @return Helper + */ + @Override + public QuickHelper setLongClickable(boolean longClickable) { + ViewHelper.get().setLongClickable(longClickable, targetView()); + return this; + } + + /** + * 切换 View 是否可以长按状态 + * @return Helper + */ + @Override + public QuickHelper toggleLongClickable() { + ViewHelper.get().toggleLongClickable(targetView()); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @return Helper + */ + @Override + public QuickHelper setVisibilitys(boolean isVisibility) { + ViewHelper.get().setVisibilitys(isVisibility, targetView()); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setVisibilitys(int isVisibility) { + ViewHelper.get().setVisibilitys(isVisibility, targetView()); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @return Helper + */ + @Override + public QuickHelper setVisibilityINs(boolean isVisibility) { + ViewHelper.get().setVisibilityINs(isVisibility, targetView()); + return this; + } + + /** + * 切换 View 显示的状态 + * @param views View[] + * @return Helper + */ + @Override + public QuickHelper toggleVisibilitys(View... views) { + ViewHelper.get().toggleVisibilitys(targetView(), views); + return this; + } + + /** + * 切换 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public QuickHelper toggleVisibilitys( + int state, + View... views + ) { + ViewHelper.get().toggleVisibilitys(state, new View[]{targetView()}, views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public QuickHelper reverseVisibilitys( + int state, + View... views + ) { + ViewHelper.get().reverseVisibilitys(state, targetView(), views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param views View[] + * @return Helper + */ + @Override + public QuickHelper reverseVisibilitys( + boolean isVisibility, + View... views + ) { + ViewHelper.get().reverseVisibilitys(isVisibility, targetView(), views); + return this; + } + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper toggleViews( + boolean isChange, + int isVisibility + ) { + ViewHelper.get().toggleViews(isChange, isVisibility, targetView()); + return this; + } + + /** + * 把自身从父 View 中移除 + * @return Helper + */ + @Override + public QuickHelper removeSelfFromParent() { + ViewHelper.get().removeSelfFromParent(targetView()); + return this; + } + + /** + * View 请求更新 + * @param allParent 是否全部父布局 View 都请求 + * @return Helper + */ + @Override + public QuickHelper requestLayoutParent(boolean allParent) { + ViewHelper.get().requestLayoutParent(allParent, targetView()); + return this; + } + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @return Helper + */ + @Override + public QuickHelper measureView(int specifiedWidth) { + ViewHelper.get().measureView(specifiedWidth, targetView()); + return this; + } + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @return Helper + */ + @Override + public QuickHelper measureView( + int specifiedWidth, + int specifiedHeight + ) { + ViewHelper.get().measureView(specifiedWidth, specifiedHeight, targetView()); + return this; + } + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @return Helper + */ + @Override + public QuickHelper setLayoutGravity(int gravity) { + ViewHelper.get().setLayoutGravity(gravity, targetView()); + return this; + } + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param isReflection 是否使用反射 + * @return Helper + */ + @Override + public QuickHelper setLayoutGravity( + int gravity, + boolean isReflection + ) { + ViewHelper.get().setLayoutGravity(gravity, isReflection, targetView()); + return this; + } + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @return Helper + */ + @Override + public QuickHelper setMarginLeft(int leftMargin) { + ViewHelper.get().setMarginLeft(leftMargin, targetView()); + return this; + } + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + @Override + public QuickHelper setMarginLeft( + int leftMargin, + boolean reset + ) { + ViewHelper.get().setMarginLeft(leftMargin, reset, targetView()); + return this; + } + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @return Helper + */ + @Override + public QuickHelper setMarginTop(int topMargin) { + ViewHelper.get().setMarginTop(topMargin, targetView()); + return this; + } + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + @Override + public QuickHelper setMarginTop( + int topMargin, + boolean reset + ) { + ViewHelper.get().setMarginTop(topMargin, reset, targetView()); + return this; + } + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @return Helper + */ + @Override + public QuickHelper setMarginRight(int rightMargin) { + ViewHelper.get().setMarginRight(rightMargin, targetView()); + return this; + } + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + @Override + public QuickHelper setMarginRight( + int rightMargin, + boolean reset + ) { + ViewHelper.get().setMarginRight(rightMargin, reset, targetView()); + return this; + } + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @return Helper + */ + @Override + public QuickHelper setMarginBottom(int bottomMargin) { + ViewHelper.get().setMarginBottom(bottomMargin, targetView()); + return this; + } + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param reset 是否重置清空其他 margin + * @return Helper + */ + @Override + public QuickHelper setMarginBottom( + int bottomMargin, + boolean reset + ) { + ViewHelper.get().setMarginBottom(bottomMargin, reset, targetView()); + return this; + } + + /** + * 设置 Margin 边距 + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @return Helper + */ + @Override + public QuickHelper setMargin( + int leftRight, + int topBottom + ) { + ViewHelper.get().setMargin(leftRight, topBottom, targetView()); + return this; + } + + /** + * 设置 Margin 边距 + * @param margin Margin + * @return Helper + */ + @Override + public QuickHelper setMargin(int margin) { + ViewHelper.get().setMargin(margin, targetView()); + return this; + } + + /** + * 设置 Margin 边距 + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @return Helper + */ + @Override + public QuickHelper setMargin( + int left, + int top, + int right, + int bottom + ) { + ViewHelper.get().setMargin(left, top, right, bottom, targetView()); + return this; + } + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingLeft(int leftPadding) { + ViewHelper.get().setPaddingLeft(leftPadding, targetView()); + return this; + } + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingLeft( + int leftPadding, + boolean reset + ) { + ViewHelper.get().setPaddingLeft(leftPadding, reset, targetView()); + return this; + } + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingTop(int topPadding) { + ViewHelper.get().setPaddingTop(topPadding, targetView()); + return this; + } + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingTop( + int topPadding, + boolean reset + ) { + ViewHelper.get().setPaddingTop(topPadding, reset, targetView()); + return this; + } + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingRight(int rightPadding) { + ViewHelper.get().setPaddingRight(rightPadding, targetView()); + return this; + } + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingRight( + int rightPadding, + boolean reset + ) { + ViewHelper.get().setPaddingRight(rightPadding, reset, targetView()); + return this; + } + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingBottom(int bottomPadding) { + ViewHelper.get().setPaddingBottom(bottomPadding, targetView()); + return this; + } + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param reset 是否重置清空其他 Padding + * @return Helper + */ + @Override + public QuickHelper setPaddingBottom( + int bottomPadding, + boolean reset + ) { + ViewHelper.get().setPaddingBottom(bottomPadding, reset, targetView()); + return this; + } + + /** + * 设置 Padding 边距 + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @return Helper + */ + @Override + public QuickHelper setPadding( + int leftRight, + int topBottom + ) { + ViewHelper.get().setPadding(leftRight, topBottom, targetView()); + return this; + } + + /** + * 设置 Padding 边距 + * @param padding Padding + * @return Helper + */ + @Override + public QuickHelper setPadding(int padding) { + ViewHelper.get().setPadding(padding, targetView()); + return this; + } + + /** + * 设置 Padding 边距 + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @return Helper + */ + @Override + public QuickHelper setPadding( + int left, + int top, + int right, + int bottom + ) { + ViewHelper.get().setPadding(left, top, right, bottom, targetView()); + return this; + } + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @return Helper + */ + @Override + public QuickHelper addRules(int verb) { + ViewHelper.get().addRules(verb, targetView()); + return this; + } + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param subject 关联 View id + * @return Helper + */ + @Override + public QuickHelper addRules( + int verb, + int subject + ) { + ViewHelper.get().addRules(verb, subject, targetView()); + return this; + } + + /** + * 移除多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @return Helper + */ + @Override + public QuickHelper removeRules(int verb) { + ViewHelper.get().removeRules(verb, targetView()); + return this; + } + + /** + * 设置动画 + * @param animation {@link Animation} + * @return Helper + */ + @Override + public QuickHelper setAnimation(Animation animation) { + ViewHelper.get().setAnimation(targetView(), animation); + return this; + } + + /** + * 清空动画 + * @return Helper + */ + @Override + public QuickHelper clearAnimation() { + ViewHelper.get().clearAnimation(targetView()); + return this; + } + + /** + * 启动动画 + * @return Helper + */ + @Override + public QuickHelper startAnimation() { + ViewHelper.get().startAnimation(targetView()); + return this; + } + + /** + * 启动动画 + * @param animation {@link Animation} + * @return Helper + */ + @Override + public QuickHelper startAnimation(Animation animation) { + ViewHelper.get().startAnimation(targetView(), animation); + return this; + } + + /** + * 取消动画 + * @return Helper + */ + @Override + public QuickHelper cancelAnimation() { + ViewHelper.get().cancelAnimation(targetView()); + return this; + } + + /** + * 设置背景图片 + * @param background 背景图片 + * @return Helper + */ + @Override + public QuickHelper setBackground(Drawable background) { + ViewHelper.get().setBackground(background, targetView()); + return this; + } + + /** + * 设置背景颜色 + * @param color 背景颜色 + * @return Helper + */ + @Override + public QuickHelper setBackgroundColor(@ColorInt int color) { + ViewHelper.get().setBackgroundColor(color, targetView()); + return this; + } + + /** + * 设置背景资源 + * @param resId resource identifier + * @return Helper + */ + @Override + public QuickHelper setBackgroundResource(@DrawableRes int resId) { + ViewHelper.get().setBackgroundResource(resId, targetView()); + return this; + } + + /** + * 设置背景着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + @Override + public QuickHelper setBackgroundTintList(ColorStateList tint) { + ViewHelper.get().setBackgroundTintList(tint, targetView()); + return this; + } + + /** + * 设置背景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + @Override + public QuickHelper setBackgroundTintMode(PorterDuff.Mode tintMode) { + ViewHelper.get().setBackgroundTintMode(tintMode, targetView()); + return this; + } + + /** + * 设置前景图片 + * @param foreground 前景图片 + * @return Helper + */ + @Override + public QuickHelper setForeground(Drawable foreground) { + ViewHelper.get().setForeground(foreground, targetView()); + return this; + } + + /** + * 设置前景重心 + * @param gravity 重心 + * @return Helper + */ + @Override + public QuickHelper setForegroundGravity(int gravity) { + ViewHelper.get().setForegroundGravity(gravity, targetView()); + return this; + } + + /** + * 设置前景着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + @Override + public QuickHelper setForegroundTintList(ColorStateList tint) { + ViewHelper.get().setForegroundTintList(tint, targetView()); + return this; + } + + /** + * 设置前景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + @Override + public QuickHelper setForegroundTintMode(PorterDuff.Mode tintMode) { + ViewHelper.get().setForegroundTintMode(tintMode, targetView()); + return this; + } + + /** + * View 着色处理 + * @param color 颜色值 + * @return Helper + */ + @Override + public QuickHelper setColorFilter(@ColorInt int color) { + ViewHelper.get().setColorFilter(color, targetView()); + return this; + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param color 颜色值 + * @return Helper + */ + @Override + public QuickHelper setColorFilter( + Drawable drawable, + @ColorInt int color + ) { + ViewHelper.get().setColorFilter(drawable, color, targetView()); + return this; + } + + /** + * View 着色处理 + * @param colorFilter 颜色过滤 ( 效果 ) + * @return Helper + */ + @Override + public QuickHelper setColorFilter(ColorFilter colorFilter) { + ViewHelper.get().setColorFilter(colorFilter, targetView()); + return this; + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @return Helper + */ + @Override + public QuickHelper setColorFilter( + Drawable drawable, + ColorFilter colorFilter + ) { + ViewHelper.get().setColorFilter(drawable, colorFilter, targetView()); + return this; + } + + /** + * 设置 ProgressBar 进度条样式 + * @param drawable {@link Drawable} + * @return Helper + */ + @Override + public QuickHelper setProgressDrawable(Drawable drawable) { + ViewHelper.get().setProgressDrawable(drawable, targetView()); + return this; + } + + /** + * 设置 ProgressBar 进度值 + * @param progress 当前进度 + * @return Helper + */ + @Override + public QuickHelper setBarProgress(int progress) { + ViewHelper.get().setBarProgress(progress, targetView()); + return this; + } + + /** + * 设置 ProgressBar 最大值 + * @param max 最大值 + * @return Helper + */ + @Override + public QuickHelper setBarMax(int max) { + ViewHelper.get().setBarMax(max, targetView()); + return this; + } + + /** + * 设置 ProgressBar 最大值 + * @param progress 当前进度 + * @param max 最大值 + * @return Helper + */ + @Override + public QuickHelper setBarValue( + int progress, + int max + ) { + ViewHelper.get().setBarValue(progress, max, targetView()); + return this; + } + + // ================= + // = ListViewUtils = + // ================= + + /** + * 滑动到指定索引 ( 有滚动过程 ) + * @param position 索引 + * @return Helper + */ + @Override + public QuickHelper smoothScrollToPosition(int position) { + ViewHelper.get().smoothScrollToPosition(position, targetView()); + return this; + } + + /** + * 滑动到指定索引 ( 无滚动过程 ) + * @param position 索引 + * @return Helper + */ + @Override + public QuickHelper scrollToPosition(int position) { + ViewHelper.get().scrollToPosition(position, targetView()); + return this; + } + + /** + * 滑动到顶部 ( 有滚动过程 ) + * @return Helper + */ + @Override + public QuickHelper smoothScrollToTop() { + ViewHelper.get().smoothScrollToTop(targetView()); + return this; + } + + /** + * 滑动到顶部 ( 无滚动过程 ) + * @return Helper + */ + @Override + public QuickHelper scrollToTop() { + ViewHelper.get().scrollToTop(targetView()); + return this; + } + + /** + * 滑动到底部 ( 有滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 smoothScrollBy 搭配到底部 )
+     *     smoothScrollToBottom(view)
+     *     smoothScrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @return Helper + */ + @Override + public QuickHelper smoothScrollToBottom() { + ViewHelper.get().smoothScrollToBottom(targetView()); + return this; + } + + /** + * 滑动到底部 ( 无滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 scrollBy 搭配到底部 )
+     *     scrollToBottom(view)
+     *     scrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @return Helper + */ + @Override + public QuickHelper scrollToBottom() { + ViewHelper.get().scrollToBottom(targetView()); + return this; + } + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + @Override + public QuickHelper smoothScrollTo( + int x, + int y + ) { + ViewHelper.get().smoothScrollTo(x, y, targetView()); + return this; + } + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + @Override + public QuickHelper smoothScrollBy( + int x, + int y + ) { + ViewHelper.get().smoothScrollBy(x, y, targetView()); + return this; + } + + /** + * 滚动方向 ( 有滚动过程 ) + * @param direction 滚动方向 如: View.FOCUS_UP、View.FOCUS_DOWN + * @return Helper + */ + @Override + public QuickHelper fullScroll(int direction) { + ViewHelper.get().fullScroll(direction, targetView()); + return this; + } + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + @Override + public QuickHelper scrollTo( + int x, + int y + ) { + ViewHelper.get().scrollTo(x, y, targetView()); + return this; + } + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @return Helper + */ + @Override + public QuickHelper scrollBy( + int x, + int y + ) { + ViewHelper.get().scrollBy(x, y, targetView()); + return this; + } + + // ================== + // = ImageViewUtils = + // ================== + + /** + * 设置 ImageView 是否保持宽高比 + * @param adjustViewBounds 是否调整此视图的边界以保持可绘制的原始纵横比 + * @return Helper + */ + @Override + public QuickHelper setAdjustViewBounds(boolean adjustViewBounds) { + ViewHelper.get().setAdjustViewBounds(adjustViewBounds, targetImageView()); + return this; + } + + /** + * 设置 ImageView 最大高度 + * @param maxHeight 最大高度 + * @return Helper + */ + @Override + public QuickHelper setMaxHeight(int maxHeight) { + ViewHelper.get().setMaxHeight(maxHeight, targetImageView()); + return this; + } + + /** + * 设置 ImageView 最大宽度 + * @param maxWidth 最大宽度 + * @return Helper + */ + @Override + public QuickHelper setMaxWidth(int maxWidth) { + ViewHelper.get().setMaxWidth(maxWidth, targetImageView()); + return this; + } + + /** + * 设置 ImageView Level + * @param level level Image + * @return Helper + */ + @Override + public QuickHelper setImageLevel(int level) { + ViewHelper.get().setImageLevel(level, targetView()); + return this; + } + + /** + * 设置 ImageView Bitmap + * @param bitmap {@link Bitmap} + * @return Helper + */ + @Override + public QuickHelper setImageBitmap(Bitmap bitmap) { + ViewHelper.get().setImageBitmap(bitmap, targetView()); + return this; + } + + /** + * 设置 ImageView Drawable + * @param drawable {@link Bitmap} + * @return Helper + */ + @Override + public QuickHelper setImageDrawable(Drawable drawable) { + ViewHelper.get().setImageDrawable(drawable, targetView()); + return this; + } + + /** + * 设置 ImageView 资源 + * @param resId resource identifier + * @return Helper + */ + @Override + public QuickHelper setImageResource(@DrawableRes int resId) { + ViewHelper.get().setImageResource(resId, targetView()); + return this; + } + + /** + * 设置 ImageView Matrix + * @param matrix {@link Matrix} + * @return Helper + */ + @Override + public QuickHelper setImageMatrix(Matrix matrix) { + ViewHelper.get().setImageMatrix(matrix, targetView()); + return this; + } + + /** + * 设置 ImageView 着色颜色 + * @param tint 着色颜色 + * @return Helper + */ + @Override + public QuickHelper setImageTintList(ColorStateList tint) { + ViewHelper.get().setImageTintList(tint, targetView()); + return this; + } + + /** + * 设置 ImageView 着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @return Helper + */ + @Override + public QuickHelper setImageTintMode(PorterDuff.Mode tintMode) { + ViewHelper.get().setImageTintMode(tintMode, targetView()); + return this; + } + + /** + * 设置 ImageView 缩放类型 + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @return Helper + */ + @Override + public QuickHelper setScaleType(ImageView.ScaleType scaleType) { + ViewHelper.get().setScaleType(scaleType, targetView()); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @return Helper + */ + @Override + public QuickHelper setBackgroundResources(@DrawableRes int resId) { + ViewHelper.get().setBackgroundResources(resId, targetView()); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setBackgroundResources( + @DrawableRes int resId, + int isVisibility + ) { + ViewHelper.get().setBackgroundResources(resId, isVisibility, targetView()); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @return Helper + */ + @Override + public QuickHelper setImageResources(@DrawableRes int resId) { + ViewHelper.get().setImageResources(resId, targetView()); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setImageResources( + @DrawableRes int resId, + int isVisibility + ) { + ViewHelper.get().setImageResources(resId, isVisibility, targetView()); + return this; + } + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @return Helper + */ + @Override + public QuickHelper setImageBitmaps(Bitmap bitmap) { + ViewHelper.get().setImageBitmaps(bitmap, targetView()); + return this; + } + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setImageBitmaps( + Bitmap bitmap, + int isVisibility + ) { + ViewHelper.get().setImageBitmaps(bitmap, isVisibility, targetView()); + return this; + } + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @return Helper + */ + @Override + public QuickHelper setImageDrawables(Drawable drawable) { + ViewHelper.get().setImageDrawables(drawable, targetView()); + return this; + } + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setImageDrawables( + Drawable drawable, + int isVisibility + ) { + ViewHelper.get().setImageDrawables(drawable, isVisibility, targetView()); + return this; + } + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @return Helper + */ + @Override + public QuickHelper setScaleTypes(ImageView.ScaleType scaleType) { + ViewHelper.get().setScaleTypes(scaleType, targetView()); + return this; + } + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @return Helper + */ + @Override + public QuickHelper setScaleTypes( + ImageView.ScaleType scaleType, + int isVisibility + ) { + ViewHelper.get().setScaleTypes(scaleType, isVisibility, targetView()); + return this; + } + + // =============================== + // = EditTextUtils、TextViewUtils = + // =============================== + + /** + * 设置文本 + * @param text TextView text + * @return Helper + */ + @Override + public QuickHelper setText(CharSequence text) { + ViewHelper.get().setText(text, targetView()); + return this; + } + + /** + * 设置长度限制 + * @param maxLength 长度限制 + * @return Helper + */ + @Override + public QuickHelper setMaxLength(int maxLength) { + ViewHelper.get().setMaxLength(maxLength, targetView()); + return this; + } + + /** + * 设置长度限制, 并且设置内容 + * @param content 文本内容 + * @param maxLength 长度限制 + * @return Helper + */ + @Override + public QuickHelper setMaxLengthAndText( + CharSequence content, + int maxLength + ) { + ViewHelper.get().setMaxLengthAndText(content, maxLength, targetView()); + return this; + } + + /** + * 设置输入类型 + * @param type 类型 + * @return Helper + */ + @Override + public QuickHelper setInputType(int type) { + ViewHelper.get().setInputType(type, targetView()); + return this; + } + + /** + * 设置软键盘右下角按钮类型 + * @param imeOptions 软键盘按钮类型 + * @return Helper + */ + @Override + public QuickHelper setImeOptions(int imeOptions) { + ViewHelper.get().setImeOptions(imeOptions, targetView()); + return this; + } + + /** + * 设置文本视图显示转换 + * @param method {@link TransformationMethod} + * @return Helper + */ + @Override + public QuickHelper setTransformationMethod(TransformationMethod method) { + ViewHelper.get().setTransformationMethod(method, targetView()); + return this; + } + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @return Helper + */ + @Override + public QuickHelper setTransformationMethod(boolean isDisplayPassword) { + ViewHelper.get().setTransformationMethod(isDisplayPassword, targetView()); + return this; + } + + // ================= + // = EditTextUtils = + // ================= + + /** + * 设置内容 + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @return Helper + */ + @Override + public QuickHelper setText( + CharSequence content, + boolean isSelect + ) { + ViewHelper.get().setText(content, isSelect, targetEditText()); + return this; + } + + /** + * 追加内容 ( 当前光标位置追加 ) + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @return Helper + */ + @Override + public QuickHelper insert( + CharSequence content, + boolean isSelect + ) { + ViewHelper.get().insert(content, isSelect, targetEditText()); + return this; + } + + /** + * 追加内容 + * @param content 文本内容 + * @param start 开始添加的位置 + * @param isSelect 是否设置光标 + * @return Helper + */ + @Override + public QuickHelper insert( + CharSequence content, + int start, + boolean isSelect + ) { + ViewHelper.get().insert(content, start, isSelect, targetEditText()); + return this; + } + + /** + * 设置是否显示光标 + * @param visible 是否显示光标 + * @return Helper + */ + @Override + public QuickHelper setCursorVisible(boolean visible) { + ViewHelper.get().setCursorVisible(visible, targetEditText()); + return this; + } + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @return Helper + */ + @Override + public QuickHelper setTextCursorDrawable(@DrawableRes int textCursorDrawable) { + ViewHelper.get().setTextCursorDrawable(textCursorDrawable, targetEditText()); + return this; + } + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @return Helper + */ + @Override + public QuickHelper setTextCursorDrawable(Drawable textCursorDrawable) { + ViewHelper.get().setTextCursorDrawable(textCursorDrawable, targetEditText()); + return this; + } + + /** + * 设置光标在第一位 + * @return Helper + */ + @Override + public QuickHelper setSelectionToTop() { + ViewHelper.get().setSelectionToTop(targetEditText()); + return this; + } + + /** + * 设置光标在最后一位 + * @return Helper + */ + @Override + public QuickHelper setSelectionToBottom() { + ViewHelper.get().setSelectionToBottom(targetEditText()); + return this; + } + + /** + * 设置光标位置 + * @param index 光标位置 + * @return Helper + */ + @Override + public QuickHelper setSelection(int index) { + ViewHelper.get().setSelection(index, targetEditText()); + return this; + } + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param isSelectBottom 是否设置光标到最后 + * @return Helper + */ + @Override + public QuickHelper setTransformationMethod( + boolean isDisplayPassword, + boolean isSelectBottom + ) { + ViewHelper.get().setTransformationMethod(isDisplayPassword, isSelectBottom, targetEditText()); + return this; + } + + /** + * 添加输入监听事件 + * @param watcher 输入监听 + * @return Helper + */ + @Override + public QuickHelper addTextChangedListener(TextWatcher watcher) { + ViewHelper.get().addTextChangedListener(watcher, targetEditText()); + return this; + } + + /** + * 移除输入监听事件 + * @param watcher 输入监听 + * @return Helper + */ + @Override + public QuickHelper removeTextChangedListener(TextWatcher watcher) { + ViewHelper.get().removeTextChangedListener(watcher, targetEditText()); + return this; + } + + /** + * 设置 KeyListener + * @param listener {@link KeyListener} + * @return Helper + */ + @Override + public QuickHelper setKeyListener(KeyListener listener) { + ViewHelper.get().setKeyListener(listener, targetEditText()); + return this; + } + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容, 如: 0123456789 + * @return Helper + */ + @Override + public QuickHelper setKeyListener(String accepted) { + ViewHelper.get().setKeyListener(accepted, targetEditText()); + return this; + } + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容 + * @return Helper + */ + @Override + public QuickHelper setKeyListener(char[] accepted) { + ViewHelper.get().setKeyListener(accepted, targetEditText()); + return this; + } + + // ================= + // = TextViewUtils = + // ================= + + /** + * 设置 Hint 文本 + * @param text Hint text + * @return Helper + */ + @Override + public QuickHelper setHint(CharSequence text) { + ViewHelper.get().setHint(text, targetView()); + return this; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @return Helper + */ + @Override + public QuickHelper setHintTextColors(@ColorInt int color) { + ViewHelper.get().setHintTextColors(color, targetView()); + return this; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @return Helper + */ + @Override + public QuickHelper setHintTextColors(ColorStateList colors) { + ViewHelper.get().setHintTextColors(colors, targetView()); + return this; + } + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @return Helper + */ + @Override + public QuickHelper setTextColors(@ColorInt int color) { + ViewHelper.get().setTextColors(color, targetView()); + return this; + } + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @return Helper + */ + @Override + public QuickHelper setTextColors(ColorStateList colors) { + ViewHelper.get().setTextColors(colors, targetView()); + return this; + } + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @return Helper + */ + @Override + public QuickHelper setHtmlTexts(String content) { + ViewHelper.get().setHtmlTexts(content, targetView()); + return this; + } + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @return Helper + */ + @Override + public QuickHelper setTypeface(Typeface typeface) { + ViewHelper.get().setTypeface(typeface, targetView()); + return this; + } + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @return Helper + */ + @Override + public QuickHelper setTypeface( + Typeface typeface, + int style + ) { + ViewHelper.get().setTypeface(typeface, style, targetView()); + return this; + } + + /** + * 设置字体大小 ( px 像素 ) + * @param size 字体大小 + * @return Helper + */ + @Override + public QuickHelper setTextSizeByPx(float size) { + ViewHelper.get().setTextSizeByPx(size, targetView()); + return this; + } + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param size 字体大小 + * @return Helper + */ + @Override + public QuickHelper setTextSizeBySp(float size) { + ViewHelper.get().setTextSizeBySp(size, targetView()); + return this; + } + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param size 字体大小 + * @return Helper + */ + @Override + public QuickHelper setTextSizeByDp(float size) { + ViewHelper.get().setTextSizeByDp(size, targetView()); + return this; + } + + /** + * 设置字体大小 ( inches 英寸 ) + * @param size 字体大小 + * @return Helper + */ + @Override + public QuickHelper setTextSizeByIn(float size) { + ViewHelper.get().setTextSizeByIn(size, targetView()); + return this; + } + + /** + * 设置字体大小 + * @param unit 字体参数类型 + * @param size 字体大小 + * @return Helper + */ + @Override + public QuickHelper setTextSize( + int unit, + float size + ) { + ViewHelper.get().setTextSize(unit, size, targetView()); + return this; + } + + /** + * 清空 flags + * @return Helper + */ + @Override + public QuickHelper clearFlags() { + ViewHelper.get().clearFlags(targetView()); + return this; + } + + /** + * 设置 TextView flags + * @param flags flags + * @return Helper + */ + @Override + public QuickHelper setPaintFlags(int flags) { + ViewHelper.get().setPaintFlags(flags, targetView()); + return this; + } + + /** + * 设置 TextView 抗锯齿 flags + * @return Helper + */ + @Override + public QuickHelper setAntiAliasFlag() { + ViewHelper.get().setAntiAliasFlag(targetView()); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @return Helper + */ + @Override + public QuickHelper setBold() { + ViewHelper.get().setBold(targetView()); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @param isBold {@code true} yes, {@code false} no + * @return Helper + */ + @Override + public QuickHelper setBold(boolean isBold) { + ViewHelper.get().setBold(isBold, targetView()); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @return Helper + */ + @Override + public QuickHelper setBold( + Typeface typeface, + boolean isBold + ) { + ViewHelper.get().setBold(typeface, isBold, targetView()); + return this; + } + + /** + * 设置下划线 + * @return Helper + */ + @Override + public QuickHelper setUnderlineText() { + ViewHelper.get().setUnderlineText(targetView()); + return this; + } + + /** + * 设置下划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @return Helper + */ + @Override + public QuickHelper setUnderlineText(boolean isAntiAlias) { + ViewHelper.get().setUnderlineText(isAntiAlias, targetView()); + return this; + } + + /** + * 设置中划线 + * @return Helper + */ + @Override + public QuickHelper setStrikeThruText() { + ViewHelper.get().setStrikeThruText(targetView()); + return this; + } + + /** + * 设置中划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @return Helper + */ + @Override + public QuickHelper setStrikeThruText(boolean isAntiAlias) { + ViewHelper.get().setStrikeThruText(isAntiAlias, targetView()); + return this; + } + + /** + * 设置文字水平间距 + * @param letterSpacing 文字水平间距 + * @return Helper + */ + @Override + public QuickHelper setLetterSpacing(float letterSpacing) { + ViewHelper.get().setLetterSpacing(letterSpacing, targetView()); + return this; + } + + /** + * 设置文字行间距 ( 行高 ) + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @return Helper + */ + @Override + public QuickHelper setLineSpacing(float lineSpacing) { + ViewHelper.get().setLineSpacing(lineSpacing, targetView()); + return this; + } + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @return Helper + */ + @Override + public QuickHelper setLineSpacingAndMultiplier( + float lineSpacing, + float multiplier + ) { + ViewHelper.get().setLineSpacingAndMultiplier(lineSpacing, multiplier, targetView()); + return this; + } + + /** + * 设置字体水平方向的缩放 + * @param size 缩放比例 + * @return Helper + */ + @Override + public QuickHelper setTextScaleX(float size) { + ViewHelper.get().setTextScaleX(size, targetView()); + return this; + } + + /** + * 设置是否保留字体留白间隙区域 + * @param includePadding 是否保留字体留白间隙区域 + * @return Helper + */ + @Override + public QuickHelper setIncludeFontPadding(boolean includePadding) { + ViewHelper.get().setIncludeFontPadding(includePadding, targetView()); + return this; + } + + /** + * 设置行数 + * @param lines 行数 + * @return Helper + */ + @Override + public QuickHelper setLines(int lines) { + ViewHelper.get().setLines(lines, targetView()); + return this; + } + + /** + * 设置最大行数 + * @param maxLines 最大行数 + * @return Helper + */ + @Override + public QuickHelper setMaxLines(int maxLines) { + ViewHelper.get().setMaxLines(maxLines, targetView()); + return this; + } + + /** + * 设置最小行数 + * @param minLines 最小行数 + * @return Helper + */ + @Override + public QuickHelper setMinLines(int minLines) { + ViewHelper.get().setMinLines(minLines, targetView()); + return this; + } + + /** + * 设置最大字符宽度限制 + * @param maxEms 最大字符 + * @return Helper + */ + @Override + public QuickHelper setMaxEms(int maxEms) { + ViewHelper.get().setMaxEms(maxEms, targetView()); + return this; + } + + /** + * 设置最小字符宽度限制 + * @param minEms 最小字符 + * @return Helper + */ + @Override + public QuickHelper setMinEms(int minEms) { + ViewHelper.get().setMinEms(minEms, targetView()); + return this; + } + + /** + * 设置指定字符宽度 + * @param ems 字符 + * @return Helper + */ + @Override + public QuickHelper setEms(int ems) { + ViewHelper.get().setEms(ems, targetView()); + return this; + } + + /** + * 设置 Ellipsize 效果 + * @param where {@link TextUtils.TruncateAt} + * @return Helper + */ + @Override + public QuickHelper setEllipsize(TextUtils.TruncateAt where) { + ViewHelper.get().setEllipsize(where, targetView()); + return this; + } + + /** + * 设置自动识别文本链接 + * @param mask {@link android.text.util.Linkify} + * @return Helper + */ + @Override + public QuickHelper setAutoLinkMask(int mask) { + ViewHelper.get().setAutoLinkMask(mask, targetView()); + return this; + } + + /** + * 设置文本全为大写 + * @param allCaps 是否全部大写 + * @return Helper + */ + @Override + public QuickHelper setAllCaps(boolean allCaps) { + ViewHelper.get().setAllCaps(allCaps, targetView()); + return this; + } + + /** + * 设置 Gravity + * @param gravity {@link android.view.Gravity} + * @return Helper + */ + @Override + public QuickHelper setGravity(int gravity) { + ViewHelper.get().setGravity(gravity, targetView()); + return this; + } + + /** + * 设置 CompoundDrawables Padding + * @param padding CompoundDrawables Padding + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablePadding(int padding) { + ViewHelper.get().setCompoundDrawablePadding(padding, targetTextView()); + return this; + } + + /** + * 设置 Left CompoundDrawables + * @param left left Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesByLeft(Drawable left) { + ViewHelper.get().setCompoundDrawablesByLeft(left, targetTextView()); + return this; + } + + /** + * 设置 Top CompoundDrawables + * @param top top Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesByTop(Drawable top) { + ViewHelper.get().setCompoundDrawablesByTop(top, targetTextView()); + return this; + } + + /** + * 设置 Right CompoundDrawables + * @param right right Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesByRight(Drawable right) { + ViewHelper.get().setCompoundDrawablesByRight(right, targetTextView()); + return this; + } + + /** + * 设置 Bottom CompoundDrawables + * @param bottom bottom Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesByBottom(Drawable bottom) { + ViewHelper.get().setCompoundDrawablesByBottom(bottom, targetTextView()); + return this; + } + + /** + * 设置 CompoundDrawables + *
+     *     CompoundDrawable 的大小控制是通过 drawable.setBounds() 控制
+     *     需要先设置 Drawable 的 setBounds
+     *     {@link dev.utils.app.image.ImageUtils#setBounds}
+     * 
+ * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawables( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom + ) { + ViewHelper.get().setCompoundDrawables(left, top, right, bottom, targetTextView()); + return this; + } + + /** + * 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesWithIntrinsicBoundsByLeft(Drawable left) { + ViewHelper.get().setCompoundDrawablesWithIntrinsicBoundsByLeft(left, targetTextView()); + return this; + } + + /** + * 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param top top Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesWithIntrinsicBoundsByTop(Drawable top) { + ViewHelper.get().setCompoundDrawablesWithIntrinsicBoundsByTop(top, targetTextView()); + return this; + } + + /** + * 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param right right Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesWithIntrinsicBoundsByRight(Drawable right) { + ViewHelper.get().setCompoundDrawablesWithIntrinsicBoundsByRight(right, targetTextView()); + return this; + } + + /** + * 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param bottom bottom Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesWithIntrinsicBoundsByBottom(Drawable bottom) { + ViewHelper.get().setCompoundDrawablesWithIntrinsicBoundsByBottom(bottom, targetTextView()); + return this; + } + + /** + * 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @return Helper + */ + @Override + public QuickHelper setCompoundDrawablesWithIntrinsicBounds( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom + ) { + ViewHelper.get().setCompoundDrawablesWithIntrinsicBounds( + left, top, right, bottom, targetTextView() + ); + return this; + } + + /** + * 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 + * @param autoSizeTextType 自动调整大小类型 + * @return Helper + */ + @Override + public QuickHelper setAutoSizeTextTypeWithDefaults( + @TextViewCompat.AutoSizeTextType int autoSizeTextType + ) { + ViewHelper.get().setAutoSizeTextTypeWithDefaults( + autoSizeTextType, targetView() + ); + return this; + } + + /** + * 设置 TextView 自动调整字体大小配置 + * @param autoSizeMinTextSize 自动调整最小字体大小 + * @param autoSizeMaxTextSize 自动调整最大字体大小 + * @param autoSizeStepGranularity 自动调整大小变动粒度 ( 跨度区间值 ) + * @param unit 字体参数类型 + * @return Helper + */ + @Override + public QuickHelper setAutoSizeTextTypeUniformWithConfiguration( + int autoSizeMinTextSize, + int autoSizeMaxTextSize, + int autoSizeStepGranularity, + int unit + ) { + ViewHelper.get().setAutoSizeTextTypeUniformWithConfiguration( + autoSizeMinTextSize, autoSizeMaxTextSize, + autoSizeStepGranularity, unit, targetView() + ); + return this; + } + + /** + * 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM + * @param presetSizes 预设字体大小范围像素为单位 + * @param unit 字体参数类型 + * @return Helper + */ + @Override + public QuickHelper setAutoSizeTextTypeUniformWithPresetSizes( + int[] presetSizes, + int unit + ) { + ViewHelper.get().setAutoSizeTextTypeUniformWithPresetSizes( + presetSizes, unit, targetView() + ); + return this; + } + + // ===================== + // = RecyclerViewUtils = + // ===================== + + /** + * 设置 RecyclerView LayoutManager + * @param layoutManager LayoutManager + * @return Helper + */ + @Override + public QuickHelper setLayoutManager(RecyclerView.LayoutManager layoutManager) { + ViewHelper.get().setLayoutManager(targetView(), layoutManager); + return this; + } + + /** + * 设置 GridLayoutManager SpanCount + * @param spanCount Span Count + * @return Helper + */ + @Override + public QuickHelper setSpanCount(int spanCount) { + ViewHelper.get().setSpanCount(targetView(), spanCount); + return this; + } + + /** + * 设置 RecyclerView Orientation + * @param orientation 方向 + * @return Helper + */ + @Override + public QuickHelper setOrientation(@RecyclerView.Orientation int orientation) { + ViewHelper.get().setOrientation(targetView(), orientation); + return this; + } + + /** + * 设置 RecyclerView Adapter + * @param adapter Adapter + * @return Helper + */ + @Override + public QuickHelper setAdapter(RecyclerView.Adapter adapter) { + ViewHelper.get().setAdapter(targetView(), adapter); + return this; + } + + /** + * RecyclerView notifyItemRemoved + * @param position 索引 + * @return Helper + */ + @Override + public QuickHelper notifyItemRemoved(int position) { + ViewHelper.get().notifyItemRemoved(targetView(), position); + return this; + } + + /** + * RecyclerView notifyItemInserted + * @param position 索引 + * @return Helper + */ + @Override + public QuickHelper notifyItemInserted(int position) { + ViewHelper.get().notifyItemInserted(targetView(), position); + return this; + } + + /** + * RecyclerView notifyItemMoved + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return Helper + */ + @Override + public QuickHelper notifyItemMoved( + int fromPosition, + int toPosition + ) { + ViewHelper.get().notifyItemMoved(targetView(), fromPosition, toPosition); + return this; + } + + /** + * RecyclerView notifyDataSetChanged + * @return Helper + */ + @Override + public QuickHelper notifyDataSetChanged() { + ViewHelper.get().notifyDataSetChanged(targetView()); + return this; + } + + /** + * 设置 RecyclerView LinearSnapHelper + * @return Helper + */ + @Override + public QuickHelper attachLinearSnapHelper() { + ViewHelper.get().attachLinearSnapHelper(targetView()); + return this; + } + + /** + * 设置 RecyclerView PagerSnapHelper + * @return Helper + */ + @Override + public QuickHelper attachPagerSnapHelper() { + ViewHelper.get().attachPagerSnapHelper(targetView()); + return this; + } + + /** + * 添加 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + @Override + public QuickHelper addItemDecoration(RecyclerView.ItemDecoration decor) { + ViewHelper.get().addItemDecoration(targetView(), decor); + return this; + } + + /** + * 添加 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return Helper + */ + @Override + public QuickHelper addItemDecoration( + RecyclerView.ItemDecoration decor, + int index + ) { + ViewHelper.get().addItemDecoration(targetView(), decor, index); + return this; + } + + /** + * 移除 RecyclerView ItemDecoration + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + @Override + public QuickHelper removeItemDecoration(RecyclerView.ItemDecoration decor) { + ViewHelper.get().removeItemDecoration(targetView(), decor); + return this; + } + + /** + * 移除 RecyclerView ItemDecoration + * @param index RecyclerView ItemDecoration 索引 + * @return Helper + */ + @Override + public QuickHelper removeItemDecorationAt(int index) { + ViewHelper.get().removeItemDecorationAt(targetView(), index); + return this; + } + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @return Helper + */ + @Override + public QuickHelper removeAllItemDecoration() { + ViewHelper.get().removeAllItemDecoration(targetView()); + return this; + } + + /** + * 设置 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + @Override + public QuickHelper setOnScrollListener(RecyclerView.OnScrollListener listener) { + ViewHelper.get().setOnScrollListener(targetView(), listener); + return this; + } + + /** + * 添加 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + @Override + public QuickHelper addOnScrollListener(RecyclerView.OnScrollListener listener) { + ViewHelper.get().addOnScrollListener(targetView(), listener); + return this; + } + + /** + * 移除 RecyclerView ScrollListener + * @param listener ScrollListener + * @return Helper + */ + @Override + public QuickHelper removeOnScrollListener(RecyclerView.OnScrollListener listener) { + ViewHelper.get().removeOnScrollListener(targetView(), listener); + return this; + } + + /** + * 清空 RecyclerView ScrollListener + * @return Helper + */ + @Override + public QuickHelper clearOnScrollListeners() { + ViewHelper.get().clearOnScrollListeners(targetView()); + return this; + } + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param enabled 嵌套滚动开关 + * @return Helper + */ + @Override + public QuickHelper setNestedScrollingEnabled(boolean enabled) { + ViewHelper.get().setNestedScrollingEnabled(enabled, targetView()); + return this; + } + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + @Override + public QuickHelper forceGetViewSize(SizeUtils.OnGetSizeListener listener) { + ViewHelper.get().forceGetViewSize(targetView(), listener); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/version/IHelperByVersion.java b/lib/DevApp/src/main/java/dev/utils/app/helper/version/IHelperByVersion.java new file mode 100644 index 0000000000..d4afffd58f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/version/IHelperByVersion.java @@ -0,0 +1,959 @@ +package dev.utils.app.helper.version; + +import android.app.Activity; +import android.app.PendingIntent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Environment; + +import androidx.annotation.IntRange; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; + +import dev.utils.app.AppUtils; +import dev.utils.app.ContentResolverUtils; +import dev.utils.app.IntentUtils; +import dev.utils.app.PathUtils; +import dev.utils.app.UriUtils; +import dev.utils.common.FileUtils; + +/** + * detail: VersionHelper 接口 + * @author Ttt + */ +public interface IHelperByVersion { + + // ============ + // = UriUtils = + // ============ + + // ================ + // = FileProvider = + // ================ + + /** + * 获取 FileProvider File Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + Uri getUriForFile(File file); + + /** + * 获取 FileProvider File Path Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + Uri getUriForPath(String filePath); + + /** + * 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) + * @param file 文件 + * @param fileProvider android:authorities = ${applicationId}.fileProvider + * @return 指定文件 {@link Uri} + */ + Uri getUriForFileToName( + File file, + String fileProvider + ); + + /** + * 获取 FileProvider File Path Uri + * @param file 文件 + * @param authority android:authorities + * @return 指定文件 {@link Uri} + */ + Uri getUriForFile( + File file, + String authority + ); + + /** + * 通过 String 获取 Uri + * @param uriString uri 路径 + * @return {@link Uri} + */ + Uri getUriForString(String uriString); + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return {@link Uri} + */ + Uri fromFile(String filePath); + + /** + * 通过 File 创建 Uri + *
+     *     File 的文件夹需要存在才能够对文件进行写入
+     *     可以传入前调用 {@link FileUtils#createFolderByPath(File)} 进行创建文件夹
+     * 
+ * @param file 文件 + * @return {@link Uri} + */ + Uri fromFile(File file); + + // ======= + // = Uri = + // ======= + + /** + * 判断是否 Uri + * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + boolean isUri(String uriString); + + /** + * 判断是否 Uri + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + boolean isUri(Uri uri); + + // = + + /** + * 获取 Uri Scheme + * @param uriString uri 路径 + * @return Uri Scheme + */ + String getUriScheme(String uriString); + + /** + * 获取 Uri Scheme + * @param uri {@link Uri} + * @return Uri Scheme + */ + String getUriScheme(Uri uri); + + /** + * 判断 Uri 路径资源是否存在 + *
+     *     uri 非 FilePath, 可通过 {@link UriUtils#getMediaUri} 获取
+     * 
+ * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + boolean isUriExists(String uriString); + + /** + * 判断 Uri 路径资源是否存在 + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + boolean isUriExists(Uri uri); + + // ========== + // = 复制文件 = + // ========== + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @return 复制后的文件路径 + */ + String copyByUri(Uri uri); + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + String copyByUri( + Uri uri, + String fileName + ); + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param file 文件 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + String copyByUri( + Uri uri, + File file, + String fileName + ); + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param filePath 文件路径 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + String copyByUri( + Uri uri, + String filePath, + String fileName + ); + + // ============= + // = 获取文件路径 = + // ============= + + /** + * 通过 Uri 获取文件路径 + * @param uri {@link Uri} + * @return 文件路径 + */ + String getFilePathByUri(Uri uri); + + /** + * 通过 Uri 获取文件路径 + *
+     *     默认不复制文件, 防止影响已经使用该方法的功能 ( 文件过大导致 ANR、耗时操作等 )
+     * 
+ * @param uri {@link Uri} + * @param isQCopy Android 10 ( Q ) 及其以上版本是否复制文件 + * @return 文件路径 + */ + String getFilePathByUri( + Uri uri, + boolean isQCopy + ); + + // ======================== + // = ContentResolverUtils = + // ======================== + + /** + * 通过 File 获取 Media Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + Uri getMediaUri(File file); + + /** + * 通过 File 获取 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + Uri getMediaUri( + Uri uri, + File file + ); + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + Uri getMediaUri(String filePath); + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + Uri getMediaUri( + Uri uri, + String filePath + ); + + // = + + /** + * 通过 File 获取 Media 信息 + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + String[] mediaQuery( + File file, + ContentResolverUtils.MediaQuery mediaQuery + ); + + /** + * 通过 File 获取 Media 信息 + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + String[] mediaQuery( + Uri uri, + File file, + ContentResolverUtils.MediaQuery mediaQuery + ); + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + String[] mediaQuery( + String filePath, + ContentResolverUtils.MediaQuery mediaQuery + ); + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + String[] mediaQuery( + Uri uri, + String filePath, + ContentResolverUtils.MediaQuery mediaQuery + ); + + // =================== + // = MediaStoreUtils = + // =================== + + // ========== + // = 通知相册 = + // ========== + + /** + * 通知刷新本地资源 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + boolean notifyMediaStore(String filePath); + + /** + * 通知刷新本地资源 + *
+     *     注意事项: 部分手机 ( 如小米 ) 通知文件地址层级过深, 将会并入相册文件夹中
+     *     尽量放在 SDCrad/XXX/xx.jpg 层级中, 推荐直接存储到 {@link PathUtils#getSDCard().getDCIMPath()}
+     *     该方法只能通知刷新 外部存储 ( 公开目录 ) SDCard 文件地址
+     *     MediaStore Cursor 无法扫描 内部存储、外部存储 ( 私有目录 )
+     *     

+ * 正确的操作应该是: 不论版本统一存储到 外部存储 ( 私有目录 ) 再通过 MediaStore 插入数据 + *
+ * @param file 文件 + * @return {@code true} success, {@code false} fail + * @deprecated Android 10 ( Q ) 以后的版本需要通过 MediaStore 插入数据 + */ + @Deprecated + boolean notifyMediaStore(File file); + + /** + * 通知刷新本地资源 + * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + boolean notifyMediaStore(Uri uri); + + // ======= + // = 图片 = + // ======= + + /** + * 创建图片 Uri + * @return 图片 Uri + */ + Uri createImageUri(); + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @return 图片 Uri + */ + Uri createImageUri(String mimeType); + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + Uri createImageUri( + String mimeType, + String relativePath + ); + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + Uri createImageUri( + String displayName, + String mimeType, + String relativePath + ); + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 图片 Uri + */ + Uri createImageUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ); + + // ======= + // = 视频 = + // ======= + + /** + * 创建视频 Uri + * @return 视频 Uri + */ + Uri createVideoUri(); + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @return 视频 Uri + */ + Uri createVideoUri(String mimeType); + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + Uri createVideoUri( + String mimeType, + String relativePath + ); + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + Uri createVideoUri( + String displayName, + String mimeType, + String relativePath + ); + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 视频 Uri + */ + Uri createVideoUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ); + + // ======= + // = 音频 = + // ======= + + /** + * 创建音频 Uri + * @return 音频 Uri + */ + Uri createAudioUri(); + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @return 音频 Uri + */ + Uri createAudioUri(String mimeType); + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + Uri createAudioUri( + String mimeType, + String relativePath + ); + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + Uri createAudioUri( + String displayName, + String mimeType, + String relativePath + ); + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 音频 Uri + */ + Uri createAudioUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ); + + // ============ + // = Download = + // ============ + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 需后缀 ) + * @return Download Uri + */ + Uri createDownloadUri(String displayName); + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @return Download Uri + */ + Uri createDownloadUri( + String displayName, + String mimeType + ); + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Download Uri + */ + Uri createDownloadUri( + String displayName, + String mimeType, + String relativePath + ); + + /** + * 创建 Download Uri + *
+     *     Android 10 ( Q ) 以下直接通过 File 写入到 {@link Environment#DIRECTORY_DOWNLOADS}
+     * 
+ * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Download Uri + */ + Uri createDownloadUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ); + + // ========== + // = 通用创建 = + // ========== + + /** + * 创建预存储 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Media Uri + */ + Uri createMediaUri( + Uri uri, + String displayName, + String mimeType, + String relativePath + ); + + /** + * 创建预存储 Media Uri + *
+     *     也可通过 {@link IntentUtils#getCreateDocumentIntent(String, String)} 创建
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Media Uri + */ + Uri createMediaUri( + Uri uri, + String displayName, + String mimeType, + String relativePath, + long createTime + ); + + // ======== + // = File = + // ======== + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @return File Uri + */ + Uri createUriByPath(String fileName); + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @param filePath 文件路径 + * @return File Uri + */ + Uri createUriByPath( + String fileName, + String filePath + ); + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return File Uri + */ + Uri createUriByFile(String filePath); + + /** + * 通过 File 创建 Uri + *
+     *     主要用于如低版本写入 Download 文件夹, 创建 Uri
+     *     统一使用 {@link #insertMedia(Uri, Uri)} 写入文件夹
+     *     高版本使用 {@link #createDownloadUri(String)}
+     *     并不局限 Download 文件夹操作
+     * 
+ * @param file 文件 + * @return File Uri + */ + Uri createUriByFile(File file); + + // ========== + // = 插入数据 = + // ========== + + /** + * 插入一张图片 + *
+     *     Android 10 ( Q ) 已抛弃仍可用, 推荐使用传入 Uri 方式 {@link #createImageUri}
+     * 
+ * @param filePath 文件路径 + * @param name 存储文件名 + * @param notify 是否通知相册 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + Uri insertImage( + String filePath, + String name, + boolean notify + ); + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + boolean insertImage( + Uri uri, + Uri inputUri, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ); + + // = + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + boolean insertImage( + Uri uri, + Uri inputUri + ); + + /** + * 插入一条视频 + * @param uri {@link #createVideoUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + boolean insertVideo( + Uri uri, + Uri inputUri + ); + + /** + * 插入一条音频 + * @param uri {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + boolean insertAudio( + Uri uri, + Uri inputUri + ); + + /** + * 插入一条文件资源 + * @param uri {@link #createDownloadUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + boolean insertDownload( + Uri uri, + Uri inputUri + ); + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Uri inputUri + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + InputStream inputStream + ); + + /** + * 插入一条多媒体资源 + * @param outputStream {@link OutputStream} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + OutputStream outputStream, + InputStream inputStream + ); + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param filePath 待存储文件路径 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + String filePath + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param file 待存储文件 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + File file + ); + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Drawable drawable + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Drawable drawable, + Bitmap.CompressFormat format + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Drawable drawable, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ); + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Bitmap bitmap + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Bitmap bitmap, + Bitmap.CompressFormat format + ); + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + boolean insertMedia( + Uri uri, + Bitmap bitmap, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ); + + /** + * 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 + *
+     *     以下四个方法搭配 startIntentSenderForResult() 使用
+     *     {@link AppUtils#startIntentSenderForResult(Activity, PendingIntent, int)}
+     *     

+ * startIntentSenderForResult(pendingIntent.getIntentSender(), EDIT_REQUEST_CODE, null, 0, 0, 0) + *
+ * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + PendingIntent createWriteRequest(Collection uris); + + /** + * 获取用户将设备上指定的媒体文件标记为收藏的请求 + *
+     *     对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为收藏
+     * 
+ * @param uris 待请求 Uri 集 + * @param favorite 是否喜欢 + * @return {@link PendingIntent} + */ + PendingIntent createFavoriteRequest( + Collection uris, + boolean favorite + ); + + /** + * 获取用户将指定的媒体文件放入设备垃圾箱的请求 + *
+     *     垃圾箱中的内容会在系统定义的时间段后被永久删除
+     * 
+ * @param uris 待请求 Uri 集 + * @param trashed 是否遗弃 + * @return {@link PendingIntent} + */ + PendingIntent createTrashRequest( + Collection uris, + boolean trashed + ); + + /** + * 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 + * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + PendingIntent createDeleteRequest(Collection uris); + + // ============= + // = PathUtils = + // ============= + + /** + * 是否获得 MANAGE_EXTERNAL_STORAGE 权限 + * @return {@code true} yes, {@code false} no + */ + boolean isExternalStorageManager(); + + /** + * 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 + * @return {@code true} yes, {@code false} no + */ + boolean checkExternalStorageAndIntentSetting(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/version/VersionHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/version/VersionHelper.java new file mode 100644 index 0000000000..a6241e5b0a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/version/VersionHelper.java @@ -0,0 +1,1265 @@ +package dev.utils.app.helper.version; + +import android.app.Activity; +import android.app.PendingIntent; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; + +import androidx.annotation.IntRange; +import androidx.annotation.RequiresApi; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collection; + +import dev.utils.app.AppUtils; +import dev.utils.app.ContentResolverUtils; +import dev.utils.app.IntentUtils; +import dev.utils.app.MediaStoreUtils; +import dev.utils.app.PathUtils; +import dev.utils.app.UriUtils; +import dev.utils.common.FileUtils; + +/** + * detail: Android 版本适配 Helper 类 + * @author Ttt + *
+ *     Android 版本适配 Helper 类, 方便快捷使用
+ *     并简化需多工具类组合使用的功能
+ *     关于路径建议及兼容建议查看 {@link PathUtils}
+ *     

+ * 推荐使用 DevEngine 库 MediaStore Engine 实现 ( DevMediaStoreEngineImpl ) 进行外部、内部文件存储统一处理 + * DevEngine README + * @see
+ *

+ * Android 10 ( Q ) 更新内容 + * @see
+ * Android 11 ( R ) 更新内容 + * @see + * Android 12 ( S ) 更新内容 + * @see + *

+ * Android 10 ( Q ) 适配指南 + * @see
+ * Android 11 ( R ) 适配指南 + * @see + *

+ * Android 版本适配全套指南 + * @see
+ *
+ */ +public final class VersionHelper + implements IHelperByVersion { + + private VersionHelper() { + } + + // VersionHelper + private static final VersionHelper HELPER = new VersionHelper(); + + /** + * 获取单例 VersionHelper + * @return {@link VersionHelper} + */ + public static VersionHelper get() { + return HELPER; + } + + // ==================== + // = IHelperByVersion = + // ==================== + + // ============ + // = UriUtils = + // ============ + + // ================ + // = FileProvider = + // ================ + + /** + * 获取 FileProvider File Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getUriForFile(File file) { + return UriUtils.getUriForFile(file); + } + + /** + * 获取 FileProvider File Path Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getUriForPath(String filePath) { + return UriUtils.getUriForPath(filePath); + } + + /** + * 获取 FileProvider File Path Uri ( 自动添加包名 ${applicationId} ) + * @param file 文件 + * @param fileProvider android:authorities = ${applicationId}.fileProvider + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getUriForFileToName( + File file, + String fileProvider + ) { + return UriUtils.getUriForFileToName(file, fileProvider); + } + + /** + * 获取 FileProvider File Path Uri + * @param file 文件 + * @param authority android:authorities + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getUriForFile( + File file, + String authority + ) { + return UriUtils.getUriForFile(file, authority); + } + + /** + * 通过 String 获取 Uri + * @param uriString uri 路径 + * @return {@link Uri} + */ + @Override + public Uri getUriForString(String uriString) { + return UriUtils.getUriForString(uriString); + } + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return {@link Uri} + */ + @Override + public Uri fromFile(String filePath) { + return UriUtils.fromFile(filePath); + } + + /** + * 通过 File 创建 Uri + *
+     *     File 的文件夹需要存在才能够对文件进行写入
+     *     可以传入前调用 {@link FileUtils#createFolderByPath(File)} 进行创建文件夹
+     * 
+ * @param file 文件 + * @return {@link Uri} + */ + @Override + public Uri fromFile(File file) { + return UriUtils.fromFile(file); + } + + // ======= + // = Uri = + // ======= + + /** + * 判断是否 Uri + * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isUri(String uriString) { + return UriUtils.isUri(uriString); + } + + /** + * 判断是否 Uri + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isUri(Uri uri) { + return UriUtils.isUri(uri); + } + + // = + + /** + * 获取 Uri Scheme + * @param uriString uri 路径 + * @return Uri Scheme + */ + @Override + public String getUriScheme(String uriString) { + return UriUtils.getUriScheme(uriString); + } + + /** + * 获取 Uri Scheme + * @param uri {@link Uri} + * @return Uri Scheme + */ + @Override + public String getUriScheme(Uri uri) { + return UriUtils.getUriScheme(uri); + } + + /** + * 判断 Uri 路径资源是否存在 + *
+     *     uri 非 FilePath, 可通过 {@link UriUtils#getMediaUri} 获取
+     * 
+ * @param uriString uri 路径 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isUriExists(String uriString) { + return UriUtils.isUriExists(uriString); + } + + /** + * 判断 Uri 路径资源是否存在 + * @param uri {@link Uri} + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isUriExists(Uri uri) { + return UriUtils.isUriExists(uri); + } + + // ========== + // = 复制文件 = + // ========== + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @return 复制后的文件路径 + */ + @Override + public String copyByUri(Uri uri) { + return UriUtils.copyByUri(uri); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + @Override + public String copyByUri( + Uri uri, + String fileName + ) { + return UriUtils.copyByUri(uri, fileName); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param file 文件 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + @Override + public String copyByUri( + Uri uri, + File file, + String fileName + ) { + return UriUtils.copyByUri(uri, file, fileName); + } + + /** + * 通过 Uri 复制文件 + * @param uri {@link Uri} + * @param filePath 文件路径 + * @param fileName 文件名 {@link ContentResolverUtils#getDisplayNameColumn} + * @return 复制后的文件路径 + */ + @Override + public String copyByUri( + Uri uri, + String filePath, + String fileName + ) { + return UriUtils.copyByUri(uri, filePath, fileName); + } + + // ============= + // = 获取文件路径 = + // ============= + + /** + * 通过 Uri 获取文件路径 + * @param uri {@link Uri} + * @return 文件路径 + */ + @Override + public String getFilePathByUri(Uri uri) { + return UriUtils.getFilePathByUri(uri); + } + + /** + * 通过 Uri 获取文件路径 + *
+     *     默认不复制文件, 防止影响已经使用该方法的功能 ( 文件过大导致 ANR、耗时操作等 )
+     * 
+ * @param uri {@link Uri} + * @param isQCopy Android 10 ( Q ) 及其以上版本是否复制文件 + * @return 文件路径 + */ + @Override + public String getFilePathByUri( + Uri uri, + boolean isQCopy + ) { + return UriUtils.getFilePathByUri(uri, isQCopy); + } + + // ======================== + // = ContentResolverUtils = + // ======================== + + /** + * 通过 File 获取 Media Uri + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getMediaUri(File file) { + return ContentResolverUtils.getMediaUri(file); + } + + /** + * 通过 File 获取 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getMediaUri( + Uri uri, + File file + ) { + return ContentResolverUtils.getMediaUri(uri, file); + } + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getMediaUri(String filePath) { + return ContentResolverUtils.getMediaUri(filePath); + } + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @return 指定文件 {@link Uri} + */ + @Override + public Uri getMediaUri( + Uri uri, + String filePath + ) { + return ContentResolverUtils.getMediaUri(uri, filePath); + } + + // = + + /** + * 通过 File 获取 Media 信息 + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + @Override + public String[] mediaQuery( + File file, + ContentResolverUtils.MediaQuery mediaQuery + ) { + return ContentResolverUtils.mediaQuery(file, mediaQuery); + } + + /** + * 通过 File 获取 Media 信息 + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param file 文件 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + @Override + public String[] mediaQuery( + Uri uri, + File file, + ContentResolverUtils.MediaQuery mediaQuery + ) { + return ContentResolverUtils.mediaQuery(uri, file, mediaQuery); + } + + /** + * 通过 File Path 获取 Media Uri + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + @Override + public String[] mediaQuery( + String filePath, + ContentResolverUtils.MediaQuery mediaQuery + ) { + return ContentResolverUtils.mediaQuery(filePath, mediaQuery); + } + + /** + * 通过 File Path 获取 Media Uri + *
+     *     只能用于查询 Media ( SDK_INT >= Q 使用, SDK_INT < Q 则直接使用 {@link Uri#fromFile(File)})
+     *     通过外部存储 ( 公开目录 ) SDCard 文件地址获取对应的 Uri content://
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param filePath 文件路径 + * @param mediaQuery 多媒体查询抽象类 + * @return Media 信息 + */ + @Override + public String[] mediaQuery( + Uri uri, + String filePath, + ContentResolverUtils.MediaQuery mediaQuery + ) { + return ContentResolverUtils.mediaQuery(uri, filePath, mediaQuery); + } + + // =================== + // = MediaStoreUtils = + // =================== + + // ========== + // = 通知相册 = + // ========== + + /** + * 通知刷新本地资源 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + @Override + public boolean notifyMediaStore(String filePath) { + return MediaStoreUtils.notifyMediaStore(filePath); + } + + /** + * 通知刷新本地资源 + *
+     *     注意事项: 部分手机 ( 如小米 ) 通知文件地址层级过深, 将会并入相册文件夹中
+     *     尽量放在 SDCrad/XXX/xx.jpg 层级中, 推荐直接存储到 {@link PathUtils#getSDCard().getDCIMPath()}
+     *     该方法只能通知刷新 外部存储 ( 公开目录 ) SDCard 文件地址
+     *     MediaStore Cursor 无法扫描 内部存储、外部存储 ( 私有目录 )
+     *     

+ * 正确的操作应该是: 不论版本统一存储到 外部存储 ( 私有目录 ) 再通过 MediaStore 插入数据 + *
+ * @param file 文件 + * @return {@code true} success, {@code false} fail + * @deprecated Android 10 ( Q ) 以后的版本需要通过 MediaStore 插入数据 + */ + @Deprecated + @Override + public boolean notifyMediaStore(File file) { + return MediaStoreUtils.notifyMediaStore(file); + } + + /** + * 通知刷新本地资源 + * @param uri {@link Uri} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean notifyMediaStore(Uri uri) { + return MediaStoreUtils.notifyMediaStore(uri); + } + + // ======= + // = 图片 = + // ======= + + /** + * 创建图片 Uri + * @return 图片 Uri + */ + @Override + public Uri createImageUri() { + return MediaStoreUtils.createImageUri(); + } + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @return 图片 Uri + */ + @Override + public Uri createImageUri(String mimeType) { + return MediaStoreUtils.createImageUri(mimeType); + } + + /** + * 创建图片 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + @Override + public Uri createImageUri( + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createImageUri(mimeType, relativePath); + } + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 图片 Uri + */ + @Override + public Uri createImageUri( + String displayName, + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createImageUri( + displayName, mimeType, relativePath + ); + } + + /** + * 创建图片 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 图片 Uri + */ + @Override + public Uri createImageUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ) { + return MediaStoreUtils.createImageUri( + displayName, mimeType, relativePath, createTime + ); + } + + // ======= + // = 视频 = + // ======= + + /** + * 创建视频 Uri + * @return 视频 Uri + */ + @Override + public Uri createVideoUri() { + return MediaStoreUtils.createVideoUri(); + } + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @return 视频 Uri + */ + @Override + public Uri createVideoUri(String mimeType) { + return MediaStoreUtils.createVideoUri(mimeType); + } + + /** + * 创建视频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + @Override + public Uri createVideoUri( + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createVideoUri(mimeType, relativePath); + } + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 视频 Uri + */ + @Override + public Uri createVideoUri( + String displayName, + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createVideoUri( + displayName, mimeType, relativePath + ); + } + + /** + * 创建视频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 视频 Uri + */ + @Override + public Uri createVideoUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ) { + return MediaStoreUtils.createVideoUri( + displayName, mimeType, relativePath, createTime + ); + } + + // ======= + // = 音频 = + // ======= + + /** + * 创建音频 Uri + * @return 音频 Uri + */ + @Override + public Uri createAudioUri() { + return MediaStoreUtils.createAudioUri(); + } + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @return 音频 Uri + */ + @Override + public Uri createAudioUri(String mimeType) { + return MediaStoreUtils.createAudioUri(mimeType); + } + + /** + * 创建音频 Uri + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + @Override + public Uri createAudioUri( + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createAudioUri(mimeType, relativePath); + } + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return 音频 Uri + */ + @Override + public Uri createAudioUri( + String displayName, + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createAudioUri( + displayName, mimeType, relativePath + ); + } + + /** + * 创建音频 Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return 音频 Uri + */ + @Override + public Uri createAudioUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ) { + return MediaStoreUtils.createAudioUri( + displayName, mimeType, relativePath, createTime + ); + } + + // ============ + // = Download = + // ============ + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 需后缀 ) + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + @Override + public Uri createDownloadUri(String displayName) { + return MediaStoreUtils.createDownloadUri(displayName); + } + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + @Override + public Uri createDownloadUri( + String displayName, + String mimeType + ) { + return MediaStoreUtils.createDownloadUri( + displayName, mimeType + ); + } + + /** + * 创建 Download Uri + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + @Override + public Uri createDownloadUri( + String displayName, + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createDownloadUri( + displayName, mimeType, relativePath + ); + } + + /** + * 创建 Download Uri + *
+     *     Android 10 ( Q ) 以下直接通过 File 写入到 {@link Environment#DIRECTORY_DOWNLOADS}
+     * 
+ * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Download Uri + */ + @RequiresApi(api = Build.VERSION_CODES.Q) + @Override + public Uri createDownloadUri( + String displayName, + String mimeType, + String relativePath, + long createTime + ) { + return MediaStoreUtils.createDownloadUri( + displayName, mimeType, relativePath, createTime + ); + } + + // ========== + // = 通用创建 = + // ========== + + /** + * 创建预存储 Media Uri + * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @return Media Uri + */ + @Override + public Uri createMediaUri( + Uri uri, + String displayName, + String mimeType, + String relativePath + ) { + return MediaStoreUtils.createMediaUri( + uri, displayName, mimeType, relativePath + ); + } + + /** + * 创建预存储 Media Uri + *
+     *     也可通过 {@link IntentUtils#getCreateDocumentIntent(String, String)} 创建
+     * 
+ * @param uri MediaStore.media-type.Media.EXTERNAL_CONTENT_URI + * @param displayName 显示名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + * @param mimeType 资源类型 + * @param relativePath 存储目录 ( 如 DCIM、Video、Pictures、Music、Download ) + * @param createTime 创建时间 + * @return Media Uri + */ + @Override + public Uri createMediaUri( + Uri uri, + String displayName, + String mimeType, + String relativePath, + long createTime + ) { + return MediaStoreUtils.createMediaUri( + uri, displayName, mimeType, relativePath, createTime + ); + } + + // ======== + // = File = + // ======== + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @return File Uri + */ + @Override + public Uri createUriByPath(String fileName) { + return MediaStoreUtils.createUriByPath(fileName); + } + + /** + * 通过 File Path 创建 Uri + * @param fileName 文件名 + * @param filePath 文件路径 + * @return File Uri + */ + @Override + public Uri createUriByPath( + String fileName, + String filePath + ) { + return MediaStoreUtils.createUriByPath(fileName, filePath); + } + + /** + * 通过 File Path 创建 Uri + * @param filePath 文件路径 + * @return File Uri + */ + @Override + public Uri createUriByFile(String filePath) { + return MediaStoreUtils.createUriByFile(filePath); + } + + /** + * 通过 File 创建 Uri + *
+     *     主要用于如低版本写入 Download 文件夹, 创建 Uri
+     *     统一使用 {@link #insertMedia(Uri, Uri)} 写入文件夹
+     *     高版本使用 {@link #createDownloadUri(String)}
+     *     并不局限 Download 文件夹操作
+     * 
+ * @param file 文件 + * @return File Uri + */ + @Override + public Uri createUriByFile(File file) { + return MediaStoreUtils.createUriByFile(file); + } + + // ========== + // = 插入数据 = + // ========== + + /** + * 插入一张图片 + *
+     *     Android 10 ( Q ) 已抛弃仍可用, 推荐使用传入 Uri 方式 {@link #createImageUri}
+     * 
+ * @param filePath 文件路径 + * @param name 存储文件名 + * @param notify 是否通知相册 + * @return {@code true} success, {@code false} fail + */ + @Deprecated + @Override + public Uri insertImage( + String filePath, + String name, + boolean notify + ) { + return MediaStoreUtils.insertImage(filePath, name, notify); + } + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertImage( + Uri uri, + Uri inputUri, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ) { + return MediaStoreUtils.insertImage(uri, inputUri, format, quality); + } + + // = + + /** + * 插入一张图片 + * @param uri {@link #createImageUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertImage( + Uri uri, + Uri inputUri + ) { + return MediaStoreUtils.insertImage(uri, inputUri); + } + + /** + * 插入一条视频 + * @param uri {@link #createVideoUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertVideo( + Uri uri, + Uri inputUri + ) { + return MediaStoreUtils.insertVideo(uri, inputUri); + } + + /** + * 插入一条音频 + * @param uri {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertAudio( + Uri uri, + Uri inputUri + ) { + return MediaStoreUtils.insertAudio(uri, inputUri); + } + + /** + * 插入一条文件资源 + * @param uri {@link #createDownloadUri} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertDownload( + Uri uri, + Uri inputUri + ) { + return MediaStoreUtils.insertDownload(uri, inputUri); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputUri 输入 Uri ( 待存储文件 Uri ) + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Uri inputUri + ) { + return MediaStoreUtils.insertMedia(uri, inputUri); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + InputStream inputStream + ) { + return MediaStoreUtils.insertMedia(uri, inputStream); + } + + /** + * 插入一条多媒体资源 + * @param outputStream {@link OutputStream} + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + OutputStream outputStream, + InputStream inputStream + ) { + return MediaStoreUtils.insertMedia(outputStream, inputStream); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param filePath 待存储文件路径 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + String filePath + ) { + return MediaStoreUtils.insertMedia(uri, filePath); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param file 待存储文件 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + File file + ) { + return MediaStoreUtils.insertMedia(uri, file); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Drawable drawable + ) { + return MediaStoreUtils.insertMedia(uri, drawable); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Drawable drawable, + Bitmap.CompressFormat format + ) { + return MediaStoreUtils.insertMedia(uri, drawable, format); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param drawable 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Drawable drawable, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ) { + return MediaStoreUtils.insertMedia(uri, drawable, format, quality); + } + + // = + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Bitmap bitmap + ) { + return MediaStoreUtils.insertMedia(uri, bitmap); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Bitmap bitmap, + Bitmap.CompressFormat format + ) { + return MediaStoreUtils.insertMedia(uri, bitmap, format); + } + + /** + * 插入一条多媒体资源 + * @param uri {@link #createImageUri} or {@link #createVideoUri} or + * {@link #createAudioUri()} or {@link #createMediaUri} + * @param bitmap 待保存图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean insertMedia( + Uri uri, + Bitmap bitmap, + Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) int quality + ) { + return MediaStoreUtils.insertMedia(uri, bitmap, format, quality); + } + + /** + * 获取用户向应用授予对指定媒体文件组的写入访问权限的请求 + *
+     *     以下四个方法搭配 startIntentSenderForResult() 使用
+     *     {@link AppUtils#startIntentSenderForResult(Activity, PendingIntent, int)}
+     *     

+ * startIntentSenderForResult(pendingIntent.getIntentSender(), EDIT_REQUEST_CODE, null, 0, 0, 0) + *
+ * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + @Override + public PendingIntent createWriteRequest(Collection uris) { + return MediaStoreUtils.createWriteRequest(uris); + } + + /** + * 获取用户将设备上指定的媒体文件标记为收藏的请求 + *
+     *     对该文件具有读取访问权限的任何应用都可以看到用户已将该文件标记为收藏
+     * 
+ * @param uris 待请求 Uri 集 + * @param favorite 是否喜欢 + * @return {@link PendingIntent} + */ + @Override + public PendingIntent createFavoriteRequest( + Collection uris, + boolean favorite + ) { + return MediaStoreUtils.createFavoriteRequest(uris, favorite); + } + + /** + * 获取用户将指定的媒体文件放入设备垃圾箱的请求 + *
+     *     垃圾箱中的内容会在系统定义的时间段后被永久删除
+     * 
+ * @param uris 待请求 Uri 集 + * @param trashed 是否遗弃 + * @return {@link PendingIntent} + */ + @Override + public PendingIntent createTrashRequest( + Collection uris, + boolean trashed + ) { + return MediaStoreUtils.createTrashRequest(uris, trashed); + } + + /** + * 获取用户立即永久删除指定的媒体文件 ( 而不是先将其放入垃圾箱 ) 的请求 + * @param uris 待请求 Uri 集 + * @return {@link PendingIntent} + */ + @Override + public PendingIntent createDeleteRequest(Collection uris) { + return MediaStoreUtils.createDeleteRequest(uris); + } + + // ============= + // = PathUtils = + // ============= + + /** + * 是否获得 MANAGE_EXTERNAL_STORAGE 权限 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isExternalStorageManager() { + return PathUtils.isExternalStorageManager(); + } + + /** + * 检查是否有 MANAGE_EXTERNAL_STORAGE 权限并跳转设置页面 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean checkExternalStorageAndIntentSetting() { + return PathUtils.checkExternalStorageAndIntentSetting(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/view/IHelperByView.java b/lib/DevApp/src/main/java/dev/utils/app/helper/view/IHelperByView.java new file mode 100644 index 0000000000..469b666ba0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/view/IHelperByView.java @@ -0,0 +1,2962 @@ +package dev.utils.app.helper.view; + +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.text.method.TransformationMethod; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IdRes; +import androidx.core.widget.TextViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import dev.utils.app.SizeUtils; + +/** + * detail: ViewHelper 接口 + * @author Ttt + */ +public interface IHelperByView { + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @param views View[] + * @return Helper + */ + T addTouchArea( + int range, + View... views + ); + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @param views View[] + * @return Helper + */ + T addTouchArea( + int left, + int top, + int right, + int bottom, + View... views + ); + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @param views View[] + * @return Helper + */ + T setOnClick( + View.OnClickListener listener, + View... views + ); + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @param views View[] + * @return Helper + */ + T setOnLongClick( + View.OnLongClickListener listener, + View... views + ); + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @param views View[] + * @return Helper + */ + T setOnTouch( + View.OnTouchListener listener, + View... views + ); + + // ============= + // = ViewUtils = + // ============= + + /** + * 设置 View Id + * @param view {@link View} + * @param id View Id + * @return Helper + */ + T setId( + View view, + int id + ); + + /** + * 设置是否限制子 View 在其边界内绘制 + * @param clipChildren {@code true} yes, {@code false} no + * @param viewGroups ViewGroup[] + * @return Helper + */ + T setClipChildren( + boolean clipChildren, + ViewGroup... viewGroups + ); + + /** + * 移除全部子 View + * @param viewGroups ViewGroup[] + * @return Helper + */ + T removeAllViews(ViewGroup... viewGroups); + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @return Helper + */ + T addView( + ViewGroup viewGroup, + View child + ); + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @return Helper + */ + T addView( + ViewGroup viewGroup, + View child, + int index + ); + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @param params LayoutParams + * @return Helper + */ + T addView( + ViewGroup viewGroup, + View child, + int index, + ViewGroup.LayoutParams params + ); + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param params LayoutParams + * @return Helper + */ + T addView( + ViewGroup viewGroup, + View child, + ViewGroup.LayoutParams params + ); + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + T addView( + ViewGroup viewGroup, + View child, + int width, + int height + ); + + /** + * 设置 View LayoutParams + * @param view {@link View} + * @param params LayoutParams + * @return Helper + */ + T setLayoutParams( + View view, + ViewGroup.LayoutParams params + ); + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param views View[] + * @return Helper + */ + T setWidthHeight( + int width, + int height, + View... views + ); + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + T setWidthHeight( + int width, + int height, + boolean nullNewLP, + View... views + ); + + /** + * 设置 View weight 权重 + * @param weight 权重比例 + * @param views View[] + * @return Helper + */ + T setWeight( + float weight, + View... views + ); + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param views View[] + * @return Helper + */ + T setWidth( + int width, + View... views + ); + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + T setWidth( + int width, + boolean nullNewLP, + View... views + ); + + /** + * 设置 View 高度 + * @param height View 高度 + * @param views View[] + * @return Helper + */ + T setHeight( + int height, + View... views + ); + + /** + * 设置 View 高度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + T setHeight( + int height, + boolean nullNewLP, + View... views + ); + + /** + * 设置 View 最小宽度 + * @param minWidth 最小宽度 + * @param views View[] + * @return Helper + */ + T setMinimumWidth( + int minWidth, + View... views + ); + + /** + * 设置 View 最小高度 + * @param minHeight 最小高度 + * @param views View[] + * @return Helper + */ + T setMinimumHeight( + int minHeight, + View... views + ); + + /** + * 设置 View 透明度 + * @param alpha 透明度 + * @param views View[] + * @return Helper + */ + T setAlpha( + @FloatRange(from = 0.0, to = 1.0) float alpha, + View... views + ); + + /** + * 设置 View TAG + * @param view View + * @param object TAG + * @return Helper + */ + T setTag( + View view, + Object object + ); + + /** + * 设置 View 滑动的 X 轴坐标 + * @param value X 轴坐标 + * @param views View[] + * @return Helper + */ + T setScrollX( + int value, + View... views + ); + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param value Y 轴坐标 + * @param views View[] + * @return Helper + */ + T setScrollY( + int value, + View... views + ); + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @param viewGroups ViewGroup[] + * @return Helper + */ + T setDescendantFocusability( + int focusability, + ViewGroup... viewGroups + ); + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @param views View[] + * @return Helper + */ + T setOverScrollMode( + int overScrollMode, + View... views + ); + + /** + * 设置是否绘制横向滚动条 + * @param horizontalScrollBarEnabled {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + T setHorizontalScrollBarEnabled( + boolean horizontalScrollBarEnabled, + View... views + ); + + /** + * 设置是否绘制垂直滚动条 + * @param verticalScrollBarEnabled {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + T setVerticalScrollBarEnabled( + boolean verticalScrollBarEnabled, + View... views + ); + + /** + * 设置 View 滚动效应 + * @param isScrollContainer 是否需要滚动效应 + * @param views View[] + * @return Helper + */ + T setScrollContainer( + boolean isScrollContainer, + View... views + ); + + /** + * 设置下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusForwardId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusForwardId( + View view, + @IdRes int nextFocusForwardId + ); + + /** + * 设置向下移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusDownId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusDownId( + View view, + @IdRes int nextFocusDownId + ); + + /** + * 设置向左移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusLeftId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusLeftId( + View view, + @IdRes int nextFocusLeftId + ); + + /** + * 设置向右移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusRightId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusRightId( + View view, + @IdRes int nextFocusRightId + ); + + /** + * 设置向上移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusUpId 下一个获取焦点的 View id + * @return Helper + */ + T setNextFocusUpId( + View view, + @IdRes int nextFocusUpId + ); + + /** + * 设置 View 旋转度数 + * @param rotation 旋转度数 + * @param views View[] + * @return Helper + */ + T setRotation( + float rotation, + View... views + ); + + /** + * 设置 View 水平旋转度数 + * @param rotationX 水平旋转度数 + * @param views View[] + * @return Helper + */ + T setRotationX( + float rotationX, + View... views + ); + + /** + * 设置 View 竖直旋转度数 + * @param rotationY 竖直旋转度数 + * @param views View[] + * @return Helper + */ + T setRotationY( + float rotationY, + View... views + ); + + /** + * 设置 View 水平方向缩放比例 + * @param scaleX 水平方向缩放比例 + * @param views View[] + * @return Helper + */ + T setScaleX( + float scaleX, + View... views + ); + + /** + * 设置 View 竖直方向缩放比例 + * @param scaleY 竖直方向缩放比例 + * @param views View[] + * @return Helper + */ + T setScaleY( + float scaleY, + View... views + ); + + /** + * 设置文本的显示方式 + * @param textAlignment 文本的显示方式 + * @param views View[] + * @return Helper + */ + T setTextAlignment( + int textAlignment, + View... views + ); + + /** + * 设置文本的显示方向 + * @param textDirection 文本的显示方向 + * @param views View[] + * @return Helper + */ + T setTextDirection( + int textDirection, + View... views + ); + + /** + * 设置水平方向偏转量 + * @param pivotX 水平方向偏转量 + * @param views View[] + * @return Helper + */ + T setPivotX( + float pivotX, + View... views + ); + + /** + * 设置竖直方向偏转量 + * @param pivotY 竖直方向偏转量 + * @param views View[] + * @return Helper + */ + T setPivotY( + float pivotY, + View... views + ); + + /** + * 设置水平方向的移动距离 + * @param translationX 水平方向的移动距离 + * @param views View[] + * @return Helper + */ + T setTranslationX( + float translationX, + View... views + ); + + /** + * 设置竖直方向的移动距离 + * @param translationY 竖直方向的移动距离 + * @param views View[] + * @return Helper + */ + T setTranslationY( + float translationY, + View... views + ); + + /** + * 设置 X 轴位置 + * @param x X 轴位置 + * @param views View[] + * @return Helper + */ + T setX( + float x, + View... views + ); + + /** + * 设置 Y 轴位置 + * @param y Y 轴位置 + * @param views View[] + * @return Helper + */ + T setY( + float y, + View... views + ); + + /** + * 设置 View 硬件加速类型 + * @param layerType 硬件加速类型 + * @param paint {@link Paint} + * @param views View[] + * @return Helper + */ + T setLayerType( + int layerType, + Paint paint, + View... views + ); + + /** + * 请求重新对 View 布局 + * @param views View[] + * @return Helper + */ + T requestLayout(View... views); + + /** + * View 请求获取焦点 + * @param views View[] + * @return Helper + */ + T requestFocus(View... views); + + /** + * View 清除焦点 + * @param views View[] + * @return Helper + */ + T clearFocus(View... views); + + /** + * 设置 View 是否在触摸模式下获得焦点 + * @param focusableInTouchMode {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return Helper + */ + T setFocusableInTouchMode( + boolean focusableInTouchMode, + View... views + ); + + /** + * 设置 View 是否可以获取焦点 + * @param focusable {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return Helper + */ + T setFocusable( + boolean focusable, + View... views + ); + + /** + * 切换获取焦点状态 + * @param views View[] + * @return Helper + */ + T toggleFocusable(View... views); + + /** + * 设置 View 是否选中 + * @param selected {@code true} 选中, {@code false} 非选中 + * @param views View[] + * @return Helper + */ + T setSelected( + boolean selected, + View... views + ); + + /** + * 切换选中状态 + * @param views View[] + * @return Helper + */ + T toggleSelected(View... views); + + /** + * 设置 View 是否启用 + * @param enabled {@code true} 启用, {@code false} 禁用 + * @param views View[] + * @return Helper + */ + T setEnabled( + boolean enabled, + View... views + ); + + /** + * 切换 View 是否启用状态 + * @param views View[] + * @return Helper + */ + T toggleEnabled(View... views); + + /** + * 设置 View 是否可以点击 + * @param clickable {@code true} 可点击, {@code false} 不可点击 + * @param views View[] + * @return Helper + */ + T setClickable( + boolean clickable, + View... views + ); + + /** + * 切换 View 是否可以点击状态 + * @param views View[] + * @return Helper + */ + T toggleClickable(View... views); + + /** + * 设置 View 是否可以长按 + * @param longClickable {@code true} 可长按, {@code false} 不可长按 + * @param views View[] + * @return Helper + */ + T setLongClickable( + boolean longClickable, + View... views + ); + + /** + * 切换 View 是否可以长按状态 + * @param views View[] + * @return Helper + */ + T toggleLongClickable(View... views); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param views View[] + * @return Helper + */ + T setVisibilitys( + boolean isVisibility, + View... views + ); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setVisibilitys( + int isVisibility, + View... views + ); + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @param views View[] + * @return Helper + */ + T setVisibilityINs( + boolean isVisibility, + View... views + ); + + /** + * 切换 View 显示的状态 + * @param view {@link View} + * @param views View[] + * @return Helper + */ + T toggleVisibilitys( + View view, + View... views + ); + + /** + * 切换 View 显示的状态 + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + T toggleVisibilitys( + View[] viewArrays, + View... views + ); + + /** + * 切换 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + T toggleVisibilitys( + int state, + View[] viewArrays, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + int state, + View[] viewArrays, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + boolean isVisibility, + View[] viewArrays, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param view {@link View} + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + int state, + View view, + View... views + ); + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param view {@link View} + * @param views View[] + * @return Helper + */ + T reverseVisibilitys( + boolean isVisibility, + View view, + View... views + ); + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T toggleViews( + boolean isChange, + int isVisibility, + View... views + ); + + /** + * 把自身从父 View 中移除 + * @param views View[] + * @return Helper + */ + T removeSelfFromParent(View... views); + + /** + * View 请求更新 + * @param allParent 是否全部父布局 View 都请求 + * @param views View[] + * @return Helper + */ + T requestLayoutParent( + boolean allParent, + View... views + ); + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param views View[] + * @return Helper + */ + T measureView( + int specifiedWidth, + View... views + ); + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @param views View[] + * @return Helper + */ + T measureView( + int specifiedWidth, + int specifiedHeight, + View... views + ); + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param views View[] + * @return Helper + */ + T setLayoutGravity( + int gravity, + View... views + ); + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param isReflection 是否使用反射 + * @param views View[] + * @return Helper + */ + T setLayoutGravity( + int gravity, + boolean isReflection, + View... views + ); + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param views View[] + * @return Helper + */ + T setMarginLeft( + int leftMargin, + View... views + ); + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + T setMarginLeft( + int leftMargin, + boolean reset, + View... views + ); + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param views View[] + * @return Helper + */ + T setMarginTop( + int topMargin, + View... views + ); + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + T setMarginTop( + int topMargin, + boolean reset, + View... views + ); + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param views View[] + * @return Helper + */ + T setMarginRight( + int rightMargin, + View... views + ); + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + T setMarginRight( + int rightMargin, + boolean reset, + View... views + ); + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param views View[] + * @return Helper + */ + T setMarginBottom( + int bottomMargin, + View... views + ); + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + T setMarginBottom( + int bottomMargin, + boolean reset, + View... views + ); + + /** + * 设置 Margin 边距 + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @param views View[] + * @return Helper + */ + T setMargin( + int leftRight, + int topBottom, + View... views + ); + + /** + * 设置 Margin 边距 + * @param margin Margin + * @param views View[] + * @return Helper + */ + T setMargin( + int margin, + View... views + ); + + /** + * 设置 Margin 边距 + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @param views View[] + * @return Helper + */ + T setMargin( + int left, + int top, + int right, + int bottom, + View... views + ); + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param views View[] + * @return Helper + */ + T setPaddingLeft( + int leftPadding, + View... views + ); + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + T setPaddingLeft( + int leftPadding, + boolean reset, + View... views + ); + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param views View[] + * @return Helper + */ + T setPaddingTop( + int topPadding, + View... views + ); + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + T setPaddingTop( + int topPadding, + boolean reset, + View... views + ); + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param views View[] + * @return Helper + */ + T setPaddingRight( + int rightPadding, + View... views + ); + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + T setPaddingRight( + int rightPadding, + boolean reset, + View... views + ); + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param views View[] + * @return Helper + */ + T setPaddingBottom( + int bottomPadding, + View... views + ); + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + T setPaddingBottom( + int bottomPadding, + boolean reset, + View... views + ); + + /** + * 设置 Padding 边距 + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @param views View[] + * @return Helper + */ + T setPadding( + int leftRight, + int topBottom, + View... views + ); + + /** + * 设置 Padding 边距 + * @param padding Padding + * @param views View[] + * @return Helper + */ + T setPadding( + int padding, + View... views + ); + + /** + * 设置 Padding 边距 + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @param views View[] + * @return Helper + */ + T setPadding( + int left, + int top, + int right, + int bottom, + View... views + ); + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return Helper + */ + T addRules( + int verb, + View... views + ); + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param subject 关联 View id + * @param views View[] + * @return Helper + */ + T addRules( + int verb, + int subject, + View... views + ); + + /** + * 移除多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return Helper + */ + T removeRules( + int verb, + View... views + ); + + /** + * 设置动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return Helper + */ + T setAnimation( + View view, + Animation animation + ); + + /** + * 清空动画 + * @param views View[] + * @return Helper + */ + T clearAnimation(View... views); + + /** + * 启动动画 + * @param views View[] + * @return Helper + */ + T startAnimation(View... views); + + /** + * 启动动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return Helper + */ + T startAnimation( + View view, + Animation animation + ); + + /** + * 取消动画 + * @param views View[] + * @return Helper + */ + T cancelAnimation(View... views); + + /** + * 设置背景图片 + * @param background 背景图片 + * @param views View[] + * @return Helper + */ + T setBackground( + Drawable background, + View... views + ); + + /** + * 设置背景颜色 + * @param color 背景颜色 + * @param views View[] + * @return Helper + */ + T setBackgroundColor( + @ColorInt int color, + View... views + ); + + /** + * 设置背景资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + T setBackgroundResource( + @DrawableRes int resId, + View... views + ); + + /** + * 设置背景着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + T setBackgroundTintList( + ColorStateList tint, + View... views + ); + + /** + * 设置背景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + T setBackgroundTintMode( + PorterDuff.Mode tintMode, + View... views + ); + + /** + * 设置前景图片 + * @param foreground 前景图片 + * @param views View[] + * @return Helper + */ + T setForeground( + Drawable foreground, + View... views + ); + + /** + * 设置前景重心 + * @param gravity 重心 + * @param views View[] + * @return Helper + */ + T setForegroundGravity( + int gravity, + View... views + ); + + /** + * 设置前景着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + T setForegroundTintList( + ColorStateList tint, + View... views + ); + + /** + * 设置前景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + T setForegroundTintMode( + PorterDuff.Mode tintMode, + View... views + ); + + /** + * View 着色处理 + * @param color 颜色值 + * @param views View[] + * @return Helper + */ + T setColorFilter( + @ColorInt int color, + View... views + ); + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param color 颜色值 + * @param views View[] + * @return Helper + */ + T setColorFilter( + Drawable drawable, + @ColorInt int color, + View... views + ); + + /** + * View 着色处理 + * @param colorFilter 颜色过滤 ( 效果 ) + * @param views View[] + * @return Helper + */ + T setColorFilter( + ColorFilter colorFilter, + View... views + ); + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @param views View[] + * @return Helper + */ + T setColorFilter( + Drawable drawable, + ColorFilter colorFilter, + View... views + ); + + /** + * 设置 ProgressBar 进度条样式 + * @param drawable {@link Drawable} + * @param views View[] + * @return Helper + */ + T setProgressDrawable( + Drawable drawable, + View... views + ); + + /** + * 设置 ProgressBar 进度值 + * @param progress 当前进度 + * @param views View[] + * @return Helper + */ + T setBarProgress( + int progress, + View... views + ); + + /** + * 设置 ProgressBar 最大值 + * @param max 最大值 + * @param views View[] + * @return Helper + */ + T setBarMax( + int max, + View... views + ); + + /** + * 设置 ProgressBar 最大值 + * @param progress 当前进度 + * @param max 最大值 + * @param views View[] + * @return Helper + */ + T setBarValue( + int progress, + int max, + View... views + ); + + // ================= + // = ListViewUtils = + // ================= + + /** + * 滑动到指定索引 ( 有滚动过程 ) + * @param position 索引 + * @param views View[] + * @return Helper + */ + T smoothScrollToPosition( + int position, + View... views + ); + + /** + * 滑动到指定索引 ( 无滚动过程 ) + * @param position 索引 + * @param views View[] + * @return Helper + */ + T scrollToPosition( + int position, + View... views + ); + + /** + * 滑动到顶部 ( 有滚动过程 ) + * @param views View[] + * @return Helper + */ + T smoothScrollToTop(View... views); + + /** + * 滑动到顶部 ( 无滚动过程 ) + * @param views View[] + * @return Helper + */ + T scrollToTop(View... views); + + /** + * 滑动到底部 ( 有滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 smoothScrollBy 搭配到底部 )
+     *     smoothScrollToBottom(view)
+     *     smoothScrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param views View[] + * @return Helper + */ + T smoothScrollToBottom(View... views); + + /** + * 滑动到底部 ( 无滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 scrollBy 搭配到底部 )
+     *     scrollToBottom(view)
+     *     scrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param views View[] + * @return Helper + */ + T scrollToBottom(View... views); + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + T smoothScrollTo( + int x, + int y, + View... views + ); + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + T smoothScrollBy( + int x, + int y, + View... views + ); + + /** + * 滚动方向 ( 有滚动过程 ) + * @param direction 滚动方向 如: View.FOCUS_UP、View.FOCUS_DOWN + * @param views View[] + * @return Helper + */ + T fullScroll( + int direction, + View... views + ); + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + T scrollTo( + int x, + int y, + View... views + ); + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + T scrollBy( + int x, + int y, + View... views + ); + + // ================== + // = ImageViewUtils = + // ================== + + /** + * 设置 ImageView 是否保持宽高比 + * @param adjustViewBounds 是否调整此视图的边界以保持可绘制的原始纵横比 + * @param imageViews ImageView[] + * @return Helper + */ + T setAdjustViewBounds( + boolean adjustViewBounds, + ImageView... imageViews + ); + + /** + * 设置 ImageView 最大高度 + * @param maxHeight 最大高度 + * @param imageViews ImageView[] + * @return Helper + */ + T setMaxHeight( + int maxHeight, + ImageView... imageViews + ); + + /** + * 设置 ImageView 最大宽度 + * @param maxWidth 最大宽度 + * @param imageViews ImageView[] + * @return Helper + */ + T setMaxWidth( + int maxWidth, + ImageView... imageViews + ); + + /** + * 设置 ImageView Level + * @param level level Image + * @param views View[] + * @return Helper + */ + T setImageLevel( + int level, + View... views + ); + + /** + * 设置 ImageView Bitmap + * @param bitmap {@link Bitmap} + * @param views View[] + * @return Helper + */ + T setImageBitmap( + Bitmap bitmap, + View... views + ); + + /** + * 设置 ImageView Drawable + * @param drawable {@link Bitmap} + * @param views View[] + * @return Helper + */ + T setImageDrawable( + Drawable drawable, + View... views + ); + + /** + * 设置 ImageView 资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + T setImageResource( + @DrawableRes int resId, + View... views + ); + + /** + * 设置 ImageView Matrix + * @param matrix {@link Matrix} + * @param views View[] + * @return Helper + */ + T setImageMatrix( + Matrix matrix, + View... views + ); + + /** + * 设置 ImageView 着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + T setImageTintList( + ColorStateList tint, + View... views + ); + + /** + * 设置 ImageView 着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + T setImageTintMode( + PorterDuff.Mode tintMode, + View... views + ); + + /** + * 设置 ImageView 缩放类型 + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @param views View[] + * @return Helper + */ + T setScaleType( + ImageView.ScaleType scaleType, + View... views + ); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + T setBackgroundResources( + @DrawableRes int resId, + View... views + ); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setBackgroundResources( + @DrawableRes int resId, + int isVisibility, + View... views + ); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + T setImageResources( + @DrawableRes int resId, + View... views + ); + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setImageResources( + @DrawableRes int resId, + int isVisibility, + View... views + ); + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param views View[] + * @return Helper + */ + T setImageBitmaps( + Bitmap bitmap, + View... views + ); + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setImageBitmaps( + Bitmap bitmap, + int isVisibility, + View... views + ); + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param views View[] + * @return Helper + */ + T setImageDrawables( + Drawable drawable, + View... views + ); + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setImageDrawables( + Drawable drawable, + int isVisibility, + View... views + ); + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param views View[] + * @return Helper + */ + T setScaleTypes( + ImageView.ScaleType scaleType, + View... views + ); + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + T setScaleTypes( + ImageView.ScaleType scaleType, + int isVisibility, + View... views + ); + + // =============================== + // = EditTextUtils、TextViewUtils = + // =============================== + + /** + * 设置文本 + * @param text TextView text + * @param views View[] + * @return Helper + */ + T setText( + CharSequence text, + View... views + ); + + /** + * 设置长度限制 + * @param maxLength 长度限制 + * @param views View[] + * @return Helper + */ + T setMaxLength( + int maxLength, + View... views + ); + + /** + * 设置长度限制, 并且设置内容 + * @param content 文本内容 + * @param maxLength 长度限制 + * @param views View[] + * @return Helper + */ + T setMaxLengthAndText( + CharSequence content, + int maxLength, + View... views + ); + + /** + * 设置输入类型 + * @param type 类型 + * @param views View[] + * @return Helper + */ + T setInputType( + int type, + View... views + ); + + /** + * 设置软键盘右下角按钮类型 + * @param imeOptions 软键盘按钮类型 + * @param views View[] + * @return Helper + */ + T setImeOptions( + int imeOptions, + View... views + ); + + /** + * 设置文本视图显示转换 + * @param method {@link TransformationMethod} + * @param views View[] + * @return Helper + */ + T setTransformationMethod( + TransformationMethod method, + View... views + ); + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param views View[] + * @return Helper + */ + T setTransformationMethod( + boolean isDisplayPassword, + View... views + ); + + // ================= + // = EditTextUtils = + // ================= + + /** + * 设置内容 + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + T setText( + CharSequence content, + boolean isSelect, + EditText... editTexts + ); + + /** + * 追加内容 ( 当前光标位置追加 ) + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + T insert( + CharSequence content, + boolean isSelect, + EditText... editTexts + ); + + /** + * 追加内容 + * @param content 文本内容 + * @param start 开始添加的位置 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + T insert( + CharSequence content, + int start, + boolean isSelect, + EditText... editTexts + ); + + /** + * 设置是否显示光标 + * @param visible 是否显示光标 + * @param editTexts EditText[] + * @return Helper + */ + T setCursorVisible( + boolean visible, + EditText... editTexts + ); + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @param editTexts EditText[] + * @return Helper + */ + T setTextCursorDrawable( + @DrawableRes int textCursorDrawable, + EditText... editTexts + ); + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @param editTexts EditText[] + * @return Helper + */ + T setTextCursorDrawable( + Drawable textCursorDrawable, + EditText... editTexts + ); + + /** + * 设置光标在第一位 + * @param editTexts EditText[] + * @return Helper + */ + T setSelectionToTop(EditText... editTexts); + + /** + * 设置光标在最后一位 + * @param editTexts EditText[] + * @return Helper + */ + T setSelectionToBottom(EditText... editTexts); + + /** + * 设置光标位置 + * @param index 光标位置 + * @param editTexts EditText[] + * @return Helper + */ + T setSelection( + int index, + EditText... editTexts + ); + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param isSelectBottom 是否设置光标到最后 + * @param editTexts EditText[] + * @return Helper + */ + T setTransformationMethod( + boolean isDisplayPassword, + boolean isSelectBottom, + EditText... editTexts + ); + + /** + * 添加输入监听事件 + * @param watcher 输入监听 + * @param editTexts EditText[] + * @return Helper + */ + T addTextChangedListener( + TextWatcher watcher, + EditText... editTexts + ); + + /** + * 移除输入监听事件 + * @param watcher 输入监听 + * @param editTexts EditText[] + * @return Helper + */ + T removeTextChangedListener( + TextWatcher watcher, + EditText... editTexts + ); + + /** + * 设置 KeyListener + * @param listener {@link KeyListener} + * @param editTexts EditText[] + * @return Helper + */ + T setKeyListener( + KeyListener listener, + EditText... editTexts + ); + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容, 如: 0123456789 + * @param editTexts EditText[] + * @return Helper + */ + T setKeyListener( + String accepted, + EditText... editTexts + ); + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容 + * @param editTexts EditText[] + * @return Helper + */ + T setKeyListener( + char[] accepted, + EditText... editTexts + ); + + // ================= + // = TextViewUtils = + // ================= + + /** + * 设置 Hint 文本 + * @param text Hint text + * @param views View[] + * @return Helper + */ + T setHint( + CharSequence text, + View... views + ); + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @param views View[] + * @return Helper + */ + T setHintTextColors( + @ColorInt int color, + View... views + ); + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return Helper + */ + T setHintTextColors( + ColorStateList colors, + View... views + ); + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @param views View[] + * @return Helper + */ + T setTextColors( + @ColorInt int color, + View... views + ); + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return Helper + */ + T setTextColors( + ColorStateList colors, + View... views + ); + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @param views View[] + * @return Helper + */ + T setHtmlTexts( + String content, + View... views + ); + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param views View[] + * @return Helper + */ + T setTypeface( + Typeface typeface, + View... views + ); + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @param views View[] + * @return Helper + */ + T setTypeface( + Typeface typeface, + int style, + View... views + ); + + /** + * 设置字体大小 ( px 像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + T setTextSizeByPx( + float size, + View... views + ); + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + T setTextSizeBySp( + float size, + View... views + ); + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + T setTextSizeByDp( + float size, + View... views + ); + + /** + * 设置字体大小 ( inches 英寸 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + T setTextSizeByIn( + float size, + View... views + ); + + /** + * 设置字体大小 + * @param unit 字体参数类型 + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + T setTextSize( + int unit, + float size, + View... views + ); + + /** + * 清空 flags + * @param views View[] + * @return Helper + */ + T clearFlags(View... views); + + /** + * 设置 TextView flags + * @param flags flags + * @param views View[] + * @return Helper + */ + T setPaintFlags( + int flags, + View... views + ); + + /** + * 设置 TextView 抗锯齿 flags + * @param views View[] + * @return Helper + */ + T setAntiAliasFlag(View... views); + + /** + * 设置 TextView 是否加粗 + * @param views View[] + * @return Helper + */ + T setBold(View... views); + + /** + * 设置 TextView 是否加粗 + * @param isBold {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + T setBold( + boolean isBold, + View... views + ); + + /** + * 设置 TextView 是否加粗 + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + T setBold( + Typeface typeface, + boolean isBold, + View... views + ); + + /** + * 设置下划线 + * @param views View[] + * @return Helper + */ + T setUnderlineText(View... views); + + /** + * 设置下划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @param views View[] + * @return Helper + */ + T setUnderlineText( + boolean isAntiAlias, + View... views + ); + + /** + * 设置中划线 + * @param views View[] + * @return Helper + */ + T setStrikeThruText(View... views); + + /** + * 设置中划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @param views View[] + * @return Helper + */ + T setStrikeThruText( + boolean isAntiAlias, + View... views + ); + + /** + * 设置文字水平间距 + * @param letterSpacing 文字水平间距 + * @param views View[] + * @return Helper + */ + T setLetterSpacing( + float letterSpacing, + View... views + ); + + /** + * 设置文字行间距 ( 行高 ) + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param views View[] + * @return Helper + */ + T setLineSpacing( + float lineSpacing, + View... views + ); + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @param views View[] + * @return Helper + */ + T setLineSpacingAndMultiplier( + float lineSpacing, + float multiplier, + View... views + ); + + /** + * 设置字体水平方向的缩放 + * @param size 缩放比例 + * @param views View[] + * @return Helper + */ + T setTextScaleX( + float size, + View... views + ); + + /** + * 设置是否保留字体留白间隙区域 + * @param includePadding 是否保留字体留白间隙区域 + * @param views View[] + * @return Helper + */ + T setIncludeFontPadding( + boolean includePadding, + View... views + ); + + /** + * 设置行数 + * @param lines 行数 + * @param views View[] + * @return Helper + */ + T setLines( + int lines, + View... views + ); + + /** + * 设置最大行数 + * @param maxLines 最大行数 + * @param views View[] + * @return Helper + */ + T setMaxLines( + int maxLines, + View... views + ); + + /** + * 设置最小行数 + * @param minLines 最小行数 + * @param views View[] + * @return Helper + */ + T setMinLines( + int minLines, + View... views + ); + + /** + * 设置最大字符宽度限制 + * @param maxEms 最大字符 + * @param views View[] + * @return Helper + */ + T setMaxEms( + int maxEms, + View... views + ); + + /** + * 设置最小字符宽度限制 + * @param minEms 最小字符 + * @param views View[] + * @return Helper + */ + T setMinEms( + int minEms, + View... views + ); + + /** + * 设置指定字符宽度 + * @param ems 字符 + * @param views View[] + * @return Helper + */ + T setEms( + int ems, + View... views + ); + + /** + * 设置 Ellipsize 效果 + * @param where {@link TextUtils.TruncateAt} + * @param views View[] + * @return Helper + */ + T setEllipsize( + TextUtils.TruncateAt where, + View... views + ); + + /** + * 设置自动识别文本链接 + * @param mask {@link android.text.util.Linkify} + * @param views View[] + * @return Helper + */ + T setAutoLinkMask( + int mask, + View... views + ); + + /** + * 设置文本全为大写 + * @param allCaps 是否全部大写 + * @param views View[] + * @return Helper + */ + T setAllCaps( + boolean allCaps, + View... views + ); + + /** + * 设置 Gravity + * @param gravity {@link android.view.Gravity} + * @param views View[] + * @return Helper + */ + T setGravity( + int gravity, + View... views + ); + + /** + * 设置 CompoundDrawables Padding + * @param padding CompoundDrawables Padding + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablePadding( + int padding, + TextView... textViews + ); + + /** + * 设置 Left CompoundDrawables + * @param left left Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesByLeft( + Drawable left, + TextView... textViews + ); + + /** + * 设置 Top CompoundDrawables + * @param top top Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesByTop( + Drawable top, + TextView... textViews + ); + + /** + * 设置 Right CompoundDrawables + * @param right right Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesByRight( + Drawable right, + TextView... textViews + ); + + /** + * 设置 Bottom CompoundDrawables + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesByBottom( + Drawable bottom, + TextView... textViews + ); + + /** + * 设置 CompoundDrawables + *
+     *     CompoundDrawable 的大小控制是通过 drawable.setBounds() 控制
+     *     需要先设置 Drawable 的 setBounds
+     *     {@link dev.utils.app.image.ImageUtils#setBounds}
+     * 
+ * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawables( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom, + TextView... textViews + ); + + /** + * 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByLeft( + Drawable left, + TextView... textViews + ); + + /** + * 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param top top Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByTop( + Drawable top, + TextView... textViews + ); + + /** + * 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param right right Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByRight( + Drawable right, + TextView... textViews + ); + + /** + * 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBoundsByBottom( + Drawable bottom, + TextView... textViews + ); + + /** + * 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + T setCompoundDrawablesWithIntrinsicBounds( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom, + TextView... textViews + ); + + /** + * 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 + * @param autoSizeTextType 自动调整大小类型 + * @param views View[] + * @return Helper + */ + T setAutoSizeTextTypeWithDefaults( + @TextViewCompat.AutoSizeTextType int autoSizeTextType, + View... views + ); + + /** + * 设置 TextView 自动调整字体大小配置 + * @param autoSizeMinTextSize 自动调整最小字体大小 + * @param autoSizeMaxTextSize 自动调整最大字体大小 + * @param autoSizeStepGranularity 自动调整大小变动粒度 ( 跨度区间值 ) + * @param unit 字体参数类型 + * @param views View[] + * @return Helper + */ + T setAutoSizeTextTypeUniformWithConfiguration( + int autoSizeMinTextSize, + int autoSizeMaxTextSize, + int autoSizeStepGranularity, + int unit, + View... views + ); + + /** + * 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM + * @param presetSizes 预设字体大小范围像素为单位 + * @param unit 字体参数类型 + * @param views View[] + * @return Helper + */ + T setAutoSizeTextTypeUniformWithPresetSizes( + int[] presetSizes, + int unit, + View... views + ); + + // ===================== + // = RecyclerViewUtils = + // ===================== + + /** + * 设置 RecyclerView LayoutManager + * @param view {@link View} + * @param layoutManager LayoutManager + * @return Helper + */ + T setLayoutManager( + View view, + RecyclerView.LayoutManager layoutManager + ); + + /** + * 设置 GridLayoutManager SpanCount + * @param view {@link View} + * @param spanCount Span Count + * @return Helper + */ + T setSpanCount( + View view, + int spanCount + ); + + /** + * 设置 GridLayoutManager SpanCount + * @param layoutManager LayoutManager + * @param spanCount Span Count + * @return Helper + */ + T setSpanCount( + RecyclerView.LayoutManager layoutManager, + int spanCount + ); + + /** + * 设置 RecyclerView Orientation + * @param view {@link View} + * @param orientation 方向 + * @return Helper + */ + T setOrientation( + View view, + @RecyclerView.Orientation int orientation + ); + + /** + * 设置 RecyclerView Adapter + * @param view {@link View} + * @param adapter Adapter + * @return Helper + */ + T setAdapter( + View view, + RecyclerView.Adapter adapter + ); + + /** + * RecyclerView notifyItemRemoved + * @param view {@link View} + * @param position 索引 + * @return Helper + */ + T notifyItemRemoved( + View view, + int position + ); + + /** + * RecyclerView notifyItemInserted + * @param view {@link View} + * @param position 索引 + * @return Helper + */ + T notifyItemInserted( + View view, + int position + ); + + /** + * RecyclerView notifyItemMoved + * @param view {@link View} + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return Helper + */ + T notifyItemMoved( + View view, + int fromPosition, + int toPosition + ); + + /** + * RecyclerView notifyDataSetChanged + * @param view {@link View} + * @return Helper + */ + T notifyDataSetChanged(View view); + + /** + * 设置 RecyclerView LinearSnapHelper + * @param view {@link View} + * @return Helper + */ + T attachLinearSnapHelper(View view); + + /** + * 设置 RecyclerView PagerSnapHelper + * @param view {@link View} + * @return Helper + */ + T attachPagerSnapHelper(View view); + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + T addItemDecoration( + View view, + RecyclerView.ItemDecoration decor + ); + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return Helper + */ + T addItemDecoration( + View view, + RecyclerView.ItemDecoration decor, + int index + ); + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + T removeItemDecoration( + View view, + RecyclerView.ItemDecoration decor + ); + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param index RecyclerView ItemDecoration 索引 + * @return Helper + */ + T removeItemDecorationAt( + View view, + int index + ); + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @param view {@link View} + * @return Helper + */ + T removeAllItemDecoration(View view); + + /** + * 设置 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + T setOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ); + + /** + * 添加 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + T addOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ); + + /** + * 移除 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + T removeOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ); + + /** + * 清空 RecyclerView ScrollListener + * @param view {@link View} + * @return Helper + */ + T clearOnScrollListeners(View view); + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param enabled 嵌套滚动开关 + * @param views View[] + * @return Helper + */ + T setNestedScrollingEnabled( + boolean enabled, + View... views + ); + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + * @param view {@link View} + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + T forceGetViewSize( + View view, + SizeUtils.OnGetSizeListener listener + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/helper/view/ViewHelper.java b/lib/DevApp/src/main/java/dev/utils/app/helper/view/ViewHelper.java new file mode 100644 index 0000000000..aa812b800a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/helper/view/ViewHelper.java @@ -0,0 +1,4587 @@ +package dev.utils.app.helper.view; + +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.text.method.TransformationMethod; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IdRes; +import androidx.core.widget.TextViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import dev.utils.app.ClickUtils; +import dev.utils.app.EditTextUtils; +import dev.utils.app.HandlerUtils; +import dev.utils.app.ImageViewUtils; +import dev.utils.app.ListViewUtils; +import dev.utils.app.RecyclerViewUtils; +import dev.utils.app.SizeUtils; +import dev.utils.app.TextViewUtils; +import dev.utils.app.ViewUtils; +import dev.utils.app.helper.BaseHelper; +import dev.utils.app.helper.flow.FlowHelper; +import dev.utils.common.ForUtils; + +/** + * detail: View 链式调用快捷设置 Helper 类 + * @author Ttt + *
+ *     通过 DevApp 工具类快捷实现
+ *     

+ * DevApp Api + * @see
+ *
+ */ +public final class ViewHelper + extends BaseHelper + implements IHelperByView { + + private ViewHelper() { + } + + // ViewHelper + private static final ViewHelper HELPER = new ViewHelper(); + + /** + * 获取单例 ViewHelper + * @return {@link ViewHelper} + */ + public static ViewHelper get() { + return HELPER; + } + + // ======== + // = Flow = + // ======== + + /** + * 执行 Action 流方法 + * @param action Action + * @return Helper + */ + @Override + public ViewHelper flow(FlowHelper.Action action) { + if (action != null) action.action(); + return this; + } + + // ================ + // = HandlerUtils = + // ================ + + /** + * 在主线程 Handler 中执行任务 + * @param runnable 可执行的任务 + * @return Helper + */ + @Override + public ViewHelper postRunnable(Runnable runnable) { + HandlerUtils.postRunnable(runnable); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @return Helper + */ + @Override + public ViewHelper postRunnable( + Runnable runnable, + long delayMillis + ) { + HandlerUtils.postRunnable(runnable, delayMillis); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @return Helper + */ + @Override + public ViewHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval); + return this; + } + + /** + * 在主线程 Handler 中执行延迟任务 + * @param runnable 可执行的任务 + * @param delayMillis 延迟时间 + * @param number 轮询次数 + * @param interval 轮询时间 + * @param listener 结束通知 + * @return Helper + */ + @Override + public ViewHelper postRunnable( + Runnable runnable, + long delayMillis, + int number, + int interval, + HandlerUtils.OnEndListener listener + ) { + HandlerUtils.postRunnable(runnable, delayMillis, number, interval, listener); + return this; + } + + /** + * 在主线程 Handler 中清除任务 + * @param runnable 需要清除的任务 + * @return Helper + */ + @Override + public ViewHelper removeRunnable(Runnable runnable) { + HandlerUtils.removeRunnable(runnable); + return this; + } + + // ================= + // = IHelperByView = + // ================= + + // ============== + // = ClickUtils = + // ============== + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param range 点击范围 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper addTouchArea( + int range, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.addTouchArea(value, range), views + ); + return this; + } + + /** + * 增加控件的触摸范围, 最大范围只能是父布局所包含的的区域 + * @param left left range + * @param top top range + * @param right right range + * @param bottom bottom range + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper addTouchArea( + int left, + int top, + int right, + int bottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.addTouchArea(value, left, top, right, bottom), views + ); + return this; + } + + /** + * 设置点击事件 + * @param listener {@link View.OnClickListener} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setOnClick( + View.OnClickListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnClick(value, listener), views + ); + return this; + } + + /** + * 设置长按事件 + * @param listener {@link View.OnLongClickListener} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setOnLongClick( + View.OnLongClickListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnLongClick(value, listener), views + ); + return this; + } + + /** + * 设置触摸事件 + * @param listener {@link View.OnTouchListener} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setOnTouch( + View.OnTouchListener listener, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ClickUtils.setOnTouch(value, listener), views + ); + return this; + } + + // ============= + // = ViewUtils = + // ============= + + /** + * 设置 View Id + * @param view {@link View} + * @param id View Id + * @return Helper + */ + @Override + public ViewHelper setId( + View view, + int id + ) { + ViewUtils.setId(view, id); + return this; + } + + /** + * 设置是否限制子 View 在其边界内绘制 + * @param clipChildren {@code true} yes, {@code false} no + * @param viewGroups ViewGroup[] + * @return Helper + */ + @Override + public ViewHelper setClipChildren( + boolean clipChildren, + ViewGroup... viewGroups + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setClipChildren(value, clipChildren), viewGroups + ); + return this; + } + + /** + * 移除全部子 View + * @param viewGroups ViewGroup[] + * @return Helper + */ + @Override + public ViewHelper removeAllViews(ViewGroup... viewGroups) { + ForUtils.forSimpleArgs( + value -> ViewUtils.removeAllViews(value), viewGroups + ); + return this; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @return Helper + */ + @Override + public ViewHelper addView( + ViewGroup viewGroup, + View child + ) { + ViewUtils.addView(viewGroup, child); + return this; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @return Helper + */ + @Override + public ViewHelper addView( + ViewGroup viewGroup, + View child, + int index + ) { + ViewUtils.addView(viewGroup, child, index); + return this; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param index 添加位置索引 + * @param params LayoutParams + * @return Helper + */ + @Override + public ViewHelper addView( + ViewGroup viewGroup, + View child, + int index, + ViewGroup.LayoutParams params + ) { + ViewUtils.addView(viewGroup, child, index, params); + return this; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param params LayoutParams + * @return Helper + */ + @Override + public ViewHelper addView( + ViewGroup viewGroup, + View child, + ViewGroup.LayoutParams params + ) { + ViewUtils.addView(viewGroup, child, params); + return this; + } + + /** + * 添加 View + * @param viewGroup {@link ViewGroup} + * @param child 待添加 View + * @param width View 宽度 + * @param height View 高度 + * @return Helper + */ + @Override + public ViewHelper addView( + ViewGroup viewGroup, + View child, + int width, + int height + ) { + ViewUtils.addView(viewGroup, child, width, height); + return this; + } + + /** + * 设置 View LayoutParams + * @param view {@link View} + * @param params LayoutParams + * @return Helper + */ + @Override + public ViewHelper setLayoutParams( + View view, + ViewGroup.LayoutParams params + ) { + ViewUtils.setLayoutParams(view, params); + return this; + } + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setWidthHeight( + int width, + int height, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setWidthHeight(value, width, height), views + ); + return this; + } + + /** + * 设置 View[] 宽度、高度 + * @param width View 宽度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setWidthHeight( + int width, + int height, + boolean nullNewLP, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setWidthHeight(value, width, height, nullNewLP), views + ); + return this; + } + + /** + * 设置 View weight 权重 + * @param weight 权重比例 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setWeight( + float weight, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setWeight(value, weight), views + ); + return this; + } + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setWidth( + int width, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setWidth(value, width), views + ); + return this; + } + + /** + * 设置 View 宽度 + * @param width View 宽度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setWidth( + int width, + boolean nullNewLP, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setWidth(value, width, nullNewLP), views + ); + return this; + } + + /** + * 设置 View 高度 + * @param height View 高度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHeight( + int height, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setHeight(value, height), views + ); + return this; + } + + /** + * 设置 View 高度 + * @param height View 高度 + * @param nullNewLP 如果 LayoutParams 为 null 是否创建新的 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHeight( + int height, + boolean nullNewLP, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setHeight(value, height, nullNewLP), views + ); + return this; + } + + /** + * 设置 View 最小宽度 + * @param minWidth 最小宽度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMinimumWidth( + int minWidth, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMinimumWidth(value, minWidth), views + ); + return this; + } + + /** + * 设置 View 最小高度 + * @param minHeight 最小高度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMinimumHeight( + int minHeight, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMinimumHeight(value, minHeight), views + ); + return this; + } + + /** + * 设置 View 透明度 + * @param alpha 透明度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAlpha( + @FloatRange(from = 0.0, to = 1.0) float alpha, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setAlpha(value, alpha), views + ); + return this; + } + + /** + * 设置 View TAG + * @param view View + * @param object TAG + * @return Helper + */ + @Override + public ViewHelper setTag( + View view, + Object object + ) { + ViewUtils.setTag(view, object); + return this; + } + + /** + * 设置 View 滑动的 X 轴坐标 + * @param value X 轴坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScrollX( + int value, + View... views + ) { + ForUtils.forSimpleArgs( + view -> ViewUtils.setScrollX(view, value), views + ); + return this; + } + + /** + * 设置 View 滑动的 Y 轴坐标 + * @param value Y 轴坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScrollY( + int value, + View... views + ) { + ForUtils.forSimpleArgs( + view -> ViewUtils.setScrollY(view, value), views + ); + return this; + } + + /** + * 设置 ViewGroup 和其子控件两者之间的关系 + *
+     *     beforeDescendants : ViewGroup 会优先其子类控件而获取到焦点
+     *     afterDescendants : ViewGroup 只有当其子类控件不需要获取焦点时才获取焦点
+     *     blocksDescendants : ViewGroup 会覆盖子类控件而直接获得焦点
+     *     android:descendantFocusability="blocksDescendants"
+     * 
+ * @param focusability {@link ViewGroup#FOCUS_BEFORE_DESCENDANTS}、{@link ViewGroup#FOCUS_AFTER_DESCENDANTS}、{@link ViewGroup#FOCUS_BLOCK_DESCENDANTS} + * @param viewGroups ViewGroup[] + * @return Helper + */ + @Override + public ViewHelper setDescendantFocusability( + int focusability, + ViewGroup... viewGroups + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setDescendantFocusability(value, focusability), viewGroups + ); + return this; + } + + /** + * 设置 View 滚动模式 + *
+     *     设置滑动到边缘时无效果模式 {@link View#OVER_SCROLL_NEVER}
+     *     android:overScrollMode="never"
+     * 
+ * @param overScrollMode {@link View#OVER_SCROLL_ALWAYS}、{@link View#OVER_SCROLL_IF_CONTENT_SCROLLS}、{@link View#OVER_SCROLL_NEVER} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setOverScrollMode( + int overScrollMode, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setOverScrollMode(value, overScrollMode), views + ); + return this; + } + + /** + * 设置是否绘制横向滚动条 + * @param horizontalScrollBarEnabled {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHorizontalScrollBarEnabled( + boolean horizontalScrollBarEnabled, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setHorizontalScrollBarEnabled(value, horizontalScrollBarEnabled), views + ); + return this; + } + + /** + * 设置是否绘制垂直滚动条 + * @param verticalScrollBarEnabled {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setVerticalScrollBarEnabled( + boolean verticalScrollBarEnabled, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setVerticalScrollBarEnabled(value, verticalScrollBarEnabled), views + ); + return this; + } + + /** + * 设置 View 滚动效应 + * @param isScrollContainer 是否需要滚动效应 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScrollContainer( + boolean isScrollContainer, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setScrollContainer(value, isScrollContainer), views + ); + return this; + } + + /** + * 设置下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusForwardId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public ViewHelper setNextFocusForwardId( + View view, + @IdRes int nextFocusForwardId + ) { + ViewUtils.setNextFocusForwardId(view, nextFocusForwardId); + return this; + } + + /** + * 设置向下移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusDownId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public ViewHelper setNextFocusDownId( + View view, + @IdRes int nextFocusDownId + ) { + ViewUtils.setNextFocusDownId(view, nextFocusDownId); + return this; + } + + /** + * 设置向左移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusLeftId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public ViewHelper setNextFocusLeftId( + View view, + @IdRes int nextFocusLeftId + ) { + ViewUtils.setNextFocusLeftId(view, nextFocusLeftId); + return this; + } + + /** + * 设置向右移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusRightId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public ViewHelper setNextFocusRightId( + View view, + @IdRes int nextFocusRightId + ) { + ViewUtils.setNextFocusRightId(view, nextFocusRightId); + return this; + } + + /** + * 设置向上移动焦点时, 下一个获取焦点的 View id + * @param view {@link View} + * @param nextFocusUpId 下一个获取焦点的 View id + * @return Helper + */ + @Override + public ViewHelper setNextFocusUpId( + View view, + @IdRes int nextFocusUpId + ) { + ViewUtils.setNextFocusUpId(view, nextFocusUpId); + return this; + } + + /** + * 设置 View 旋转度数 + * @param rotation 旋转度数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setRotation( + float rotation, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setRotation(value, rotation), views + ); + return this; + } + + /** + * 设置 View 水平旋转度数 + * @param rotationX 水平旋转度数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setRotationX( + float rotationX, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setRotationX(value, rotationX), views + ); + return this; + } + + /** + * 设置 View 竖直旋转度数 + * @param rotationY 竖直旋转度数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setRotationY( + float rotationY, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setRotationY(value, rotationY), views + ); + return this; + } + + /** + * 设置 View 水平方向缩放比例 + * @param scaleX 水平方向缩放比例 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScaleX( + float scaleX, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setScaleX(value, scaleX), views + ); + return this; + } + + /** + * 设置 View 竖直方向缩放比例 + * @param scaleY 竖直方向缩放比例 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScaleY( + float scaleY, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setScaleY(value, scaleY), views + ); + return this; + } + + /** + * 设置文本的显示方式 + * @param textAlignment 文本的显示方式 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextAlignment( + int textAlignment, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setTextAlignment(value, textAlignment), views + ); + } + return this; + } + + /** + * 设置文本的显示方向 + * @param textDirection 文本的显示方向 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextDirection( + int textDirection, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setTextDirection(value, textDirection), views + ); + } + return this; + } + + /** + * 设置水平方向偏转量 + * @param pivotX 水平方向偏转量 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPivotX( + float pivotX, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPivotX(value, pivotX), views + ); + return this; + } + + /** + * 设置竖直方向偏转量 + * @param pivotY 竖直方向偏转量 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPivotY( + float pivotY, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPivotY(value, pivotY), views + ); + return this; + } + + /** + * 设置水平方向的移动距离 + * @param translationX 水平方向的移动距离 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTranslationX( + float translationX, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setTranslationX(value, translationX), views + ); + return this; + } + + /** + * 设置竖直方向的移动距离 + * @param translationY 竖直方向的移动距离 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTranslationY( + float translationY, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setTranslationY(value, translationY), views + ); + return this; + } + + /** + * 设置 X 轴位置 + * @param x X 轴位置 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setX( + float x, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setX(value, x), views + ); + return this; + } + + /** + * 设置 Y 轴位置 + * @param y Y 轴位置 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setY( + float y, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setY(value, y), views + ); + return this; + } + + /** + * 设置 View 硬件加速类型 + * @param layerType 硬件加速类型 + * @param paint {@link Paint} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLayerType( + int layerType, + Paint paint, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setLayerType(value, layerType, paint), views + ); + return this; + } + + /** + * 请求重新对 View 布局 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper requestLayout(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.requestLayout(value), views + ); + return this; + } + + /** + * View 请求获取焦点 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper requestFocus(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.requestFocus(value), views + ); + return this; + } + + /** + * View 清除焦点 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper clearFocus(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.clearFocus(value), views + ); + return this; + } + + /** + * 设置 View 是否在触摸模式下获得焦点 + * @param focusableInTouchMode {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setFocusableInTouchMode( + boolean focusableInTouchMode, + View... views + ) { + ViewUtils.setFocusableInTouchMode(focusableInTouchMode, views); + return this; + } + + /** + * 设置 View 是否可以获取焦点 + * @param focusable {@code true} 可获取, {@code false} 不可获取 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setFocusable( + boolean focusable, + View... views + ) { + ViewUtils.setFocusable(focusable, views); + return this; + } + + /** + * 切换获取焦点状态 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleFocusable(View... views) { + ViewUtils.toggleFocusable(views); + return this; + } + + /** + * 设置 View 是否选中 + * @param selected {@code true} 选中, {@code false} 非选中 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setSelected( + boolean selected, + View... views + ) { + ViewUtils.setSelected(selected, views); + return this; + } + + /** + * 切换选中状态 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleSelected(View... views) { + ViewUtils.toggleSelected(views); + return this; + } + + /** + * 设置 View 是否启用 + * @param enabled {@code true} 启用, {@code false} 禁用 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setEnabled( + boolean enabled, + View... views + ) { + ViewUtils.setEnabled(enabled, views); + return this; + } + + /** + * 切换 View 是否启用状态 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleEnabled(View... views) { + ViewUtils.toggleEnabled(views); + return this; + } + + /** + * 设置 View 是否可以点击 + * @param clickable {@code true} 可点击, {@code false} 不可点击 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setClickable( + boolean clickable, + View... views + ) { + ViewUtils.setClickable(clickable, views); + return this; + } + + /** + * 切换 View 是否可以点击状态 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleClickable(View... views) { + ViewUtils.toggleClickable(views); + return this; + } + + /** + * 设置 View 是否可以长按 + * @param longClickable {@code true} 可长按, {@code false} 不可长按 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLongClickable( + boolean longClickable, + View... views + ) { + ViewUtils.setLongClickable(longClickable, views); + return this; + } + + /** + * 切换 View 是否可以长按状态 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleLongClickable(View... views) { + ViewUtils.toggleLongClickable(views); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setVisibilitys( + boolean isVisibility, + View... views + ) { + ViewUtils.setVisibilitys(isVisibility, views); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setVisibilitys( + int isVisibility, + View... views + ) { + ViewUtils.setVisibilitys(isVisibility, views); + return this; + } + + /** + * 设置 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.INVISIBLE + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setVisibilityINs( + boolean isVisibility, + View... views + ) { + ViewUtils.setVisibilityINs(isVisibility, views); + return this; + } + + /** + * 切换 View 显示的状态 + * @param view {@link View} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleVisibilitys( + View view, + View... views + ) { + ViewUtils.toggleVisibilitys(view, views); + return this; + } + + /** + * 切换 View 显示的状态 + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleVisibilitys( + View[] viewArrays, + View... views + ) { + ViewUtils.toggleVisibilitys(viewArrays, views); + return this; + } + + /** + * 切换 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleVisibilitys( + int state, + View[] viewArrays, + View... views + ) { + ViewUtils.toggleVisibilitys(state, viewArrays, views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper reverseVisibilitys( + int state, + View[] viewArrays, + View... views + ) { + ViewUtils.reverseVisibilitys(state, viewArrays, views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param viewArrays View[] + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper reverseVisibilitys( + boolean isVisibility, + View[] viewArrays, + View... views + ) { + ViewUtils.reverseVisibilitys(isVisibility, viewArrays, views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param state {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param view {@link View} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper reverseVisibilitys( + int state, + View view, + View... views + ) { + ViewUtils.reverseVisibilitys(state, view, views); + return this; + } + + /** + * 反转 View 显示的状态 + * @param isVisibility {@code true} View.VISIBLE, {@code false} View.GONE + * @param view {@link View} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper reverseVisibilitys( + boolean isVisibility, + View view, + View... views + ) { + ViewUtils.reverseVisibilitys(isVisibility, view, views); + return this; + } + + /** + * 切换 View 状态 + * @param isChange 是否改变 + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper toggleViews( + boolean isChange, + int isVisibility, + View... views + ) { + ViewUtils.toggleViews(isChange, isVisibility, views); + return this; + } + + /** + * 把自身从父 View 中移除 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper removeSelfFromParent(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.removeSelfFromParent(value), views + ); + return this; + } + + /** + * View 请求更新 + * @param allParent 是否全部父布局 View 都请求 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper requestLayoutParent( + boolean allParent, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.requestLayoutParent(value, allParent), views + ); + return this; + } + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper measureView( + int specifiedWidth, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.measureView(value, specifiedWidth), views + ); + return this; + } + + /** + * 测量 View + * @param specifiedWidth 指定宽度 + * @param specifiedHeight 指定高度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper measureView( + int specifiedWidth, + int specifiedHeight, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.measureView(value, specifiedWidth, specifiedHeight), views + ); + return this; + } + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLayoutGravity( + int gravity, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setLayoutGravity(value, gravity), views + ); + return this; + } + + /** + * 设置 View Layout Gravity + * @param gravity Gravity + * @param isReflection 是否使用反射 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLayoutGravity( + int gravity, + boolean isReflection, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setLayoutGravity(value, gravity, isReflection), views + ); + return this; + } + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginLeft( + int leftMargin, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginLeft(value, leftMargin), views + ); + return this; + } + + /** + * 设置 View Left Margin + * @param leftMargin Left Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginLeft( + int leftMargin, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginLeft(value, leftMargin, reset), views + ); + return this; + } + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginTop( + int topMargin, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginTop(value, topMargin), views + ); + return this; + } + + /** + * 设置 View Top Margin + * @param topMargin Top Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginTop( + int topMargin, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginTop(value, topMargin, reset), views + ); + return this; + } + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginRight( + int rightMargin, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginRight(value, rightMargin), views + ); + return this; + } + + /** + * 设置 View Right Margin + * @param rightMargin Right Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginRight( + int rightMargin, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginRight(value, rightMargin, reset), views + ); + return this; + } + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginBottom( + int bottomMargin, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginBottom(value, bottomMargin), views + ); + return this; + } + + /** + * 设置 View Bottom Margin + * @param bottomMargin Bottom Margin + * @param reset 是否重置清空其他 margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMarginBottom( + int bottomMargin, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMarginBottom(value, bottomMargin, reset), views + ); + return this; + } + + /** + * 设置 Margin 边距 + * @param leftRight Left and Right Margin + * @param topBottom Top and bottom Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMargin( + int leftRight, + int topBottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMargin(value, leftRight, topBottom), views + ); + return this; + } + + /** + * 设置 Margin 边距 + * @param margin Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMargin( + int margin, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMargin(value, margin), views + ); + return this; + } + + /** + * 设置 Margin 边距 + * @param left Left Margin + * @param top Top Margin + * @param right Right Margin + * @param bottom Bottom Margin + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMargin( + int left, + int top, + int right, + int bottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setMargin(value, left, top, right, bottom), views + ); + return this; + } + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingLeft( + int leftPadding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingLeft(value, leftPadding), views + ); + return this; + } + + /** + * 设置 View Left Padding + * @param leftPadding Left Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingLeft( + int leftPadding, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingLeft(value, leftPadding, reset), views + ); + return this; + } + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingTop( + int topPadding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingTop(value, topPadding), views + ); + return this; + } + + /** + * 设置 View Top Padding + * @param topPadding Top Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingTop( + int topPadding, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingTop(value, topPadding, reset), views + ); + return this; + } + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingRight( + int rightPadding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingRight(value, rightPadding), views + ); + return this; + } + + /** + * 设置 View Right Padding + * @param rightPadding Right Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingRight( + int rightPadding, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingRight(value, rightPadding, reset), views + ); + return this; + } + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingBottom( + int bottomPadding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingBottom(value, bottomPadding), views + ); + return this; + } + + /** + * 设置 View Bottom Padding + * @param bottomPadding Bottom Padding + * @param reset 是否重置清空其他 Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaddingBottom( + int bottomPadding, + boolean reset, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPaddingBottom(value, bottomPadding, reset), views + ); + return this; + } + + /** + * 设置 Padding 边距 + * @param leftRight Left and Right Padding + * @param topBottom Top and bottom Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPadding( + int leftRight, + int topBottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPadding(value, leftRight, topBottom), views + ); + return this; + } + + /** + * 设置 Padding 边距 + * @param padding Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPadding( + int padding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPadding(value, padding), views + ); + return this; + } + + /** + * 设置 Padding 边距 + * @param left Left Padding + * @param top Top Padding + * @param right Right Padding + * @param bottom Bottom Padding + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPadding( + int left, + int top, + int right, + int bottom, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setPadding(value, left, top, right, bottom), views + ); + return this; + } + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper addRules( + int verb, + View... views + ) { + ViewUtils.addRules(verb, views); + return this; + } + + /** + * 设置多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param subject 关联 View id + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper addRules( + int verb, + int subject, + View... views + ) { + ViewUtils.addRules(verb, subject, views); + return this; + } + + /** + * 移除多个 RelativeLayout View 布局规则 + * @param verb 布局位置 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper removeRules( + int verb, + View... views + ) { + ViewUtils.removeRules(verb, views); + return this; + } + + /** + * 设置动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return Helper + */ + @Override + public ViewHelper setAnimation( + View view, + Animation animation + ) { + ViewUtils.setAnimation(view, animation); + return this; + } + + /** + * 清空动画 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper clearAnimation(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.clearAnimation(value), views + ); + return this; + } + + /** + * 启动动画 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper startAnimation(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.startAnimation(value), views + ); + return this; + } + + /** + * 启动动画 + * @param view {@link View} + * @param animation {@link Animation} + * @return Helper + */ + @Override + public ViewHelper startAnimation( + View view, + Animation animation + ) { + ViewUtils.startAnimation(view, animation); + return this; + } + + /** + * 取消动画 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper cancelAnimation(View... views) { + ForUtils.forSimpleArgs( + value -> ViewUtils.cancelAnimation(value), views + ); + return this; + } + + /** + * 设置背景图片 + * @param background 背景图片 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackground( + Drawable background, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBackground(value, background), views + ); + return this; + } + + /** + * 设置背景颜色 + * @param color 背景颜色 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundColor( + @ColorInt int color, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBackgroundColor(value, color), views + ); + return this; + } + + /** + * 设置背景资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundResource( + @DrawableRes int resId, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBackgroundResource(value, resId), views + ); + return this; + } + + /** + * 设置背景着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundTintList( + ColorStateList tint, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBackgroundTintList(value, tint), views + ); + } + return this; + } + + /** + * 设置背景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundTintMode( + PorterDuff.Mode tintMode, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBackgroundTintMode(value, tintMode), views + ); + } + return this; + } + + /** + * 设置前景图片 + * @param foreground 前景图片 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setForeground( + Drawable foreground, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setForeground(value, foreground), views + ); + } + return this; + } + + /** + * 设置前景重心 + * @param gravity 重心 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setForegroundGravity( + int gravity, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setForegroundGravity(value, gravity), views + ); + } + return this; + } + + /** + * 设置前景着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setForegroundTintList( + ColorStateList tint, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setForegroundTintList(value, tint), views + ); + } + return this; + } + + /** + * 设置前景着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setForegroundTintMode( + PorterDuff.Mode tintMode, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setForegroundTintMode(value, tintMode), views + ); + } + return this; + } + + /** + * View 着色处理 + * @param color 颜色值 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setColorFilter( + @ColorInt int color, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setColorFilter(value, color), views + ); + return this; + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param color 颜色值 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setColorFilter( + Drawable drawable, + @ColorInt int color, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setColorFilter(value, drawable, color), views + ); + return this; + } + + /** + * View 着色处理 + * @param colorFilter 颜色过滤 ( 效果 ) + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setColorFilter( + ColorFilter colorFilter, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setColorFilter(value, colorFilter), views + ); + return this; + } + + /** + * View 着色处理, 并且设置 Background Drawable + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setColorFilter( + Drawable drawable, + ColorFilter colorFilter, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setColorFilter(value, drawable, colorFilter), views + ); + return this; + } + + /** + * 设置 ProgressBar 进度条样式 + * @param drawable {@link Drawable} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setProgressDrawable( + Drawable drawable, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setProgressDrawable(value, drawable), views + ); + return this; + } + + /** + * 设置 ProgressBar 进度值 + * @param progress 当前进度 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBarProgress( + int progress, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBarProgress(value, progress), views + ); + return this; + } + + /** + * 设置 ProgressBar 最大值 + * @param max 最大值 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBarMax( + int max, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBarMax(value, max), views + ); + return this; + } + + /** + * 设置 ProgressBar 最大值 + * @param progress 当前进度 + * @param max 最大值 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBarValue( + int progress, + int max, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ViewUtils.setBarValue(value, progress, max), views + ); + return this; + } + + // ================= + // = ListViewUtils = + // ================= + + /** + * 滑动到指定索引 ( 有滚动过程 ) + * @param position 索引 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper smoothScrollToPosition( + int position, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.smoothScrollToPosition(value, position), views + ); + return this; + } + + /** + * 滑动到指定索引 ( 无滚动过程 ) + * @param position 索引 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper scrollToPosition( + int position, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.scrollToPosition(value, position), views + ); + return this; + } + + /** + * 滑动到顶部 ( 有滚动过程 ) + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper smoothScrollToTop(View... views) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.smoothScrollToTop(value), views + ); + return this; + } + + /** + * 滑动到顶部 ( 无滚动过程 ) + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper scrollToTop(View... views) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.scrollToTop(value), views + ); + return this; + } + + /** + * 滑动到底部 ( 有滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 smoothScrollBy 搭配到底部 )
+     *     smoothScrollToBottom(view)
+     *     smoothScrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param views View[] + * @return Helper + */ + @Override + public ViewHelper smoothScrollToBottom(View... views) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.smoothScrollToBottom(value), views + ); + return this; + } + + /** + * 滑动到底部 ( 无滚动过程 ) + *
+     *     如果未到达底部 ( position 可以再加上 scrollBy 搭配到底部 )
+     *     scrollToBottom(view)
+     *     scrollBy(view, 0, Integer.MAX_VALUE);
+     * 
+ * @param views View[] + * @return Helper + */ + @Override + public ViewHelper scrollToBottom(View... views) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.scrollToBottom(value), views + ); + return this; + } + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于初始位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper smoothScrollTo( + int x, + int y, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.smoothScrollTo(value, x, y), views + ); + return this; + } + + /** + * 滚动到指定位置 ( 有滚动过程, 相对于上次移动的最后位置移动 ) + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper smoothScrollBy( + int x, + int y, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.smoothScrollBy(value, x, y), views + ); + return this; + } + + /** + * 滚动方向 ( 有滚动过程 ) + * @param direction 滚动方向 如: View.FOCUS_UP、View.FOCUS_DOWN + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper fullScroll( + int direction, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.fullScroll(value, direction), views + ); + return this; + } + + /** + * View 内容滚动位置 ( 相对于初始位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper scrollTo( + int x, + int y, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.scrollTo(value, x, y), views + ); + return this; + } + + /** + * View 内部滚动位置 ( 相对于上次移动的最后位置移动 ) + *
+     *     无滚动过程
+     * 
+ * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper scrollBy( + int x, + int y, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ListViewUtils.scrollBy(value, x, y), views + ); + return this; + } + + // ================== + // = ImageViewUtils = + // ================== + + /** + * 设置 ImageView 是否保持宽高比 + * @param adjustViewBounds 是否调整此视图的边界以保持可绘制的原始纵横比 + * @param imageViews ImageView[] + * @return Helper + */ + @Override + public ViewHelper setAdjustViewBounds( + boolean adjustViewBounds, + ImageView... imageViews + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setAdjustViewBounds(value, adjustViewBounds), imageViews + ); + return this; + } + + /** + * 设置 ImageView 最大高度 + * @param maxHeight 最大高度 + * @param imageViews ImageView[] + * @return Helper + */ + @Override + public ViewHelper setMaxHeight( + int maxHeight, + ImageView... imageViews + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setMaxHeight(value, maxHeight), imageViews + ); + return this; + } + + /** + * 设置 ImageView 最大宽度 + * @param maxWidth 最大宽度 + * @param imageViews ImageView[] + * @return Helper + */ + @Override + public ViewHelper setMaxWidth( + int maxWidth, + ImageView... imageViews + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setMaxWidth(value, maxWidth), imageViews + ); + return this; + } + + /** + * 设置 ImageView Level + * @param level level Image + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageLevel( + int level, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageLevel(value, level), views + ); + return this; + } + + /** + * 设置 ImageView Bitmap + * @param bitmap {@link Bitmap} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageBitmap( + Bitmap bitmap, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageBitmap(value, bitmap), views + ); + return this; + } + + /** + * 设置 ImageView Drawable + * @param drawable {@link Bitmap} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageDrawable( + Drawable drawable, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageDrawable(value, drawable), views + ); + return this; + } + + /** + * 设置 ImageView 资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageResource( + @DrawableRes int resId, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageResource(value, resId), views + ); + return this; + } + + /** + * 设置 ImageView Matrix + * @param matrix {@link Matrix} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageMatrix( + Matrix matrix, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageMatrix(value, matrix), views + ); + return this; + } + + /** + * 设置 ImageView 着色颜色 + * @param tint 着色颜色 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageTintList( + ColorStateList tint, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageTintList(value, tint), views + ); + } + return this; + } + + /** + * 设置 ImageView 着色模式 + * @param tintMode 着色模式 {@link PorterDuff.Mode} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageTintMode( + PorterDuff.Mode tintMode, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setImageTintMode(value, tintMode), views + ); + } + return this; + } + + /** + * 设置 ImageView 缩放类型 + * @param scaleType 缩放类型 {@link ImageView.ScaleType} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScaleType( + ImageView.ScaleType scaleType, + View... views + ) { + ForUtils.forSimpleArgs( + value -> ImageViewUtils.setScaleType(value, scaleType), views + ); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundResources( + @DrawableRes int resId, + View... views + ) { + ImageViewUtils.setBackgroundResources(resId, views); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBackgroundResources( + @DrawableRes int resId, + int isVisibility, + View... views + ) { + ImageViewUtils.setBackgroundResources(resId, isVisibility, views); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageResources( + @DrawableRes int resId, + View... views + ) { + ImageViewUtils.setImageResources(resId, views); + return this; + } + + /** + * 设置 View 图片资源 + * @param resId resource identifier + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageResources( + @DrawableRes int resId, + int isVisibility, + View... views + ) { + ImageViewUtils.setImageResources(resId, isVisibility, views); + return this; + } + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageBitmaps( + Bitmap bitmap, + View... views + ) { + ImageViewUtils.setImageBitmaps(bitmap, views); + return this; + } + + /** + * 设置 View Bitmap + * @param bitmap {@link Bitmap} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageBitmaps( + Bitmap bitmap, + int isVisibility, + View... views + ) { + ImageViewUtils.setImageBitmaps(bitmap, isVisibility, views); + return this; + } + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageDrawables( + Drawable drawable, + View... views + ) { + ImageViewUtils.setImageDrawables(drawable, views); + return this; + } + + /** + * 设置 View Drawable + * @param drawable {@link drawable} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImageDrawables( + Drawable drawable, + int isVisibility, + View... views + ) { + ImageViewUtils.setImageDrawables(drawable, isVisibility, views); + return this; + } + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScaleTypes( + ImageView.ScaleType scaleType, + View... views + ) { + ImageViewUtils.setScaleTypes(scaleType, views); + return this; + } + + /** + * 设置 View 缩放模式 + * @param scaleType {@link ImageView.ScaleType} + * @param isVisibility {@link View#VISIBLE}、{@link View#INVISIBLE}、{@link View#GONE} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setScaleTypes( + ImageView.ScaleType scaleType, + int isVisibility, + View... views + ) { + ImageViewUtils.setScaleTypes(scaleType, isVisibility, views); + return this; + } + + // =============================== + // = EditTextUtils、TextViewUtils = + // =============================== + + /** + * 设置文本 + * @param text TextView text + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setText( + CharSequence text, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setText(EditTextUtils.getEditText(value), text); + } else { + TextViewUtils.setText(value, text); + } + }, views + ); + return this; + } + + /** + * 设置长度限制 + * @param maxLength 长度限制 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMaxLength( + int maxLength, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setMaxLength(EditTextUtils.getEditText(value), maxLength); + } else { + TextViewUtils.setMaxLength(value, maxLength); + } + }, views + ); + return this; + } + + /** + * 设置长度限制, 并且设置内容 + * @param content 文本内容 + * @param maxLength 长度限制 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMaxLengthAndText( + CharSequence content, + int maxLength, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setMaxLengthAndText( + EditTextUtils.getEditText(value), content, maxLength + ); + } else { + TextViewUtils.setMaxLengthAndText(value, content, maxLength); + } + }, views + ); + return this; + } + + /** + * 设置输入类型 + * @param type 类型 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setInputType( + int type, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setInputType( + EditTextUtils.getEditText(value), type + ); + } else { + TextViewUtils.setInputType(value, type); + } + }, views + ); + return this; + } + + /** + * 设置软键盘右下角按钮类型 + * @param imeOptions 软键盘按钮类型 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setImeOptions( + int imeOptions, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setImeOptions( + EditTextUtils.getEditText(value), imeOptions + ); + } else { + TextViewUtils.setImeOptions(value, imeOptions); + } + }, views + ); + return this; + } + + /** + * 设置文本视图显示转换 + * @param method {@link TransformationMethod} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTransformationMethod( + TransformationMethod method, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setTransformationMethod( + EditTextUtils.getEditText(value), method + ); + } else { + TextViewUtils.setTransformationMethod(value, method); + } + }, views + ); + return this; + } + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTransformationMethod( + boolean isDisplayPassword, + View... views + ) { + ForUtils.forSimpleArgs( + value -> { + if (value instanceof EditText) { + EditTextUtils.setTransformationMethod( + EditTextUtils.getEditText(value), isDisplayPassword + ); + } else { + TextViewUtils.setTransformationMethod(value, isDisplayPassword); + } + }, views + ); + return this; + } + + // ================= + // = EditTextUtils = + // ================= + + /** + * 设置内容 + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setText( + CharSequence content, + boolean isSelect, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setText(value, content, isSelect), editTexts + ); + return this; + } + + /** + * 追加内容 ( 当前光标位置追加 ) + * @param content 文本内容 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper insert( + CharSequence content, + boolean isSelect, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.insert(value, content, isSelect), editTexts + ); + return this; + } + + /** + * 追加内容 + * @param content 文本内容 + * @param start 开始添加的位置 + * @param isSelect 是否设置光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper insert( + CharSequence content, + int start, + boolean isSelect, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.insert(value, content, start, isSelect), editTexts + ); + return this; + } + + /** + * 设置是否显示光标 + * @param visible 是否显示光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setCursorVisible( + boolean visible, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setCursorVisible(value, visible), editTexts + ); + return this; + } + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setTextCursorDrawable( + @DrawableRes int textCursorDrawable, + EditText... editTexts + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setTextCursorDrawable(value, textCursorDrawable), editTexts + ); + } + return this; + } + + /** + * 设置光标 + * @param textCursorDrawable 光标 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setTextCursorDrawable( + Drawable textCursorDrawable, + EditText... editTexts + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setTextCursorDrawable(value, textCursorDrawable), editTexts + ); + } + return this; + } + + /** + * 设置光标在第一位 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setSelectionToTop(EditText... editTexts) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setSelectionToTop(value), editTexts + ); + return this; + } + + /** + * 设置光标在最后一位 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setSelectionToBottom(EditText... editTexts) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setSelectionToBottom(value), editTexts + ); + return this; + } + + /** + * 设置光标位置 + * @param index 光标位置 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setSelection( + int index, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setSelection(value, index), editTexts + ); + return this; + } + + /** + * 设置密码文本视图显示转换 + * @param isDisplayPassword 是否显示密码 + * @param isSelectBottom 是否设置光标到最后 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setTransformationMethod( + boolean isDisplayPassword, + boolean isSelectBottom, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setTransformationMethod( + value, isDisplayPassword, isSelectBottom + ), editTexts + ); + return this; + } + + /** + * 添加输入监听事件 + * @param watcher 输入监听 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper addTextChangedListener( + TextWatcher watcher, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.addTextChangedListener(value, watcher), editTexts + ); + return this; + } + + /** + * 移除输入监听事件 + * @param watcher 输入监听 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper removeTextChangedListener( + TextWatcher watcher, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.removeTextChangedListener(value, watcher), editTexts + ); + return this; + } + + /** + * 设置 KeyListener + * @param listener {@link KeyListener} + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setKeyListener( + KeyListener listener, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setKeyListener(value, listener), editTexts + ); + return this; + } + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容, 如: 0123456789 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setKeyListener( + String accepted, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setKeyListener(value, accepted), editTexts + ); + return this; + } + + /** + * 设置 KeyListener + * @param accepted 允许输入的内容 + * @param editTexts EditText[] + * @return Helper + */ + @Override + public ViewHelper setKeyListener( + char[] accepted, + EditText... editTexts + ) { + ForUtils.forSimpleArgs( + value -> EditTextUtils.setKeyListener(value, accepted), editTexts + ); + return this; + } + + // ================= + // = TextViewUtils = + // ================= + + /** + * 设置 Hint 文本 + * @param text Hint text + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHint( + CharSequence text, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setHint(value, text), views + ); + return this; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param color R.color.id + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHintTextColors( + @ColorInt int color, + View... views + ) { + TextViewUtils.setHintTextColors(color, views); + return this; + } + + /** + * 设置多个 TextView Hint 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHintTextColors( + ColorStateList colors, + View... views + ) { + TextViewUtils.setHintTextColors(colors, views); + return this; + } + + /** + * 设置多个 TextView 字体颜色 + * @param color R.color.id + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextColors( + @ColorInt int color, + View... views + ) { + TextViewUtils.setTextColors(color, views); + return this; + } + + /** + * 设置多个 TextView 字体颜色 + * @param colors {@link ColorStateList} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextColors( + ColorStateList colors, + View... views + ) { + TextViewUtils.setTextColors(colors, views); + return this; + } + + /** + * 设置多个 TextView Html 内容 + * @param content Html content + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setHtmlTexts( + String content, + View... views + ) { + TextViewUtils.setHtmlTexts(content, views); + return this; + } + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTypeface( + Typeface typeface, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTypeface(value, typeface), views + ); + return this; + } + + /** + * 设置字体 + * @param typeface {@link Typeface} 字体样式 + * @param style 样式 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTypeface( + Typeface typeface, + int style, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTypeface(value, typeface, style), views + ); + return this; + } + + /** + * 设置字体大小 ( px 像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextSizeByPx( + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextSizeByPx(value, size), views + ); + return this; + } + + /** + * 设置字体大小 ( sp 缩放像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextSizeBySp( + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextSizeBySp(value, size), views + ); + return this; + } + + /** + * 设置字体大小 ( dp 与设备无关的像素 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextSizeByDp( + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextSizeByDp(value, size), views + ); + return this; + } + + /** + * 设置字体大小 ( inches 英寸 ) + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextSizeByIn( + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextSizeByIn(value, size), views + ); + return this; + } + + /** + * 设置字体大小 + * @param unit 字体参数类型 + * @param size 字体大小 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextSize( + int unit, + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextSize(value, unit, size), views + ); + return this; + } + + /** + * 清空 flags + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper clearFlags(View... views) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.clearFlags(value), views + ); + return this; + } + + /** + * 设置 TextView flags + * @param flags flags + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setPaintFlags( + int flags, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setPaintFlags(value, flags), views + ); + return this; + } + + /** + * 设置 TextView 抗锯齿 flags + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAntiAliasFlag(View... views) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAntiAliasFlag(value), views + ); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBold(View... views) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setBold(value), views + ); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @param isBold {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBold( + boolean isBold, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setBold(value, isBold), views + ); + return this; + } + + /** + * 设置 TextView 是否加粗 + * @param typeface {@link Typeface} 字体样式 + * @param isBold {@code true} yes, {@code false} no + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setBold( + Typeface typeface, + boolean isBold, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setBold(value, typeface, isBold), views + ); + return this; + } + + /** + * 设置下划线 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setUnderlineText(View... views) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setUnderlineText(value), views + ); + return this; + } + + /** + * 设置下划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setUnderlineText( + boolean isAntiAlias, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setUnderlineText(value, isAntiAlias), views + ); + return this; + } + + /** + * 设置中划线 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setStrikeThruText(View... views) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setStrikeThruText(value), views + ); + return this; + } + + /** + * 设置中划线并加清晰 + * @param isAntiAlias 是否消除锯齿 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setStrikeThruText( + boolean isAntiAlias, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setStrikeThruText(value, isAntiAlias), views + ); + return this; + } + + /** + * 设置文字水平间距 + * @param letterSpacing 文字水平间距 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLetterSpacing( + float letterSpacing, + View... views + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setLetterSpacing(value, letterSpacing), views + ); + } + return this; + } + + /** + * 设置文字行间距 ( 行高 ) + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLineSpacing( + float lineSpacing, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setLineSpacing(value, lineSpacing), views + ); + return this; + } + + /** + * 设置文字行间距 ( 行高 ) 、行间距倍数 + * @param lineSpacing 文字行间距 ( 行高 ), android:lineSpacingExtra + * @param multiplier 行间距倍数, android:lineSpacingMultiplier + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLineSpacingAndMultiplier( + float lineSpacing, + float multiplier, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setLineSpacingAndMultiplier( + value, lineSpacing, multiplier + ), views + ); + return this; + } + + /** + * 设置字体水平方向的缩放 + * @param size 缩放比例 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setTextScaleX( + float size, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setTextScaleX(value, size), views + ); + return this; + } + + /** + * 设置是否保留字体留白间隙区域 + * @param includePadding 是否保留字体留白间隙区域 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setIncludeFontPadding( + boolean includePadding, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setIncludeFontPadding(value, includePadding), views + ); + return this; + } + + /** + * 设置行数 + * @param lines 行数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setLines( + int lines, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setLines(value, lines), views + ); + return this; + } + + /** + * 设置最大行数 + * @param maxLines 最大行数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMaxLines( + int maxLines, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setMaxLines(value, maxLines), views + ); + return this; + } + + /** + * 设置最小行数 + * @param minLines 最小行数 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMinLines( + int minLines, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setMinLines(value, minLines), views + ); + return this; + } + + /** + * 设置最大字符宽度限制 + * @param maxEms 最大字符 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMaxEms( + int maxEms, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setMaxEms(value, maxEms), views + ); + return this; + } + + /** + * 设置最小字符宽度限制 + * @param minEms 最小字符 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setMinEms( + int minEms, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setMinEms(value, minEms), views + ); + return this; + } + + /** + * 设置指定字符宽度 + * @param ems 字符 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setEms( + int ems, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setEms(value, ems), views + ); + return this; + } + + /** + * 设置 Ellipsize 效果 + * @param where {@link TextUtils.TruncateAt} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setEllipsize( + TextUtils.TruncateAt where, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setEllipsize(value, where), views + ); + return this; + } + + /** + * 设置自动识别文本链接 + * @param mask {@link android.text.util.Linkify} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAutoLinkMask( + int mask, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAutoLinkMask(value, mask), views + ); + return this; + } + + /** + * 设置文本全为大写 + * @param allCaps 是否全部大写 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAllCaps( + boolean allCaps, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAllCaps(value, allCaps), views + ); + return this; + } + + /** + * 设置 Gravity + * @param gravity {@link android.view.Gravity} + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setGravity( + int gravity, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setGravity(value, gravity), views + ); + return this; + } + + /** + * 设置 CompoundDrawables Padding + * @param padding CompoundDrawables Padding + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablePadding( + int padding, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablePadding(value, padding), textViews + ); + return this; + } + + /** + * 设置 Left CompoundDrawables + * @param left left Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesByLeft( + Drawable left, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesByLeft(value, left), textViews + ); + return this; + } + + /** + * 设置 Top CompoundDrawables + * @param top top Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesByTop( + Drawable top, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesByTop(value, top), textViews + ); + return this; + } + + /** + * 设置 Right CompoundDrawables + * @param right right Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesByRight( + Drawable right, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesByRight(value, right), textViews + ); + return this; + } + + /** + * 设置 Bottom CompoundDrawables + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesByBottom( + Drawable bottom, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesByBottom(value, bottom), textViews + ); + return this; + } + + /** + * 设置 CompoundDrawables + *
+     *     CompoundDrawable 的大小控制是通过 drawable.setBounds() 控制
+     *     需要先设置 Drawable 的 setBounds
+     *     {@link dev.utils.app.image.ImageUtils#setBounds}
+     * 
+ * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawables( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawables( + value, left, top, right, bottom + ), textViews + ); + return this; + } + + /** + * 设置 Left CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesWithIntrinsicBoundsByLeft( + Drawable left, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesWithIntrinsicBoundsByLeft( + value, left + ), textViews + ); + return this; + } + + /** + * 设置 Top CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param top top Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesWithIntrinsicBoundsByTop( + Drawable top, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesWithIntrinsicBoundsByTop( + value, top + ), textViews + ); + return this; + } + + /** + * 设置 Right CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param right right Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesWithIntrinsicBoundsByRight( + Drawable right, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesWithIntrinsicBoundsByRight( + value, right + ), textViews + ); + return this; + } + + /** + * 设置 Bottom CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesWithIntrinsicBoundsByBottom( + Drawable bottom, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesWithIntrinsicBoundsByBottom( + value, bottom + ), textViews + ); + return this; + } + + /** + * 设置 CompoundDrawables ( 按照原有比例大小显示图片 ) + * @param left left Drawable + * @param top top Drawable + * @param right right Drawable + * @param bottom bottom Drawable + * @param textViews TextView[] + * @return Helper + */ + @Override + public ViewHelper setCompoundDrawablesWithIntrinsicBounds( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom, + TextView... textViews + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setCompoundDrawablesWithIntrinsicBounds( + value, left, top, right, bottom + ), textViews + ); + return this; + } + + /** + * 通过设置默认的自动调整大小配置, 决定是否自动缩放文本 + * @param autoSizeTextType 自动调整大小类型 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAutoSizeTextTypeWithDefaults( + @TextViewCompat.AutoSizeTextType int autoSizeTextType, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAutoSizeTextTypeWithDefaults( + value, autoSizeTextType + ), views + ); + return this; + } + + /** + * 设置 TextView 自动调整字体大小配置 + * @param autoSizeMinTextSize 自动调整最小字体大小 + * @param autoSizeMaxTextSize 自动调整最大字体大小 + * @param autoSizeStepGranularity 自动调整大小变动粒度 ( 跨度区间值 ) + * @param unit 字体参数类型 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAutoSizeTextTypeUniformWithConfiguration( + int autoSizeMinTextSize, + int autoSizeMaxTextSize, + int autoSizeStepGranularity, + int unit, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAutoSizeTextTypeUniformWithConfiguration( + value, autoSizeMinTextSize, autoSizeMaxTextSize, + autoSizeStepGranularity, unit + ), views + ); + return this; + } + + /** + * 设置 TextView 自动调整如果预设字体大小范围有效则修改类型为 AUTO_SIZE_TEXT_TYPE_UNIFORM + * @param presetSizes 预设字体大小范围像素为单位 + * @param unit 字体参数类型 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setAutoSizeTextTypeUniformWithPresetSizes( + int[] presetSizes, + int unit, + View... views + ) { + ForUtils.forSimpleArgs( + value -> TextViewUtils.setAutoSizeTextTypeUniformWithPresetSizes( + value, presetSizes, unit + ), views + ); + return this; + } + + // ===================== + // = RecyclerViewUtils = + // ===================== + + /** + * 设置 RecyclerView LayoutManager + * @param view {@link View} + * @param layoutManager LayoutManager + * @return Helper + */ + @Override + public ViewHelper setLayoutManager( + View view, + RecyclerView.LayoutManager layoutManager + ) { + RecyclerViewUtils.setLayoutManager(view, layoutManager); + return this; + } + + /** + * 设置 GridLayoutManager SpanCount + * @param view {@link View} + * @param spanCount Span Count + * @return Helper + */ + @Override + public ViewHelper setSpanCount( + View view, + int spanCount + ) { + RecyclerViewUtils.setSpanCount(view, spanCount); + return this; + } + + /** + * 设置 GridLayoutManager SpanCount + * @param layoutManager LayoutManager + * @param spanCount Span Count + * @return Helper + */ + @Override + public ViewHelper setSpanCount( + RecyclerView.LayoutManager layoutManager, + int spanCount + ) { + RecyclerViewUtils.setSpanCount(layoutManager, spanCount); + return this; + } + + /** + * 设置 RecyclerView Orientation + * @param view {@link View} + * @param orientation 方向 + * @return Helper + */ + @Override + public ViewHelper setOrientation( + View view, + @RecyclerView.Orientation int orientation + ) { + RecyclerViewUtils.setOrientation(view, orientation); + return this; + } + + /** + * 设置 RecyclerView Adapter + * @param view {@link View} + * @param adapter Adapter + * @return Helper + */ + @Override + public ViewHelper setAdapter( + View view, + RecyclerView.Adapter adapter + ) { + RecyclerViewUtils.setAdapter(view, adapter); + return this; + } + + /** + * RecyclerView notifyItemRemoved + * @param view {@link View} + * @param position 索引 + * @return Helper + */ + @Override + public ViewHelper notifyItemRemoved( + View view, + int position + ) { + RecyclerViewUtils.notifyItemRemoved(view, position); + return this; + } + + /** + * RecyclerView notifyItemInserted + * @param view {@link View} + * @param position 索引 + * @return Helper + */ + @Override + public ViewHelper notifyItemInserted( + View view, + int position + ) { + RecyclerViewUtils.notifyItemInserted(view, position); + return this; + } + + /** + * RecyclerView notifyItemMoved + * @param view {@link View} + * @param fromPosition 当前索引 + * @param toPosition 更新后索引 + * @return Helper + */ + @Override + public ViewHelper notifyItemMoved( + View view, + int fromPosition, + int toPosition + ) { + RecyclerViewUtils.notifyItemMoved(view, fromPosition, toPosition); + return this; + } + + /** + * RecyclerView notifyDataSetChanged + * @param view {@link View} + * @return Helper + */ + @Override + public ViewHelper notifyDataSetChanged(View view) { + RecyclerViewUtils.notifyDataSetChanged(view); + return this; + } + + /** + * 设置 RecyclerView LinearSnapHelper + * @param view {@link View} + * @return Helper + */ + @Override + public ViewHelper attachLinearSnapHelper(View view) { + RecyclerViewUtils.attachLinearSnapHelper(view); + return this; + } + + /** + * 设置 RecyclerView PagerSnapHelper + * @param view {@link View} + * @return Helper + */ + @Override + public ViewHelper attachPagerSnapHelper(View view) { + RecyclerViewUtils.attachPagerSnapHelper(view); + return this; + } + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + @Override + public ViewHelper addItemDecoration( + View view, + RecyclerView.ItemDecoration decor + ) { + RecyclerViewUtils.addItemDecoration(view, decor); + return this; + } + + /** + * 添加 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @param index 添加索引 + * @return Helper + */ + @Override + public ViewHelper addItemDecoration( + View view, + RecyclerView.ItemDecoration decor, + int index + ) { + RecyclerViewUtils.addItemDecoration(view, decor, index); + return this; + } + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param decor RecyclerView ItemDecoration + * @return Helper + */ + @Override + public ViewHelper removeItemDecoration( + View view, + RecyclerView.ItemDecoration decor + ) { + RecyclerViewUtils.removeItemDecoration(view, decor); + return this; + } + + /** + * 移除 RecyclerView ItemDecoration + * @param view {@link View} + * @param index RecyclerView ItemDecoration 索引 + * @return Helper + */ + @Override + public ViewHelper removeItemDecorationAt( + View view, + int index + ) { + RecyclerViewUtils.removeItemDecorationAt(view, index); + return this; + } + + /** + * 移除 RecyclerView 全部 ItemDecoration + * @param view {@link View} + * @return Helper + */ + @Override + public ViewHelper removeAllItemDecoration(View view) { + RecyclerViewUtils.removeAllItemDecoration(view); + return this; + } + + /** + * 设置 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + @Override + public ViewHelper setOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ) { + RecyclerViewUtils.setOnScrollListener(view, listener); + return this; + } + + /** + * 添加 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + @Override + public ViewHelper addOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ) { + RecyclerViewUtils.addOnScrollListener(view, listener); + return this; + } + + /** + * 移除 RecyclerView ScrollListener + * @param view {@link View} + * @param listener ScrollListener + * @return Helper + */ + @Override + public ViewHelper removeOnScrollListener( + View view, + RecyclerView.OnScrollListener listener + ) { + RecyclerViewUtils.removeOnScrollListener(view, listener); + return this; + } + + /** + * 清空 RecyclerView ScrollListener + * @param view {@link View} + * @return Helper + */ + @Override + public ViewHelper clearOnScrollListeners(View view) { + RecyclerViewUtils.clearOnScrollListeners(view); + return this; + } + + /** + * 设置 RecyclerView 嵌套滚动开关 + * @param enabled 嵌套滚动开关 + * @param views View[] + * @return Helper + */ + @Override + public ViewHelper setNestedScrollingEnabled( + boolean enabled, + View... views + ) { + ForUtils.forSimpleArgs( + value -> RecyclerViewUtils.setNestedScrollingEnabled(value, enabled), views + ); + return this; + } + + // ============= + // = SizeUtils = + // ============= + + /** + * 在 onCreate 中获取视图的尺寸 ( 需回调 onGetSizeListener 接口, 在 onGetSize 中获取 View 宽高 ) + * @param view {@link View} + * @param listener {@link SizeUtils.OnGetSizeListener} + * @return Helper + */ + @Override + public ViewHelper forceGetViewSize( + View view, + SizeUtils.OnGetSizeListener listener + ) { + SizeUtils.forceGetViewSize(view, listener); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/image/BitmapUtils.java b/lib/DevApp/src/main/java/dev/utils/app/image/BitmapUtils.java new file mode 100644 index 0000000000..0f1f92cc8a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/image/BitmapUtils.java @@ -0,0 +1,1712 @@ +package dev.utils.app.image; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.media.MediaMetadataRetriever; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.FloatRange; +import androidx.annotation.IntRange; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.InputStream; +import java.util.HashMap; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.app.assist.exif.ExifAssist; +import dev.utils.common.FileUtils; +import dev.utils.common.ScaleUtils; + +/** + * detail: Bitmap 工具类 + * @author Ttt + *
+ *     Android PorterDuffXfermode 的正确使用方式
+ *     @see 
+ *     Android 中一张图片占据的内存大小是如何计算
+ *     @see 
+ *     Bitmap 的六种压缩方式, Android 图片压缩
+ *     @see 
+ * 
+ */ +public final class BitmapUtils { + + private BitmapUtils() { + } + + // 日志 TAG + private static final String TAG = BitmapUtils.class.getSimpleName(); + + // ============== + // = ImageUtils = + // ============== + + /** + * 判断 Bitmap 对象是否为 null + * @param bitmap {@link Bitmap} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Bitmap bitmap) { + return ImageUtils.isEmpty(bitmap); + } + + /** + * 判断 Bitmap 对象是否不为 null + * @param bitmap {@link Bitmap} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Bitmap bitmap) { + return ImageUtils.isNotEmpty(bitmap); + } + + // ========== + // = 图片判断 = + // ========== + + /** + * 根据文件判断是否为图片 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImage(final File file) { + return file != null && isImage(file.getPath()); + } + + /** + * 根据文件判断是否为图片 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImage(final String filePath) { + if (!FileUtils.isFileExists(filePath)) return false; + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + try { + BitmapFactory.decodeFile(filePath, options); + return options.outWidth != -1 && options.outHeight != -1; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isImage"); + } + return false; + } + + // ======= + // = 宽高 = + // ======= + + /** + * 获取 Bitmap 宽度 + * @param bitmap 源图片 + * @return Bitmap 宽度 + */ + public static int getBitmapWidth(final Bitmap bitmap) { + if (isEmpty(bitmap)) return 0; + return bitmap.getWidth(); + } + + /** + * 获取 Bitmap 高度 + * @param bitmap 源图片 + * @return Bitmap 高度 + */ + public static int getBitmapHeight(final Bitmap bitmap) { + if (isEmpty(bitmap)) return 0; + return bitmap.getHeight(); + } + + /** + * 获取 Bitmap 宽高 + * @param bitmap 源图片 + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final Bitmap bitmap) { + if (isEmpty(bitmap)) return new int[]{0, 0}; + return new int[]{bitmap.getWidth(), bitmap.getHeight()}; + } + + // = + + /** + * 获取 Bitmap 宽高 + * @param file 文件 + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final File file) { + return getBitmapWidthHeight(FileUtils.getAbsolutePath(file)); + } + + /** + * 获取 Bitmap 宽高 + * @param filePath 文件路径 + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final String filePath) { + if (!FileUtils.isFileExists(filePath)) return new int[]{0, 0}; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + // 返回的 bitmap 为 null + BitmapFactory.decodeFile(filePath, options); + // options.outHeight 为原始图片的高 + return new int[]{options.outWidth, options.outHeight}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapWidthHeight"); + } + return new int[]{0, 0}; + } + + // = + + /** + * 获取 Bitmap 宽高 + * @param resId resource identifier + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(@DrawableRes final int resId) { + return getBitmapWidthHeight(DevUtils.getContext(), resId); + } + + /** + * 获取 Bitmap 宽高 + * @param context {@link Context} + * @param resId resource identifier + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight( + final Context context, + @DrawableRes final int resId + ) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + // 返回的 bitmap 为 null + BitmapFactory.decodeResource(ResourceUtils.getResources(context), resId, options); + // options.outHeight 为原始图片的高 + return new int[]{options.outWidth, options.outHeight}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapWidthHeight"); + } + return new int[]{0, 0}; + } + + // = + + /** + * 获取 Bitmap 宽高 + * @param inputStream {@link InputStream} + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final InputStream inputStream) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + // 返回的 bitmap 为 null + BitmapFactory.decodeStream(inputStream, null, options); + // options.outHeight 为原始图片的高 + return new int[]{options.outWidth, options.outHeight}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapWidthHeight"); + } + return new int[]{0, 0}; + } + + // = + + /** + * 获取 Bitmap 宽高 + * @param fd 文件描述 + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final FileDescriptor fd) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + // 返回的 bitmap 为 null + BitmapFactory.decodeFileDescriptor(fd, null, options); + // options.outHeight 为原始图片的高 + return new int[]{options.outWidth, options.outHeight}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapWidthHeight"); + } + return new int[]{0, 0}; + } + + /** + * 获取 Bitmap 宽高 + * @param data byte[] + * @return int[] { 宽度, 高度 } + */ + public static int[] getBitmapWidthHeight(final byte[] data) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + // 只解析图片信息, 不加载到内存中 + options.inJustDecodeBounds = true; + // 返回的 bitmap 为 null + BitmapFactory.decodeByteArray(data, 0, data.length, options); + // options.outHeight 为原始图片的高 + return new int[]{options.outWidth, options.outHeight}; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapWidthHeight"); + } + return new int[]{0, 0}; + } + + // = + + /** + * 复制 Bitmap + * @param bitmap {@link Bitmap} + * @return {@link Bitmap} + */ + public static Bitmap copy(final Bitmap bitmap) { + return copy(bitmap, true); + } + + /** + * 复制 Bitmap + * @param bitmap {@link Bitmap} + * @param isMutable 是否允许编辑 + * @return {@link Bitmap} + */ + public static Bitmap copy( + final Bitmap bitmap, + final boolean isMutable + ) { + if (isEmpty(bitmap)) return null; + return bitmap.copy(bitmap.getConfig(), isMutable); + } + + // = + + /** + * 获取 Alpha 位图 ( 获取源图片的轮廓 rgb 为 0 ) + * @param bitmap {@link Bitmap} + * @return Alpha 位图 + */ + public static Bitmap extractAlpha(final Bitmap bitmap) { + if (isEmpty(bitmap)) return null; + return bitmap.extractAlpha(); + } + + /** + * 重新编码 Bitmap + * @param bitmap 需要重新编码的 bitmap + * @param format 编码后的格式 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return 重新编码后的图片 + */ + public static Bitmap recode( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + return recode(bitmap, format, quality, null); + } + + /** + * 重新编码 Bitmap + * @param bitmap 需要重新编码的 bitmap + * @param format 编码后的格式 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @param options {@link BitmapFactory.Options} + * @return 重新编码后的图片 + */ + public static Bitmap recode( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality, + final BitmapFactory.Options options + ) { + if (isEmpty(bitmap) || format == null) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, quality, baos); + byte[] data = baos.toByteArray(); + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "recode"); + } + return null; + } + + /** + * Bitmap 通知回收 + * @param bitmap 待回收图片 + * @return {@code true} success, {@code false} fail + */ + public static boolean recycle(final Bitmap bitmap) { + if (bitmap == null) return false; + if (!bitmap.isRecycled()) { + try { + bitmap.recycle(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "recycle"); + } + } + return false; + } + + // ============== + // = Bitmap 操作 = + // ============== + + // ======= + // = 旋转 = + // ======= + + /** + * 旋转图片 + * @param bitmap 待操作源图片 + * @param degrees 旋转角度 + * @return 旋转后的图片 + */ + public static Bitmap rotate( + final Bitmap bitmap, + final float degrees + ) { + if (isEmpty(bitmap)) return null; + Matrix matrix = new Matrix(); + matrix.postRotate(degrees); + return Bitmap.createBitmap( + bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), + matrix, false + ); + } + + /** + * 旋转图片 + * @param bitmap 待操作源图片 + * @param degrees 旋转角度 + * @param px 旋转中心点在 X 轴的坐标 + * @param py 旋转中心点在 Y 轴的坐标 + * @return 旋转后的图片 + */ + public static Bitmap rotate( + final Bitmap bitmap, + final float degrees, + final float px, + final float py + ) { + if (isEmpty(bitmap)) return null; + Matrix matrix = new Matrix(); + matrix.postRotate(degrees, px, py); + return Bitmap.createBitmap( + bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), + matrix, false + ); + } + + /** + * 读取图片属性, 获取图片旋转角度 + * @param filePath 文件路径 + * @return 图片旋转角度 + */ + public static int getRotationDegrees(final String filePath) { + return ExifAssist.get(filePath).getRotationDegrees(); + } + + // ======= + // = 翻转 = + // ======= + + /** + * 水平翻转图片 ( 左右颠倒 ) + * @param bitmap 待操作源图片 + * @return 翻转后的图片 + */ + public static Bitmap reverseByHorizontal(final Bitmap bitmap) { + return reverse(bitmap, true); + } + + /** + * 垂直翻转图片 ( 上下颠倒 ) + * @param bitmap 待操作源图片 + * @return 翻转后的图片 + */ + public static Bitmap reverseByVertical(final Bitmap bitmap) { + return reverse(bitmap, false); + } + + /** + * 翻转图片 + * @param bitmap 待操作源图片 + * @param horizontal 是否水平翻转 + * @return 翻转后的图片 + */ + public static Bitmap reverse( + final Bitmap bitmap, + final boolean horizontal + ) { + if (isEmpty(bitmap)) return null; + Matrix matrix = new Matrix(); + if (horizontal) { + matrix.preScale(-1, 1); + } else { + matrix.preScale(1, -1); + } + return Bitmap.createBitmap( + bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), + matrix, false + ); + } + + // ======= + // = 缩放 = + // ======= + + /** + * 缩放图片 ( 指定所需宽高 ) + * @param bitmap 待操作源图片 + * @param newSize 新尺寸 ( 宽高 ) + * @return 缩放后的图片 + */ + public static Bitmap zoom( + final Bitmap bitmap, + final int newSize + ) { + return zoom(bitmap, newSize, newSize); + } + + /** + * 缩放图片 ( 指定所需宽高 ) + * @param bitmap 待操作源图片 + * @param newWidth 新宽度 + * @param newHeight 新高度 + * @return 缩放后的图片 + */ + public static Bitmap zoom( + final Bitmap bitmap, + final int newWidth, + final int newHeight + ) { + if (isEmpty(bitmap)) return null; + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } + + // = + + /** + * 缩放图片 ( 比例缩放 ) + * @param bitmap 待操作源图片 + * @param scale 缩放倍数 + * @return 缩放后的图片 + */ + public static Bitmap scale( + final Bitmap bitmap, + final float scale + ) { + return scale(bitmap, scale, scale); + } + + /** + * 缩放图片 ( 比例缩放 ) + * @param bitmap 待操作源图片 + * @param scaleX 横向缩放比例 ( 缩放宽度倍数 ) + * @param scaleY 纵向缩放比例 ( 缩放高度倍数 ) + * @return 缩放后的图片 + */ + public static Bitmap scale( + final Bitmap bitmap, + final float scaleX, + final float scaleY + ) { + if (isEmpty(bitmap)) return null; + Matrix matrix = new Matrix(); + matrix.postScale(scaleX, scaleY); + return Bitmap.createBitmap( + bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), + matrix, true + ); + } + + // ======= + // = 倾斜 = + // ======= + + /** + * 倾斜图片 + * @param bitmap 待操作源图片 + * @param kx X 轴倾斜因子 + * @param ky Y 轴倾斜因子 + * @return 倾斜后的图片 + */ + public static Bitmap skew( + final Bitmap bitmap, + final float kx, + final float ky + ) { + return skew(bitmap, kx, ky, 0, 0); + } + + /** + * 倾斜图片 + *
+     *     倾斜因子 以小数点倾斜 如: 0.1 防止数值过大 Canvas: trying to draw too large
+     * 
+ * @param bitmap 待操作源图片 + * @param kx X 轴倾斜因子 + * @param ky Y 轴倾斜因子 + * @param px X 轴轴心点 + * @param py Y 轴轴心点 + * @return 倾斜后的图片 + */ + public static Bitmap skew( + final Bitmap bitmap, + final float kx, + final float ky, + final float px, + final float py + ) { + if (isEmpty(bitmap)) return null; + Matrix matrix = new Matrix(); + matrix.setSkew(kx, ky, px, py); + return Bitmap.createBitmap( + bitmap, 0, 0, + bitmap.getWidth(), bitmap.getHeight(), + matrix, true + ); + } + + // ======= + // = 裁剪 = + // ======= + + /** + * 裁剪图片 + * @param bitmap 待操作源图片 + * @param width 裁剪宽度 + * @param height 裁剪高度 + * @return 裁剪后的图片 + */ + public static Bitmap clip( + final Bitmap bitmap, + final int width, + final int height + ) { + return clip(bitmap, 0, 0, width, height); + } + + /** + * 裁剪图片 + * @param bitmap 待操作源图片 + * @param x X 轴开始坐标 + * @param y Y 轴开始坐标 + * @param width 裁剪宽度 + * @param height 裁剪高度 + * @return 裁剪后的图片 + */ + public static Bitmap clip( + final Bitmap bitmap, + final int x, + final int y, + final int width, + final int height + ) { + if (isEmpty(bitmap)) return null; + return Bitmap.createBitmap(bitmap, x, y, width, height); + } + + // = + + /** + * 裁剪图片 ( 返回指定比例图片 ) + * @param bitmap 待操作源图片 + * @return 裁剪指定比例的图片 + */ + public static Bitmap crop(final Bitmap bitmap) { + return crop(bitmap, 16.0F, 9.0F); + } + + /** + * 裁剪图片 ( 返回指定比例图片 ) + * @param bitmap 待操作源图片 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return 裁剪指定比例的图片 + */ + public static Bitmap crop( + final Bitmap bitmap, + final float widthScale, + final float heightScale + ) { + if (bitmap == null) return null; + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + // 获取需要裁剪的高度 + int reHeight = (int) ((width * heightScale) / widthScale); + // 判断需要裁剪的高度与偏移差距 + int diffHeight = height - reHeight; + + // 属于宽度 * 对应比例 >= 高度 + if (diffHeight >= 0) { // 以高度做偏移 + return Bitmap.createBitmap( + bitmap, 0, diffHeight / 2, + width, reHeight, null, false + ); + } else { // 以宽度做偏移 + // 获取需要裁剪的宽度 + int reWidth = (int) ((height * widthScale) / heightScale); + // 判断需要裁剪的宽度与偏移差距 + int diffWidth = width - reWidth; + // 创建图片 + return Bitmap.createBitmap( + bitmap, diffWidth / 2, 0, + reWidth, height, null, false + ); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "crop"); + } + return null; + } + + // ============= + // = 合并 / 叠加 = + // ============= + + /** + * 合并图片 + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @return 合并后的图片 + */ + public static Bitmap combine( + final Bitmap bgd, + final Bitmap fg + ) { + return combine(bgd, fg, PorterDuff.Mode.SRC_ATOP, null, null); + } + + /** + * 合并图片 + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @param mode 合并模式 {@link PorterDuff.Mode} + * @return 合并后的图片 + */ + public static Bitmap combine( + final Bitmap bgd, + final Bitmap fg, + final PorterDuff.Mode mode + ) { + return combine(bgd, fg, mode, null, null); + } + + /** + * 合并图片 + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @param mode 合并模式 {@link PorterDuff.Mode} + * @param bgdPoint 后景绘制 left、top 坐标 + * @param fgPoint 前景绘制 left、top 坐标 + * @return 合并后的图片 + */ + public static Bitmap combine( + final Bitmap bgd, + final Bitmap fg, + final PorterDuff.Mode mode, + final Point bgdPoint, + final Point fgPoint + ) { + if (isEmpty(bgd) || isEmpty(fg)) return null; + + int width = Math.max(bgd.getWidth(), fg.getWidth()); + int height = Math.max(bgd.getHeight(), fg.getHeight()); + + Paint paint = new Paint(); + if (mode != null) { + paint.setXfermode(new PorterDuffXfermode(mode)); + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(newBitmap); + canvas.drawBitmap( + bgd, (bgdPoint != null) ? bgdPoint.x : 0, + (bgdPoint != null) ? bgdPoint.y : 0, null + ); + canvas.drawBitmap( + fg, (fgPoint != null) ? fgPoint.x : 0, + (fgPoint != null) ? fgPoint.y : 0, paint + ); + return newBitmap; + } + + // = + + /** + * 合并图片 ( 居中 ) + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @return 合并后的图片 + */ + public static Bitmap combineToCenter( + final Bitmap bgd, + final Bitmap fg + ) { + return combineToCenter(bgd, fg, null); + } + + /** + * 合并图片 ( 居中 ) + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @param mode 合并模式 {@link PorterDuff.Mode} + * @return 合并后的图片 + */ + public static Bitmap combineToCenter( + Bitmap bgd, + Bitmap fg, + final PorterDuff.Mode mode + ) { + if (isEmpty(bgd) || isEmpty(fg)) return null; + + // 绘制坐标点 + Point bgdPoint = new Point(); + Point fgPoint = new Point(); + + // 宽高信息 + int bgdWidth = bgd.getWidth(); + int bgdHeight = bgd.getHeight(); + + int fgWidth = fg.getWidth(); + int fgHeight = fg.getHeight(); + + if (bgdWidth > fgWidth) { + fgPoint.x = (bgdWidth - fgWidth) / 2; + } else { + bgdPoint.x = (fgWidth - bgdWidth) / 2; + } + + if (bgdHeight > fgHeight) { + fgPoint.y = (bgdHeight - fgHeight) / 2; + } else { + bgdPoint.y = (fgHeight - bgdHeight) / 2; + } + + return combine(bgd, fg, mode, bgdPoint, fgPoint); + } + + // = + + /** + * 合并图片 ( 转为相同大小 ) + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @return 合并后的图片 + */ + public static Bitmap combineToSameSize( + Bitmap bgd, + Bitmap fg + ) { + return combineToSameSize(bgd, fg, PorterDuff.Mode.SRC_ATOP); + } + + /** + * 合并图片 ( 转为相同大小 ) + * @param bgd 后景 Bitmap + * @param fg 前景 Bitmap + * @param mode 合并模式 {@link PorterDuff.Mode} + * @return 合并后的图片 + */ + public static Bitmap combineToSameSize( + Bitmap bgd, + Bitmap fg, + final PorterDuff.Mode mode + ) { + if (isEmpty(bgd) || isEmpty(fg)) return null; + + int width = Math.min(bgd.getWidth(), fg.getWidth()); + int height = Math.min(bgd.getHeight(), fg.getHeight()); + + if (fg.getWidth() != width && fg.getHeight() != height) { + fg = zoom(fg, width, height); + } + + if (bgd.getWidth() != width && bgd.getHeight() != height) { + bgd = zoom(bgd, width, height); + } + + Paint paint = new Paint(); + if (mode != null) { + paint.setXfermode(new PorterDuffXfermode(mode)); + } + + Bitmap newBitmap = Bitmap.createBitmap( + width, height, Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + canvas.drawBitmap(bgd, 0, 0, null); + canvas.drawBitmap(fg, 0, 0, paint); + return newBitmap; + } + + // ======= + // = 倒影 = + // ======= + + /** + * 图片倒影处理 + * @param bitmap 待操作源图片 + * @return 倒影处理后的图片 + */ + public static Bitmap reflection(final Bitmap bitmap) { + return reflection(bitmap, 0, getBitmapHeight(bitmap)); + } + + /** + * 图片倒影处理 + * @param bitmap 待操作源图片 + * @param reflectionHeight 倒影高度 + * @return 倒影处理后的图片 + */ + public static Bitmap reflection( + final Bitmap bitmap, + final int reflectionHeight + ) { + return reflection(bitmap, 0, reflectionHeight); + } + + /** + * 图片倒影处理 + * @param bitmap 待操作源图片 + * @param reflectionSpacing 源图片与倒影之间的间距 + * @param reflectionHeight 倒影高度 + * @return 倒影处理后的图片 + */ + public static Bitmap reflection( + final Bitmap bitmap, + final int reflectionSpacing, + final int reflectionHeight + ) { + if (isEmpty(bitmap)) return null; + if (reflectionHeight <= 0) return null; + // 获取图片宽高 + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + // 创建画布, 画布分为上中下三部分, 上: 是源图片, 中: 是源图片与倒影的间距, 下: 是倒影 + + // 创建倒影图片 + Bitmap reflectionImage = reverseByVertical(bitmap); // 垂直翻转图片 ( 上下颠倒 ) + // 创建一张宽度与源图片相同, 但高度等于 源图片的高度 + 间距 + 倒影的高度的图片 + Bitmap bitmapWithReflection = Bitmap.createBitmap( + width, height + reflectionSpacing + reflectionHeight, + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(bitmapWithReflection); + + // 将源图片画到画布的上半部分, 将倒影画到画布的下半部分, 倒影与画布顶部的间距是源图片的高度加上源图片与倒影之间的间距 + canvas.drawBitmap(bitmap, 0, 0, null); + canvas.drawBitmap(reflectionImage, 0, height + reflectionSpacing, null); + BitmapUtils.recycle(reflectionImage); + + // 边距负数处理 + int spacing = Math.max(reflectionSpacing, 0); + + // 将倒影改成半透明, 创建画笔, 并设置画笔的渐变从半透明的白色到全透明的白色, 然后再倒影上面画半透明效果 + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setShader( + new LinearGradient( + 0, bitmap.getHeight(), 0, + bitmapWithReflection.getHeight() + spacing, + 0x70ffffff, 0x00ffffff, Shader.TileMode.CLAMP + ) + ); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); + canvas.drawRect( + 0, height + spacing, width, + bitmapWithReflection.getHeight() + spacing, paint + ); + return bitmapWithReflection; + } + + // ======= + // = 圆角 = + // ======= + + /** + * 图片圆角处理 ( 非圆形 ) + *
+     *     以宽高中最小值设置为圆角尺寸, 如果宽高一致, 则处理为圆形图片
+     * 
+ * @param bitmap 待操作源图片 + * @return 圆角处理后的图片 + */ + public static Bitmap roundCorner(final Bitmap bitmap) { + if (isEmpty(bitmap)) return null; + return roundCorner(bitmap, Math.min(bitmap.getWidth(), bitmap.getHeight())); + } + + /** + * 图片圆角处理 ( 非圆形 ) + * @param bitmap 待操作源图片 + * @param pixels 圆角大小 + * @return 圆角处理后的图片 + */ + public static Bitmap roundCorner( + final Bitmap bitmap, + final float pixels + ) { + if (isEmpty(bitmap)) return null; + // 创建一个同源图片一样大小的矩形, 用于把源图片绘制到这个矩形上 + Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + RectF rectF = new RectF(rect); // 创建一个精度更高的矩形, 用于画出圆角效果 + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setColor(0xff424242); // 设置画笔的颜色为不透明的灰色 + + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + canvas.drawARGB(0, 0, 0, 0); + canvas.drawRoundRect(rectF, pixels, pixels, paint); + // 绘制底圆后, 进行合并 ( 交集处理 ) + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + return newBitmap; + } + + // = + + /** + * 图片圆角处理 ( 非圆形, 只有 leftTop、rightTop ) + * @param bitmap 待操作源图片 + * @param pixels 圆角大小 + * @return 圆角处理后的图片 + */ + public static Bitmap roundCornerTop( + final Bitmap bitmap, + final float pixels + ) { + return roundCorner(bitmap, pixels, new boolean[]{true, true, true, false}); + } + + /** + * 图片圆角处理 ( 非圆形, 只有 leftBottom、rightBottom ) + * @param bitmap 待操作源图片 + * @param pixels 圆角大小 + * @return 圆角处理后的图片 + */ + public static Bitmap roundCornerBottom( + final Bitmap bitmap, + final float pixels + ) { + return roundCorner(bitmap, pixels, new boolean[]{true, false, true, true}); + } + + // = + + /** + * 图片圆角处理 ( 非圆形 ) + *
+     *     只要左上圆角: new boolean[] {true, true, false, false};
+     *     只要右上圆角: new boolean[] {false, true, true, false};
+     *     

+ * 只要左下圆角: new boolean[] {true, false, false, true}; + * 只要右下圆角: new boolean[] {false, false, true, true}; + *
+ * @param bitmap 待操作源图片 + * @param pixels 圆角大小 + * @param directions 需要圆角的方向 [left, top, right, bottom] + * @return 圆角处理后的图片 + */ + public static Bitmap roundCorner( + final Bitmap bitmap, + final float pixels, + final boolean[] directions + ) { + if (isEmpty(bitmap)) return null; + if (directions == null || directions.length != 4) return null; + // 创建一个同源图片一样大小的矩形, 用于把源图片绘制到这个矩形上 + Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); + RectF rectF = new RectF(rect); // 创建一个精度更高的矩形, 用于画出圆角效果 + + // ============= + // = 圆角方向控制 = + // ============= + + if (!directions[0]) { + rectF.left -= pixels; + } + + if (!directions[1]) { + rectF.top -= pixels; + } + + if (!directions[2]) { + rectF.right += pixels; + } + + if (!directions[3]) { + rectF.bottom += pixels; + } + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setColor(0xff424242); // 设置画笔的颜色为不透明的灰色 + + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + canvas.drawARGB(0, 0, 0, 0); + canvas.drawRoundRect(rectF, pixels, pixels, paint); + // 绘制底圆后, 进行合并 ( 交集处理 ) + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, rect, rect, paint); + return newBitmap; + } + + // ======= + // = 圆形 = + // ======= + + /** + * 图片圆形处理 + * @param bitmap 待操作源图片 + * @return 圆形处理后的图片 + */ + public static Bitmap round(final Bitmap bitmap) { + return round(bitmap, 0, 0); + } + + /** + * 图片圆形处理 + * @param bitmap 待操作源图片 + * @param borderSize 边框尺寸 + * @param borderColor 边框颜色 + * @return 圆形处理后的图片 + */ + public static Bitmap round( + final Bitmap bitmap, + @IntRange(from = 0) final int borderSize, + @ColorInt final int borderColor + ) { + if (isEmpty(bitmap)) return null; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int size = Math.min(width, height); + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + float center = size / 2F; + RectF rectF = new RectF(0, 0, width, height); + rectF.inset((width - size) / 2F, (height - size) / 2F); + + Matrix matrix = new Matrix(); + matrix.setTranslate(rectF.left, rectF.top); + if (width != height) { + matrix.preScale((float) size / width, (float) size / height); + } + BitmapShader shader = new BitmapShader( + bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP + ); + shader.setLocalMatrix(matrix); + paint.setShader(shader); + + Bitmap newBitmap = Bitmap.createBitmap(width, height, bitmap.getConfig()); + Canvas canvas = new Canvas(newBitmap); + canvas.drawRoundRect(rectF, center, center, paint); + + if (borderSize > 0) { + paint.setShader(null); + paint.setColor(borderColor); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(borderSize); + float radius = center - borderSize / 2F; + canvas.drawCircle(width / 2F, height / 2F, radius, paint); + } + return newBitmap; + } + + // =============== + // = 圆角、圆形边框 = + // =============== + + /** + * 添加圆角边框 + * @param bitmap 源图片 + * @param borderSize 边框尺寸 + * @param color 边框颜色 + * @param cornerRadius 圆角半径 + * @return 圆角边框图 + */ + public static Bitmap addCornerBorder( + final Bitmap bitmap, + @IntRange(from = 1) final int borderSize, + @ColorInt final int color, + @FloatRange(from = 0) final float cornerRadius + ) { + return addBorder(bitmap, borderSize, color, false, cornerRadius); + } + + /** + * 添加圆形边框 + * @param bitmap 源图片 + * @param borderSize 边框尺寸 + * @param color 边框颜色 + * @return 圆形边框图 + */ + public static Bitmap addCircleBorder( + final Bitmap bitmap, + @IntRange(from = 1) final int borderSize, + @ColorInt final int color + ) { + return addBorder(bitmap, borderSize, color, true, 0); + } + + /** + * 添加边框 + * @param bitmap 待操作源图片 + * @param borderSize 边框尺寸 + * @param color 边框颜色 + * @param isCircle 是否画圆 + * @param cornerRadius 圆角半径 + * @return 添加边框后的图片 + */ + public static Bitmap addBorder( + final Bitmap bitmap, + @IntRange(from = 1) final int borderSize, + @ColorInt final int color, + final boolean isCircle, + final float cornerRadius + ) { + if (isEmpty(bitmap)) return null; + + Bitmap newBitmap = bitmap.copy(bitmap.getConfig(), true); + int width = newBitmap.getWidth(); + int height = newBitmap.getHeight(); + + Canvas canvas = new Canvas(newBitmap); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(color); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(borderSize); + if (isCircle) { + float radius = Math.min(width, height) / 2F - borderSize / 2F; + canvas.drawCircle(width / 2F, height / 2F, radius, paint); + } else { + int halfBorderSize = borderSize >> 1; + RectF rectF = new RectF( + halfBorderSize, halfBorderSize, + width - halfBorderSize, + height - halfBorderSize + ); + canvas.drawRoundRect(rectF, cornerRadius, cornerRadius, paint); + } + return newBitmap; + } + + // ======= + // = 水印 = + // ======= + + /** + * 添加文字水印 + * @param bitmap 待操作源图片 + * @param content 水印文本 + * @param textSize 水印字体大小 pixel + * @param color 水印字体颜色 + * @param x 起始坐标 x + * @param y 起始坐标 y + * @return 添加文字水印后的图片 + */ + public static Bitmap addTextWatermark( + final Bitmap bitmap, + final String content, + final float textSize, + @ColorInt final int color, + final float x, + final float y + ) { + if (isEmpty(bitmap) || content == null) return null; + + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(color); + paint.setTextSize(textSize); + Rect bounds = new Rect(); + paint.getTextBounds(content, 0, content.length(), bounds); + + Bitmap newBitmap = bitmap.copy(bitmap.getConfig(), true); + Canvas canvas = new Canvas(newBitmap); + canvas.drawText(content, x, y + textSize, paint); + return newBitmap; + } + + /** + * 添加图片水印 + * @param bitmap 待操作源图片 + * @param watermark 水印图片 + * @param x 起始坐标 x + * @param y 起始坐标 y + * @param alpha 透明度 + * @return 添加图片水印后的图片 + */ + public static Bitmap addImageWatermark( + final Bitmap bitmap, + final Bitmap watermark, + final int x, + final int y, + @IntRange(from = 0, to = 255) final int alpha + ) { + if (isEmpty(bitmap)) return null; + Bitmap newBitmap = bitmap.copy(bitmap.getConfig(), true); + if (!isEmpty(watermark)) { + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + Canvas canvas = new Canvas(newBitmap); + paint.setAlpha(alpha); + canvas.drawBitmap(watermark, x, y, paint); + } + return newBitmap; + } + + // ======= + // = 压缩 = + // ======= + + /** + * 按缩放宽高压缩 + *
+     *     可搭配 {@link ScaleUtils} 工具类使用
+     * 
+ * @param bitmap 待操作源图片 + * @param newWidth 新宽度 + * @param newHeight 新高度 + * @return 缩放宽高压缩后的图片 + */ + public static Bitmap compressByZoom( + final Bitmap bitmap, + final int newWidth, + final int newHeight + ) { + return zoom(bitmap, newWidth, newHeight); + } + + /** + * 按缩放比例压缩 + *
+     *     可搭配 {@link ScaleUtils} 工具类使用
+     * 
+ * @param bitmap 待操作源图片 + * @param scaleX 横向缩放比例 ( 缩放宽度倍数 ) + * @param scaleY 纵向缩放比例 ( 缩放高度倍数 ) + * @return 缩放比例压缩后的图片 + */ + public static Bitmap compressByScale( + final Bitmap bitmap, + final float scaleX, + final float scaleY + ) { + return scale(bitmap, scaleX, scaleY); + } + + // = + + /** + * 按质量压缩 + * @param bitmap 待操作源图片 + * @param quality 质量 + * @return 质量压缩过的图片 + */ + public static Bitmap compressByQuality( + final Bitmap bitmap, + @IntRange(from = 0, to = 100) final int quality + ) { + return compressByQuality( + bitmap, Bitmap.CompressFormat.JPEG, quality, null + ); + } + + /** + * 按质量压缩 + * @param bitmap 待操作源图片 + * @param quality 质量 + * @param options {@link BitmapFactory.Options} + * @return 质量压缩过的图片 + */ + public static Bitmap compressByQuality( + final Bitmap bitmap, + @IntRange(from = 0, to = 100) final int quality, + final BitmapFactory.Options options + ) { + return compressByQuality(bitmap, Bitmap.CompressFormat.JPEG, quality, options); + } + + /** + * 按质量压缩 + * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param quality 质量 + * @param options {@link BitmapFactory.Options} + * @return 质量压缩过的图片 + */ + public static Bitmap compressByQuality( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality, + final BitmapFactory.Options options + ) { + if (isEmpty(bitmap) || format == null) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, quality, baos); + byte[] data = baos.toByteArray(); + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "compressByQuality"); + } + return null; + } + + // = + + /** + * 按质量压缩 ( 图片大小 ) + * @param bitmap 待操作源图片 + * @param maxByteSize 允许最大值字节数 + * @return 质量压缩过的图片 + */ + public static Bitmap compressByByteSize( + final Bitmap bitmap, + final long maxByteSize + ) { + return compressByByteSize( + bitmap, Bitmap.CompressFormat.JPEG, maxByteSize, null + ); + } + + /** + * 按质量压缩 ( 图片大小 ) + * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param maxByteSize 允许最大值字节数 + * @return 质量压缩过的图片 + */ + public static Bitmap compressByByteSize( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + final long maxByteSize + ) { + return compressByByteSize(bitmap, format, maxByteSize, null); + } + + /** + * 按质量压缩 ( 图片大小 ) + * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param maxByteSize 允许最大值字节数 + * @param options {@link BitmapFactory.Options} + * @return 质量压缩过的图片 + */ + public static Bitmap compressByByteSize( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + final long maxByteSize, + final BitmapFactory.Options options + ) { + if (isEmpty(bitmap) || maxByteSize <= 0) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, 100, baos); + byte[] data; + if (baos.size() <= maxByteSize) { // 最好质量的不大于最大字节, 则返回最佳质量 + data = baos.toByteArray(); + } else { + baos.reset(); + bitmap.compress(format, 0, baos); + if (baos.size() >= maxByteSize) { // 最差质量不小于最大字节, 则返回最差质量 + data = baos.toByteArray(); + } else { // 二分法寻找最佳质量 + int start = 0; + int end = 100; + int mid = 0; + while (start < end) { + mid = (start + end) / 2; + baos.reset(); + bitmap.compress(format, mid, baos); + int len = baos.size(); + if (len == maxByteSize) { + break; + } else if (len > maxByteSize) { + end = mid - 1; + } else { + start = mid + 1; + } + } + if (end == mid - 1) { + baos.reset(); + bitmap.compress(format, start, baos); + } + data = baos.toByteArray(); + } + } + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "compressByByteSize"); + } + return null; + } + + // = + + /** + * 按采样大小压缩 + * @param bitmap 待操作源图片 + * @param sampleSize 采样率大小 + * @return 按采样率压缩后的图片 + */ + public static Bitmap compressBySampleSize( + final Bitmap bitmap, + final int sampleSize + ) { + return compressBySampleSize(bitmap, Bitmap.CompressFormat.JPEG, sampleSize); + } + + /** + * 按采样大小压缩 + * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param sampleSize 采样率大小 + * @return 按采样率压缩后的图片 + */ + public static Bitmap compressBySampleSize( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + final int sampleSize + ) { + if (isEmpty(bitmap) || format == null) return null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = sampleSize; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, 100, baos); + byte[] data = baos.toByteArray(); + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "compressBySampleSize"); + } + return null; + } + + // = + + /** + * 按采样大小压缩 + * @param bitmap 待操作源图片 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return 按采样率压缩后的图片 + */ + public static Bitmap compressBySampleSize( + final Bitmap bitmap, + final int maxWidth, + final int maxHeight + ) { + return compressBySampleSize( + bitmap, Bitmap.CompressFormat.JPEG, maxWidth, maxHeight + ); + } + + /** + * 按采样大小压缩 + * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return 按采样率压缩后的图片 + */ + public static Bitmap compressBySampleSize( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + final int maxWidth, + final int maxHeight + ) { + if (isEmpty(bitmap)) return null; + try { + // 获取宽高信息 + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, 100, baos); + + // 进行采样压缩 + byte[] data = baos.toByteArray(); + BitmapFactory.decodeByteArray(data, 0, data.length, options); + options.inSampleSize = calculateInSampleSize(options, maxWidth, maxHeight); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "compressBySampleSize"); + } + return null; + } + + // = + + /** + * 计算采样大小 + *
+     *     最大宽高只是阀值, 实际算出来的图片将小于等于这个值
+     * 
+ * @param options {@link BitmapFactory.Options} + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return 采样大小 + */ + public static int calculateInSampleSize( + final BitmapFactory.Options options, + final int maxWidth, + final int maxHeight + ) { + if (options == null) return 0; + + int height = options.outHeight; + int width = options.outWidth; + int inSampleSize = 1; + while (height > maxHeight || width > maxWidth) { + height >>= 1; + width >>= 1; + inSampleSize <<= 1; + } + return inSampleSize; + } + + /** + * 计算最佳压缩质量值 + *
+     *     搭配 {@link ImageUtils#saveBitmapToSDCard} 等需要传入 quality 方法使用
+     * 
+ * @param bitmap 待操作源图片 + * @param format 图片压缩格式 + * @param maxByteSize 允许最大值字节数 + * @return 最佳压缩质量值 + */ + public static int calculateQuality( + final Bitmap bitmap, + final Bitmap.CompressFormat format, + final long maxByteSize + ) { + if (isEmpty(bitmap) || maxByteSize <= 0) return -1; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, 100, baos); + if (baos.size() <= maxByteSize) { // 最好质量的不大于最大字节, 则返回最佳质量 + return 100; + } else { + baos.reset(); + bitmap.compress(format, 0, baos); + if (baos.size() >= maxByteSize) { // 最差质量不小于最大字节, 则返回最差质量 + return 0; + } else { // 二分法寻找最佳质量 + int start = 0; + int end = 100; + int mid = 0; + while (start < end) { + mid = (start + end) / 2; + baos.reset(); + bitmap.compress(format, mid, baos); + int len = baos.size(); + if (len == maxByteSize) { + return mid; + } else if (len > maxByteSize) { + end = mid - 1; + } else { + start = mid + 1; + } + } + if (end == mid - 1) { + return start; + } + return mid; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "calculateQuality"); + } + return -1; + } + + // ============ + // = 视频缩略图 = + // ============ + + /** + * 获取视频缩略图 + * @param path 视频路径 + * @return {@link Bitmap} + */ + public static Bitmap getVideoThumbnail(final String path) { + return getVideoThumbnail(path, -1); + } + + /** + * 获取视频缩略图 + *
+     *     // 获取视频的长度 ( 单位为毫秒 )
+     *     String duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+     *     // 缩放缩略图
+     *     ThumbnailUtils.extractThumbnail(bitmap,  width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
+     *     // 获取视频缩略图
+     *     ThumbnailUtils.createVideoThumbnail(path, MediaStore.Video.Thumbnails.MICRO_KIND);
+     * 
+ * @param path 视频路径 + * @param millis 对应毫秒视频帧 + * @return {@link Bitmap} + */ + public static Bitmap getVideoThumbnail( + final String path, + final long millis + ) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { + // 设置视频路径 + if (FileUtils.isFileExists(path)) { + retriever.setDataSource(path); + } else { + retriever.setDataSource(path, new HashMap<>()); + } + return retriever.getFrameAtTime(millis); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getVideoThumbnail"); + } finally { + try { + retriever.release(); + } catch (Exception ignored) { + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/image/ImageConvertUtils.java b/lib/DevApp/src/main/java/dev/utils/app/image/ImageConvertUtils.java new file mode 100644 index 0000000000..e0b4d69f97 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/image/ImageConvertUtils.java @@ -0,0 +1,194 @@ +package dev.utils.app.image; + +import android.graphics.Bitmap; + +import dev.utils.LogPrintUtils; + +/** + * detail: 图片格式转换工具类 + * @author Ttt + */ +public final class ImageConvertUtils { + + private ImageConvertUtils() { + } + + // 日志 TAG + private static final String TAG = ImageConvertUtils.class.getSimpleName(); + + // =============== + // = 转换 BMP 图片 = + // =============== + + /** + * detail: 转换 BMP 内部类 + * @author Ttt + */ + private static final class BMP { + + private BMP() { + } + + /** + * BMP 位图, 头结构 + * @param size 文件总大小 ( 字节数 ) + * @return BMP 头结构 byte[] + */ + private static byte[] addBMPImageHeader(final int size) { + byte[] buffer = new byte[14]; + // = 文件标识 BM = + buffer[0] = 0x42; + buffer[1] = 0x4D; + // = 位图文件大小 = + buffer[2] = (byte) (size); + buffer[3] = (byte) (size >> 8); + buffer[4] = (byte) (size >> 16); + buffer[5] = (byte) (size >> 24); + // = 位图文件保留字, 必须为 0 = + buffer[6] = 0x00; + buffer[7] = 0x00; + buffer[8] = 0x00; + buffer[9] = 0x00; + // = 位图数据开始之间的偏移量 = + buffer[10] = 0x36; + buffer[11] = 0x00; + buffer[12] = 0x00; + buffer[13] = 0x00; + return buffer; + } + + /** + * BMP 位图, 头信息 + * @param width 宽度 + * @param height 高度 + * @return BMP 头信息 byte[] + */ + private static byte[] addBMPImageInfosHeader( + final int width, + final int height + ) { + byte[] buffer = new byte[40]; + // = + buffer[0] = 0x28; + buffer[1] = 0x00; + buffer[2] = 0x00; + buffer[3] = 0x00; + // = + buffer[4] = (byte) (width); + buffer[5] = (byte) (width >> 8); + buffer[6] = (byte) (width >> 16); + buffer[7] = (byte) (width >> 24); + // = + buffer[8] = (byte) (height); + buffer[9] = (byte) (height >> 8); + buffer[10] = (byte) (height >> 16); + buffer[11] = (byte) (height >> 24); + // = + buffer[12] = 0x01; + buffer[13] = 0x00; + // = + buffer[14] = 0x20; // 位数 0x20 ( 32 位 ) + buffer[15] = 0x00; + // = + buffer[16] = 0x00; + buffer[17] = 0x00; + buffer[18] = 0x00; + buffer[19] = 0x00; + // = + buffer[20] = 0x00; + buffer[21] = 0x00; + buffer[22] = 0x00; + buffer[23] = 0x00; + // = + buffer[24] = (byte) 0xE0; + buffer[25] = 0x01; + buffer[26] = 0x00; + buffer[27] = 0x00; + // = + buffer[28] = 0x02; + buffer[29] = 0x03; + buffer[30] = 0x00; + buffer[31] = 0x00; + // = + buffer[32] = 0x00; + buffer[33] = 0x00; + buffer[34] = 0x00; + buffer[35] = 0x00; + // = + buffer[36] = 0x00; + buffer[37] = 0x00; + buffer[38] = 0x00; + buffer[39] = 0x00; + return buffer; + } + + /** + * 增加位图 ARGB 值 + * @param data 图片数据 + * @param width 宽度 + * @param height 高度 + * @return BMP ARGB byte[] + */ + private static byte[] addBMP_ARGB8888( + final int[] data, + final int width, + final int height + ) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + byte[] buffer = new byte[width * height * 4]; // A + R + G + B = 4 + int offset = 0; // 计算偏移量 + for (int i = len - 1; i >= 0; i -= width) { + // DIB 文件格式最后一行为第一行, 每行按从左到右顺序 + int start = i - width + 1; + for (int j = start; j <= i; j++) { + buffer[offset] = (byte) (data[j]); + buffer[offset + 1] = (byte) (data[j] >> 8); + buffer[offset + 2] = (byte) (data[j] >> 16); + buffer[offset + 3] = (byte) (data[j] >> 24); + offset += 4; + } + } + return buffer; + } + + /** + * 图片转换 BMP 格式 byte[] 数据 + * @param bitmap 待转换图片 + * @return BMP 格式 byte[] 数据 + */ + public static byte[] convertBMP(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + int width = bitmap.getWidth(), height = bitmap.getHeight(); + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + + byte[] rgb = addBMP_ARGB8888(pixels, width, height); + byte[] header = addBMPImageHeader(rgb.length); + byte[] infos = addBMPImageInfosHeader(width, height); + byte[] buffer = new byte[54 + rgb.length]; + + System.arraycopy(header, 0, buffer, 0, header.length); + System.arraycopy(infos, 0, buffer, 14, infos.length); + System.arraycopy(rgb, 0, buffer, 54, rgb.length); + return buffer; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "convertBMP"); + } + return null; + } + } + + // = + + /** + * 图片转换 BMP 格式 byte[] 数据 + * @param bitmap 待转换图片 + * @return BMP 格式 byte[] 数据 + */ + public static byte[] convertBMP(final Bitmap bitmap) { + return BMP.convertBMP(bitmap); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/image/ImageFilterUtils.java b/lib/DevApp/src/main/java/dev/utils/app/image/ImageFilterUtils.java new file mode 100644 index 0000000000..d9b65f93e3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/image/ImageFilterUtils.java @@ -0,0 +1,891 @@ +package dev.utils.app.image; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.os.Build; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; + +import androidx.annotation.IntRange; +import androidx.annotation.RequiresApi; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: 图片 ( 滤镜、效果 ) 工具类 + * @author Ttt + */ +public final class ImageFilterUtils { + + private ImageFilterUtils() { + } + + // 日志 TAG + private static final String TAG = ImageFilterUtils.class.getSimpleName(); + + // ======= + // = 效果 = + // ======= + + // ======= + // = 模糊 = + // ======= + + /** + * 图片模糊处理 ( Android RenderScript 实现, 效率最高 ) + * @param bitmap 待模糊图片 + * @param radius 模糊度 (0-25) + * @return 模糊后的图片 + */ + @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) + public static Bitmap blur( + final Bitmap bitmap, + @IntRange(from = 0, to = 25) final int radius + ) { + if (bitmap == null) return null; + try { + // 创建 RenderScript 内核对象 + RenderScript rs = RenderScript.create(DevUtils.getContext()); + // 由于 RenderScript 并没有使用 VM 来分配内存, 所以需要使用 Allocation 类来创建和分配内存空间 + // 创建 Allocation 对象的时候其实内存是空的, 需要使用 copyTo() 将数据填充进去 + Allocation input = Allocation.createFromBitmap( + rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, + Allocation.USAGE_SCRIPT + ); + Allocation output = Allocation.createTyped(rs, input.getType()); + // 创建一个模糊效果的 RenderScript 的工具对象, 第二个参数 Element 相当于一种像素处理的算法, 高斯模糊的话用这个就好 + ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + + // 设置 blur 对象的输入内存 + blur.setInput(input); + // 设置渲染的模糊程度, 25F 是最大模糊度 + blur.setRadius(radius); + // 将输出数据保存到输出内存中 + blur.forEach(output); + // 将数据填充到 bitmap 中 + output.copyTo(bitmap); + + // 销毁它们释放内存 + input.destroy(); + output.destroy(); + blur.destroy(); + rs.destroy(); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "blur"); + } + return null; + } + + /** + * 图片模糊处理 ( 毛玻璃化 FastBlur Java 实现 ) + *
+     *     模糊程度越低, 处理速度越快
+     * 
+ * @param bitmap 待模糊图片 + * @param radius 模糊度 + * @return 模糊后的图片 + */ + public static Bitmap fastBlur( + final Bitmap bitmap, + final int radius + ) { + if (bitmap == null) return null; + // 如果 Bitmap 不允许编辑, 则返回 null + if (!bitmap.isMutable()) return null; + + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + int[] pix = new int[width * height]; + + bitmap.getPixels(pix, 0, width, 0, 0, width, height); + + int wm = width - 1; + int hm = height - 1; + int wh = width * height; + int div = radius + radius + 1; + + int[] r = new int[wh]; + int[] g = new int[wh]; + int[] b = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int[] vmin = new int[Math.max(width, height)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + int[] dv = new int[256 * divsum]; + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } + + yw = yi = 0; + + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + for (y = 0; y < height; y++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + for (i = -radius; i <= radius; i++) { + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + sir = stack[i + radius]; + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + rbs = r1 - Math.abs(i); + rsum += sir[0] * rbs; + gsum += sir[1] * rbs; + bsum += sir[2] * rbs; + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + stackpointer = radius; + + for (x = 0; x < width; x++) { + + r[yi] = dv[rsum]; + g[yi] = dv[gsum]; + b[yi] = dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (y == 0) { + vmin[x] = Math.min(x + radius + 1, wm); + } + p = pix[yw + vmin[x]]; + + sir[0] = (p & 0xff0000) >> 16; + sir[1] = (p & 0x00ff00) >> 8; + sir[2] = (p & 0x0000ff); + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[(stackpointer) % div]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi++; + } + yw += width; + } + for (x = 0; x < width; x++) { + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + yp = -radius * width; + for (i = -radius; i <= radius; i++) { + yi = Math.max(0, yp) + x; + + sir = stack[i + radius]; + + sir[0] = r[yi]; + sir[1] = g[yi]; + sir[2] = b[yi]; + + rbs = r1 - Math.abs(i); + + rsum += r[yi] * rbs; + gsum += g[yi] * rbs; + bsum += b[yi] * rbs; + + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + + if (i < hm) { + yp += width; + } + } + yi = x; + stackpointer = radius; + for (y = 0; y < height; y++) { + // Preserve alpha channel: ( 0xff000000 & pix[yi] ) + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + rsum -= routsum; + gsum -= goutsum; + bsum -= boutsum; + + stackstart = stackpointer - radius + div; + sir = stack[stackstart % div]; + + routsum -= sir[0]; + goutsum -= sir[1]; + boutsum -= sir[2]; + + if (x == 0) { + vmin[y] = Math.min(y + r1, hm) * width; + } + p = x + vmin[y]; + + sir[0] = r[p]; + sir[1] = g[p]; + sir[2] = b[p]; + + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + + rsum += rinsum; + gsum += ginsum; + bsum += binsum; + + stackpointer = (stackpointer + 1) % div; + sir = stack[stackpointer]; + + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + + rinsum -= sir[0]; + ginsum -= sir[1]; + binsum -= sir[2]; + + yi += width; + } + } + bitmap.setPixels(pix, 0, width, 0, 0, width, height); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "fastBlur"); + } + return null; + } + + // ========== + // = 滤镜效果 = + // ========== + + /** + * 怀旧效果处理 + * @param bitmap 待操作源图片 + * @return 怀旧效果处理后的图片 + */ + public static Bitmap nostalgic(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB, newR, newG, newB; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 0; i < height; i++) { + for (int k = 0; k < width; k++) { + pixColor = pixels[width * i + k]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + newR = (int) (0.393 * pixR + 0.769 * pixG + 0.189 * pixB); + newG = (int) (0.349 * pixR + 0.686 * pixG + 0.168 * pixB); + newB = (int) (0.272 * pixR + 0.534 * pixG + 0.131 * pixB); + int newColor = Color.argb(255, Math.min(newR, 255), + Math.min(newG, 255), Math.min(newB, 255) + ); + pixels[width * i + k] = newColor; + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "nostalgic"); + } + return null; + } + + /** + * 光照效果处理 + * @param bitmap 待操作源图片 + * @param centerX 光源在 X 轴的位置 + * @param centerY 光源在 Y 轴的位置 + * @return 光照效果处理后的图片 + */ + public static Bitmap sunshine( + final Bitmap bitmap, + final int centerX, + final int centerY + ) { + if (bitmap == null) return null; + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB, newR, newG, newB; + + int radius = Math.min(centerX, centerY); + + final float strength = 150F; // 光照强度 100 ~ 150 + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = pixR; + newG = pixG; + newB = pixB; + + // 计算当前点到光照中心的距离, 平面座标系中求两点之间的距离 + int distance = (int) (Math.pow((centerY - i), 2) + Math.pow(centerX - k, 2)); + if (distance < radius * radius) { + // 按照距离大小计算增加的光照值 + int result = (int) (strength * (1.0 - Math.sqrt(distance) / radius)); + newR = pixR + result; + newG = pixG + result; + newB = pixB + result; + } + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[pos] = Color.argb(255, newR, newG, newB); + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sunshine"); + } + return null; + } + + /** + * 底片效果处理 + * @param bitmap 待操作源图片 + * @return 底片效果处理后的图片 + */ + public static Bitmap film(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + // ARGB 的最大值 + final int MAX_VALUE = 255; + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB, newR, newG, newB; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = MAX_VALUE - pixR; + newG = MAX_VALUE - pixG; + newB = MAX_VALUE - pixB; + + newR = Math.min(MAX_VALUE, Math.max(0, newR)); + newG = Math.min(MAX_VALUE, Math.max(0, newG)); + newB = Math.min(MAX_VALUE, Math.max(0, newB)); + + pixels[pos] = Color.argb(MAX_VALUE, newR, newG, newB); + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "film"); + } + return null; + } + + /** + * 柔化效果处理 + *
+     *     delta 取值范围只要大于等于 1 就可以, 但是避免太大, 导致变得很暗, 限制 1-24
+     * 
+ * @param bitmap 待操作源图片 + * @param delta 图片的亮暗程度值, 越小图片会越亮 + * @return 柔化效果处理后的图片 + */ + public static Bitmap soften( + final Bitmap bitmap, + @IntRange(from = 1, to = 24) final int delta + ) { + if (bitmap == null) return null; + if (delta > 24 || delta <= 0) return null; + try { + // 高斯矩阵 + int[] gauss = new int[]{1, 2, 1, 2, 4, 2, 1, 2, 1}; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB; + + int newR = 0, newG = 0, newB = 0; + + int idx; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + idx = 0; + for (int m = -1; m <= 1; m++) { + for (int n = -1; n <= 1; n++) { + pixColor = pixels[(i + m) * width + k + n]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR += (pixR * gauss[idx]); + newG += (pixG * gauss[idx]); + newB += (pixB * gauss[idx]); + idx++; + } + } + newR /= delta; + newG /= delta; + newB /= delta; + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[i * width + k] = Color.argb(255, newR, newG, newB); + + newR = 0; + newG = 0; + newB = 0; + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "soften"); + } + return null; + } + + /** + * 锐化效果处理 + * @param bitmap 待操作源图片 + * @return 锐化效果处理后的图片 + */ + public static Bitmap sharpen(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + // 拉普拉斯矩阵 + int[] laplacian = new int[]{-1, -1, -1, -1, 9, -1, -1, -1, -1}; + + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB; + + int newR = 0, newG = 0, newB = 0; + + int idx; + + float alpha = 0.3F; + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + idx = 0; + for (int m = -1; m <= 1; m++) { + for (int n = -1; n <= 1; n++) { + pixColor = pixels[(i + n) * width + k + m]; + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + newR = newR + (int) (pixR * laplacian[idx] * alpha); + newG = newG + (int) (pixG * laplacian[idx] * alpha); + newB = newB + (int) (pixB * laplacian[idx] * alpha); + idx++; + } + } + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[i * width + k] = Color.argb(255, newR, newG, newB); + newR = 0; + newG = 0; + newB = 0; + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "sharpen"); + } + return null; + } + + /** + * 浮雕效果处理 + * @param bitmap 待操作源图片 + * @return 浮雕效果处理后的图片 + */ + public static Bitmap emboss(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + int pixColor, pixR, pixG, pixB, newR, newG, newB; + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + int pos; + for (int i = 1, length = height - 1; i < length; i++) { + for (int k = 1, len = width - 1; k < len; k++) { + pos = i * width + k; + pixColor = pixels[pos]; + + pixR = Color.red(pixColor); + pixG = Color.green(pixColor); + pixB = Color.blue(pixColor); + + pixColor = pixels[pos + 1]; + newR = Color.red(pixColor) - pixR + 127; + newG = Color.green(pixColor) - pixG + 127; + newB = Color.blue(pixColor) - pixB + 127; + + newR = Math.min(255, Math.max(0, newR)); + newG = Math.min(255, Math.max(0, newG)); + newB = Math.min(255, Math.max(0, newB)); + + pixels[pos] = Color.argb(255, newR, newG, newB); + } + } + + Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); + newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "emboss"); + } + return null; + } + +// /** +// * 转为灰度图片 +// * @param bitmap 待操作源图片 +// * @return 灰度图 +// */ +// public static Bitmap gray(final Bitmap bitmap) { +// if (bitmap == null) return null; +// try { +// int width = bitmap.getWidth(); // 获取位图的宽 +// int height = bitmap.getHeight(); // 获取位图的高 +// int[] pixels = new int[width * height]; // 通过位图的大小创建像素点数组 +// bitmap.getPixels(pixels, 0, width, 0, 0, width, height); +// +// int alpha = 0xFF << 24; // 默认将 bitmap 当成 24 色图片 +// for (int i = 0; i < height; i++) { +// for (int j = 0; j < width; j++) { +// int grey = pixels[width * i + j]; +// +// int red = ((grey & 0x00FF0000) >> 16); +// int green = ((grey & 0x0000FF00) >> 8); +// int blue = (grey & 0x000000FF); +// +// grey = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11); +// grey = alpha | (grey << 16) | (grey << 8) | grey; +// pixels[width * i + j] = grey; +// } +// } +// Bitmap newBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565); +// newBitmap.setPixels(pixels, 0, width, 0, 0, width, height); +// return newBitmap; +// } catch (Exception e) { +// LogPrintUtils.eTag(TAG, e, "gray"); +// } +// return null; +// } + + /** + * 转为灰度图片 + * @param bitmap 待操作源图片 + * @return 灰度图 + */ + public static Bitmap gray(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig() + ); + Canvas canvas = new Canvas(newBitmap); + Paint paint = new Paint(); + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.setSaturation(0); + ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix); + paint.setColorFilter(colorMatrixColorFilter); + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "gray"); + } + return null; + } + + // ======= + // = 色彩 = + // ======= + + /** + * 饱和度处理 + * @param bitmap 待操作源图片 + * @param saturationValue 新的饱和度值 + * @return 改变了饱和度值之后的图片 + */ + public static Bitmap saturation( + final Bitmap bitmap, + final int saturationValue + ) { + if (bitmap == null) return null; + try { + // 计算出符合要求的饱和度值 + float newSaturationValue = saturationValue * 1.0F / 127; + // 创建一个颜色矩阵 + ColorMatrix saturationColorMatrix = new ColorMatrix(); + // 设置饱和度值 + saturationColorMatrix.setSaturation(newSaturationValue); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(saturationColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + // 将源图片使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saturation"); + } + return null; + } + + /** + * 亮度处理 + * @param bitmap 待操作源图片 + * @param lumValue 新的亮度值 + * @return 改变了亮度值之后的图片 + */ + public static Bitmap lum( + final Bitmap bitmap, + final int lumValue + ) { + if (bitmap == null) return null; + try { + // 计算出符合要求的亮度值 + float newLumValue = lumValue * 1.0F / 127; + // 创建一个颜色矩阵 + ColorMatrix lumColorMatrix = new ColorMatrix(); + // 设置亮度值 + lumColorMatrix.setScale(newLumValue, newLumValue, newLumValue, 1); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(lumColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + // 将源图片使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "lum"); + } + return null; + } + + /** + * 色相处理 + * @param bitmap 待操作源图片 + * @param hueValue 新的色相值 + * @return 改变了色相值之后的图片 + */ + public static Bitmap hue( + final Bitmap bitmap, + final int hueValue + ) { + if (bitmap == null) return null; + try { + // 计算出符合要求的色相值 + float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; + // 创建一个颜色矩阵 + ColorMatrix hueColorMatrix = new ColorMatrix(); + // 控制让红色区在色轮上旋转的角度 + hueColorMatrix.setRotate(0, newHueValue); + // 控制让绿红色区在色轮上旋转的角度 + hueColorMatrix.setRotate(1, newHueValue); + // 控制让蓝色区在色轮上旋转的角度 + hueColorMatrix.setRotate(2, newHueValue); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(hueColorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + // 将源图片使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "hue"); + } + return null; + } + + /** + * 亮度、色相、饱和度处理 + * @param bitmap 待操作源图片 + * @param lumValue 亮度值 + * @param hueValue 色相值 + * @param saturationValue 饱和度值 + * @return 亮度、色相、饱和度处理后的图片 + */ + public static Bitmap lumHueSaturation( + final Bitmap bitmap, + final int lumValue, + final int hueValue, + final int saturationValue + ) { + if (bitmap == null) return null; + try { + // 计算出符合要求的饱和度值 + float newSaturationValue = saturationValue * 1.0F / 127; + // 计算出符合要求的亮度值 + float newLumValue = lumValue * 1.0F / 127; + // 计算出符合要求的色相值 + float newHueValue = (hueValue - 127) * 1.0F / 127 * 180; + // 创建一个颜色矩阵并设置其饱和度 + ColorMatrix colorMatrix = new ColorMatrix(); + // 设置饱和度值 + colorMatrix.setSaturation(newSaturationValue); + // 设置亮度值 + colorMatrix.setScale(newLumValue, newLumValue, newLumValue, 1); + // 控制让红色区在色轮上旋转的角度 + colorMatrix.setRotate(0, newHueValue); + // 控制让绿红色区在色轮上旋转的角度 + colorMatrix.setRotate(1, newHueValue); + // 控制让蓝色区在色轮上旋转的角度 + colorMatrix.setRotate(2, newHueValue); + // 创建一个画笔并设置其颜色过滤器 + Paint paint = new Paint(); + paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix)); + // 创建一个新的图片并创建画布 + Bitmap newBitmap = Bitmap.createBitmap( + bitmap.getWidth(), bitmap.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(newBitmap); + // 将源图片使用给定的画笔画到画布上 + canvas.drawBitmap(bitmap, 0, 0, paint); + return newBitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "lumHueSaturation"); + } + return null; + } + + // = + + /** + * 将 YUV 格式的图片的源数据从横屏模式转为竖屏模式 + *
+     *     注: 将源图片的宽高互换就是新图片的宽高
+     * 
+ * @param sourceData YUV 格式的图片的源数据 + * @param width 宽 + * @param height 高 + * @return byte[] + */ + public static byte[] yuvLandscapeToPortrait( + final byte[] sourceData, + final int width, + final int height + ) { + if (sourceData == null || sourceData.length == 0) return null; + byte[] rotatedData = new byte[sourceData.length]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + rotatedData[x * height + height - y - 1] = sourceData[x + y * width]; + } + } + return rotatedData; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/image/ImageUtils.java b/lib/DevApp/src/main/java/dev/utils/app/image/ImageUtils.java new file mode 100644 index 0000000000..f3ef1092c4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/image/ImageUtils.java @@ -0,0 +1,1433 @@ +package dev.utils.app.image; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntRange; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileUtils; + +/** + * detail: Image ( Bitmap、Drawable 等 ) 工具类 + * @author Ttt + *
+ *     图片文件头标识信息
+ *     @see 
+ *     @see 
+ *     各类文件的文件头标志
+ *     @see 
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class ImageUtils { + + private ImageUtils() { + } + + // 日志 TAG + private static final String TAG = ImageUtils.class.getSimpleName(); + + /** + * 判断 Bitmap 对象是否为 null + * @param bitmap {@link Bitmap} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Bitmap bitmap) { + return bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0; + } + + /** + * 判断 Bitmap 对象是否不为 null + * @param bitmap {@link Bitmap} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Bitmap bitmap) { + return bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0; + } + + // = + + /** + * 判断 Drawable 对象是否为 null + * @param drawable {@link Drawable} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Drawable drawable) { + return drawable == null; + } + + /** + * 判断 Drawable 对象是否不为 null + * @param drawable {@link Drawable} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Drawable drawable) { + return drawable != null; + } + + // ============= + // = 图片类型判断 = + // ============= + + /** + * 根据文件名判断文件是否为图片 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final File file) { + return FileUtils.isImageFormats(file); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final String filePath) { + return FileUtils.isImageFormats(filePath); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @param imageFormats 图片格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats( + final String filePath, + final String[] imageFormats + ) { + return FileUtils.isImageFormats(filePath, imageFormats); + } + + // = + + /** + * detail: 图片类型 + * @author Ttt + */ + public enum ImageType { + + TYPE_PNG("png"), + + TYPE_JPG("jpg"), + + TYPE_BMP("bmp"), + + TYPE_GIF("gif"), + + TYPE_WEBP("webp"), + + TYPE_ICO("ico"), + + TYPE_TIFF("tiff"), + + TYPE_UNKNOWN("unknown"); + + private final String value; + + ImageType(String value) { + this.value = value; + } + + /** + * 获取图片类型字符串 + * @return 图片类型字符串 + */ + public String getValue() { + return value; + } + } + + /** + * 获取图片类型 + * @param filePath 文件路径 + * @return {@link ImageType} 图片类型 + */ + public static ImageType getImageType(final String filePath) { + return getImageType(FileUtils.getFileByPath(filePath)); + } + + /** + * 获取图片类型 + * @param file 文件 + * @return {@link ImageType} 图片类型 + */ + public static ImageType getImageType(final File file) { + if (file == null) return ImageType.TYPE_UNKNOWN; + InputStream is = null; + try { + is = new FileInputStream(file); + return getImageType(is); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageType"); + return ImageType.TYPE_UNKNOWN; + } finally { + CloseUtils.closeIOQuietly(is); + } + } + + /** + * 获取图片类型 + * @param inputStream {@link InputStream} + * @return {@link ImageType} 图片类型 + */ + public static ImageType getImageType(final InputStream inputStream) { + if (inputStream == null) return ImageType.TYPE_UNKNOWN; + try { + byte[] bytes = new byte[12]; + return inputStream.read(bytes, 0, 12) != -1 ? getImageType(bytes) : null; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getImageType"); + return ImageType.TYPE_UNKNOWN; + } + } + + /** + * 获取图片类型 + * @param data 图片 byte[] + * @return {@link ImageType} 图片类型 + */ + public static ImageType getImageType(final byte[] data) { + if (isPNG(data)) return ImageType.TYPE_PNG; + if (isJPEG(data)) return ImageType.TYPE_JPG; + if (isBMP(data)) return ImageType.TYPE_BMP; + if (isGif(data)) return ImageType.TYPE_GIF; + if (isWEBP(data)) return ImageType.TYPE_WEBP; + if (isICO(data)) return ImageType.TYPE_ICO; + if (isTIFF(data)) return ImageType.TYPE_TIFF; + return ImageType.TYPE_UNKNOWN; + } + + // = + + /** + * 判断是否 PNG 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isPNG(final byte[] data) { + return data != null && data.length >= 8 + && (data[0] == (byte) 137 && data[1] == (byte) 80 + && data[2] == (byte) 78 && data[3] == (byte) 71 + && data[4] == (byte) 13 && data[5] == (byte) 10 + && data[6] == (byte) 26 && data[7] == (byte) 10); + } + + /** + * 判断是否 JPG 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isJPEG(final byte[] data) { + return data != null && data.length >= 2 + && (data[0] == (byte) 0xFF) && (data[1] == (byte) 0xD8); + } + + /** + * 判断是否 BMP 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isBMP(final byte[] data) { + return data != null && data.length >= 2 + && (data[0] == (byte) 0x42) && (data[1] == (byte) 0x4d); + } + + /** + * 判断是否 GIF 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isGif(final byte[] data) { + return data != null && data.length >= 6 + && data[0] == 'G' && data[1] == 'I' && data[2] == 'F' + && data[3] == '8' && (data[4] == '7' || data[4] == '9') + && data[5] == 'a'; + } + + /** + * 判断是否 WEBP 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isWEBP(final byte[] data) { + return data != null && data.length >= 12 + && data[0] == 'R' && data[1] == 'I' && data[2] == 'F' + && data[3] == 'F' && data[8] == 'W' + && (data[9] == 'E' || data[10] == 'B') + && data[11] == 'P'; + } + + /** + * 判断是否 ICO 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isICO(final byte[] data) { + return data != null && data.length >= 4 + && data[0] == 0 && data[1] == 0 + && data[2] == 1 && data[3] == 0; + } + + /** + * 判断是否 TIFF 图片 + * @param data 图片 byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isTIFF(final byte[] data) { + if (data != null && data.length >= 4) { + if (data[0] == (byte) 73 && data[1] == (byte) 73 + && data[2] == (byte) 0x2a && data[3] == 0) { + return true; // 49 49 2a 00 + } else if (data[0] == (byte) 0x4d && data[1] == (byte) 0x4d + && data[2] == 0 && data[3] == (byte) 0x2a) { + return true; // 4d 4d 00 2a + } else if (data[0] == (byte) 0x4d && data[1] == (byte) 0x4d + && data[2] == 0 && data[3] == (byte) 0x2b) { + return true; // 4d 4d 00 2b + } else { + return data[0] == (byte) 73 && data[1] == (byte) 32 && data[2] == (byte) 73; // 49 20 49 + } + } + return false; + } + + // ========== + // = 本地获取 = + // ========== + + /** + * 获取 Bitmap + * @param file 文件 + * @return {@link Bitmap} + */ + public static Bitmap decodeFile(final File file) { + return decodeFile(FileUtils.getAbsolutePath(file), null); + } + + /** + * 获取 Bitmap + * @param file 文件 + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeFile( + final File file, + final BitmapFactory.Options options + ) { + return decodeFile(FileUtils.getAbsolutePath(file), options); + } + + /** + * 获取 Bitmap + * @param filePath 文件路径 + * @return {@link Bitmap} + */ + public static Bitmap decodeFile(final String filePath) { + return decodeFile(filePath, null); + } + + /** + * 获取 Bitmap + * @param filePath 文件路径 + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeFile( + final String filePath, + final BitmapFactory.Options options + ) { + if (filePath == null) return null; + try { + return BitmapFactory.decodeFile(filePath, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "decodeFile"); + return null; + } + } + + // = + + /** + * 获取 Bitmap + * @param resId resource identifier + * @return {@link Bitmap} + */ + public static Bitmap decodeResource(@DrawableRes final int resId) { + return decodeResource(DevUtils.getContext(), resId, null); + } + + /** + * 获取 Bitmap + * @param context {@link Context} + * @param resId resource identifier + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeResource( + final Context context, + @DrawableRes final int resId, + final BitmapFactory.Options options + ) { + try { + return BitmapFactory.decodeResource( + ResourceUtils.getResources(context), resId, options + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "decodeResource"); + return null; + } + } + + // = + + /** + * 获取 Bitmap + * @param inputStream {@link InputStream} + * @return {@link Bitmap} + */ + public static Bitmap decodeStream(final InputStream inputStream) { + return decodeStream(inputStream, null); + } + + /** + * 获取 Bitmap + * @param inputStream {@link InputStream} + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeStream( + final InputStream inputStream, + final BitmapFactory.Options options + ) { + if (inputStream == null) return null; + try { + return BitmapFactory.decodeStream(inputStream, null, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "decodeStream"); + return null; + } + } + + // = + + /** + * 获取 Bitmap + * @param fd 文件描述 + * @return {@link Bitmap} + */ + public static Bitmap decodeFileDescriptor(final FileDescriptor fd) { + return decodeFileDescriptor(fd, null); + } + + /** + * 获取 Bitmap + * @param fd 文件描述 + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeFileDescriptor( + final FileDescriptor fd, + final BitmapFactory.Options options + ) { + if (fd == null) return null; + try { + return BitmapFactory.decodeFileDescriptor(fd, null, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "decodeFileDescriptor"); + return null; + } + } + + // = + + /** + * 获取 Bitmap + * @param data byte[] + * @return {@link Bitmap} + */ + public static Bitmap decodeByteArray(final byte[] data) { + return decodeByteArray(data, 0, (data == null) ? 0 : data.length, null); + } + + /** + * 获取 Bitmap + * @param data byte[] + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeByteArray( + final byte[] data, + final BitmapFactory.Options options + ) { + return decodeByteArray(data, 0, (data == null) ? 0 : data.length, options); + } + + /** + * 获取 Bitmap + * @param data byte[] + * @param offset 偏移量 + * @param length 所需长度 + * @return {@link Bitmap} + */ + public static Bitmap decodeByteArray( + final byte[] data, + final int offset, + final int length + ) { + return decodeByteArray(data, offset, length, null); + } + + /** + * 获取 Bitmap + * @param data byte[] + * @param offset 偏移量 + * @param length 所需长度 + * @param options {@link BitmapFactory.Options} + * @return {@link Bitmap} + */ + public static Bitmap decodeByteArray( + final byte[] data, + final int offset, + final int length, + final BitmapFactory.Options options + ) { + if (data == null) return null; + if ((offset | length) < 0 || data.length < offset + length) return null; + try { + return BitmapFactory.decodeByteArray(data, offset, length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "decodeByteArray"); + return null; + } + } + + // ========== + // = 本地保存 = + // ========== + + /** + * 保存图片到 SDCard ( JPEG ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardJPEG( + final Bitmap bitmap, + final String filePath + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.JPEG, 100); + } + + /** + * 保存图片到 SDCard ( JPEG ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardJPEG( + final Bitmap bitmap, + final File file + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.JPEG, 100); + } + + // = + + /** + * 保存图片到 SDCard ( JPEG ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardJPEG( + final Bitmap bitmap, + final String filePath, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.JPEG, quality); + } + + /** + * 保存图片到 SDCard ( JPEG ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardJPEG( + final Bitmap bitmap, + final File file, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.JPEG, quality); + } + + // = + + /** + * 保存图片到 SDCard ( PNG ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardPNG( + final Bitmap bitmap, + final String filePath + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.PNG, 100); + } + + /** + * 保存图片到 SDCard ( PNG ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardPNG( + final Bitmap bitmap, + final File file + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.PNG, 100); + } + + // = + + /** + * 保存图片到 SDCard ( PNG ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardPNG( + final Bitmap bitmap, + final String filePath, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.PNG, quality); + } + + /** + * 保存图片到 SDCard ( PNG ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardPNG( + final Bitmap bitmap, + final File file, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.PNG, quality); + } + + // = + + /** + * 保存图片到 SDCard ( WEBP ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardWEBP( + final Bitmap bitmap, + final String filePath + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.WEBP, 100); + } + + /** + * 保存图片到 SDCard ( WEBP ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardWEBP( + final Bitmap bitmap, + final File file + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.WEBP, 100); + } + + // = + + /** + * 保存图片到 SDCard ( WEBP ) + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardWEBP( + final Bitmap bitmap, + final String filePath, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, filePath, Bitmap.CompressFormat.WEBP, quality); + } + + /** + * 保存图片到 SDCard ( WEBP ) + * @param bitmap 待保存图片 + * @param file 存储路径 + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCardWEBP( + final Bitmap bitmap, + final File file, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, file, Bitmap.CompressFormat.WEBP, quality); + } + + // = + + /** + * 保存图片到 SDCard + * @param bitmap 待保存图片 + * @param filePath 存储路径 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCard( + final Bitmap bitmap, + final String filePath, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToSDCard(bitmap, FileUtils.getFileByPath(filePath), format, quality); + } + + /** + * 保存图片到 SDCard + * @param bitmap 待保存图片 + * @param file 存储路径 + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToSDCard( + final Bitmap bitmap, + final File file, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + if (bitmap == null || file == null || format == null) return false; + // 防止 Bitmap 为 null, 或者创建文件夹失败 ( 文件存在则删除 ) + if (isEmpty(bitmap) || !FileUtils.createFileByDeleteOldFile(file)) return false; + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + bitmap.compress(format, quality, os); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saveBitmapToSDCard"); + return false; + } finally { + CloseUtils.closeIOQuietly(os); + } + return true; + } + + // ================ + // = OutputStream = + // ================ + + /** + * 保存 JPEG 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamJPEG( + final Bitmap bitmap, + final OutputStream stream + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.JPEG, 100); + } + + /** + * 保存 JPEG 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamJPEG( + final Bitmap bitmap, + final OutputStream stream, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.JPEG, quality); + } + + /** + * 保存 PNG 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamPNG( + final Bitmap bitmap, + final OutputStream stream + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.PNG, 100); + } + + /** + * 保存 PNG 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamPNG( + final Bitmap bitmap, + final OutputStream stream, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.PNG, quality); + } + + /** + * 保存 WEBP 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamWEBP( + final Bitmap bitmap, + final OutputStream stream + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.WEBP, 100); + } + + /** + * 保存 WEBP 图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStreamWEBP( + final Bitmap bitmap, + final OutputStream stream, + @IntRange(from = 0, to = 100) final int quality + ) { + return saveBitmapToStream(bitmap, stream, Bitmap.CompressFormat.WEBP, quality); + } + + /** + * 保存图片 + * @param bitmap 待保存图片 + * @param stream {@link OutputStream} + * @param format 如 Bitmap.CompressFormat.PNG + * @param quality 质量 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveBitmapToStream( + final Bitmap bitmap, + final OutputStream stream, + final Bitmap.CompressFormat format, + @IntRange(from = 0, to = 100) final int quality + ) { + if (bitmap == null || stream == null || format == null) return false; + try { + bitmap.compress(format, quality, stream); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "saveBitmapToStream"); + return false; + } finally { + CloseUtils.closeIOQuietly(stream); + } + return true; + } + + // ============ + // = Drawable = + // ============ + + /** + * 获取 .9 Drawable + *
+     *     .9 图片如果需要着色, 需要传入 NinePatchDrawable
+     *     setColorFilter(get9PatchDrawable(drawable), color);
+     * 
+ * @param drawable {@link Drawable} + * @return .9 {@link NinePatchDrawable} + */ + public static NinePatchDrawable get9PatchDrawable(final Drawable drawable) { + try { + return (NinePatchDrawable) drawable; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get9PatchDrawable"); + } + return null; + } + + /** + * 图片着色 ( tint ) + * @param drawable {@link Drawable} + * @param color 颜色值 + * @return 着色后的 {@link Drawable} + */ + public static Drawable setColorFilter( + final Drawable drawable, + @ColorInt final int color + ) { + return setColorFilter(drawable, color, PorterDuff.Mode.SRC_IN); + } + + /** + * 图片着色 ( tint ) + * @param drawable {@link Drawable} + * @param color 颜色值 + * @param mode 着色模式 {@link PorterDuff.Mode} + * @return 着色后的 {@link Drawable} + */ + public static Drawable setColorFilter( + final Drawable drawable, + @ColorInt final int color, + final PorterDuff.Mode mode + ) { + if (drawable != null && mode != null) { + try { + drawable.setColorFilter(color, mode); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setColorFilter"); + } + } + return drawable; + } + + // = + + /** + * 图片着色 ( tint ) + * @param drawable {@link Drawable} + * @param colorFilter 颜色过滤 ( 效果 ) + * @return 着色后的 {@link Drawable} + */ + public static Drawable setColorFilter( + final Drawable drawable, + final ColorFilter colorFilter + ) { + if (drawable != null && colorFilter != null) { + try { + drawable.setColorFilter(colorFilter); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setColorFilter"); + } + } + return drawable; + } + + // ========== + // = Bitmap = + // ========== + + /** + * 获取 Bitmap + * @param file 文件 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final File file, + final int maxWidth, + final int maxHeight + ) { + return getBitmap(FileUtils.getAbsolutePath(file), maxWidth, maxHeight); + } + + /** + * 获取 Bitmap + * @param filePath 文件路径 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final String filePath, + final int maxWidth, + final int maxHeight + ) { + if (filePath == null) return null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, options); + options.inSampleSize = BitmapUtils.calculateInSampleSize( + options, maxWidth, maxHeight + ); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(filePath, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + return null; + } + } + + /** + * 获取 Bitmap + * @param resId resource identifier + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + @DrawableRes final int resId, + final int maxWidth, + final int maxHeight + ) { + return getBitmap( + DevUtils.getContext(), resId, + maxWidth, maxHeight + ); + } + + /** + * 获取 Bitmap + * @param context {@link Context} + * @param resId resource identifier + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final Context context, + @DrawableRes final int resId, + final int maxWidth, + final int maxHeight + ) { + try { + Resources resources = ResourceUtils.getResources(context); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(resources, resId, options); + options.inSampleSize = BitmapUtils.calculateInSampleSize( + options, maxWidth, maxHeight + ); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(resources, resId, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + return null; + } + } + + /** + * 获取 Bitmap + * @param inputStream {@link InputStream} + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final InputStream inputStream, + final int maxWidth, + final int maxHeight + ) { + if (inputStream == null) return null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(inputStream, null, options); + options.inSampleSize = BitmapUtils.calculateInSampleSize( + options, maxWidth, maxHeight + ); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeStream(inputStream, null, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + return null; + } + } + + /** + * 获取 Bitmap + * @param fd 文件描述 + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final FileDescriptor fd, + final int maxWidth, + final int maxHeight + ) { + if (fd == null) return null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fd, null, options); + options.inSampleSize = BitmapUtils.calculateInSampleSize( + options, maxWidth, maxHeight + ); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFileDescriptor(fd, null, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + return null; + } + } + + /** + * 获取 Bitmap + * @param data byte[] + * @param maxWidth 最大宽度 + * @param maxHeight 最大高度 + * @return {@link Bitmap} + */ + public static Bitmap getBitmap( + final byte[] data, + final int maxWidth, + final int maxHeight + ) { + if (data == null) return null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(data, 0, data.length, options); + options.inSampleSize = BitmapUtils.calculateInSampleSize( + options, maxWidth, maxHeight + ); + options.inJustDecodeBounds = false; + return BitmapFactory.decodeByteArray(data, 0, data.length, options); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmap"); + return null; + } + } + + // = + + /** + * 通过 View 绘制为 Bitmap + * @param view {@link View} + * @return {@link Bitmap} + */ + public static Bitmap getBitmapFromView(final View view) { + if (view == null) return null; + try { + Bitmap bitmap = Bitmap.createBitmap( + view.getWidth(), view.getHeight(), + Bitmap.Config.ARGB_8888 + ); + Canvas canvas = new Canvas(bitmap); + view.layout(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); + view.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapFromView"); + } + return null; + } + + /** + * 通过 View Cache 绘制为 Bitmap + * @param view {@link View} + * @return {@link Bitmap} + */ + public static Bitmap getBitmapFromViewCache(final View view) { + if (view == null) return null; + try { + // 清除视图焦点 + view.clearFocus(); + // 将视图设为不可点击 + view.setPressed(false); + + // 获取视图是否可以保存画图缓存 + boolean willNotCache = view.willNotCacheDrawing(); + view.setWillNotCacheDrawing(false); + + // 获取绘制缓存位图的背景颜色 + int color = view.getDrawingCacheBackgroundColor(); + // 设置绘图背景颜色 + view.setDrawingCacheBackgroundColor(0); + if (color != 0) { // 获取的背景不是黑色的则释放以前的绘图缓存 + view.destroyDrawingCache(); // 释放绘图资源所使用的缓存 + } + + // 重新创建绘图缓存, 此时的背景色是黑色 + view.buildDrawingCache(); + // 获取绘图缓存, 注意这里得到的只是一个图像的引用 + Bitmap cacheBitmap = view.getDrawingCache(); + if (cacheBitmap == null) return null; + + Bitmap bitmap = Bitmap.createBitmap(cacheBitmap); + // 释放位图内存 + view.destroyDrawingCache(); + // 回滚以前的缓存设置、缓存颜色设置 + view.setWillNotCacheDrawing(willNotCache); + view.setDrawingCacheBackgroundColor(color); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapFromViewCache"); + } + return null; + } + + // ======================= + // = Bitmap、Drawable 转换 = + // ======================= + + // ============== + // = 转为 byte[] = + // ============== + + /** + * Bitmap 转换成 byte[] + * @param bitmap 待转换图片 + * @return byte[] + */ + public static byte[] bitmapToByte(final Bitmap bitmap) { + return bitmapToByte(bitmap, 100, Bitmap.CompressFormat.PNG); + } + + /** + * Bitmap 转换成 byte[] + * @param bitmap 待转换图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return byte[] + */ + public static byte[] bitmapToByte( + final Bitmap bitmap, + final Bitmap.CompressFormat format + ) { + return bitmapToByte(bitmap, 100, format); + } + + /** + * Bitmap 转换成 byte[] + * @param bitmap 待转换图片 + * @param quality 质量 + * @param format 如 Bitmap.CompressFormat.PNG + * @return byte[] + */ + public static byte[] bitmapToByte( + final Bitmap bitmap, + @IntRange(from = 0, to = 100) final int quality, + final Bitmap.CompressFormat format + ) { + if (bitmap == null || format == null) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(format, quality, baos); + return baos.toByteArray(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "bitmapToByte"); + } + return null; + } + + // = + + /** + * Drawable 转换成 byte[] + * @param drawable 待转换图片 + * @return byte[] + */ + public static byte[] drawableToByte(final Drawable drawable) { + return drawableToByte(drawable, 100, Bitmap.CompressFormat.PNG); + } + + /** + * Drawable 转换成 byte[] + * @param drawable 待转换图片 + * @param format 如 Bitmap.CompressFormat.PNG + * @return byte[] + */ + public static byte[] drawableToByte( + final Drawable drawable, + final Bitmap.CompressFormat format + ) { + return drawableToByte(drawable, 100, format); + } + + /** + * Drawable 转换成 byte[] + * @param drawable 待转换图片 + * @param quality 质量 + * @param format 如 Bitmap.CompressFormat.PNG + * @return byte[] + */ + public static byte[] drawableToByte( + final Drawable drawable, + @IntRange(from = 0, to = 100) final int quality, + final Bitmap.CompressFormat format + ) { + if (drawable == null || format == null) return null; + return bitmapToByte(drawableToBitmap(drawable), quality, format); + } + + // ========== + // = Bitmap = + // ========== + + /** + * byte[] 转 Bitmap + * @param data byte[] + * @return {@link Bitmap} + */ + public static Bitmap byteToBitmap(final byte[] data) { + return decodeByteArray(data); + } + + /** + * Bitmap 转 Drawable + * @param bitmap 待转换图片 + * @return {@link Drawable} + */ + public static Drawable bitmapToDrawable(final Bitmap bitmap) { + if (bitmap == null) return null; + try { + return new BitmapDrawable(ResourceUtils.getResources(), bitmap); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "bitmapToDrawable"); + } + return null; + } + + // ============ + // = Drawable = + // ============ + + /** + * byte[] 转 Drawable + * @param data byte[] + * @return {@link Drawable} + */ + public static Drawable byteToDrawable(final byte[] data) { + return bitmapToDrawable(decodeByteArray(data)); + } + + /** + * Drawable 转 Bitmap + * @param drawable 待转换图片 + * @return {@link Bitmap} + */ + public static Bitmap drawableToBitmap(final Drawable drawable) { + if (drawable == null) return null; + // 属于 BitmapDrawable 直接转换 + if (drawable instanceof BitmapDrawable) { + try { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "drawableToBitmap - BitmapDrawable"); + } + } + try { + // 获取 drawable 的宽高 + int width = drawable.getIntrinsicWidth(); + int height = drawable.getIntrinsicHeight(); + // 获取 drawable 的颜色格式 + Bitmap.Config config = (drawable.getOpacity() != PixelFormat.OPAQUE) + ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + // 创建 bitmap + Bitmap bitmap = Bitmap.createBitmap(width, height, config); + // 创建 bitmap 画布 + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, width, height); + // 把 drawable 内容画到画布中 + drawable.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "drawableToBitmap"); + } + return null; + } + + // = + + /** + * 设置 Drawable 绘制区域 + * @param drawable {@link Drawable} + * @return {@link Drawable} + */ + public static Drawable setBounds(final Drawable drawable) { + try { + drawable.setBounds( + 0, 0, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight() + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBounds"); + } + return drawable; + } + + /** + * 设置 Drawable 绘制区域 + * @param drawable {@link Drawable} + * @param right right 坐标 + * @param bottom bottom 坐标 + * @return {@link Drawable} + */ + public static Drawable setBounds( + final Drawable drawable, + final int right, + final int bottom + ) { + return setBounds(drawable, 0, 0, right, bottom); + } + + /** + * 设置 Drawable 绘制区域 + * @param drawable {@link Drawable} + * @param left left 坐标 + * @param top top 坐标 + * @param right right 坐标 + * @param bottom bottom 坐标 + * @return {@link Drawable} + */ + public static Drawable setBounds( + final Drawable drawable, + final int left, + final int top, + final int right, + final int bottom + ) { + try { + drawable.setBounds(left, top, right, bottom); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setBounds"); + } + return drawable; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/info/ApkInfoItem.java b/lib/DevApp/src/main/java/dev/utils/app/info/ApkInfoItem.java new file mode 100644 index 0000000000..d3e7cf538b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/info/ApkInfoItem.java @@ -0,0 +1,396 @@ +package dev.utils.app.info; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.text.format.Formatter; + +import androidx.annotation.Keep; + +import java.io.File; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.R; +import dev.utils.app.SignaturesUtils; +import dev.utils.app.VersionUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; + +/** + * detail: APK 信息 Item + * @author Ttt + */ +public final class ApkInfoItem { + + // 日志 TAG + private static final String TAG = ApkInfoItem.class.getSimpleName(); + + @Keep // APP 基本信息实体类 + private final AppInfoBean appInfoBean; + @Keep // APP MD5 签名 + private final String appMD5; + @Keep // APP SHA1 签名 + private final String appSHA1; + @Keep // APP SHA256 签名 + private final String appSHA256; + @Keep // APP 最低支持 Android SDK 版本 + private int minSdkVersion = -1; + @Keep // APP 兼容 SDK 版本 + private final int targetSdkVersion; + @Keep // APP 安装包大小 + private final String apkLength; + @Keep // 证书对象 + private X509Certificate cert; + @Keep // 证书生成日期 + private Date notBefore; + @Keep // 证书有效期 + private Date notAfter; + @Keep // 证书是否过期 + private boolean effective; + @Keep // 证书发布方 + private String certPrincipal; + @Keep // 证书版本号 + private String certVersion; + @Keep // 证书算法名称 + private String certSigAlgName; + @Keep // 证书算法 OID + private String certSigAlgOID; + @Keep // 证书机器码 + private String certSerialnumber; + @Keep // 证书 DER 编码 + private String certDERCode; + @Keep // APP 参数集 + private final List listKeyValues = new ArrayList<>(); + + /** + * 获取 ApkInfoItem + * @param packageInfo {@link PackageInfo} + * @return {@link ApkInfoItem} + */ + protected static ApkInfoItem get(final PackageInfo packageInfo) { + try { + return new ApkInfoItem(packageInfo); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get"); + } + return null; + } + + /** + * 初始化 ApkInfoItem + * @param packageInfo {@link PackageInfo} + */ + private ApkInfoItem(final PackageInfo packageInfo) { + // 获取 Context + Context context = DevUtils.getContext(); + // 格式化日期 + SimpleDateFormat sdf = DateUtils.getDefaultFormat(); + // = + // 获取 APP 信息 + appInfoBean = new AppInfoBean(packageInfo); + // 获取签名信息 + Signature[] signatures = SignaturesUtils.getSignaturesFromApk( + new File(appInfoBean.getSourceDir()) + ); + // = + // APP MD5 签名 + appMD5 = SignaturesUtils.signatureMD5(signatures); + // APP SHA1 + appSHA1 = SignaturesUtils.signatureSHA1(signatures); + // APP SHA256 + appSHA256 = SignaturesUtils.signatureSHA256(signatures); + // 属于 7.0 以上才有的方法 + if (VersionUtils.isN()) { + // APP 最低支持 Android SDK 版本 + minSdkVersion = packageInfo.applicationInfo.minSdkVersion; + } + // APP 兼容 SDK 版本 + targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; + // APP 安装包大小 + apkLength = Formatter.formatFileSize( + DevUtils.getContext(), + FileUtils.getFileLength(appInfoBean.getSourceDir()) + ); + + // 是否保存 + boolean isError = false; + // 临时签名信息 + List listTemps = new ArrayList<>(); + + try { + // 证书对象 + cert = SignaturesUtils.getX509Certificate(signatures); + // 证书生成日期 + notBefore = cert.getNotBefore(); + // 证书有效期 + notAfter = cert.getNotAfter(); + // 设置有效期 + StringBuilder builder = new StringBuilder(); + builder.append(sdf.format(notBefore)); + builder.append(" ").append(context.getString(R.string.dev_str_to)).append(" "); // 至 + builder.append(sdf.format(notAfter)); + builder.append(DevFinal.SYMBOL.NEW_LINE_X2); + builder.append(notBefore); + builder.append(" ").append(context.getString(R.string.dev_str_to)).append(" "); + builder.append(notAfter); + // 保存有效期转换信息 + String effectiveStr = builder.toString(); + // 证书是否过期 + effective = false; + try { + cert.checkValidity(); + // CertificateExpiredException ( 证书已过期 ) + // CertificateNotYetValidException ( 证书不再有效 ) + } catch (CertificateExpiredException ce) { + effective = true; + } catch (CertificateNotYetValidException ce) { + effective = true; + } + // 证书发布方 + certPrincipal = cert.getIssuerX500Principal().toString(); + // 证书版本号 + certVersion = String.valueOf(cert.getVersion()); + // 证书算法名称 + certSigAlgName = cert.getSigAlgName(); + // 证书算法 OID + certSigAlgOID = cert.getSigAlgOID(); + // 证书机器码 + certSerialnumber = cert.getSerialNumber().toString(); + try { + // 证书 DER 编码 + certDERCode = ConvertUtils.toHexString(cert.getTBSCertificate()); + } catch (CertificateEncodingException ignored) { + } + // 证书有效期 + listTemps.add(KeyValue.get(R.string.dev_str_effective, effectiveStr)); + // 判断是否过期 + listTemps.add( + KeyValue.get( + R.string.dev_str_iseffective, + effective ? context.getString(R.string.dev_str_overdue) + : context.getString(R.string.dev_str_notoverdue) + ) + ); + // 证书发布方 + listTemps.add(KeyValue.get(R.string.dev_str_principal, certPrincipal)); + // 证书版本号 + listTemps.add(KeyValue.get(R.string.dev_str_version, certVersion)); + // 证书算法名称 + listTemps.add(KeyValue.get(R.string.dev_str_sigalgname, certSigAlgName)); + // 证书算法 OID + listTemps.add(KeyValue.get(R.string.dev_str_sigalgoid, certSigAlgOID)); + // 证书机器码 + listTemps.add(KeyValue.get(R.string.dev_str_dercode, certSerialnumber)); + // 证书 DER 编码 + listTemps.add(KeyValue.get(R.string.dev_str_serialnumber, certDERCode)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "ApkInfoItem"); + isError = true; + } + + // = 保存集合 = + + // APP 包名 + listKeyValues.add(KeyValue.get(R.string.dev_str_packname, appInfoBean.getAppPackName())); + // 没报错才存储 MD5 信息 + if (!isError) { + // APP MD5 签名 + listKeyValues.add(KeyValue.get(R.string.dev_str_md5, appMD5)); + } + // APP 版本号 ( 主要用于 APP 内部版本判断 int 类型 ) + listKeyValues.add( + KeyValue.get( + R.string.dev_str_version_code, + String.valueOf(appInfoBean.getVersionCode()) + ) + ); + // APP 版本名 ( 主要用于对用户显示版本信息 ) + listKeyValues.add(KeyValue.get(R.string.dev_str_version_name, appInfoBean.getVersionName())); + // 安装包地址 + listKeyValues.add(KeyValue.get(R.string.dev_str_apk_uri, appInfoBean.getSourceDir())); + // 没报错才存储 SHA 信息 + if (!isError) { + // APP SHA1 + listKeyValues.add(KeyValue.get(R.string.dev_str_sha1, appSHA1)); + // APP SHA256 + listKeyValues.add(KeyValue.get(R.string.dev_str_sha256, appSHA256)); + } + // APP 最低支持 Android SDK 版本 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_minsdkversion, + minSdkVersion + + " ( " + VersionUtils.convertSDKVersion(minSdkVersion) + "+ )" + ) + ); + // APP 兼容 SDK 版本 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_targetsdkversion, + targetSdkVersion + + " ( " + VersionUtils.convertSDKVersion(targetSdkVersion) + "+ )" + ) + ); + // 获取 APK 大小 + listKeyValues.add(KeyValue.get(R.string.dev_str_apk_length, apkLength)); + // 没报错才存储 其他签名信息 + if (!isError) { + listKeyValues.addAll(listTemps); + } + } + + /** + * 获取 AppInfoBean + * @return {@link AppInfoBean} + */ + public AppInfoBean getAppInfoBean() { + return appInfoBean; + } + + /** + * 获取 List 信息键对值集合 + * @return APP 信息键对值集合 + */ + public List getListKeyValues() { + return listKeyValues; + } + + /** + * 获取 APP MD5 签名 + * @return APP MD5 签名 + */ + public String getAppMD5() { + return appMD5; + } + + /** + * 获取 APP SHA1 签名 + * @return APP SHA1 签名 + */ + public String getAppSHA1() { + return appSHA1; + } + + /** + * 获取 APP SHA256 签名 + * @return APP SHA256 签名 + */ + public String getAppSHA256() { + return appSHA256; + } + + /** + * 获取 APP 最低支持 Android SDK 版本 + * @return APP 最低支持 Android SDK 版本 + */ + public int getMinSdkVersion() { + return minSdkVersion; + } + + /** + * 获取 APP 兼容 SDK 版本 + * @return APP 兼容 SDK 版本 + */ + public int getTargetSdkVersion() { + return targetSdkVersion; + } + + /** + * 获取 APP 安装包大小 + * @return APP 安装包大小 + */ + public String getApkLength() { + return apkLength; + } + + /** + * 获取证书对象 + * @return {@link X509Certificate} + */ + public X509Certificate getX509Certificate() { + return cert; + } + + /** + * 获取证书生成日期 + * @return 证书生成日期 + */ + public Date getNotBefore() { + return notBefore; + } + + /** + * 获取证书有效期 + * @return 证书有效期 + */ + public Date getNotAfter() { + return notAfter; + } + + /** + * 获取证书是否过期 + * @return {@code true} 过期, {@code false} 未过期 + */ + public boolean isEffective() { + return effective; + } + + /** + * 获取证书发布方 + * @return 证书发布方 + */ + public String getCertPrincipal() { + return certPrincipal; + } + + /** + * 获取证书版本号 + * @return 证书版本号 + */ + public String getCertVersion() { + return certVersion; + } + + /** + * 获取证书算法名称 + * @return 证书算法名称 + */ + public String getCertSigAlgName() { + return certSigAlgName; + } + + /** + * 获取证书算法 OID + * @return 证书算法 OID + */ + public String getCertSigAlgOID() { + return certSigAlgOID; + } + + /** + * 获取证书机器码 + * @return 证书机器码 + */ + public String getCertSerialnumber() { + return certSerialnumber; + } + + /** + * 获取证书 DER 编码 + * @return 证书 DER 编码 + */ + public String getCertDERCode() { + return certDERCode; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoBean.java b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoBean.java new file mode 100644 index 0000000000..83480d9bf8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoBean.java @@ -0,0 +1,226 @@ +package dev.utils.app.info; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import androidx.annotation.Keep; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.FileUtils; + +/** + * detail: APP 信息实体类 + * @author Ttt + */ +public class AppInfoBean { + + // 日志 TAG + private static final String TAG = AppInfoBean.class.getSimpleName(); + + @Keep // APP 包名 + private final String appPackName; + @Keep // APP 应用名 + private final String appName; + @Keep // APP 图标 + private final transient Drawable appIcon; + @Keep // APP 类型 + private final AppType appType; + @Keep // APP 版本号 + private final long versionCode; + @Keep // APP 版本名 + private final String versionName; + @Keep // APP 首次安装时间 + private final long firstInstallTime; + @Keep // APP 最后一次更新时间 + private final long lastUpdateTime; + @Keep // APP 地址 + private final String sourceDir; + @Keep // APK 大小 + private final long apkSize; + + /** + * 获取 AppInfoBean + * @param packageInfo {@link PackageInfo} + * @return {@link AppInfoBean} + */ + protected static AppInfoBean get(final PackageInfo packageInfo) { + try { + return new AppInfoBean(packageInfo); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get"); + } + return null; + } + + /** + * 初始化 AppInfoBean + * @param packageInfo {@link PackageInfo} + */ + protected AppInfoBean(final PackageInfo packageInfo) { + this(packageInfo, AppUtils.getPackageManager()); + } + + /** + * 初始化 AppInfoBean + * @param packageInfo {@link PackageInfo} + * @param packageManager {@link PackageManager} + */ + protected AppInfoBean( + final PackageInfo packageInfo, + final PackageManager packageManager + ) { + // APP 包名 + appPackName = packageInfo.applicationInfo.packageName; + // APP 应用名 + appName = packageManager.getApplicationLabel(packageInfo.applicationInfo).toString(); + // APP 图标 + appIcon = packageManager.getApplicationIcon(packageInfo.applicationInfo); + // APP 类型 + appType = AppInfoBean.getAppType(packageInfo); + // APP 版本号 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + versionCode = packageInfo.getLongVersionCode(); + } else { + versionCode = packageInfo.versionCode; + } + // APP 版本名 + versionName = packageInfo.versionName; + // APP 首次安装时间 + firstInstallTime = packageInfo.firstInstallTime; + // APP 最后一次更新时间 + lastUpdateTime = packageInfo.lastUpdateTime; + // APP 地址 + sourceDir = packageInfo.applicationInfo.sourceDir; + // APK 大小 + apkSize = FileUtils.getFileLength(sourceDir); + } + + /** + * 获取 APP 包名 + * @return APP 包名 + */ + public String getAppPackName() { + return appPackName; + } + + /** + * 获取 APP 应用名 + * @return APP 应用名 + */ + public String getAppName() { + return appName; + } + + /** + * 获取 APP 图标 + * @return APP 图标 + */ + public Drawable getAppIcon() { + return appIcon; + } + + /** + * 获取 APP 类型 + * @return APP 类型 + */ + public AppType getAppType() { + return appType; + } + + /** + * 获取 versionCode + * @return versionCode + */ + public long getVersionCode() { + return versionCode; + } + + /** + * 获取 versionName + * @return versionName + */ + public String getVersionName() { + return versionName; + } + + /** + * 获取 APP 首次安装时间 + * @return APP 首次安装时间 + */ + public long getFirstInstallTime() { + return firstInstallTime; + } + + /** + * 获取 APP 最后更新时间 + * @return APP 最后更新时间 + */ + public long getLastUpdateTime() { + return lastUpdateTime; + } + + /** + * 获取 APK 地址 + * @return APK 地址 + */ + public String getSourceDir() { + return sourceDir; + } + + /** + * 获取 APK 大小 + * @return APK 大小 + */ + public long getApkSize() { + return apkSize; + } + + // = + + /** + * detail: 应用类型 + * @author Ttt + */ + public enum AppType { + + USER, // 用户 APP + + SYSTEM, // 系统 APP + + ALL // 全部 APP + } + + /** + * 获取 APP 类型 + * @param packageInfo {@link PackageInfo} + * @return {@link AppType} 应用类型 + */ + public static AppType getAppType(final PackageInfo packageInfo) { + if (!isSystemApp(packageInfo) && !isSystemUpdateApp(packageInfo)) { + return AppType.USER; + } + return AppType.SYSTEM; + } + + /** + * 是否系统程序 + * @param packageInfo {@link PackageInfo} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSystemApp(final PackageInfo packageInfo) { + return ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0); + } + + /** + * 是否系统程序被手动更新后, 也成为第三方应用程序 + * @param packageInfo {@link PackageInfo} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSystemUpdateApp(final PackageInfo packageInfo) { + return ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoItem.java b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoItem.java new file mode 100644 index 0000000000..2c4224a2c3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoItem.java @@ -0,0 +1,380 @@ +package dev.utils.app.info; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.text.format.Formatter; + +import androidx.annotation.Keep; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import dev.DevUtils; +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.R; +import dev.utils.app.SignaturesUtils; +import dev.utils.app.VersionUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; + +/** + * detail: APP 信息 Item + * @author Ttt + */ +public final class AppInfoItem { + + // 日志 TAG + private static final String TAG = AppInfoItem.class.getSimpleName(); + + @Keep // APP 基本信息实体类 + private final AppInfoBean appInfoBean; + @Keep // APP MD5 签名 + private final String appMD5; + @Keep // APP SHA1 签名 + private final String appSHA1; + @Keep // APP SHA256 签名 + private final String appSHA256; + @Keep // APP 最低支持 Android SDK 版本 + private int minSdkVersion = -1; + @Keep // APP 兼容 SDK 版本 + private final int targetSdkVersion; + @Keep // APP 安装包大小 + private final String apkLength; + @Keep // 证书对象 + private final X509Certificate cert; + @Keep // 证书生成日期 + private final Date notBefore; + @Keep // 证书有效期 + private final Date notAfter; + @Keep // 证书是否过期 + private boolean effective; + @Keep // 证书发布方 + private final String certPrincipal; + @Keep // 证书版本号 + private final String certVersion; + @Keep // 证书算法名称 + private final String certSigAlgName; + @Keep // 证书算法 OID + private final String certSigAlgOID; + @Keep // 证书机器码 + private final String certSerialnumber; + @Keep // 证书 DER 编码 + private String certDERCode; + @Keep // APP 参数集 + private final List listKeyValues = new ArrayList<>(); + + /** + * 获取 AppInfoItem + * @param packageInfo {@link PackageInfo} + * @return {@link AppInfoItem} + */ + protected static AppInfoItem get(final PackageInfo packageInfo) { + try { + return new AppInfoItem(packageInfo); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get"); + } + return null; + } + + /** + * 初始化 AppInfoItem + * @param packageInfo {@link PackageInfo} + */ + private AppInfoItem(final PackageInfo packageInfo) { + // 获取 Context + Context context = DevUtils.getContext(); + // 格式化日期 + SimpleDateFormat sdf = DateUtils.getDefaultFormat(); + // = + // 获取 APP 信息 + appInfoBean = new AppInfoBean(packageInfo); + // APP MD5 签名 + appMD5 = SignaturesUtils.signatureMD5(packageInfo.signatures); + // APP SHA1 + appSHA1 = SignaturesUtils.signatureSHA1(packageInfo.signatures); + // APP SHA256 + appSHA256 = SignaturesUtils.signatureSHA256(packageInfo.signatures); + // 属于 7.0 以上才有的方法 + if (VersionUtils.isN()) { + // APP 最低支持 Android SDK 版本 + minSdkVersion = packageInfo.applicationInfo.minSdkVersion; + } + // APP 兼容 SDK 版本 + targetSdkVersion = packageInfo.applicationInfo.targetSdkVersion; + // APP 安装包大小 + apkLength = Formatter.formatFileSize( + DevUtils.getContext(), + FileUtils.getFileLength(appInfoBean.getSourceDir()) + ); + // 证书对象 + cert = SignaturesUtils.getX509Certificate(packageInfo.signatures); + // 证书生成日期 + notBefore = cert.getNotBefore(); + // 证书有效期 + notAfter = cert.getNotAfter(); + // 设置有效期 + StringBuilder builder = new StringBuilder(); + builder.append(sdf.format(notBefore)); + builder.append(" ").append(context.getString(R.string.dev_str_to)).append(" "); // 至 + builder.append(sdf.format(notAfter)); + builder.append(DevFinal.SYMBOL.NEW_LINE_X2); + builder.append(notBefore); + builder.append(" ").append(context.getString(R.string.dev_str_to)).append(" "); + builder.append(notAfter); + // 保存有效期转换信息 + String effectiveStr = builder.toString(); + // 证书是否过期 + effective = false; + try { + cert.checkValidity(); + // CertificateExpiredException ( 证书已过期 ) + // CertificateNotYetValidException ( 证书不再有效 ) + } catch (CertificateExpiredException ce) { + effective = true; + } catch (CertificateNotYetValidException ce) { + effective = true; + } + // 证书发布方 + certPrincipal = cert.getIssuerX500Principal().toString(); + // 证书版本号 + certVersion = String.valueOf(cert.getVersion()); + // 证书算法名称 + certSigAlgName = cert.getSigAlgName(); + // 证书算法 OID + certSigAlgOID = cert.getSigAlgOID(); + // 证书机器码 + certSerialnumber = cert.getSerialNumber().toString(); + try { + // 证书 DER 编码 + certDERCode = ConvertUtils.toHexString(cert.getTBSCertificate()); + } catch (CertificateEncodingException ignored) { + } + + // = 保存集合 = + + // APP 包名 + listKeyValues.add(KeyValue.get(R.string.dev_str_packname, appInfoBean.getAppPackName())); + // APP MD5 签名 + listKeyValues.add(KeyValue.get(R.string.dev_str_md5, appMD5)); + // APP 版本号 ( 主要用于 APP 内部版本判断 int 类型 ) + listKeyValues.add( + KeyValue.get( + R.string.dev_str_version_code, + String.valueOf(appInfoBean.getVersionCode()) + ) + ); + // APP 版本名 ( 主要用于对用户显示版本信息 ) + listKeyValues.add(KeyValue.get(R.string.dev_str_version_name, appInfoBean.getVersionName())); + // APP SHA1 + listKeyValues.add(KeyValue.get(R.string.dev_str_sha1, appSHA1)); + // APP SHA256 + listKeyValues.add(KeyValue.get(R.string.dev_str_sha256, appSHA256)); + // APP 首次安装时间 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_first_install_time, + sdf.format(packageInfo.firstInstallTime) + ) + ); + // 获取最后一次更新时间 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_last_update_time, + sdf.format(packageInfo.lastUpdateTime) + ) + ); + // APP 最低支持 Android SDK 版本 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_minsdkversion, + minSdkVersion + + " ( " + VersionUtils.convertSDKVersion(minSdkVersion) + "+ )" + ) + ); + // APP 兼容 SDK 版本 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_targetsdkversion, + targetSdkVersion + + " ( " + VersionUtils.convertSDKVersion(targetSdkVersion) + "+ )" + ) + ); + // APK 大小 + listKeyValues.add(KeyValue.get(R.string.dev_str_apk_length, apkLength)); + // 证书有效期 + listKeyValues.add(KeyValue.get(R.string.dev_str_effective, effectiveStr)); + // 判断是否过期 + listKeyValues.add( + KeyValue.get( + R.string.dev_str_iseffective, + effective ? context.getString(R.string.dev_str_overdue) + : context.getString(R.string.dev_str_notoverdue) + ) + ); + // 证书发布方 + listKeyValues.add(KeyValue.get(R.string.dev_str_principal, certPrincipal)); + // 证书版本号 + listKeyValues.add(KeyValue.get(R.string.dev_str_version, certVersion)); + // 证书算法名称 + listKeyValues.add(KeyValue.get(R.string.dev_str_sigalgname, certSigAlgName)); + // 证书算法 OID + listKeyValues.add(KeyValue.get(R.string.dev_str_sigalgoid, certSigAlgOID)); + // 证书机器码 + listKeyValues.add(KeyValue.get(R.string.dev_str_dercode, certSerialnumber)); + // 证书 DER 编码 + listKeyValues.add(KeyValue.get(R.string.dev_str_serialnumber, certDERCode)); + } + + /** + * 获取 AppInfoBean + * @return {@link AppInfoBean} + */ + public AppInfoBean getAppInfoBean() { + return appInfoBean; + } + + /** + * 获取 List 信息键对值集合 + * @return APP 信息键对值集合 + */ + public List getListKeyValues() { + return listKeyValues; + } + + /** + * 获取 APP MD5 签名 + * @return APP MD5 签名 + */ + public String getAppMD5() { + return appMD5; + } + + /** + * 获取 APP SHA1 签名 + * @return APP SHA1 签名 + */ + public String getAppSHA1() { + return appSHA1; + } + + /** + * 获取 APP SHA256 签名 + * @return APP SHA256 签名 + */ + public String getAppSHA256() { + return appSHA256; + } + + /** + * 获取 APP 最低支持 Android SDK 版本 + * @return APP 最低支持 Android SDK 版本 + */ + public int getMinSdkVersion() { + return minSdkVersion; + } + + /** + * 获取 APP 兼容 SDK 版本 + * @return APP 兼容 SDK 版本 + */ + public int getTargetSdkVersion() { + return targetSdkVersion; + } + + /** + * 获取 APP 安装包大小 + * @return APP 安装包大小 + */ + public String getApkLength() { + return apkLength; + } + + /** + * 获取证书对象 + * @return {@link X509Certificate} + */ + public X509Certificate getX509Certificate() { + return cert; + } + + /** + * 获取证书生成日期 + * @return 证书生成日期 + */ + public Date getNotBefore() { + return notBefore; + } + + /** + * 获取证书有效期 + * @return 证书有效期 + */ + public Date getNotAfter() { + return notAfter; + } + + /** + * 获取证书是否过期 + * @return {@code true} 过期, {@code false} 未过期 + */ + public boolean isEffective() { + return effective; + } + + /** + * 获取证书发布方 + * @return 证书发布方 + */ + public String getCertPrincipal() { + return certPrincipal; + } + + /** + * 获取证书版本号 + * @return 证书版本号 + */ + public String getCertVersion() { + return certVersion; + } + + /** + * 获取证书算法名称 + * @return 证书算法名称 + */ + public String getCertSigAlgName() { + return certSigAlgName; + } + + /** + * 获取证书算法 OID + * @return 证书算法 OID + */ + public String getCertSigAlgOID() { + return certSigAlgOID; + } + + /** + * 获取证书机器码 + * @return 证书机器码 + */ + public String getCertSerialnumber() { + return certSerialnumber; + } + + /** + * 获取证书 DER 编码 + * @return 证书 DER 编码 + */ + public String getCertDERCode() { + return certDERCode; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoUtils.java b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoUtils.java new file mode 100644 index 0000000000..c9f32eca07 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/info/AppInfoUtils.java @@ -0,0 +1,356 @@ +package dev.utils.app.info; + +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.FileUtils; + +/** + * detail: APP 信息获取工具类 + * @author Ttt + *
+ *     Android 11 ( R ) 适配
+ *     @see 
+ *     

+ * Android 11 ( R ) 需要加该权限 ( 无需申请 ) 才能够获取安装列表 + * + *
+ */ +public final class AppInfoUtils { + + private AppInfoUtils() { + } + + // 日志 TAG + private static final String TAG = AppInfoUtils.class.getSimpleName(); + + /** + * 通过 APK 路径 初始化 PackageInfo + * @param file APK 文件路径 + * @return {@link PackageInfo} + */ + public static PackageInfo getPackageInfoToFile(final File file) { + if (!FileUtils.isFileExists(file)) return null; + return getPackageInfoToPath(file.getAbsolutePath()); + } + + /** + * 通过 APK 路径 初始化 PackageInfo + * @param apkUri APK 文件路径 + * @return {@link PackageInfo} + */ + public static PackageInfo getPackageInfoToPath(final String apkUri) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return null; + try { + PackageInfo packageInfo = packageManager.getPackageArchiveInfo( + apkUri, PackageManager.GET_ACTIVITIES + ); + // 设置 APK 位置信息 + ApplicationInfo appInfo = packageInfo.applicationInfo; + // 必须加这两句, 不然下面 icon 获取是 default icon 而不是应用包的 icon + appInfo.sourceDir = apkUri; + appInfo.publicSourceDir = apkUri; + return packageInfo; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPackageInfoToPath"); + } + return null; + } + + /** + * 获取当前应用 PackageInfo + * @return {@link PackageInfo} + */ + public static PackageInfo getPackageInfo() { + return getPackageInfo(AppUtils.getPackageName()); + } + + /** + * 通过包名 获取 PackageInfo + * @param packageName 应用包名 + * @return {@link PackageInfo} + */ + public static PackageInfo getPackageInfo(final String packageName) { + try { + // 获取对应的 PackageInfo ( 原始的 PackageInfo 获取 signatures 等于 null, 需要这样获取 ) + return AppUtils.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPackageInfo"); + } + return null; + } + + // ============= + // = 获取基本信息 = + // ============= + + /** + * 通过 APK 路径 获取 AppInfoBean + * @param file APK 文件路径 + * @return {@link AppInfoBean} + */ + public static AppInfoBean getAppInfoBeanToFile(final File file) { + return AppInfoBean.get(getPackageInfoToFile(file)); + } + + /** + * 通过 APK 路径 获取 AppInfoBean + * @param apkUri APK 文件路径 + * @return {@link AppInfoBean} + */ + public static AppInfoBean getAppInfoBeanToPath(final String apkUri) { + return AppInfoBean.get(getPackageInfoToPath(apkUri)); + } + + /** + * 获取当前应用 AppInfoBean + * @return {@link AppInfoBean} + */ + public static AppInfoBean getAppInfoBean() { + return AppInfoBean.get(getPackageInfo()); + } + + /** + * 通过包名 获取 AppInfoBean + * @param packageName 应用包名 + * @return {@link AppInfoBean} + */ + public static AppInfoBean getAppInfoBean(final String packageName) { + return AppInfoBean.get(getPackageInfo(packageName)); + } + + // ============= + // = 获取详细信息 = + // ============= + + /** + * 获取 APK 详细信息 + * @param file APK 文件路径 + * @return {@link ApkInfoItem} + */ + public static ApkInfoItem getApkInfoItem(final File file) { + if (!FileUtils.isFileExists(file)) return null; + return getApkInfoItem(file.getAbsolutePath()); + } + + /** + * 获取 APK 详细信息 + * @param apkUri APK 文件路径 + * @return {@link ApkInfoItem} + */ + public static ApkInfoItem getApkInfoItem(final String apkUri) { + try { + return ApkInfoItem.get(getPackageInfoToPath(apkUri)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getApkInfoItem"); + return null; + } + } + + // = + + /** + * 获取 APP 详细信息 + * @return {@link AppInfoItem} + */ + public static AppInfoItem getAppInfoItem() { + try { + return AppInfoItem.get(getPackageInfo()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppInfoItem"); + return null; + } + } + + /** + * 获取 APP 详细信息 + * @param packageName 应用包名 + * @return {@link AppInfoItem} + */ + public static AppInfoItem getAppInfoItem(final String packageName) { + try { + return AppInfoItem.get(getPackageInfo(packageName)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppInfoItem"); + return null; + } + } + + // = + + /** + * 获取全部 APP 列表 + * @return APP 列表 + */ + public static List getAppLists() { + return getAppLists(AppInfoBean.AppType.ALL); + } + + /** + * 获取 APP 列表 + * @param appType APP 类型 + * @return APP 列表 + */ + public static List getAppLists(final AppInfoBean.AppType appType) { + // APP 信息 + List listApps = new ArrayList<>(); + // 防止为 null + if (appType != null) { + // 管理应用程序包 + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return listApps; + try { + // 获取手机内所有应用 + List packList = packageManager.getInstalledPackages(0); + // 判断是否属于添加全部 + if (appType == AppInfoBean.AppType.ALL) { + // 遍历 APP 列表 + for (int i = 0, len = packList.size(); i < len; i++) { + PackageInfo packageInfo = packList.get(i); + // 添加符合条件的 APP 应用信息 + listApps.add(new AppInfoBean(packageInfo, packageManager)); + } + } else { + // 遍历 APP 列表 + for (int i = 0, len = packList.size(); i < len; i++) { + PackageInfo packageInfo = packList.get(i); + // 获取 APP 类型 + AppInfoBean.AppType currentAppType = AppInfoBean.getAppType(packageInfo); + // 判断类型 + if (appType.equals(currentAppType)) { + // 添加符合条件的 APP 应用信息 + listApps.add(new AppInfoBean(packageInfo, packageManager)); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppLists"); + } + } + return listApps; + } + + // = + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限 + */ + public static List getAppPermissionToList() { + return new ArrayList<>(getAppPermissionToSet()); + } + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限 + */ + public static Set getAppPermissionToSet() { + String[] permissions = getAppPermission(); + // 防止数据为 null + if (permissions != null && permissions.length != 0) { + Set permissionSets = new HashSet<>(); + Collections.addAll(permissionSets, permissions); + return permissionSets; + } + return Collections.emptySet(); + } + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限数组 + */ + public static String[] getAppPermission() { + return getAppPermission(AppUtils.getPackageName()); + } + + /** + * 获取 APP 注册的权限 + * @param packageName 应用包名 + * @return APP 注册的权限数组 + */ + public static String[] getAppPermission(final String packageName) { + PackageInfo packageInfo = AppUtils.getPackageInfo( + packageName, PackageManager.GET_PERMISSIONS + ); + if (packageInfo == null) return null; + try { + return packageInfo.requestedPermissions; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getAppPermission"); + } + return null; + } + + /** + * 打印 APP 注册的权限 + * @param packageName 应用包名 + */ + public static void printAppPermission(final String packageName) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) { + LogPrintUtils.eTag(TAG, "printAppPermission => packageManager is null"); + return; + } + StringBuilder builder = new StringBuilder(); + try { + PackageInfo packageInfo = packageManager.getPackageInfo( + packageName, PackageManager.GET_PERMISSIONS + ); + String[] usesPermissionsArray = packageInfo.requestedPermissions; + for (String usesPermissionName : usesPermissionsArray) { + // 获取每个权限的名字, 如: android.permission.INTERNET + // 拼接日志 + builder.append("usesPermissionName = ").append(usesPermissionName); + builder.append(DevFinal.SYMBOL.NEW_LINE); + + // 通过 usesPermissionName 获取该权限的详细信息 + PermissionInfo permissionInfo = packageManager.getPermissionInfo( + usesPermissionName, 0 + ); + + // 获取该权限属于哪个权限组, 如: 网络通信 + PermissionGroupInfo permissionGroupInfo = packageManager.getPermissionGroupInfo( + permissionInfo.group, 0 + ); + // 拼接日志 + builder.append("permissionGroup = ") + .append(permissionGroupInfo.loadLabel(packageManager).toString()) + .append(DevFinal.SYMBOL.NEW_LINE); + + // 获取该权限的标签信息, 比如: 完全的网络访问权限 + String permissionLabel = permissionInfo.loadLabel(packageManager).toString(); + // 拼接日志 + builder.append("permissionLabel = ") + .append(permissionLabel) + .append(DevFinal.SYMBOL.NEW_LINE); + + // 获取该权限的详细描述信息, 比如: 允许该应用创建网络套接字和使用自定义网络协议 + // 浏览器和其他某些应用提供了向互联网发送数据的途径, 因此应用无需该权限即可向互联网发送数据 + String permissionDescription = permissionInfo.loadDescription(packageManager).toString(); + // 拼接日志 + builder.append("permissionDescription = ").append(permissionDescription); + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + // 打印日志 + LogPrintUtils.dTag(TAG, builder.toString()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "printAppPermission"); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/info/KeyValue.java b/lib/DevApp/src/main/java/dev/utils/app/info/KeyValue.java new file mode 100644 index 0000000000..a6b28d0c69 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/info/KeyValue.java @@ -0,0 +1,68 @@ +package dev.utils.app.info; + +import androidx.annotation.Keep; +import androidx.annotation.StringRes; + +import java.io.Serializable; + +import dev.utils.app.ResourceUtils; + +/** + * detail: 键对值实体类 + * @author Ttt + */ +public class KeyValue + implements Serializable { + + @Keep + protected String key; + @Keep + protected String value; + + /** + * 构造函数 + * @param key key + * @param value value + */ + public KeyValue( + final String key, + final String value + ) { + this.key = key; + this.value = value; + } + + /** + * 获取 key + * @return key + */ + public String getKey() { + return key; + } + + /** + * 获取 value + * @return value + */ + public String getValue() { + return value; + } + + @Override + public String toString() { + return key + ": " + value; + } + + /** + * 通过 resId 设置 key + * @param resId R.string.id + * @param value value + * @return {@link KeyValue} + */ + public static KeyValue get( + @StringRes final int resId, + final String value + ) { + return new KeyValue(ResourceUtils.getString(resId), value); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/DevLogger.java b/lib/DevApp/src/main/java/dev/utils/app/logger/DevLogger.java new file mode 100644 index 0000000000..f2c3c6fb2b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/DevLogger.java @@ -0,0 +1,370 @@ +package dev.utils.app.logger; + +import android.util.Log; + +/** + * detail: 日志操作类 ( 对外公开直接调用 ) + * @author Ttt + */ +public final class DevLogger { + + private DevLogger() { + } + + // 包下 LoggerPrinter 类持有对象 + private static final IPrinter sPrinter = new LoggerPrinter(); + + // ========== + // = 配置方法 = + // ========== + + /** + * 使用单次其他日志配置 + * @param logConfig 日志配置 + * @return {@link IPrinter} + */ + public static IPrinter other(final LogConfig logConfig) { + return sPrinter.other(logConfig); + } + + /** + * 获取日志配置信息 + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getLogConfig() { + return sPrinter.getLogConfig(); + } + + /** + * 初始化日志配置信息 ( 使用默认配置 ) + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig initialize() { + return sPrinter.initialize(); + } + + /** + * 自定义日志配置信息 + * @param logConfig 日志配置 + */ + public static void initialize(final LogConfig logConfig) { + sPrinter.initialize(logConfig); + } + + // ============================= + // = 使用默认 TAG ( 日志打印方法 ) = + // ============================= + + /** + * 打印 Log.DEBUG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void d( + final String message, + final Object... args + ) { + sPrinter.d(message, args); + } + + /** + * 打印 Log.ERROR + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void e( + final String message, + final Object... args + ) { + sPrinter.e(message, args); + } + + /** + * 打印 Log.ERROR + * @param throwable 异常 + */ + public static void e(final Throwable throwable) { + sPrinter.e(throwable, null); + } + + /** + * 打印 Log.ERROR + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void e( + final Throwable throwable, + final String message, + final Object... args + ) { + sPrinter.e(throwable, message, args); + } + + /** + * 打印 Log.WARN + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void w( + final String message, + final Object... args + ) { + sPrinter.w(message, args); + } + + /** + * 打印 Log.INFO + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void i( + final String message, + final Object... args + ) { + sPrinter.i(message, args); + } + + /** + * 打印 Log.VERBOSE + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void v( + final String message, + final Object... args + ) { + sPrinter.v(message, args); + } + + /** + * 打印 Log.ASSERT + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void wtf( + final String message, + final Object... args + ) { + sPrinter.wtf(message, args); + } + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param json JSON 格式字符串 + */ + public static void json(final String json) { + sPrinter.json(json); + } + + /** + * 格式化 XML 格式数据, 并打印 + * @param xml XML 格式字符串 + */ + public static void xml(final String xml) { + sPrinter.xml(xml); + } + + // ============================== + // = 使用自定义 TAG ( 日志打印方法 ) = + // ============================== + + /** + * 打印 Log.DEBUG + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void dTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.dTag(tag, message, args); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void eTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.eTag(tag, message, args); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + */ + public static void eTag( + final String tag, + final Throwable throwable + ) { + sPrinter.eTag(tag, throwable, null); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void eTag( + final String tag, + final Throwable throwable, + final String message, + final Object... args + ) { + sPrinter.eTag(tag, throwable, message, args); + } + + /** + * 打印 Log.WARN + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void wTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.wTag(tag, message, args); + } + + /** + * 打印 Log.INFO + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void iTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.iTag(tag, message, args); + } + + /** + * 打印 Log.VERBOSE + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void vTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.vTag(tag, message, args); + } + + /** + * 打印 Log.ASSERT + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + public static void wtfTag( + final String tag, + final String message, + final Object... args + ) { + sPrinter.wtfTag(tag, message, args); + } + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param tag 日志 TAG + * @param json JSON 格式字符串 + */ + public static void jsonTag( + final String tag, + final String json + ) { + sPrinter.jsonTag(tag, json); + } + + /** + * 格式化 XML 格式数据, 并打印 + * @param tag 日志 TAG + * @param xml XML 格式字符串 + */ + public static void xmlTag( + final String tag, + final String xml + ) { + sPrinter.xmlTag(tag, xml); + } + + // ========== + // = 通知输出 = + // ========== + + // 默认日志输出接口 + static Print sPrint = (logType, tag, message) -> { + // 防止 null 处理 + if (message == null) return; + // 获取日志类型 + switch (logType) { + case Log.VERBOSE: + Log.v(tag, message); + break; + case Log.DEBUG: + Log.d(tag, message); + break; + case Log.INFO: + Log.i(tag, message); + break; + case Log.WARN: + Log.w(tag, message); + break; + case Log.ERROR: + Log.e(tag, message); + break; + case Log.ASSERT: + default: + Log.wtf(tag, message); + break; + } + }; + + /** + * 设置日志输出接口 + * @param print 日志输出接口 + */ + public static void setPrint(final Print print) { + DevLogger.sPrint = print; + } + + /** + * detail: 日志输出接口 + * @author Ttt + */ + public interface Print { + + /** + * 日志打印 + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + void printLog( + int logType, + String tag, + String message + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/IPrinter.java b/lib/DevApp/src/main/java/dev/utils/app/logger/IPrinter.java new file mode 100644 index 0000000000..9132c6c447 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/IPrinter.java @@ -0,0 +1,255 @@ +package dev.utils.app.logger; + +/** + * detail: 日志接口 + * @author Ttt + */ +public interface IPrinter { + + // ========== + // = 配置方法 = + // ========== + + /** + * 使用单次其他日志配置 + * @param logConfig 日志配置 + * @return {@link IPrinter} + */ + IPrinter other(LogConfig logConfig); + + /** + * 获取日志配置信息 + * @return {@link LogConfig} 日志配置 + */ + LogConfig getLogConfig(); + + /** + * 初始化日志配置信息 ( 使用默认配置 ) + * @return {@link LogConfig} 日志配置 + */ + LogConfig initialize(); + + /** + * 自定义日志配置信息 + * @param logConfig 日志配置 + */ + void initialize(LogConfig logConfig); + + // ============================= + // = 使用默认 TAG ( 日志打印方法 ) = + // ============================= + + /** + * 打印 Log.DEBUG + * @param message 日志信息 + * @param args 格式化参数 + */ + void d( + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param message 日志信息 + * @param args 格式化参数 + */ + void e( + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param throwable 异常 + */ + void e(Throwable throwable); + + /** + * 打印 Log.ERROR + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + void e( + Throwable throwable, + String message, + Object... args + ); + + /** + * 打印 Log.WARN + * @param message 日志信息 + * @param args 格式化参数 + */ + void w( + String message, + Object... args + ); + + /** + * 打印 Log.INFO + * @param message 日志信息 + * @param args 格式化参数 + */ + void i( + String message, + Object... args + ); + + /** + * 打印 Log.VERBOSE + * @param message 日志信息 + * @param args 格式化参数 + */ + void v( + String message, + Object... args + ); + + /** + * 打印 Log.ASSERT + * @param message 日志信息 + * @param args 格式化参数 + */ + void wtf( + String message, + Object... args + ); + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param json JSON 格式字符串 + */ + void json(String json); + + /** + * 格式化 XML 格式数据, 并打印 + * @param xml XML 格式字符串 + */ + void xml(String xml); + + // ============================== + // = 使用自定义 TAG ( 日志打印方法 ) = + // ============================== + + /** + * 打印 Log.DEBUG + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void dTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void eTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + */ + void eTag( + String tag, + Throwable throwable + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + void eTag( + String tag, + Throwable throwable, + String message, + Object... args + ); + + /** + * 打印 Log.WARN + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void wTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.INFO + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void iTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.VERBOSE + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void vTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ASSERT + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void wtfTag( + String tag, + String message, + Object... args + ); + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param tag 日志 TAG + * @param json JSON 格式字符串 + */ + void jsonTag( + String tag, + String json + ); + + /** + * 格式化 XML 格式数据, 并打印 + * @param tag 日志 TAG + * @param xml XML 格式字符串 + */ + void xmlTag( + String tag, + String xml + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/LogConfig.java b/lib/DevApp/src/main/java/dev/utils/app/logger/LogConfig.java new file mode 100644 index 0000000000..316c305701 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/LogConfig.java @@ -0,0 +1,249 @@ +package dev.utils.app.logger; + +/** + * detail: 日志配置类 + * @author Ttt + */ +public class LogConfig { + + /** + * 堆栈方法总数 ( 显示经过的方法 ) + */ + public int methodCount = LogConstants.DEFAULT_LOG_METHOD_COUNT; + + /** + * 堆栈方法索引偏移 ( 0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + */ + public int methodOffset = LogConstants.DEFAULT_LOG_METHOD_OFFSET; + + /** + * 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + */ + public boolean outputMethodAll = LogConstants.JUDGE_OUTPUT_METHOD_ALL; + + /** + * 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + */ + public boolean displayThreadInfo = LogConstants.JUDGE_DISPLAY_THREAD_LOG; + + /** + * 是否排序日志 ( 格式化 ) + */ + public boolean sortLog = LogConstants.JUDGE_SORT_LOG; + + /** + * 日志级别 ( 只有 e, wtf 才进行显示 ) + */ + public LogLevel logLevel = LogConstants.DEFAULT_LOG_LEVEL; + + /** + * 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + */ + public String tag = LogConstants.DEFAULT_LOG_TAG; + + // ================== + // = 初始化 LogConfig = + // ================== + + /** + * 获取 Release Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、不进行排序、默认只打印 ERROR 级别日志 ) + * @param tag 日志 TAG + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getReleaseLogConfig(final String tag) { + return getLogConfig( + tag, 3, 0, false, + true, false, LogLevel.ERROR + ); + } + + /** + * 获取 Release Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、不进行排序 ) + * @param tag 日志 TAG + * @param logLevel 日志级别 + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getReleaseLogConfig( + final String tag, + final LogLevel logLevel + ) { + return getLogConfig( + tag, 3, 0, false, + true, false, logLevel + ); + } + + // = + + /** + * 获取 Debug Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、不进行排序、默认只打印 ERROR 级别日志 ) + * @param tag 日志 TAG + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getDebugLogConfig(final String tag) { + return getLogConfig( + tag, 3, 0, false, + true, false, LogLevel.DEBUG + ); + } + + /** + * 获取 Debug Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、进行排序 ) + * @param tag 日志 TAG + * @param logLevel 日志级别 + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getDebugLogConfig( + final String tag, + final LogLevel logLevel + ) { + return getLogConfig( + tag, 3, 0, false, + true, false, logLevel + ); + } + + // = + + /** + * 获取 Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、并且美化日志信息、默认打印 DEBUG 级别及以上日志 ) + * @param tag 日志 TAG + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getSortLogConfig(final String tag) { + return getLogConfig( + tag, 3, 0, false, + true, true, LogLevel.DEBUG + ); + } + + /** + * 获取 Log 配置 ( 打印线程信息、显示方法总数 3、从 0 开始、并且美化日志信息 ) + * @param tag 日志 TAG + * @param logLevel 日志级别 + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getSortLogConfig( + final String tag, + final LogLevel logLevel + ) { + return getLogConfig( + tag, 3, 0, false, + true, true, logLevel + ); + } + + // = + + /** + * 获取 Log 配置 + * @param tag 日志 TAG + * @param count 显示的方法总数 ( 推荐 3) + * @param offset 方法偏移索引 ( 从第几个方法开始打印, 默认推荐 0) + * @param allMethod 是否打印全部方法 + * @param threadInfo 是否显示线程信息 + * @param sortLog 是否排序日志 ( 美化 ) + * @param logLevel 日志级别 + * @return {@link LogConfig} 日志配置 + */ + public static LogConfig getLogConfig( + final String tag, + final int count, + final int offset, + final boolean allMethod, + final boolean threadInfo, + final boolean sortLog, + final LogLevel logLevel + ) { + // 生成默认配置信息 + LogConfig logConfig = new LogConfig(); + // 堆栈方法总数 ( 显示经过的方法 ) + logConfig.methodCount = count; + // 堆栈方法索引偏移 (0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息 ) + logConfig.methodOffset = offset; + // 是否输出全部方法 ( 在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数 ) + logConfig.outputMethodAll = allMethod; + // 显示日志线程信息 ( 特殊情况, 显示经过的线程信息, 具体情况如上 ) + logConfig.displayThreadInfo = threadInfo; + // 是否排序日志 ( 格式化 ) + logConfig.sortLog = sortLog; + // 日志级别 + logConfig.logLevel = logLevel; + // 设置 TAG ( 特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下 ) + logConfig.tag = tag; + // 返回日志配置 + return logConfig; + } + + // = + + /** + * 设置堆栈方法总数 + * @param methodCount 堆栈方法总数 + * @return {@link LogConfig} + */ + public LogConfig methodCount(int methodCount) { + this.methodCount = methodCount; + return this; + } + + /** + * 设置堆栈方法索引偏移 + * @param methodOffset 堆栈方法索引偏移 + * @return {@link LogConfig} + */ + public LogConfig methodOffset(int methodOffset) { + this.methodOffset = methodOffset; + return this; + } + + /** + * 设置是否输出全部方法 + * @param outputMethodAll {@code true} yes, {@code false} no + * @return {@link LogConfig} + */ + public LogConfig outputMethodAll(boolean outputMethodAll) { + this.outputMethodAll = outputMethodAll; + return this; + } + + /** + * 设置是否显示日志线程信息 + * @param displayThreadInfo {@code true} yes, {@code false} no + * @return {@link LogConfig} + */ + public LogConfig displayThreadInfo(boolean displayThreadInfo) { + this.displayThreadInfo = displayThreadInfo; + return this; + } + + /** + * 设置是否排序日志 + * @param sortLog {@code true} yes, {@code false} no + * @return {@link LogConfig} + */ + public LogConfig sortLog(boolean sortLog) { + this.sortLog = sortLog; + return this; + } + + /** + * 设置日志级别 + * @param logLevel 日志级别 + * @return {@link LogConfig} + */ + public LogConfig logLevel(LogLevel logLevel) { + this.logLevel = logLevel; + return this; + } + + /** + * 设置 TAG + * @param tag TAG + * @return {@link LogConfig} + */ + public LogConfig tag(String tag) { + this.tag = tag; + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/LogConstants.java b/lib/DevApp/src/main/java/dev/utils/app/logger/LogConstants.java new file mode 100644 index 0000000000..ad20e296b5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/LogConstants.java @@ -0,0 +1,89 @@ +package dev.utils.app.logger; + +/** + * detail: 日志常量类 + * @author Ttt + */ +final class LogConstants { + + private LogConstants() { + } + + // ============= + // = 日志配置常量 = + // ============= + + /** + * 判断是否排序日志 + */ + public static final boolean JUDGE_SORT_LOG = false; + + /** + * 判断是否输出全部方法 ( 异常的全部方法 ) + */ + public static final boolean JUDGE_OUTPUT_METHOD_ALL = false; + + /** + * 判断是否显示日志线程信息 + */ + public static final boolean JUDGE_DISPLAY_THREAD_LOG = false; + + /** + * 默认的日志 TAG + */ + public static final String DEFAULT_LOG_TAG = DevLogger.class.getSimpleName(); + + /** + * 默认输出方法数量 + */ + public static final int DEFAULT_LOG_METHOD_COUNT = 3; + + /** + * 默认方法索引偏移 + */ + public static final int DEFAULT_LOG_METHOD_OFFSET = 0; + + /** + * 默认日志级别 ( 只有 e, wtf 才进行显示 ) + */ + public static final LogLevel DEFAULT_LOG_LEVEL = LogLevel.ERROR; + + // ============= + // = 日志配置信息 = + // ============= + + /** + * Android 一个日志条目最大限制为 4076 字节, 设置 4000 字节作为块的大小从默认字符集是 UTF-8 + *
+     *     Android's max limit for a log entry is ~4076 bytes,
+     *     so 4000 bytes is used as chunk size since default charset is UTF-8
+     * 
+ */ + public static final int CHUNK_SIZE = 4000; + + /** + * JSON 格式内容缩进 + */ + public static final int JSON_INDENT = 4; + + /** + * 最小堆栈跟踪索引 + */ + public static final int MIN_STACK_OFFSET = 3; + + // ===================== + // = 绘制日志格式 ( 字符 ) = + // ===================== + + public static final char TOP_LEFT_CORNER = '╔'; + public static final char BOTTOM_LEFT_CORNER = '╚'; + public static final char MIDDLE_CORNER = '╟'; + public static final char HORIZONTAL_DOUBLE_LINE = '║'; + public static final String DOUBLE_DIVIDER = "═══════"; + public static final String SINGLE_DIVIDER = "───────"; + + public static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; + public static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER; + public static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER; + +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/LogLevel.java b/lib/DevApp/src/main/java/dev/utils/app/logger/LogLevel.java new file mode 100644 index 0000000000..2ba0647bde --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/LogLevel.java @@ -0,0 +1,40 @@ +package dev.utils.app.logger; + +/** + * detail: 日志级别 + * @author Ttt + *
+ *     Log.v 输出颜色为黑色, 任何消息都会输出, 这里的 v 代表 verbose 啰嗦的意思, 平时使用就是 Log.v
+ *     Log.d 输出颜色为蓝色, 仅输出 debug 调试的意思, 但他会输出上层的信息, 过滤起来可以通过 DDMS 的 Logcat 标签来选择
+ *     Log.i 输出颜色为绿色, 一般提示性的消息 information, 它不会输出 Log.v 和 Log.d 的信息, 但会显示 i、w 和 e 的信息
+ *     Log.w 输出颜色为橙色, 可以看作为 warning 警告, 一般需要我们注意优化 Android 代码, 同时选择它后还会输出 Log.e 的信息
+ *     Log.e 输出颜色为红色, 可以想到 error 错误, 这里仅显示红色的错误信息, 这些错误就需要我们认真的分析, 查看栈的信息了
+ * 
+ */ +public enum LogLevel { + + /** + * 全部不打印 + */ + NONE, + + /** + * 调试级别 v, d ( 全部打印 ) + */ + DEBUG, + + /** + * 正常级别 i + */ + INFO, + + /** + * 警告级别 w + */ + WARN, + + /** + * 异常级别 e, wtf + */ + ERROR +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/logger/LoggerPrinter.java b/lib/DevApp/src/main/java/dev/utils/app/logger/LoggerPrinter.java new file mode 100644 index 0000000000..c3e903881a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/logger/LoggerPrinter.java @@ -0,0 +1,973 @@ +package dev.utils.app.logger; + +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import dev.utils.DevFinal; + +/** + * detail: 日志输出类 ( 处理方法 ) + * @author Ttt + */ +final class LoggerPrinter + implements IPrinter { + + // 日志配置 + private static LogConfig LOG_CONFIG = null; + // 每个线程的日志配置信息 + private static final ThreadLocal LOCAL_LOG_CONFIGS = new ThreadLocal<>(); + + // ================================ + // = 实现 IPrinter 接口, 对外公开方法 = + // ================================ + + /** + * 使用单次其他日志配置 + * @param logConfig 日志配置 + * @return {@link IPrinter} + */ + @Override + public IPrinter other(final LogConfig logConfig) { + if (logConfig != null) { + LOCAL_LOG_CONFIGS.set(logConfig); + } + return this; + } + + /** + * 获取日志配置信息 + * @return {@link LogConfig} 日志配置 + */ + @Override + public LogConfig getLogConfig() { + return LOG_CONFIG; + } + + /** + * 初始化日志配置信息 ( 使用默认配置 ) + * @return {@link LogConfig} 日志配置 + */ + @Override + public LogConfig initialize() { + // 判断日志配置信息是否等于 null + if (LOG_CONFIG == null) { + // 生成默认配置信息 + LOG_CONFIG = new LogConfig(); + } + // 返回配置信息 + return LOG_CONFIG; + } + + /** + * 自定义日志配置信息 + * @param logConfig 日志配置 + */ + @Override + public void initialize(final LogConfig logConfig) { + LOG_CONFIG = logConfig; + // 防止日志配置参数为 null + initialize(); + } + + // ============================= + // = 使用默认 TAG ( 日志打印方法 ) = + // ============================= + + /** + * 打印 Log.DEBUG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void d( + final String message, + final Object... args + ) { + logHandle(Log.DEBUG, message, args); + } + + /** + * 打印 Log.ERROR + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void e( + final String message, + final Object... args + ) { + e(null, message, args); + } + + /** + * 打印 Log.ERROR + * @param throwable 异常 + */ + @Override + public void e(final Throwable throwable) { + e(throwable, null); + } + + /** + * 打印 Log.ERROR + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void e( + final Throwable throwable, + final String message, + final Object... args + ) { + // 日志消息 + String logMsg = message; + // 判断消息 + if (throwable != null && message != null) { + logMsg = message + " : " + throwable.toString(); + } else if (throwable != null) { + logMsg = throwable.toString(); + } else if (message == null) { + // 没有日志信息, 也没有异常信息传入 + logMsg = "No message/exception is set"; + } + logHandle(Log.ERROR, logMsg, args); + } + + /** + * 打印 Log.WARN + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void w( + final String message, + final Object... args + ) { + logHandle(Log.WARN, message, args); + } + + /** + * 打印 Log.INFO + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void i( + final String message, + final Object... args + ) { + logHandle(Log.INFO, message, args); + } + + /** + * 打印 Log.VERBOSE + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void v( + final String message, + final Object... args + ) { + logHandle(Log.VERBOSE, message, args); + } + + /** + * 打印 Log.ASSERT + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void wtf( + final String message, + final Object... args + ) { + logHandle(Log.ASSERT, message, args); + } + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param json JSON 格式字符串 + */ + @Override + public void json(final String json) { + // 获取当前线程日志配置信息 + LogConfig logConfig = getThreadLogConfig(); + // 判断是否打印日志 ( 日志级别 ) + if (!isPrintLog(logConfig, Log.DEBUG)) { + return; + } + // 日志 TAG + String tag = logConfig.tag; + // 判断传入 JSON 格式信息是否为 null + if (TextUtils.isEmpty(json)) { + logHandle(logConfig, tag, Log.DEBUG, "Empty/Null json content"); + return; + } + try { + // 属于对象的 JSON 格式信息 + if (json.startsWith("{")) { + JSONObject jsonObject = new JSONObject(json); + // 进行缩进 + String message = jsonObject.toString(LogConstants.JSON_INDENT); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } else if (json.startsWith("[")) { + // 属于数据的 JSON 格式信息 + JSONArray jsonArray = new JSONArray(json); + // 进行缩进 + String message = jsonArray.toString(LogConstants.JSON_INDENT); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } else { + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, "json content format error"); + } + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + logHandle( + logConfig, tag, Log.ERROR, + errorInfo + DevFinal.SYMBOL.NEW_LINE + json + ); + } + } + + /** + * 格式化 XML 格式数据, 并打印 + * @param xml XML 格式字符串 + */ + @Override + public void xml(final String xml) { + // 获取当前线程日志配置信息 + LogConfig logConfig = getThreadLogConfig(); + // 判断是否打印日志 ( 日志级别 ) + if (!isPrintLog(logConfig, Log.DEBUG)) { + return; + } + // 日志 TAG + String tag = logConfig.tag; + // 判断传入 XML 格式信息是否为 null + if (TextUtils.isEmpty(xml)) { + logHandle(logConfig, tag, Log.DEBUG, "Empty/Null xml content"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty( + "{http://xml.apache.org/xslt}indent-amount", "2" + ); + transformer.transform(xmlInput, xmlOutput); + // 获取打印消息 + String message = xmlOutput.getWriter().toString() + .replaceFirst(">", ">\n"); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + logHandle( + logConfig, tag, Log.ERROR, + errorInfo + DevFinal.SYMBOL.NEW_LINE + xml + ); + } + } + + // ============================== + // = 使用自定义 TAG ( 日志打印方法 ) = + // ============================== + + /** + * 打印 Log.DEBUG + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void dTag( + final String tag, + final String message, + final Object... args + ) { + logHandle(tag, Log.DEBUG, message, args); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void eTag( + final String tag, + final String message, + final Object... args + ) { + eTag(tag, null, message, args); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + */ + @Override + public void eTag( + final String tag, + final Throwable throwable + ) { + eTag(tag, throwable, null); + } + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void eTag( + final String tag, + final Throwable throwable, + final String message, + final Object... args + ) { + // 日志消息 + String logMsg = message; + // 判断消息 + if (throwable != null && message != null) { + logMsg = message + " : " + throwable.toString(); + } else if (throwable != null) { + logMsg = throwable.toString(); + } else if (message == null) { + // 没有日志信息, 也没有异常信息传入 + logMsg = "No message/exception is set"; + } + logHandle(tag, Log.ERROR, logMsg, args); + } + + /** + * 打印 Log.WARN + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void wTag( + final String tag, + final String message, + final Object... args + ) { + logHandle(tag, Log.WARN, message, args); + } + + /** + * 打印 Log.INFO + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void iTag( + final String tag, + final String message, + final Object... args + ) { + logHandle(tag, Log.INFO, message, args); + } + + /** + * 打印 Log.VERBOSE + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void vTag( + final String tag, + final String message, + final Object... args + ) { + logHandle(tag, Log.VERBOSE, message, args); + } + + /** + * 打印 Log.ASSERT + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + @Override + public void wtfTag( + final String tag, + final String message, + final Object... args + ) { + logHandle(tag, Log.ASSERT, message, args); + } + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param tag 日志 TAG + * @param json JSON 格式字符串 + */ + @Override + public void jsonTag( + final String tag, + final String json + ) { + // 获取当前线程日志配置信息 + LogConfig logConfig = getThreadLogConfig(); + // 判断是否打印日志 ( 日志级别 ) + if (!isPrintLog(logConfig, Log.DEBUG)) { + return; + } + // 判断传入 JSON 格式信息是否为 null + if (TextUtils.isEmpty(json)) { + logHandle(logConfig, tag, Log.DEBUG, "Empty/Null json content"); + return; + } + try { + // 属于对象的 JSON 格式信息 + if (json.startsWith("{")) { + JSONObject jsonObject = new JSONObject(json); + // 进行缩进 + String message = jsonObject.toString(LogConstants.JSON_INDENT); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } else if (json.startsWith("[")) { + // 属于数据的 JSON 格式信息 + JSONArray jsonArray = new JSONArray(json); + // 进行缩进 + String message = jsonArray.toString(LogConstants.JSON_INDENT); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } else { + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, "json content format error"); + } + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + logHandle( + logConfig, tag, Log.ERROR, + errorInfo + DevFinal.SYMBOL.NEW_LINE + json + ); + } + } + + /** + * 格式化 XML 格式数据, 并打印 + * @param tag 日志 TAG + * @param xml XML 格式字符串 + */ + @Override + public void xmlTag( + final String tag, + final String xml + ) { + // 获取当前线程日志配置信息 + LogConfig logConfig = getThreadLogConfig(); + // 判断是否打印日志 ( 日志级别 ) + if (!isPrintLog(logConfig, Log.DEBUG)) { + return; + } + // 判断传入 XML 格式信息是否为 null + if (TextUtils.isEmpty(xml)) { + logHandle(logConfig, tag, Log.DEBUG, "Empty/Null xml content"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty( + "{http://xml.apache.org/xslt}indent-amount", "2" + ); + transformer.transform(xmlInput, xmlOutput); + // 获取打印消息 + String message = xmlOutput.getWriter().toString() + .replaceFirst(">", ">\n"); + // 打印信息 + logHandle(logConfig, tag, Log.DEBUG, message); + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + logHandle( + logConfig, tag, Log.ERROR, + errorInfo + DevFinal.SYMBOL.NEW_LINE + xml + ); + } + } + + // ============= + // = 内部判断方法 = + // ============= + + /** + * 是否打印日志 + * @param logConfig 日志配置 + * @param logType 日志类型 + * @return {@code true} yes, {@code false} no + */ + private boolean isPrintLog( + final LogConfig logConfig, + final int logType + ) { + // 是否打印日志 ( 默认不打印 ) + boolean isPrint = false; + // 日志级别 + LogLevel logLevel = logConfig.logLevel; + // = + switch (logLevel) { + case NONE: // 全部不打印 + break; + case DEBUG: // 调试级别 v, d ( 全部打印 ) + isPrint = true; + break; + case INFO: // 正常级别 i + case WARN: // 警告级别 w + case ERROR: // 异常级别 e, wtf + isPrint = checkLogLevel(logLevel, logType); + break; + default: + break; + } + return isPrint; + } + + /** + * 判断日志级别是否允许输出 + * @param logLevel 日志级别 + * @param logType 日志类型 + * @return {@code true} yes, {@code false} no + */ + private boolean checkLogLevel( + final LogLevel logLevel, + final int logType + ) { + switch (logLevel) { + case INFO: // 正常级别 i + if (logType != Log.VERBOSE && logType != Log.DEBUG) { + return true; + } + break; + case WARN: // 警告级别 w + if (logType != Log.VERBOSE && logType != Log.DEBUG && logType != Log.INFO) { + return true; + } + break; + case ERROR: // 异常级别 e, wtf + if (logType == Log.ERROR || logType == Log.ASSERT) { + return true; + } + break; + default: + break; + } + return false; + } + + // ================ + // = 打印日志处理方法 = + // ================ + + /** + * 最终打印方法 + * @param logType 日志类型 + * @param tag 日志 TAG + * @param message 日志信息 + */ + private void finalLogPrinter( + final int logType, + final String tag, + final String message + ) { + if (DevLogger.sPrint != null) { + DevLogger.sPrint.printLog(logType, tag, message); + } + } + + /** + * 日志处理方法 + * @param logType 日志类型 + * @param message 日志信息 + * @param args 占位符替换 + */ + private void logHandle( + final int logType, + final String message, + final Object... args + ) { + logHandle(null, null, logType, message, args); + } + + /** + * 日志处理方法 + * @param tag 日志 TAG + * @param logType 日志类型 + * @param message 日志信息 + * @param args 占位符替换 + */ + private void logHandle( + final String tag, + final int logType, + final String message, + final Object... args + ) { + logHandle(null, tag, logType, message, args); + } + + /** + * 日志处理方法 ( 此方法是同步的, 以避免混乱的日志的顺序 ) + * @param config 配置信息 + * @param tag 日志 TAG + * @param logType 日志类型 + * @param msg 日志信息 + * @param args 占位符替换 + */ + private synchronized void logHandle( + final LogConfig config, + final String tag, + final int logType, + final String msg, + final Object... args + ) { + LogConfig logConfig = config; + // 如果配置为 null, 才进行获取 + if (logConfig == null) { + // 获取当前线程日志配置信息 + logConfig = getThreadLogConfig(); + } + // 判断是否打印日志 ( 日志级别 ) + if (!isPrintLog(logConfig, logType)) { + return; + } + String logTag = tag; + // 防止 TAG 为 null + if (TextUtils.isEmpty(logTag)) { + // 获取配置的 TAG + logTag = logConfig.tag; + // 防止配置的 TAG 也为 null + if (TextUtils.isEmpty(logTag)) { + // 使用默认的 TAG + logTag = LogConstants.DEFAULT_LOG_TAG; + } + } + // 判断是否显示排序后的日志 ( 如果不排序, 则显示默认 ) + if (!logConfig.sortLog) { + finalLogPrinter(logType, logTag, createMessage(msg, args)); + return; + } + // = 日志配置信息获取 = + // 获取方法总数 + int methodCount = logConfig.methodCount; + // 获取方法偏移索引 + int methodOffset = logConfig.methodOffset; + // 如果出现小于 0 的设置, 则设置默认值处理 + if (methodOffset < 0) { + methodOffset = LogConstants.DEFAULT_LOG_METHOD_OFFSET; + } + // 如果出现小于 0 的设置, 则设置默认值处理 + if (methodCount < 0) { + methodCount = LogConstants.DEFAULT_LOG_METHOD_COUNT; + } + // 获取打印的日志信息 + String message = createMessage(msg, args); + // 防止 null 处理 + if (message == null) return; + // 打印头部 + logTopBorder(logType, logTag); + // 打印头部线程信息 + logHeaderContent(logConfig, logType, logTag, methodCount, methodOffset); + // 获取系统的默认字符集的信息字节 (UTF-8) + byte[] bytes = message.getBytes(); + // 获取字节总数 + int length = bytes.length; + // 判断是否超过总数, 没有超过则一次性打印, 超过则遍历打印 + if (length <= LogConstants.CHUNK_SIZE) { + if (methodCount > 0) { + logDivider(logType, logTag); + } + // 打印日志内容 + logContent(logType, logTag, message); + // 打印结尾 + logBottomBorder(logType, logTag); + return; + } + // 打印换行符 + if (methodCount > 0) { + // 换行 + logDivider(logType, logTag); + } + // 因为超过系统打印字节总数, 遍历打印 + for (int i = 0; i < length; i += LogConstants.CHUNK_SIZE) { + int count = Math.min(length - i, LogConstants.CHUNK_SIZE); + // 创建系统的默认字符集的一个新的字符串 (UTF-8), 并打印日志内容 + logContent(logType, logTag, new String(bytes, i, count)); + } + // 打印结尾 + logBottomBorder(logType, logTag); + } + + // ============= + // = 日志格式拼接 = + // ============= + + /** + * 日志线程信息主体部分 + * @param logConfig 日志配置 + * @param logType 日志类型 + * @param tag 日志 TAG + * @param methodCount 方法总数 + * @param methodOffset 方法偏移索引 + */ + private void logHeaderContent( + final LogConfig logConfig, + final int logType, + final String tag, + int methodCount, + int methodOffset + ) { + StackTraceElement[] trace = Thread.currentThread().getStackTrace(); + // 判断是否显示日志线程信息 + if (!logConfig.displayThreadInfo) return; + + // 打印线程信息 ( 线程名 ) + finalLogPrinter( + logType, tag, LogConstants.HORIZONTAL_DOUBLE_LINE + + " Thread: " + Thread.currentThread().getName() + ); + // 进行换行 + logDivider(logType, tag); + + // 堆栈总数 + int traceCount = trace.length; + // 获取堆栈偏移量 + int stackOffset = getStackOffset(trace) + methodOffset; + // 对应的方法计数与当前堆栈可能超过, 进行堆栈跟踪 + if (methodCount + stackOffset > traceCount) { + methodCount = traceCount - stackOffset - 1; + } + // 判断是否显示全部方法 + if (logConfig.outputMethodAll) { + // 设置方法总数 + methodCount = traceCount; + // 设置方法偏移索引为 0 + stackOffset = 0; + } else if (methodCount <= 0) { + // 如果打印数小于等于 0, 则直接跳过 + return; + } + + // 手动进行偏移 + StringBuilder traceLevel = new StringBuilder(); + // 遍历打印的方法数量 ( 类名、行数、操作的方法名 ) + for (int i = methodCount; i > 0; i--) { + int stackIndex = i + stackOffset; + if (stackIndex >= traceCount) { + continue; + } + // 拼接中间内容、操作的类名、行数、方法名等信息 + StringBuilder builder = new StringBuilder(); + builder.append("║ ").append(traceLevel); + builder.append(getSimpleClassName(trace[stackIndex].getClassName())); + builder.append(".").append(trace[stackIndex].getMethodName()); + builder.append(" ("); + builder.append(trace[stackIndex].getFileName()); + builder.append(":"); + builder.append(trace[stackIndex].getLineNumber()); + builder.append(")"); + traceLevel.append(" "); + // 打印日志信息 + finalLogPrinter(logType, tag, builder.toString()); + } + } + + /** + * 日志顶部 + * @param logType 日志类型 + * @param tag 日志 TAG + */ + private void logTopBorder( + final int logType, + final String tag + ) { + finalLogPrinter(logType, tag, LogConstants.TOP_BORDER); + } + + /** + * 日志结尾 + * @param logType 日志类型 + * @param tag 日志 TAG + */ + private void logBottomBorder( + final int logType, + final String tag + ) { + finalLogPrinter(logType, tag, LogConstants.BOTTOM_BORDER); + } + + /** + * 日志换行 + * @param logType 日志类型 + * @param tag 日志 TAG + */ + private void logDivider( + final int logType, + final String tag + ) { + finalLogPrinter(logType, tag, LogConstants.MIDDLE_BORDER); + } + + /** + * 日志内容 + * @param logType 日志类型 + * @param tag 日志 TAG + * @param msg 日志信息 + */ + private void logContent( + final int logType, + final String tag, + final String msg + ) { + String[] lines = msg.split(DevFinal.SYMBOL.NEW_LINE); + for (String line : lines) { + finalLogPrinter( + logType, tag, + LogConstants.HORIZONTAL_DOUBLE_LINE + " " + line + ); + } + } + + /** + * 处理信息 + * @param message 日志信息 + * @param args 占位符替换 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private String createMessage( + final String message, + final Object... args + ) { + if (message != null) { + try { + return args.length == 0 ? message : String.format(message, args); + } catch (Exception ignored) { + } + } + return "message is null"; + } + + // ============= + // = 获取堆栈信息 = + // ============= + + /** + * 获取类名 + * @param name 类.class + * @return ClassName + */ + private String getSimpleClassName(final String name) { + int lastIndex = name.lastIndexOf('.'); + return name.substring(lastIndex + 1); + } + + /** + * 确定该类的方法调用后的堆栈跟踪的起始索引 + * @param trace 堆栈 + * @return 堆栈跟踪索引 + */ + private int getStackOffset(final StackTraceElement[] trace) { + for (int i = LogConstants.MIN_STACK_OFFSET, len = trace.length; i < len; i++) { + StackTraceElement e = trace[i]; + String name = e.getClassName(); + if (!LoggerPrinter.class.getName().equals(name) + && !DevLogger.class.getName().equals(name)) { + return --i; + } + } + return -1; + } + + // ============= + // = 日志配置获取 = + // ============= + + /** + * 返回对应线程的日志配置信息 + * @return {@link LogConfig} 日志配置 + */ + private LogConfig getThreadLogConfig() { + // 获取当前线程的日志配置信息 + LogConfig logConfig = LOCAL_LOG_CONFIGS.get(); + // 如果等于 null, 则返回默认配置信息 + if (logConfig == null) { + return initialize(); + } else { + LOCAL_LOG_CONFIGS.remove(); + } + // 如果存在当前线程的配置信息, 则返回 + return logConfig; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/permission/PermissionUtils.java b/lib/DevApp/src/main/java/dev/utils/app/permission/PermissionUtils.java new file mode 100644 index 0000000000..44258e0647 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/permission/PermissionUtils.java @@ -0,0 +1,579 @@ +package dev.utils.app.permission; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.ActivityCompat; +import androidx.core.content.PermissionChecker; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.app.IntentUtils; +import dev.utils.app.info.AppInfoUtils; +import dev.utils.common.CollectionUtils; + +/** + * detail: 权限请求工具类 + * @author Ttt + */ +public final class PermissionUtils { + + // 日志 TAG + private static final String TAG = PermissionUtils.class.getSimpleName(); + + // 权限 Activity.class name + public static final String PERMISSION_ACTIVITY_CLASS_NAME = "dev.utils.app.permission.PermissionUtils$PermissionActivity"; + + /** + * 判断是否授予了权限 + * @param permissions 待判断权限 + * @return {@code true} yes, {@code false} no + */ + public static boolean isGranted(final String... permissions) { + // 防止数据为 null + if (permissions != null && permissions.length != 0) { + // 遍历全部需要申请的权限 + for (String permission : permissions) { + if (!isGranted(DevUtils.getContext(), permission)) { + return false; + } + } + return true; + } + return false; + } + + /** + * 判断是否授予了权限 + * @param context {@link Context} + * @param permission 待判断权限 + * @return {@code true} yes, {@code false} no + */ + private static boolean isGranted( + final Context context, + final String permission + ) { + if (context == null || permission == null) return false; + // SDK 版本小于 23 则表示直接通过 || 检查是否通过权限 + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || + PermissionChecker.PERMISSION_GRANTED == PermissionChecker.checkSelfPermission( + context, permission + ); + } + + /** + * 获取拒绝权限询问勾选状态 + *
+     *     拒绝过一次, 再次申请时, 弹出选择进行拒绝, 获取询问勾选状态
+     *     true 表示没有勾选不再询问, 而 false 则表示勾选了不再询问
+     * 
+ * @param activity {@link Activity} + * @param permissions 待判断权限 + * @return {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问 + */ + public static boolean shouldShowRequestPermissionRationale( + final Activity activity, + final String... permissions + ) { + if (activity == null || permissions == null) return false; + boolean state = false; // 表示勾选了不再询问 + for (String permission : permissions) { + if (permission != null && !isGranted(activity, permission)) { + state = ActivityCompat.shouldShowRequestPermissionRationale( + activity, permission + ); + if (!state) return false; + } + } + return state; + } + + /** + * 获取拒绝权限询问状态集合 + * @param activity {@link Activity} + * @param shouldShow {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问 + * @param permissions 待判断权限 + * @return 拒绝权限询问状态集合 + */ + public static List getDeniedPermissionStatus( + final Activity activity, + final boolean shouldShow, + final String... permissions + ) { + if (activity == null || permissions == null) return new ArrayList<>(); + Set sets = new HashSet<>(); + for (String permission : permissions) { + if (permission != null && !sets.contains(permission) && !isGranted(activity, permission)) { + boolean state = ActivityCompat.shouldShowRequestPermissionRationale( + activity, permission + ); + if (shouldShow == state) { + sets.add(permission); + } + } + } + return new ArrayList<>(sets); + } + + /** + * 是否存在 APK 安装权限 + * @return {@code true} yes, {@code false} no + */ + public static boolean canRequestPackageInstalls() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + PackageManager packageManager = AppUtils.getPackageManager(); + if (packageManager == null) return false; + try { + return packageManager.canRequestPackageInstalls(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "canRequestPackageInstalls"); + } + return false; + } + return true; + } + + /** + * 获取全部权限 + * @return {@link Set} 全部权限 + */ + public static Set getAllPermissionToSet() { + Set permissionSets = new HashSet<>(); + Field[] fields = Manifest.permission.class.getFields(); + for (Field field : fields) { + try { + String name = (String) field.get(""); + permissionSets.add(name); + } catch (Exception ignored) { + } + } + return permissionSets; + } + + /** + * 获取全部权限 + * @return {@link List} 全部权限 + */ + public static List getAllPermissionToList() { + return new ArrayList<>(getAllPermissionToSet()); + } + + // ================ + // = AppInfoUtils = + // ================ + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限 + */ + public static List getAppPermissionToList() { + return AppInfoUtils.getAppPermissionToList(); + } + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限 + */ + public static Set getAppPermissionToSet() { + return AppInfoUtils.getAppPermissionToSet(); + } + + /** + * 获取 APP 注册的权限 + * @return APP 注册的权限数组 + */ + public static String[] getAppPermission() { + return AppInfoUtils.getAppPermission(); + } + + /** + * 获取 APP 注册的权限 + * @param packageName 应用包名 + * @return APP 注册的权限数组 + */ + public static String[] getAppPermission(final String packageName) { + return AppInfoUtils.getAppPermission(packageName); + } + + // ========== + // = 权限申请 = + // ========== + + // APP 注册的权限 + private static final Set sAppPermissionSets = getAppPermissionToSet(); + // 申请未通过的权限 ( 永久拒绝 ) + private static final List sPermissionsDeniedForeverLists = new ArrayList<>(); + // 申请的权限 ( 传入的权限参数 ) + private final Set mPermissionSets = new HashSet<>(); + // 准备请求的权限 + private final List mPermissionsRequestLists = new ArrayList<>(); + // 申请通过的权限 + private final List mPermissionsGrantedLists = new ArrayList<>(); + // 申请未通过的权限 + private final List mPermissionsDeniedLists = new ArrayList<>(); + // 查询不到的权限 ( 包含未注册 ) + private final List mPermissionsNotFoundLists = new ArrayList<>(); + // 操作回调 + private PermissionCallback mCallback; + // 回调方法 Handler + private final Handler mHandler = new Handler(Looper.getMainLooper()); + // 判断是否请求过 + private boolean mRequest = false; + // 是否需要在 Activity 的 onRequestPermissionsResult 回调中, 调用 PermissionUtils.onRequestPermissionsResult(this); + private boolean mRequestPermissionsResult = false; // 默认使用内部 PermissionActivity + // Permission 请求 Code + public static final int P_REQUEST_CODE = 10101; + + /** + * 构造函数 + * @param permissions 待申请权限 + */ + private PermissionUtils(final String... permissions) { + mPermissionSets.clear(); + // 防止数据为 null + if (permissions != null && permissions.length != 0) { + // 遍历全部需要申请的权限 + for (String permission : permissions) { + if (!TextUtils.isEmpty(permission)) { + mPermissionSets.add(permission); + } + } + } + } + + // ========== + // = 使用方法 = + // ========== + + /** + * 申请权限初始化 + * @param permissions 待申请权限 + * @return {@link PermissionUtils} + */ + public static PermissionUtils permission(final String... permissions) { + return new PermissionUtils(permissions); + } + + /** + * 设置回调方法 + * @param callback {@link PermissionCallback} + * @return {@link PermissionUtils} + */ + public PermissionUtils callback(final PermissionCallback callback) { + if (mRequest) return this; + this.mCallback = callback; + return this; + } + + /** + * 设置是否需要在 Activity 的 onRequestPermissionsResult 回调中, 调用 PermissionUtils.onRequestPermissionsResult(this); + * @param requestPermissionsResult {@code true} yes, {@code false} no + * @return {@link PermissionUtils} + */ + public PermissionUtils setRequestPermissionsResult(final boolean requestPermissionsResult) { + if (mRequest) return this; + this.mRequestPermissionsResult = requestPermissionsResult; + return this; + } + + /** + * 请求权限 + * @param activity {@link Activity} + */ + public void request(final Activity activity) { + request(activity, P_REQUEST_CODE); + } + + /** + * 请求权限 + * @param activity {@link Activity} + * @param requestCode 请求 code + */ + public void request( + final Activity activity, + final int requestCode + ) { + if (checkPermissions(activity) == 1) { + // 如果 SDK 版本大于 23 才请求 + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + sInstance = this; + // 请求权限 + String[] permissions = mPermissionsRequestLists.toArray(new String[0]); + // 判断请求方式 + if (this.mRequestPermissionsResult) { + // 请求权限 + ActivityCompat.requestPermissions(activity, permissions, requestCode); + } else { + // 自定义权限 Activity + PermissionActivity.start(activity); + } + } + } + } + + // ============= + // = 请求权限回调 = + // ============= + + /** + * detail: 权限请求回调 + * @author Ttt + */ + public interface PermissionCallback { + + /** + * 授权通过权限回调 + */ + void onGranted(); + + /** + * 授权未通过权限回调 + *
+         *     判断 deniedList 申请未通过的权限中拒绝状态
+         *     可通过 {@link #getDeniedPermissionStatus(Activity, boolean, String...)} 进行获取
+         *     第二个参数 shouldShow ( boolean )
+         *     {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问
+         * 
+ * @param grantedList 申请通过的权限 + * @param deniedList 申请未通过的权限 + * @param notFoundList 查询不到的权限 ( 包含未注册 ) + */ + void onDenied( + List grantedList, + List deniedList, + List notFoundList + ); + } + + // ================ + // = 内部 Activity = + // ================ + + // 内部持有对象 + private static PermissionUtils sInstance; + + /** + * detail: 请求权限 Activity + * @author Ttt + */ + @RequiresApi(api = Build.VERSION_CODES.M) + public static class PermissionActivity + extends Activity { + + /** + * 跳转 PermissionActivity 请求权限 内部方法 + * @param context {@link Context} + */ + protected static void start(final Context context) { + Intent starter = new Intent(context, PermissionActivity.class); + starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(starter); + } + + /** + * PermissionActivity ( onCreate 内部方法 ) + * @param savedInstanceState 关闭时存储数据 + */ + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // 请求权限 + int size = sInstance.mPermissionsRequestLists.size(); + requestPermissions( + sInstance.mPermissionsRequestLists.toArray( + new String[size] + ), 1 + ); + } + + /** + * 请求权限回调 + * @param requestCode 请求 code + * @param permissions 请求权限 + * @param grantResults 权限授权结果 + */ + @Override + public void onRequestPermissionsResult( + int requestCode, + @NonNull String[] permissions, + @NonNull int[] grantResults + ) { + sInstance.onRequestPermissionsResultCommon(this); // 处理回调 + finish(); // 关闭当前页面 + } + } + + /** + * 请求回调权限回调处理 + * @param activity {@link Activity} + */ + private void onRequestPermissionsResultCommon(final Activity activity) { + // 获取权限状态 + getPermissionsStatus(activity); + // 判断请求结果 + requestCallback(); + } + + /** + * 请求权限回调 ( 需要在 Activity 的 onRequestPermissionsResult 回调中, 调用 PermissionUtils.onRequestPermissionsResult(this); ) + * @param activity {@link Activity} + */ + public static void onRequestPermissionsResult(final Activity activity) { + if (activity != null && sInstance != null) { // 触发回调 + sInstance.onRequestPermissionsResultCommon(activity); + } + } + + /** + * 刷新权限改变处理 ( 清空已拒绝的权限记录 ) + */ + public static void notifyPermissionsChange() { + sPermissionsDeniedForeverLists.clear(); + } + + /** + * 再次请求处理操作 + *
+     *     如果存在拒绝了且不再询问则跳转到应用设置页面
+     *     否则则再次请求拒绝的权限
+     * 
+ * @param activity {@link Activity} + * @param callback {@link PermissionCallback} + * @param deniedList 申请未通过的权限集合 + * @return 0 不符合要求无任何操作、1 再次请求操作、2 跳转到应用设置页面 + */ + public static int againRequest( + final Activity activity, + final PermissionCallback callback, + final List deniedList + ) { + if (activity == null || CollectionUtils.isEmpty(deniedList)) return 0; + // 获取拒绝的权限记录 + String[] deniedArrays = deniedList.toArray(new String[0]); + // 获取拒绝权限询问勾选状态 true 表示没有勾选不再询问, 而 false 则表示勾选了不再询问 + if (PermissionUtils.shouldShowRequestPermissionRationale(activity, deniedArrays)) { // 再次请求 + PermissionUtils.permission(deniedArrays) + .callback(callback).request(activity); + return 1; + } else { // 拒绝权限且不再询问, 跳转到应用设置页面 + AppUtils.startActivity(IntentUtils.getLaunchAppDetailsSettingsIntent()); + return 2; + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 权限判断处理 + * @param activity {@link Activity} + * @return -1 已经请求 ( 中 ) 过, 0 = 不处理 ( 通知回调 ), 1 = 需要请求 + */ + private int checkPermissions(final Activity activity) { + if (activity == null) { + // 处理请求回调 + requestCallback(); + // 不处理 + return 0; + } + if (mRequest) { + return -1; // 已经请求 ( 中 ) 过 + } + mRequest = true; + // 如果 SDK 版本小于 23 则直接通过 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // 表示全部权限都通过 + mPermissionsGrantedLists.addAll(mPermissionSets); + // 处理请求回调 + requestCallback(); + } else { + for (String permission : mPermissionSets) { + // 首先判断是否存在 + if (sAppPermissionSets.contains(permission)) { + // 判断是否通过请求 + if (isGranted(activity, permission)) { + mPermissionsGrantedLists.add(permission); // 权限允许通过 + // 如果原本已经永久拒绝现在通过, 则移除 + if (sPermissionsDeniedForeverLists.contains(permission)) { // 移除永久拒绝的权限记录 + CollectionUtils.remove(sPermissionsDeniedForeverLists, permission); + } + } else { // 判断是否已拒绝但可再次请求 + if (!sPermissionsDeniedForeverLists.contains(permission)) { // 不存在, 则进行保存 + mPermissionsRequestLists.add(permission); // 准备请求权限 + } + } + } else { + // 保存到没找到的权限集合 + mPermissionsNotFoundLists.add(permission); + } + } + // 判断是否存在等待请求的权限 + if (mPermissionsRequestLists.isEmpty()) { + // 处理请求回调 + requestCallback(); + } else { // 表示需要申请 + return 1; + } + } + return 0; + } + + /** + * 内部请求回调, 统一处理方法 + */ + private void requestCallback() { + if (mCallback != null) { + // 判断是否授权全部权限 + boolean isGrantedAll = (mPermissionSets.size() == mPermissionsGrantedLists.size()); + // 允许则触发回调 + if (isGrantedAll) { + mHandler.post(() -> mCallback.onGranted()); + } else { + mHandler.post(() -> mCallback.onDenied( + mPermissionsGrantedLists, + mPermissionsDeniedLists, + mPermissionsNotFoundLists + )); + } + } + } + + /** + * 获取权限状态 + * @param activity {@link Activity} + */ + private void getPermissionsStatus(final Activity activity) { + for (String permission : mPermissionsRequestLists) { + // 判断是否通过请求 + if (isGranted(activity, permission)) { + mPermissionsGrantedLists.add(permission); + } else { + // 未授权 + mPermissionsDeniedLists.add(permission); + // 拒绝权限并不再询问 + if (!shouldShowRequestPermissionRationale(activity, permission)) { + sPermissionsDeniedForeverLists.add(permission); + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java b/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java new file mode 100644 index 0000000000..4a65d8e8a3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java @@ -0,0 +1,765 @@ +package dev.utils.app.player; + +import android.content.res.AssetFileDescriptor; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.MediaPlayer.OnBufferingUpdateListener; +import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; +import android.media.MediaPlayer.OnPreparedListener; +import android.media.MediaPlayer.OnSeekCompleteListener; +import android.media.MediaPlayer.OnVideoSizeChangedListener; + +import androidx.annotation.RawRes; + +import dev.utils.LogPrintUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: MediaPlayer 统一管理类 + * @author Ttt + */ +public final class DevMediaManager + implements OnBufferingUpdateListener, + OnCompletionListener, + OnPreparedListener, + OnVideoSizeChangedListener, + OnErrorListener, + OnSeekCompleteListener { + + private DevMediaManager() { + } + + // 日志 TAG + private static String TAG = DevMediaManager.class.getSimpleName(); + + // MediaPlayer 对象 + private MediaPlayer mMediaPlayer; + // DevMediaManager 实例 + private static volatile DevMediaManager sInstance; + + /** + * 获取 DevMediaManager 实例 + * @return {@link DevMediaManager} + */ + public static DevMediaManager getInstance() { + if (sInstance == null) { + synchronized (DevMediaManager.class) { + if (sInstance == null) { + sInstance = new DevMediaManager(); + } + } + } + return sInstance; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 创建 MediaPlayer + */ + private void createMedia() { + // 销毁 MediaPlayer + destroyMedia(); + // 初始化 MediaPlayer + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.reset(); + // 绑定事件 + bindListener(); + // 设置默认流类型 + setAudioStreamType(mStreamType); + } + + /** + * 销毁 MediaPlayer + */ + private void destroyMedia() { + try { + // 表示非播放状态 + if (mMediaPlayer != null) { + if (mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); // 停止播放 + } + mMediaPlayer.release(); // 释放资源 + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "destroyMedia"); + } + // 重置为 null + mMediaPlayer = null; + // 清空播放信息 + clearMediaPlayerData(); + } + + /** + * 绑定事件 + */ + private void bindListener() { + if (mMediaPlayer != null) { + // 播放结束回调 + mMediaPlayer.setOnBufferingUpdateListener(this); + // 播放结束回调 + mMediaPlayer.setOnCompletionListener(this); + // 预加载完成回调 + mMediaPlayer.setOnPreparedListener(this); + // 视频宽高大小改变回调 + mMediaPlayer.setOnVideoSizeChangedListener(this); + // 错误回调 + mMediaPlayer.setOnErrorListener(this); + // 滑动加载完成回调 + mMediaPlayer.setOnSeekCompleteListener(this); + } + } + + /** + * 设置流类型 + * @param streamType Audio streamType + * @return {@link DevMediaManager} + */ + public DevMediaManager setAudioStreamType(final int streamType) { + this.mStreamType = streamType; + // 防止为 null + if (mMediaPlayer != null) { + try { + // 播放流类型 + mMediaPlayer.setAudioStreamType(streamType); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setAudioStreamType"); + } + } + return this; + } + + // ========== + // = 播放操作 = + // ========== + + /** + * 播放 Raw 资源 + * @param rawId 播放资源 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepareRaw(@RawRes final int rawId) { + return playPrepareRaw(rawId, false); + } + + /** + * 播放 Raw 资源 + * @param rawId 播放资源 + * @param isLooping 是否循环播放 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepareRaw( + @RawRes final int rawId, + final boolean isLooping + ) { + try { + mPlayRawId = rawId; + mPlayUri = null; + // 预播放 + return playPrepare(new MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + // 获取资源文件 + AssetFileDescriptor afd = ResourceUtils.openRawResourceFd(rawId); + try { + // 设置播放路径 + mMediaPlayer.setDataSource( + afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getLength() + ); + } finally { + CloseUtils.closeIOQuietly(afd); + } + } + + @Override + public boolean isLooping() { + return isLooping; + } + }); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "playPrepareRaw"); + // 销毁资源 + destroyMedia(); + } + return false; + } + + // = + + /** + * 播放 Assets 资源 + * @param playUri 播放地址 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepareAssets(final String playUri) { + return playPrepareAssets(playUri, false); + } + + /** + * 播放 Assets 资源 + * @param playUri 播放地址 + * @param isLooping 是否循环播放 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepareAssets( + final String playUri, + final boolean isLooping + ) { + try { + mPlayRawId = -1; + if (playUri.startsWith("/")) { + mPlayUri = playUri; + } else { + mPlayUri = "/" + playUri; + } + + final String tempPlayUri = mPlayUri; + // 预播放 + return playPrepare(new MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + // 获取资源文件 + AssetFileDescriptor afd = ResourceUtils.openNonAssetFd( + "assets" + tempPlayUri + ); + try { + // 设置播放路径 + mMediaPlayer.setDataSource( + afd.getFileDescriptor(), + afd.getStartOffset(), + afd.getLength() + ); + } finally { + CloseUtils.closeIOQuietly(afd); + } + } + + @Override + public boolean isLooping() { + return isLooping; + } + }); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "playPrepareAssets %s", playUri); + // 销毁资源 + destroyMedia(); + } + return false; + } + + // ========== + // = 播放路径 = + // ========== + + /** + * 预加载播放 ( file-path or http/rtsp URL ) http 资源、本地资源 + * @param playUri 播放地址 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepare(final String playUri) { + return playPrepare(playUri, false); + } + + /** + * 预加载播放 ( file-path or http/rtsp URL ) http 资源、本地资源 + * @param playUri 播放地址 + * @param isLooping 是否循环播放 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepare( + final String playUri, + final boolean isLooping + ) { + try { + mPlayRawId = -1; + mPlayUri = playUri; + // 预播放 + return playPrepare(new MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + mediaPlayer.setDataSource(playUri); + } + + @Override + public boolean isLooping() { + return isLooping; + } + }); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "playPrepare playUri: %s", playUri); + // 销毁资源 + destroyMedia(); + } + return false; + } + + // ============= + // = 最终调用方法 = + // ============= + + /** + * 预加载播放 ( 最终调用方法, 加载成功触发 onPrepared, 该方法内调用 mMediaPlayer.start() ) + * @param mediaSet 播放设置 + * @return {@code true} 执行成功, {@code false} 执行失败 + */ + public boolean playPrepare(final MediaSet mediaSet) { + // 防止为 null + if (mediaSet == null) { + return false; + } + try { + // 初始化 MediaPlayer + createMedia(); + // 设置循环播放 + mMediaPlayer.setLooping(mediaSet.isLooping()); + // 设置播放音量 + if (mediaSet.getVolume() >= 0F) { + mMediaPlayer.setVolume(mediaSet.getVolume(), mediaSet.getVolume()); + } + // 设置播放路径 + mediaSet.setMediaConfig(mMediaPlayer); + // 异步加载 + mMediaPlayer.prepareAsync(); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "playPrepare"); + // 销毁资源 + destroyMedia(); + } + return false; + } + + // =================== + // = MediaPlayer 操作 = + // =================== + + /** + * 是否播放中 + * @return {@code true} yes, {@code false} no + */ + public boolean isPlaying() { + if (mMediaPlayer != null) { + return mMediaPlayer.isPlaying(); + } + return false; + } + + /** + * 暂停操作 + */ + public void pause() { + if (mMediaPlayer != null) { + try { + mMediaPlayer.pause(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "pause"); + } + } + } + + /** + * 停止操作 ( 销毁 MediaPlayer ) + */ + public void stop() { + // 销毁 MediaPlayer + destroyMedia(); + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 是否忽略错误类型 + * @param errorWhat onError 方法回调 what + * @return {@code true} yes, {@code false} no + */ + public static boolean isIgnoreWhat(final int errorWhat) { + // 是否忽略 + boolean ignore = false; + switch (errorWhat) { + case -38: + case 1: + case 100: + case 700: + case 701: + case 800: + ignore = true; + break; + } + return ignore; + } + + // ========== + // = 回调事件 = + // ========== + + /** + * 播放出错回调 + * @param mp {@link MediaPlayer} + * @param what 异常 what + * @param extra 异常 extra + * @return {@code true} 处理异常, {@code false} 调用 OnCompletionListener + */ + @Override + public boolean onError( + MediaPlayer mp, + int what, + int extra + ) { + LogPrintUtils.dTag( + TAG, "onError what: %s, extra: %s", what, extra + ); + // 触发回调 + if (mMediaListener != null) { + return mMediaListener.onError(what, extra); + } + return false; + } + + /** + * 视频大小改变通知 + * @param mp {@link MediaPlayer} + * @param width 宽度 + * @param height 高度 + */ + @Override + public void onVideoSizeChanged( + MediaPlayer mp, + int width, + int height + ) { + LogPrintUtils.dTag( + TAG, "onVideoSizeChanged - width: %s, height: %s", + width, height + ); + mVideoWidth = width; + mVideoHeight = height; + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onVideoSizeChanged(width, height); + } + } + + /** + * 使用 mMediaPlayer.prepareAsync() 异步播放准备成功回调 + * @param mp {@link MediaPlayer} + */ + @Override + public void onPrepared(MediaPlayer mp) { + LogPrintUtils.dTag(TAG, "onPrepared"); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onPrepared(); + } + } + + /** + * 视频播放结束回调 + * @param mp {@link MediaPlayer} + */ + @Override + public void onCompletion(MediaPlayer mp) { + LogPrintUtils.dTag(TAG, "onCompletion"); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onCompletion(); + } + } + + /** + * MediaPlayer 缓冲更新回调 + * @param mp {@link MediaPlayer} + * @param percent 缓冲百分比进度 + */ + @Override + public void onBufferingUpdate( + MediaPlayer mp, + int percent + ) { + LogPrintUtils.dTag(TAG, "onBufferingUpdate - percent: %s", percent); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onBufferingUpdate(percent); + } + } + + /** + * 滑动加载完成回调 + * @param mp {@link MediaPlayer} + */ + @Override + public void onSeekComplete(MediaPlayer mp) { + LogPrintUtils.dTag(TAG, "onSeekComplete"); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onSeekComplete(); + } + } + + // ============= + // = 封装回调事件 = + // ============= + + // MediaPlayer 回调事件 + private MediaListener mMediaListener; + + /** + * detail: MediaPlayer 回调接口 + * @author Ttt + */ + public interface MediaListener { + + /** + * 使用 mMediaPlayer.prepareAsync() 异步播放准备成功回调 + */ + void onPrepared(); + + /** + * 视频播放结束回调 + */ + void onCompletion(); + + /** + * MediaPlayer 缓冲更新回调 + * @param percent 缓冲百分比进度 + */ + void onBufferingUpdate(int percent); + + /** + * 滑动加载完成回调 + */ + void onSeekComplete(); + + /** + * 播放出错回调 + * @param what 异常 what + * @param extra 异常 extra + * @return {@code true} 处理异常, {@code false} 调用 OnCompletionListener + */ + boolean onError( + int what, + int extra + ); + + /** + * 视频大小改变通知 + * @param width 宽度 + * @param height 高度 + */ + void onVideoSizeChanged( + int width, + int height + ); + } + + /** + * 设置 MediaPlayer 回调事件 + * @param mediaListener {@link MediaListener} MediaPlayer 回调事件 + * @return {@link DevMediaManager} + */ + public DevMediaManager setMediaListener(final MediaListener mediaListener) { + this.mMediaListener = mediaListener; + return this; + } + + /** + * detail: Media 播放设置 + * @author Ttt + */ + public static abstract class MediaSet { + + /** + * 是否循环播放 ( 默认不循环 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isLooping() { + return false; + } + + /** + * 获取播放音量 ( 设置, 默认使用全局统一音量 ) + * @return 播放音量 + */ + public float getVolume() { + return DevMediaManager.getInstance().getVolume(); + } + + /** + * 设置播放配置 + * @param mediaPlayer {@link MediaPlayer} + * @throws Exception 设置异常 + */ + public abstract void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception; + } + + // =========== + // = get/set = + // =========== + + /** + * 判断 MediaPlayer 是否为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isNullMediaPlayer() { + return mMediaPlayer == null; + } + + /** + * 判断 MediaPlayer 是否不为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isNotNullMediaPlayer() { + return mMediaPlayer != null; + } + + /** + * 获取 MediaPlayer 对象 + * @return {@link MediaPlayer} + */ + public MediaPlayer getMediaPlayer() { + return mMediaPlayer; + } + + /** + * 设置 MediaPlayer 对象 + * @param mediaPlayer {@link MediaPlayer} + * @return {@link DevMediaManager} + */ + public DevMediaManager setMediaPlayer(final MediaPlayer mediaPlayer) { + this.mMediaPlayer = mediaPlayer; + return this; + } + + /** + * 设置日志打印 TAG + * @param tag 日志 TAG + * @return {@link DevMediaManager} + */ + public DevMediaManager setTAG(final String tag) { + TAG = tag; + return this; + } + + /** + * 获取播放音量 + * @return 播放音量 + */ + public float getVolume() { + return mVolume; + } + + /** + * 设置播放音量 + * @param volume 播放音量 + * @return {@link DevMediaManager} + */ + public DevMediaManager setVolume(final float volume) { + this.mVolume = volume; + return this; + } + + // ========== + // = 内部变量 = + // ========== + + // 流类型 + private int mStreamType = AudioManager.STREAM_MUSIC; + // 本地资源 + private int mPlayRawId = -1; + // 播放路径 / 地址 + private String mPlayUri = null; + // 视频宽度 + private int mVideoWidth = 0; + // 视频高度 + private int mVideoHeight = 0; + // 播放音量 + private float mVolume = -1F; + + /** + * 清空播放信息 + */ + private void clearMediaPlayerData() { + mPlayRawId = -1; + mPlayUri = null; + mVideoWidth = 0; + mVideoHeight = 0; + } + + /** + * 获取播放资源 id + * @return 播放资源 id + */ + public int getPlayRawId() { + return mPlayRawId; + } + + /** + * 获取播放地址 + * @return 播放地址 + */ + public String getPlayUri() { + return mPlayUri; + } + + /** + * 获取视频宽度 + * @return 视频宽度 + */ + public int getVideoWidth() { + return mVideoWidth; + } + + /** + * 获取视频高度 + * @return 视频高度 + */ + public int getVideoHeight() { + return mVideoHeight; + } + + /** + * 获取播放时间 + * @return 播放时间 + */ + public int getCurrentPosition() { + if (mMediaPlayer != null) { + return mMediaPlayer.getCurrentPosition(); + } + return 0; + } + + /** + * 获取资源总时间 + * @return 资源总时间 + */ + public int getDuration() { + if (mMediaPlayer != null) { + return mMediaPlayer.getDuration(); + } + return 0; + } + + /** + * 获取播放进度百分比 + * @return 播放进度百分比 + */ + public int getPlayPercent() { + try { + return (getCurrentPosition() * 100) / getDuration(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getPlayPercent"); + } + return 0; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java b/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java new file mode 100644 index 0000000000..839c0f3b8e --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java @@ -0,0 +1,435 @@ +package dev.utils.app.player; + +import android.graphics.PixelFormat; +import android.media.MediaPlayer; +import android.text.TextUtils; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import dev.utils.LogPrintUtils; + +/** + * detail: 视频播放控制器 + * @author Ttt + */ +public class DevVideoPlayerControl + implements SurfaceHolder.Callback, + DevMediaManager.MediaListener { + + // ========== + // = 外部回调 = + // ========== + + // 日志 TAG + private final String TAG = DevVideoPlayerControl.class.getSimpleName(); + + // 播放设置 + private DevMediaManager.MediaSet mMediaSet; + + // ======== + // = View = + // ======== + + // 播放预览载体 SurfaceView + private final SurfaceView mSurfaceView; + // 画面预览回调 + private SurfaceHolder mSurfaceHolder; + // 判断是否自动播放 + private boolean mAutoPlay; + + /** + * 构造函数 + * @param surfaceView {@link SurfaceView} + */ + public DevVideoPlayerControl(final SurfaceView surfaceView) { + this(surfaceView, false); + } + + /** + * 构造函数 + * @param surfaceView {@link SurfaceView} + * @param autoPlay 是否自动播放 + */ + public DevVideoPlayerControl( + final SurfaceView surfaceView, + final boolean autoPlay + ) { + this.mSurfaceView = surfaceView; + this.mAutoPlay = autoPlay; + + // 初始化 DevMediaManager 回调事件类 + DevMediaManager.getInstance().setMediaListener(this); + } + + // ============= + // = 内部快捷控制 = + // ============= + + /** + * 重置操作 + */ + private void resetOperate() { + // 移除旧的回调 + if (mSurfaceHolder != null) { + mSurfaceHolder.removeCallback(this); + } + // 设置 Holder + mSurfaceHolder = mSurfaceView.getHolder(); + // 移除旧的回调 + if (mSurfaceHolder != null) { + // 重新添加回调 + mSurfaceHolder.removeCallback(this); + mSurfaceHolder.addCallback(this); + } + } + + // ================== + // = Surface 回调事件 = + // ================== + + /** + * Surface 改变通知 + * @param holder {@link SurfaceHolder} + * @param format {@link PixelFormat} 像素格式 + * @param width 宽度 + * @param height 高度 + */ + @Override + public void surfaceChanged( + SurfaceHolder holder, + int format, + int width, + int height + ) { + LogPrintUtils.dTag( + TAG, "surfaceChanged - format: %s, width: %s, height: %s", + format, width, height + ); + } + + /** + * Surface 创建 + * @param holder {@link SurfaceHolder} + */ + @Override + public void surfaceCreated(SurfaceHolder holder) { + LogPrintUtils.dTag(TAG, "surfaceCreated"); + try { + // 开始播放 + DevMediaManager.getInstance().playPrepare(mMediaSet); + // = + LogPrintUtils.dTag(TAG, "setDisplay(surfaceHolder) - Success"); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setDisplay(surfaceHolder) - Error"); + } + } + + /** + * Surface 销毁 + * @param holder {@link SurfaceHolder} + */ + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + LogPrintUtils.dTag(TAG, "surfaceDestroyed"); + } + + // ====================== + // = MediaPlayer 回调事件 = + // ====================== + + /** + * 准备完成回调 + */ + @Override + public void onPrepared() { + LogPrintUtils.dTag(TAG, "onPrepared"); + // = + if (mSurfaceView != null) { + // 如果等于 null, 或者不在显示中, 则跳过 + if (mSurfaceHolder.getSurface() == null || !mSurfaceHolder.getSurface().isValid()) { + return; + } + + try { + MediaPlayer mPlayer = DevMediaManager.getInstance().getMediaPlayer(); + mPlayer.setDisplay(mSurfaceHolder); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "onPrepared"); + } + // 判断是否自动播放 + if (mAutoPlay) { + try { // 如果没有设置则直接播放 + DevMediaManager.getInstance().getMediaPlayer().start(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "onPrepared - start"); + } + } + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onPrepared(); + } + } + } + + /** + * 播放完成 / 结束 + */ + @Override + public void onCompletion() { + LogPrintUtils.dTag(TAG, "onCompletion"); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onCompletion(); + } + } + + /** + * 缓存进度 + * @param percent 缓冲百分比进度 + */ + @Override + public void onBufferingUpdate(int percent) { + LogPrintUtils.dTag(TAG, "onBufferingUpdate: %s", percent); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onBufferingUpdate(percent); + } + } + + /** + * 滑动进度加载成功 + */ + @Override + public void onSeekComplete() { + LogPrintUtils.dTag(TAG, "onSeekComplete"); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onSeekComplete(); + } + } + + /** + * 播放出错回调 + * @param what 异常 what + * @param extra 异常 extra + * @return {@code true} 处理异常, {@code false} 调用 OnCompletionListener + */ + @Override + public boolean onError( + int what, + int extra + ) { + LogPrintUtils.dTag( + TAG, "onError what: %s, extra: %s", + what, extra + ); + // 触发回调 + if (mMediaListener != null) { + return mMediaListener.onError(what, extra); + } + return false; + } + + /** + * 视频大小改变通知 + * @param width 宽度 + * @param height 高度 + */ + @Override + public void onVideoSizeChanged( + int width, + int height + ) { + LogPrintUtils.dTag( + TAG, "onVideoSizeChanged - width: %s, height: %s", + width, height + ); + // 触发回调 + if (mMediaListener != null) { + mMediaListener.onVideoSizeChanged(width, height); + } + } + + // = + + // 播放事件监听 + private DevMediaManager.MediaListener mMediaListener; + + /** + * 设置播放监听事件 + * @param mediaListener {@link DevMediaManager.MediaListener} + * @return {@link DevVideoPlayerControl} + */ + public DevVideoPlayerControl setMediaListener(final DevMediaManager.MediaListener mediaListener) { + this.mMediaListener = mediaListener; + return this; + } + + // ============= + // = 播放快捷方法 = + // ============= + + /** + * 暂停播放 + */ + public void pausePlayer() { + DevMediaManager.getInstance().pause(); + } + + /** + * 停止播放 + */ + public void stopPlayer() { + DevMediaManager.getInstance().stop(); + } + + /** + * 开始播放 + * @param playUri 播放地址 + */ + public void startPlayer(final String playUri) { + startPlayer(playUri, false); + } + + /** + * 开始播放 + * @param playUri 播放地址 + * @param isLooping 是否循环播放 + */ + public void startPlayer( + final String playUri, + final boolean isLooping + ) { + // 设置播放信息 + this.mMediaSet = new DevMediaManager.MediaSet() { + @Override + public boolean isLooping() { + return isLooping; + } + + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) + throws Exception { + mediaPlayer.setDataSource(playUri); + } + }; + // 重置操作 + resetOperate(); + } + + /** + * 开始播放 + * @param mediaSet 播放设置 + */ + public void startPlayer(final DevMediaManager.MediaSet mediaSet) { + // 设置播放信息 + this.mMediaSet = mediaSet; + // 重置操作 + resetOperate(); + } + + /** + * 获取 SurfaceView + * @return {@link SurfaceView} + */ + public SurfaceView getSurfaceView() { + return mSurfaceView; + } + + // = + + /** + * 是否播放中 + * @return {@code true} yes, {@code false} no + */ + public boolean isPlaying() { + return DevMediaManager.getInstance().isPlaying(); + } + + /** + * 是否播放中 + * @param uri 播放地址 + * @return {@code true} yes, {@code false} no + */ + public boolean isPlaying(final String uri) { + if (!TextUtils.isEmpty(uri)) { // 需要播放的地址, 必须不等于 null + // 获取之前播放路径 + String playUri = DevMediaManager.getInstance().getPlayUri(); + // 如果不等于 null, 并且播放地址相同 + if (playUri != null && playUri.equals(uri)) { + try { + return DevMediaManager.getInstance().isPlaying(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isPlaying uri: %s", uri); + } + } + } + return false; + } + + /** + * 判断是否自动播放 + * @return {@code true} yes, {@code false} no + */ + public boolean isAutoPlay() { + return mAutoPlay; + } + + /** + * 设置自动播放 + * @param autoPlay 是否自动播放 + * @return {@link DevVideoPlayerControl} + */ + public DevVideoPlayerControl setAutoPlay(final boolean autoPlay) { + this.mAutoPlay = autoPlay; + return this; + } + + /** + * 获取播放地址 + * @return 播放地址 + */ + public String getPlayUri() { + return DevMediaManager.getInstance().getPlayUri(); + } + + /** + * 获取视频宽度 + * @return 视频宽度 + */ + public int getVideoWidth() { + return DevMediaManager.getInstance().getVideoWidth(); + } + + /** + * 获取视频高度 + * @return 视频高度 + */ + public int getVideoHeight() { + return DevMediaManager.getInstance().getVideoHeight(); + } + + /** + * 获取播放时间 + * @return 播放时间 + */ + public int getCurrentPosition() { + return DevMediaManager.getInstance().getCurrentPosition(); + } + + /** + * 获取资源总时间 + * @return 资源总时间 + */ + public int getDuration() { + return DevMediaManager.getInstance().getDuration(); + } + + /** + * 获取播放进度百分比 + * @return 播放进度百分比 + */ + public int getPlayPercent() { + return DevMediaManager.getInstance().getPlayPercent(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/IPreference.java b/lib/DevApp/src/main/java/dev/utils/app/share/IPreference.java new file mode 100644 index 0000000000..b2b80e060f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/IPreference.java @@ -0,0 +1,276 @@ +package dev.utils.app.share; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * detail: SharedPreferences 操作接口 + * @author Ttt + */ +public interface IPreference { + + /** + * detail: 存储 / 取出 数据类型 + * @author Ttt + */ + enum DataType { + INTEGER, + LONG, + FLOAT, + DOUBLE, + BOOLEAN, + STRING, + STRING_SET + } + + // ========== + // = 监听方法 = + // ========== + + /** + * 注册 SharedPreferences 操作监听器 + * @param listener SharedPreferences 操作监听器 + */ + void registerListener(OnSPOperateListener listener); + + /** + * 注销 SharedPreferences 操作监听器 + */ + void unregisterListener(); + + // ========== + // = 操作方法 = + // ========== + + /** + * 保存数据 + *
+     *     注意事项, 进行存储 int、long、float、double 需要明确类型
+     *     例 1、1L、1F、1D 不能存储 long 传入 1 会导致获取数据转换异常
+     *     必须传入 1L 或者定义变量 long value = xxx, 然后传入 value
+     * 
+ * @param key 保存的 key + * @param value 保存的 value + */ + void put( + String key, + Object value + ); + + /** + * 保存 Map 集合 ( 只能是 Integer、Long、Boolean、Float、String、Set ) + * @param map {@link Map} + */ + void putAll(Map map); + + /** + * 保存 List 集合 + * @param key 保存的 key + * @param list 保存的 value + */ + void putAll( + String key, + List list + ); + + /** + * 保存 List 集合, 并且自定义保存顺序 + * @param key 保存的 key + * @param list 保存的 value + * @param comparator 排序 {@link Comparator} + */ + void putAll( + String key, + List list, + Comparator comparator + ); + + /** + * 根据 key 获取数据 + * @param key 保存的 key + * @param type 数据类型 + * @param defaultValue 默认值 + * @param 泛型 + * @return 存储的数据 + */ + T get( + String key, + DataType type, + Object defaultValue + ); + + /** + * 获取全部数据 + * @return 存储的数据 + */ + Map getAll(); + + /** + * 获取 List 集合 + * @param key 保存的 key + * @return 存储的数据 + */ + List getAll(String key); + + /** + * 移除数据 + * @param key 保存的 key + */ + void remove(String key); + + /** + * 移除集合的数据 + * @param keys 保存的 key 集合 + */ + void removeAll(List keys); + + /** + * 移除数组的数据 + * @param keys 保存的 key 数组 + */ + void removeAll(String[] keys); + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + boolean contains(String key); + + /** + * 清除全部数据 + */ + void clear(); + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + int getInt(String key); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + long getLong(String key); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + float getFloat(String key); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + double getDouble(String key); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + boolean getBoolean(String key); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + String getString(String key); + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + Set getSet(String key); + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + int getInt( + String key, + int defaultValue + ); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + long getLong( + String key, + long defaultValue + ); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + float getFloat( + String key, + float defaultValue + ); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + double getDouble( + String key, + double defaultValue + ); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + boolean getBoolean( + String key, + boolean defaultValue + ); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + String getString( + String key, + String defaultValue + ); + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + Set getSet( + String key, + Set defaultValue + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/IPreferenceHolder.java b/lib/DevApp/src/main/java/dev/utils/app/share/IPreferenceHolder.java new file mode 100644 index 0000000000..aa020fa543 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/IPreferenceHolder.java @@ -0,0 +1,84 @@ +package dev.utils.app.share; + +import android.content.Context; + +import java.util.HashMap; +import java.util.Map; + +/** + * detail: IPreference 持有类, 内部返回实现类 + * @author Ttt + */ +class IPreferenceHolder { + + protected IPreferenceHolder() { + } + + // HashMap 保存持有对象 + private static final Map sHashMaps = new HashMap<>(); + + /** + * 获取 IPreference (SharedPreferences) 操作接口 + * @param context {@link Context} + * @return {@link IPreference} + */ + public static IPreference getPreference(final Context context) { + // 判断是否存在对应的持有类 + IPreference ipref = sHashMaps.get(null); + // 判断是否存在 + if (ipref != null) { + return ipref; + } + // 初始化并保存 + ipref = new PreferenceImpl(context); + sHashMaps.put(null, ipref); + return ipref; + } + + /** + * 获取 IPreference (SharedPreferences) 操作接口 + * @param context {@link Context} + * @param fileName 文件名 + * @return {@link IPreference} + */ + public static IPreference getPreference( + final Context context, + final String fileName + ) { + // 判断是否存在对应的持有类 + IPreference ipref = sHashMaps.get(fileName); + // 判断是否存在 + if (ipref != null) { + return ipref; + } + // 初始化并保存 + ipref = new PreferenceImpl(context, fileName); + sHashMaps.put(fileName, ipref); + return ipref; + } + + /** + * 获取 IPreference (SharedPreferences) 操作接口 + * @param context {@link Context} + * @param fileName 文件名 + * @param mode SharedPreferences 操作模式 + * @return {@link IPreference} + */ + public static IPreference getPreference( + final Context context, + final String fileName, + final int mode + ) { + String key = fileName + "_" + mode; + // 判断是否存在对应的持有类 + IPreference ipref = sHashMaps.get(key); + // 判断是否存在 + if (ipref != null) { + return ipref; + } + // 初始化并保存 + ipref = new PreferenceImpl(context, fileName, mode); + sHashMaps.put(key, ipref); + return ipref; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/OnSPOperateListener.java b/lib/DevApp/src/main/java/dev/utils/app/share/OnSPOperateListener.java new file mode 100644 index 0000000000..698ddb66fa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/OnSPOperateListener.java @@ -0,0 +1,79 @@ +package dev.utils.app.share; + +import java.util.List; +import java.util.Map; + +/** + * detail: SharedPreferences 操作监听器 + * @author Ttt + *
+ *     慎用 ( 最好只用于监听等观察行为 ), 防止在通知方法中再次操作导致死循环的可能性
+ * 
+ */ +public interface OnSPOperateListener { + + /** + * put 操作回调 + * @param preference SharedPreferences 操作接口 + * @param dataType 数据类型 + * @param key 保存的 key + * @param value 保存的 value + */ + void onPut( + IPreference preference, + IPreference.DataType dataType, + String key, + Object value + ); + + /** + * put 操作回调 ( 循环 Map 触发 ) + * @param preference SharedPreferences 操作接口 + * @param maps 传入集合参数 + */ + void onPutByMap( + IPreference preference, + Map maps + ); + + /** + * remove 操作回调 + * @param preference SharedPreferences 操作接口 + * @param key 操作的 key + */ + void onRemove( + IPreference preference, + String key + ); + + /** + * remove 操作回调 ( 循环 List 触发 ) + * @param preference SharedPreferences 操作接口 + * @param lists 传入集合参数 + */ + void onRemoveByList( + IPreference preference, + List lists + ); + + /** + * 清除全部数据 + */ + void clear(); + + /** + * get 操作回调 + * @param preference SharedPreferences 操作接口 + * @param dataType 数据类型 + * @param key 操作的 key + * @param value 获取的 value + * @param defaultValue 默认值 + */ + void onGet( + IPreference preference, + IPreference.DataType dataType, + String key, + Object value, + Object defaultValue + ); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/PreferenceImpl.java b/lib/DevApp/src/main/java/dev/utils/app/share/PreferenceImpl.java new file mode 100644 index 0000000000..5f78183c8b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/PreferenceImpl.java @@ -0,0 +1,578 @@ +package dev.utils.app.share; + +import android.content.Context; +import android.content.SharedPreferences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import dev.utils.DevFinal; + +/** + * detail: SharedPreferences 操作接口具体实现类 + * @author Ttt + *
+ *     1.apply 没有返回值而 commit 返回 boolean 表明修改是否提交成功
+ *     2.apply 是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而 commit 是同步的提交到硬件磁盘
+ *     3.apply 方法不会提示任何失败的提示 apply 的效率高一些, 如果没有必要确认是否提交成功建议使用 apply
+ * 
+ */ +final class PreferenceImpl + implements IPreference { + + // 文件名 + private static final String NAME = "SPConfig"; + // SharedPreferences 对象 + private final SharedPreferences mPreferences; + // SharedPreferences 操作监听器 + private OnSPOperateListener mListener; + // 默认值 + private final int INT_DEFAULT = DevFinal.DEFAULT.INT; + private final long LONG_DEFAULT = DevFinal.DEFAULT.LONG; + private final float FLOAT_DEFAULT = DevFinal.DEFAULT.FLOAT; + private final double DOUBLE_DEFAULT = DevFinal.DEFAULT.DOUBLE; + private final boolean BOOLEAN_DEFAULT = DevFinal.DEFAULT.BOOLEAN; + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param context {@link Context} + */ + public PreferenceImpl(final Context context) { + mPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE); + } + + /** + * 构造函数 + * @param context {@link Context} + * @param fileName 文件名 + */ + public PreferenceImpl( + final Context context, + final String fileName + ) { + mPreferences = context.getSharedPreferences(fileName, Context.MODE_PRIVATE); + } + + /** + * 构造函数 + * @param context {@link Context} + * @param fileName 文件名 + * @param mode SharedPreferences 操作模式 + */ + public PreferenceImpl( + final Context context, + final String fileName, + final int mode + ) { + mPreferences = context.getSharedPreferences(fileName, mode); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 保存数据 + * @param editor {@link SharedPreferences.Editor} + * @param key 保存的 key + * @param object 保存的 value + * @return 存储数据所属类型 + */ + private DataType put( + final SharedPreferences.Editor editor, + final String key, + final Object object + ) { + // key 不为 null 时再存入, 否则不存储 + if (key != null && object != null) { + if (object instanceof Integer) { + editor.putInt(key, (Integer) object); + return DataType.INTEGER; + } else if (object instanceof Long) { + editor.putLong(key, (Long) object); + return DataType.LONG; + } else if (object instanceof Float) { + editor.putFloat(key, (Float) object); + return DataType.FLOAT; + } else if (object instanceof Double) { + editor.putLong(key, Double.doubleToRawLongBits((Double) object)); + return DataType.DOUBLE; + } else if (object instanceof Boolean) { + editor.putBoolean(key, (Boolean) object); + return DataType.BOOLEAN; + } else if (object instanceof String) { + editor.putString(key, String.valueOf(object)); + return DataType.STRING; + } else if (object instanceof Set) { + try { + editor.putStringSet(key, (Set) object); + return DataType.STRING_SET; + } catch (Exception ignored) { + } + } + } + return null; + } + + /** + * 根据 key 和 数据类型 取出数据 + * @param key 保存的 key + * @param type 数据类型 {@link DataType} + * @param defaultValue 默认值 + * @return 指定 key 存储的数据 ( 传入的 type 类型 ) + */ + private Object getValue( + final String key, + final DataType type, + final Object defaultValue + ) { + switch (type) { + case INTEGER: + int intValue = INT_DEFAULT; + if (defaultValue instanceof Integer) intValue = (Integer) defaultValue; + return mPreferences.getInt(key, intValue); + case LONG: + long longValue = LONG_DEFAULT; + if (defaultValue instanceof Long) longValue = (Long) defaultValue; + return mPreferences.getLong(key, longValue); + case FLOAT: + float floatValue = FLOAT_DEFAULT; + if (defaultValue instanceof Float) floatValue = (Float) defaultValue; + return mPreferences.getFloat(key, floatValue); + case DOUBLE: + double doubleValue = DOUBLE_DEFAULT; + if (defaultValue instanceof Double) doubleValue = (Double) defaultValue; + long value = mPreferences.getLong(key, Double.doubleToLongBits(doubleValue)); + return Double.longBitsToDouble(value); + case BOOLEAN: + boolean booleanValue = BOOLEAN_DEFAULT; + if (defaultValue instanceof Boolean) booleanValue = (Boolean) defaultValue; + return mPreferences.getBoolean(key, booleanValue); + case STRING: + String stringValue = null; + if (defaultValue instanceof String) stringValue = (String) defaultValue; + return mPreferences.getString(key, stringValue); + case STRING_SET: + Set stringSetValue = null; + try { + if (defaultValue instanceof Set) stringSetValue = (Set) defaultValue; + } catch (Exception ignored) { + } + return mPreferences.getStringSet(key, stringSetValue); + default: + return null; + } + } + + /** + * detail: 默认比较器, 当存储 List 集合中的 String 类型数据时, 没有指定比较器, 就使用默认比较器 + * @author Ttt + */ + static class ComparatorImpl + implements Comparator { + @Override + public int compare( + String lhs, + String rhs + ) { + return lhs.compareTo(rhs); + } + } + + // ========== + // = 监听方法 = + // ========== + + /** + * 注册 SharedPreferences 操作监听器 + * @param listener SharedPreferences 操作监听器 + */ + @Override + public void registerListener(final OnSPOperateListener listener) { + this.mListener = listener; + } + + /** + * 注销 SharedPreferences 操作监听器 + */ + @Override + public void unregisterListener() { + this.mListener = null; + } + + // ============= + // = 接口实现方法 = + // ============= + + /** + * 保存数据 + * @param key 保存的 key + * @param value 保存的 value + */ + @Override + public void put( + final String key, + final Object value + ) { + SharedPreferences.Editor edit = mPreferences.edit(); + DataType dataType = put(edit, key, value); + if (dataType != null) { + edit.apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.onPut(this, dataType, key, value); + } + } + } + + /** + * 保存 Map 集合 ( 只能是 Integer、Long、Boolean、Float、String、Set ) + * @param map {@link Map} + */ + @Override + public void putAll(final Map map) { + SharedPreferences.Editor edit = mPreferences.edit(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + put(edit, key, value); + } + edit.apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.onPutByMap(this, map); + } + } + + /** + * 保存 List 集合 + * @param key 保存的 key + * @param list 保存的 value + */ + @Override + public void putAll( + final String key, + final List list + ) { + putAll(key, list, new ComparatorImpl()); + } + + /** + * 保存 List 集合, 并且自定义保存顺序 + * @param key 保存的 key + * @param list 保存的 value + * @param comparator 排序 {@link Comparator} + */ + @Override + public void putAll( + final String key, + final List list, + final Comparator comparator + ) { + Set value = new TreeSet<>(comparator); + value.addAll(list); + + SharedPreferences.Editor edit = mPreferences.edit(); + DataType dataType = put(edit, key, value); + if (dataType != null) { + edit.apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.onPut(this, dataType, key, value); + } + } + } + + /** + * 根据 key 获取数据 + * @param key 保存的 key + * @param type 数据类型 + * @param defaultValue 默认值 + * @param 泛型 + * @return 存储的数据 + */ + @Override + public T get( + final String key, + final DataType type, + final Object defaultValue + ) { + Object value = getValue(key, type, defaultValue); + + // 触发操作回调 + if (mListener != null) { + mListener.onGet(this, type, key, value, defaultValue); + } + return (T) value; + } + + /** + * 获取全部数据 + * @return 存储的数据 + */ + @Override + public Map getAll() { + return mPreferences.getAll(); + } + + /** + * 获取 List 集合 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public List getAll(final String key) { + List list = new ArrayList<>(); + Set set = getSet(key); + if (set != null) list.addAll(set); + return list; + } + + /** + * 移除数据 + * @param key 保存的 key + */ + @Override + public void remove(final String key) { + mPreferences.edit().remove(key).apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.onRemove(this, key); + } + } + + /** + * 移除集合的数据 + * @param keys 保存的 key 集合 + */ + @Override + public void removeAll(final List keys) { + SharedPreferences.Editor edit = mPreferences.edit(); + for (String key : keys) { + edit.remove(key); + } + edit.apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.onRemoveByList(this, keys); + } + } + + /** + * 移除数组的数据 + * @param keys 保存的 key 数组 + */ + @Override + public void removeAll(final String[] keys) { + removeAll(Arrays.asList(keys)); + } + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean contains(final String key) { + return mPreferences.contains(key); + } + + /** + * 清除全部数据 + */ + @Override + public void clear() { + mPreferences.edit().clear().apply(); + + // 触发操作回调 + if (mListener != null) { + mListener.clear(); + } + } + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public int getInt(final String key) { + return getInt(key, INT_DEFAULT); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public long getLong(final String key) { + return getLong(key, LONG_DEFAULT); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public float getFloat(final String key) { + return getFloat(key, FLOAT_DEFAULT); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public double getDouble(final String key) { + return getDouble(key, DOUBLE_DEFAULT); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public boolean getBoolean(final String key) { + return getBoolean(key, BOOLEAN_DEFAULT); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public String getString(final String key) { + return getString(key, null); + } + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + @Override + public Set getSet(final String key) { + return getSet(key, null); + } + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public int getInt( + final String key, + final int defaultValue + ) { + return get(key, DataType.INTEGER, defaultValue); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public long getLong( + final String key, + final long defaultValue + ) { + return get(key, DataType.LONG, defaultValue); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public float getFloat( + final String key, + final float defaultValue + ) { + return get(key, DataType.FLOAT, defaultValue); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public double getDouble( + final String key, + final double defaultValue + ) { + return get(key, DataType.DOUBLE, defaultValue); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public boolean getBoolean( + final String key, + final boolean defaultValue + ) { + return get(key, DataType.BOOLEAN, defaultValue); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public String getString( + final String key, + final String defaultValue + ) { + return get(key, DataType.STRING, defaultValue); + } + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + @Override + public Set getSet( + final String key, + final Set defaultValue + ) { + return get(key, DataType.STRING_SET, defaultValue); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/SPUtils.java b/lib/DevApp/src/main/java/dev/utils/app/share/SPUtils.java new file mode 100644 index 0000000000..66db501478 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/SPUtils.java @@ -0,0 +1,12 @@ +package dev.utils.app.share; + +/** + * detail: SharedPreferences 工具类 + * @author Ttt + */ +public final class SPUtils + extends IPreferenceHolder { + + private SPUtils() { + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/share/SharedUtils.java b/lib/DevApp/src/main/java/dev/utils/app/share/SharedUtils.java new file mode 100644 index 0000000000..920349eba4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/share/SharedUtils.java @@ -0,0 +1,328 @@ +package dev.utils.app.share; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import dev.DevUtils; + +/** + * detail: SPUtils 快捷工具类 + * @author Ttt + */ +public final class SharedUtils { + + private SharedUtils() { + } + + // ========== + // = 监听方法 = + // ========== + + /** + * 注册 SharedPreferences 操作监听器 + * @param listener SharedPreferences 操作监听器 + */ + public static void registerListener(final OnSPOperateListener listener) { + SPUtils.getPreference(DevUtils.getContext()).registerListener( + listener + ); + } + + /** + * 注销 SharedPreferences 操作监听器 + */ + public static void unregisterListener() { + SPUtils.getPreference(DevUtils.getContext()).unregisterListener(); + } + + // ========== + // = 操作方法 = + // ========== + + /** + * 保存数据 + * @param key 保存的 key + * @param value 保存的 value + */ + public static void put( + final String key, + final Object value + ) { + SPUtils.getPreference(DevUtils.getContext()).put(key, value); + } + + /** + * 保存 Map 集合 ( 只能是 Integer、Long、Boolean、Float、String、Set ) + * @param map {@link Map} + */ + public static void putAll(final Map map) { + SPUtils.getPreference(DevUtils.getContext()).putAll(map); + } + + /** + * 保存 List 集合 + * @param key 保存的 key + * @param list 保存的 value + */ + public static void putAll( + final String key, + final List list + ) { + SPUtils.getPreference(DevUtils.getContext()).putAll(key, list); + } + + /** + * 保存 List 集合, 并且自定义保存顺序 + * @param key 保存的 key + * @param list 保存的 value + * @param comparator 排序 {@link Comparator} + */ + public static void putAll( + final String key, + final List list, + final Comparator comparator + ) { + SPUtils.getPreference(DevUtils.getContext()).putAll(key, list, comparator); + } + + /** + * 根据 key 获取数据 + * @param key 保存的 key + * @param type 数据类型 + * @param defaultValue 默认值 + * @param 泛型 + * @return 存储的数据 + */ + public static T get( + final String key, + final IPreference.DataType type, + final Object defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .get(key, type, defaultValue); + } + + /** + * 获取全部数据 + * @return 存储的数据 + */ + public static Map getAll() { + return SPUtils.getPreference(DevUtils.getContext()).getAll(); + } + + /** + * 获取 List 集合 + * @param key 保存的 key + * @return 存储的数据 + */ + public static List getAll(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getAll(key); + } + + /** + * 移除数据 + * @param key 保存的 key + */ + public static void remove(final String key) { + SPUtils.getPreference(DevUtils.getContext()).remove(key); + } + + /** + * 移除集合的数据 + * @param keys 保存的 key 集合 + */ + public static void removeAll(final List keys) { + SPUtils.getPreference(DevUtils.getContext()).removeAll(keys); + } + + /** + * 移除数组的数据 + * @param keys 保存的 key 数组 + */ + public static void removeAll(final String[] keys) { + SPUtils.getPreference(DevUtils.getContext()).removeAll(keys); + } + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).contains(key); + } + + /** + * 清除全部数据 + */ + public static void clear() { + SPUtils.getPreference(DevUtils.getContext()).clear(); + } + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static int getInt(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getInt(key); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static long getLong(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getLong(key); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static float getFloat(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getFloat(key); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static double getDouble(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getDouble(key); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static boolean getBoolean(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getBoolean(key); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static String getString(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getString(key); + } + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + public static Set getSet(final String key) { + return SPUtils.getPreference(DevUtils.getContext()).getSet(key); + } + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static int getInt( + final String key, + final int defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getInt(key, defaultValue); + } + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static long getLong( + final String key, + final long defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getLong(key, defaultValue); + } + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static float getFloat( + final String key, + final float defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getFloat(key, defaultValue); + } + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static double getDouble( + final String key, + final double defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getDouble(key, defaultValue); + } + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static boolean getBoolean( + final String key, + final boolean defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getBoolean(key, defaultValue); + } + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static String getString( + final String key, + final String defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getString(key, defaultValue); + } + + /** + * 获取 Set 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + public static Set getSet( + final String key, + final Set defaultValue + ) { + return SPUtils.getPreference(DevUtils.getContext()) + .getSet(key, defaultValue); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java b/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java new file mode 100644 index 0000000000..64e634c4e5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java @@ -0,0 +1,382 @@ +package dev.utils.app.timer; + +import android.os.Handler; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * detail: 定时器 + * @author Ttt + */ +public class DevTimer { + + // 定时器 TAG + private final String mTag; + // 延迟时间 ( 多少毫秒后开始执行 ) + private final long mDelay; + // 循环时间 ( 每隔多少毫秒执行一次 ) + private final long mPeriod; + // 触发次数上限 ( 负数为无限循环 ) + private final int mTriggerLimit; + + private DevTimer(final Builder builder) { + mTag = builder.tag; + mDelay = builder.delay; + mPeriod = builder.period; + mTriggerLimit = builder.limit; + } + + /** + * detail: 定时器构建类 + * @author Ttt + */ + public static final class Builder { + + // 定时器 TAG + private String tag; + // 延迟时间 ( 多少毫秒后开始执行 ) + private long delay; + // 循环时间 ( 每隔多少毫秒执行一次 ) + private long period; + // 触发次数上限 ( 负数为无限循环 ) + private int limit = -1; + + public Builder(long period) { + this.period = period; + } + + public Builder( + long delay, + long period + ) { + this.delay = delay; + this.period = period; + } + + public Builder( + long delay, + long period, + int limit + ) { + this.delay = delay; + this.period = period; + this.limit = limit; + } + + public Builder( + long delay, + long period, + int limit, + String tag + ) { + this.delay = delay; + this.period = period; + this.limit = limit; + this.tag = tag; + } + + public Builder(Builder builder) { + if (builder != null) { + tag = builder.tag; + delay = builder.delay; + period = builder.period; + limit = builder.limit; + } + } + + public String getTag() { + return tag; + } + + public Builder setTag(final String tag) { + this.tag = tag; + return this; + } + + public long getDelay() { + return delay; + } + + public Builder setDelay(final long delay) { + this.delay = delay; + return this; + } + + public long getPeriod() { + return period; + } + + public Builder setPeriod(final long period) { + this.period = period; + return this; + } + + public int getLimit() { + return limit; + } + + public Builder setLimit(final int limit) { + this.limit = limit; + return this; + } + + public DevTimer build() { + return new DevTimer(this); + } + } + + /** + * detail: 回调接口 + * @author Ttt + */ + public interface Callback { + + /** + * 触发回调方法 + * @param timer 定时器 + * @param number 触发次数 + * @param end 是否结束 + * @param infinite 是否无限循环 + */ + void callback( + DevTimer timer, + int number, + boolean end, + boolean infinite + ); + } + + // ============= + // = 对外公开方法 = + // ============= + + // uuid ( 一定程度上唯一 ) + private final int mUUID = UUID.randomUUID().hashCode(); + // 触发次数 + private final AtomicInteger mTriggerNumber = new AtomicInteger(); + // 定时器是否运行中 + private boolean mRunning; + // 状态标识 ( 是否标记清除 ) + private boolean mMarkSweep; + // UI Handler + private Handler mHandler; + // 回调方法 + private Callback mCallback; + // 定时器 + private Timer mTimer; + // 定时器任务 + private TimerTask mTimerTask; + + /** + * 获取 TAG + * @return TAG + */ + public String getTag() { + return mTag; + } + + /** + * 获取 UUID HashCode + * @return UUID HashCode + */ + public int getUUID() { + return mUUID; + } + + /** + * 获取延迟时间 ( 多少毫秒后开始执行 ) + * @return 延迟时间 ( 多少毫秒后开始执行 ) + */ + public long getDelay() { + return mDelay; + } + + /** + * 获取循环时间 ( 每隔多少毫秒执行一次 ) + * @return 循环时间 ( 每隔多少毫秒执行一次 ) + */ + public long getPeriod() { + return mPeriod; + } + + // = + + /** + * 判断是否运行中 + * @return {@code true} yes, {@code false} no + */ + public boolean isRunning() { + return mRunning; + } + + /** + * 是否标记清除 + * @return {@code true} yes, {@code false} no + */ + public boolean isMarkSweep() { + return mMarkSweep; + } + + /** + * 获取已经触发的次数 + * @return 已经触发的次数 + */ + public int getTriggerNumber() { + return mTriggerNumber.get(); + } + + /** + * 获取允许触发的上限次数 + * @return 允许触发的上限次数 + */ + public int getTriggerLimit() { + return mTriggerLimit; + } + + /** + * 是否触发结束 ( 到达最大次数 ) + *
+     *     如果属于无限循环, 该方法永远返回 false, 可用 {@link #isRunning()} 组合判断
+     * 
+ * @return {@code true} yes, {@code false} no + */ + public boolean isTriggerEnd() { + return (mTriggerLimit >= 0 && mTriggerNumber.get() >= mTriggerLimit); + } + + /** + * 是否无限循环 + * @return {@code true} yes, {@code false} no + */ + public boolean isInfinite() { + return (mTriggerLimit <= -1); + } + + /** + * 设置 UI Handler + *
+     *     如果没设置 Handler 回调方法则属于非 UI 线程
+     * 
+ * @param handler {@link Handler} + * @return {@link DevTimer} + */ + public DevTimer setHandler(final Handler handler) { + mHandler = handler; + return this; + } + + /** + * 设置回调事件 + * @param callback {@link Callback} + * @return {@link DevTimer} + */ + public DevTimer setCallback(final Callback callback) { + mCallback = callback; + return this; + } + + // ========== + // = 核心方法 = + // ========== + + /** + * 运行定时器 + * @return {@link DevTimer} + */ + public DevTimer start() { + // 标记状态 ( 不需要回收 ) + mMarkSweep = false; + TimerManager.addContainsChecker(this); + return startTimer(); + } + + /** + * 关闭定时器 + * @return {@link DevTimer} + */ + public DevTimer stop() { + // 标记状态 ( 需要回收 ) + mMarkSweep = true; + return cancelTimer(); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 执行定时器 + * @return {@link DevTimer} + */ + private DevTimer startTimer() { + // 先关闭旧的定时器 + cancelTimer(); + // 表示运行定时器中 + mRunning = true; + // 每次重置触发次数 + mTriggerNumber.set(0); + // 开启定时器 + mTimer = new Timer(); // 每次重新 new 防止被取消 + // 重新生成定时器, 防止出现 TimerTask is scheduled already 同一个定时器只能被放置一次 + mTimerTask = new TimerTask() { + @Override + public void run() { + // 表示运行定时器中 + mRunning = true; + // 累计触发次数 + int _number = mTriggerNumber.incrementAndGet(); + // 是否结束 + boolean _end = isTriggerEnd(); + // 是否无限循环 + boolean _infinite = isInfinite(); + // 关闭定时器, 进行标记需要回收 + if (_end) stop(); + + if (mCallback != null) { + // 判断是否 UI 线程通知 + if (mHandler != null) { + mHandler.post(() -> mCallback.callback( + DevTimer.this, _number, _end, _infinite)); + } else { + mCallback.callback(DevTimer.this, _number, _end, _infinite); + } + } + } + }; + try { + // xx 毫秒后执行, 每隔 xx 毫秒再执行一次 + mTimer.schedule(mTimerTask, mDelay, mPeriod); + } catch (Exception e) { + // 表示非运行定时器中 + mRunning = false; + // 关闭定时器, 进行标记需要回收 + stop(); // 启动失败, 则进行标记需要回收 + } + return this; + } + + /** + * 取消定时器 + * @return {@link DevTimer} + */ + private DevTimer cancelTimer() { + // 表示非运行定时器中 + mRunning = false; + try { + // 取消定时器 + if (mTimer != null) { + mTimer.cancel(); + mTimer = null; + } + if (mTimerTask != null) { + mTimerTask.cancel(); + mTimerTask = null; + } + } catch (Exception ignored) { + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java b/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java new file mode 100644 index 0000000000..c35d8f1cc0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java @@ -0,0 +1,287 @@ +package dev.utils.app.timer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dev.utils.LogPrintUtils; + +/** + * detail: 定时器管理类 + * @author Ttt + */ +public final class TimerManager { + + private TimerManager() { + } + + // 日志 TAG + private static final String TAG = TimerManager.class.getSimpleName(); + + // 内部保存定时器对象 ( 统一管理 ) + protected static final List mTimerLists = Collections.synchronizedList(new ArrayList<>()); + + /** + * 添加包含校验 + * @param timer 定时器 + */ + protected static void addContainsChecker(final DevTimer timer) { + synchronized (mTimerLists) { + if (!mTimerLists.contains(timer)) { + mTimerLists.add(timer); + } + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取全部定时器总数 + * @return 全部定时器总数 + */ + public static int getSize() { + return mTimerLists.size(); + } + + /** + * 回收定时器资源 + */ + public static void recycle() { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer == null || timer.isMarkSweep()) { + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "recycle"); + } + } + } + + // ============ + // = 获取定时器 = + // ============ + + /** + * 获取对应 TAG 定时器 ( 优先获取符合的 ) + * @param tag 判断 {@link DevTimer#getTag()} + * @return {@link DevTimer} + */ + public static DevTimer getTimer(final String tag) { + if (tag != null) { + synchronized (mTimerLists) { + try { + for (DevTimer timer : mTimerLists) { + if (timer != null && tag.equals(timer.getTag())) { + return timer; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTimer"); + } + } + } + return null; + } + + /** + * 获取对应 UUID 定时器 ( 优先获取符合的 ) + * @param uuid 判断 {@link DevTimer#getTag()} + * @return {@link DevTimer} + */ + public static DevTimer getTimer(final int uuid) { + synchronized (mTimerLists) { + try { + for (DevTimer timer : mTimerLists) { + if (timer != null && uuid == timer.getUUID()) { + return timer; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTimer"); + } + } + return null; + } + + // = + + /** + * 获取对应 TAG 定时器集合 + * @param tag 判断 {@link DevTimer#getTag()} + * @return 定时器集合 + */ + public static List getTimers(final String tag) { + List lists = new ArrayList<>(); + if (tag != null) { + synchronized (mTimerLists) { + try { + for (DevTimer timer : mTimerLists) { + if (timer != null && tag.equals(timer.getTag())) { + lists.add(timer); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTimers"); + } + } + } + return lists; + } + + /** + * 获取对应 UUID 定时器集合 + * @param uuid 判断 {@link DevTimer#getTag()} + * @return 定时器集合 + */ + public static List getTimers(final int uuid) { + List lists = new ArrayList<>(); + synchronized (mTimerLists) { + try { + for (DevTimer timer : mTimerLists) { + if (timer != null && uuid == timer.getUUID()) { + lists.add(timer); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getTimers"); + } + } + return lists; + } + + // ============ + // = 关闭定时器 = + // ============ + + /** + * 关闭全部定时器 + */ + public static void closeAll() { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer != null) { + timer.stop(); + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeAll"); + } + } + } + + /** + * 关闭所有未运行的定时器 + */ + public static void closeAllNotRunning() { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer != null && !timer.isRunning()) { + timer.stop(); + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeAllNotRunning"); + } + } + } + + /** + * 关闭所有无限循环的定时器 + */ + public static void closeAllInfinite() { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer != null && timer.isInfinite()) { + timer.stop(); + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeAllInfinite"); + } + } + } + + /** + * 关闭所有对应 TAG 定时器 + * @param tag 判断 {@link DevTimer#getTag()} + */ + public static void closeAllTag(final String tag) { + if (tag != null) { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer != null && tag.equals(timer.getTag())) { + timer.stop(); + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeAllTag"); + } + } + } + } + + /** + * 关闭所有对应 UUID 定时器 + * @param uuid 判断 {@link DevTimer#getUUID()} + */ + public static void closeAllUUID(final int uuid) { + synchronized (mTimerLists) { + try { + Iterator iterator = mTimerLists.iterator(); + while (iterator.hasNext()) { + DevTimer timer = iterator.next(); + if (timer != null && uuid == timer.getUUID()) { + timer.stop(); + iterator.remove(); + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeAllUUID"); + } + } + } + + // ============ + // = 定时器操作 = + // ============ + + /** + * 运行定时器 + * @param timer 定时器 + */ + public static void startTimer(final DevTimer timer) { + if (timer != null) timer.start(); + } + + /** + * 关闭定时器 + * @param timer 定时器 + */ + public static void stopTimer(final DevTimer timer) { + if (timer != null) timer.stop(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/ToastTintUtils.java b/lib/DevApp/src/main/java/dev/utils/app/toast/ToastTintUtils.java new file mode 100644 index 0000000000..afa3172018 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/ToastTintUtils.java @@ -0,0 +1,1749 @@ +package dev.utils.app.toast; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.lang.reflect.Field; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.R; +import dev.utils.app.ResourceUtils; +import dev.utils.app.ViewUtils; +import dev.utils.app.image.ImageUtils; + +/** + * detail: 自定义 View 着色美化 Toast 工具类 + * @author Ttt + *
+ *     支持子线程弹出 Toast, 可通过开关配置
+ *     内部解决 Android 7.1.1 崩溃问题
+ *     但无处理 部分 ROM 如魅族、小米、三星等关闭应用通知, 无法显示 Toast 问题
+ * 
+ */ +public final class ToastTintUtils { + + private ToastTintUtils() { + } + + // 日志 TAG + private static final String TAG = ToastTintUtils.class.getSimpleName(); + + // Toast 判断过滤 + private static ToastTintUtils.Filter sToastFilter = null; + // 内部持有单个 Toast + private static Toast sToast = null; + // 判断是否使用 Handler + private static boolean sUseHandler = true; + // 内部 Handler + private static final Handler sHandler = new Handler(Looper.getMainLooper()); + // Null 值 ( null 提示值 ) + private static String sNullText = null; + + // ========== + // = 部分配置 = + // ========== + + // 判断是否使用配置 + private static boolean sUseConfig = true; + // Toast 的重心、X、Y 轴偏移 + private static int sGravity, sX, sY; + // 水平边距、垂直边距 + private static float sHorizontalMargin, sVerticalMargin; + + // ========== + // = 样式相关 = + // ========== + + // 默认样式 + private static final ToastTintUtils.Style sDefaultStyle = new DefaultStyle(); + // Normal 样式 + private static ToastTintUtils.Style sNormalStyle = new NormalStyle(); + // Info 样式 + private static ToastTintUtils.Style sInfoStyle = new InfoStyle(); + // Warning 样式 + private static ToastTintUtils.Style sWarningStyle = new WarningStyle(); + // Error 样式 + private static ToastTintUtils.Style sErrorStyle = new ErrorStyle(); + // Success 样式 + private static ToastTintUtils.Style sSuccessStyle = new SuccessStyle(); + // = + // info icon + private static Drawable sInfoDrawable = null; + // warning icon + private static Drawable sWarningDrawable = null; + // error icon + private static Drawable sErrorDrawable = null; + // Success icon + private static Drawable sSuccessDrawable = null; + + /** + * 重置默认参数 + */ + public static void reset() { + sUseHandler = true; + sUseConfig = true; + sNullText = null; + sGravity = sX = sY = 0; + sHorizontalMargin = sVerticalMargin = 0.0F; + } + + /** + * 设置 Toast 过滤器 + * @param toastFilter {@link ToastUtils.Filter} + */ + public static void setToastFilter(final ToastTintUtils.Filter toastFilter) { + ToastTintUtils.sToastFilter = toastFilter; + } + + /** + * 设置是否使用 Handler 显示 Toast + * @param useHandler {@code true} 使用, {@code false} 不使用 + */ + public static void setUseHandler(final boolean useHandler) { + ToastTintUtils.sUseHandler = useHandler; + } + + /** + * 设置 Text 为 null 的文本 + * @param nullText 显示内容为 null 时, 使用的提示值 + */ + public static void setNullText(final String nullText) { + ToastTintUtils.sNullText = nullText; + } + + /** + * 设置是否使用配置 + * @param useConfig {@code true} 使用, {@code false} 不使用 + */ + public static void setUseConfig(final boolean useConfig) { + ToastTintUtils.sUseConfig = useConfig; + } + + /** + * 设置 Toast 显示在屏幕上的位置 + * @param gravity 重心 + * @param xOffset X 轴偏移 + * @param yOffset Y 轴偏移 + */ + public static void setGravity( + final int gravity, + final int xOffset, + final int yOffset + ) { + ToastTintUtils.sGravity = gravity; + ToastTintUtils.sX = xOffset; + ToastTintUtils.sY = yOffset; + } + + /** + * 设置边距 + * @param horizontalMargin 水平边距 + * @param verticalMargin 垂直边距 + */ + public static void setMargin( + final float horizontalMargin, + final float verticalMargin + ) { + ToastTintUtils.sHorizontalMargin = horizontalMargin; + ToastTintUtils.sVerticalMargin = verticalMargin; + } + + // = + + /** + * 获取默认样式 + * @return {@link Style} 默认样式 + */ + public static Style getDefaultStyle() { + return sDefaultStyle; + } + + /** + * 获取 Normal 样式 + * @return {@link Style} Normal 样式 + */ + public static Style getNormalStyle() { + return sNormalStyle; + } + + /** + * 获取 Info 样式 + * @return {@link Style} Info 样式 + */ + public static Style getInfoStyle() { + return sInfoStyle; + } + + /** + * 获取 Warning 样式 + * @return {@link Style} Warning 样式 + */ + public static Style getWarningStyle() { + return sWarningStyle; + } + + /** + * 获取 Error 样式 + * @return {@link Style} Error 样式 + */ + public static Style getErrorStyle() { + return sErrorStyle; + } + + /** + * 获取 Success 样式 + * @return {@link Style} Success 样式 + */ + public static Style getSuccessStyle() { + return sSuccessStyle; + } + + // = + + /** + * 设置 Normal 样式 + * @param normalStyle Normal 样式 + */ + public static void setNormalStyle(final Style normalStyle) { + ToastTintUtils.sNormalStyle = normalStyle; + } + + /** + * 设置 Info 样式 + * @param infoStyle Info 样式 + */ + public static void setInfoStyle(final Style infoStyle) { + ToastTintUtils.sInfoStyle = infoStyle; + } + + /** + * 设置 Warning 样式 + * @param warningStyle Warning 样式 + */ + public static void setWarningStyle(final Style warningStyle) { + ToastTintUtils.sWarningStyle = warningStyle; + } + + /** + * 设置 Error 样式 + * @param errorStyle Error 样式 + */ + public static void setErrorStyle(final Style errorStyle) { + ToastTintUtils.sErrorStyle = errorStyle; + } + + /** + * 设置 Success 样式 + * @param successStyle Success 样式 + */ + public static void setSuccessStyle(final Style successStyle) { + ToastTintUtils.sSuccessStyle = successStyle; + } + + /** + * 获取 Info 样式 icon + * @return {@link Drawable} Info 样式 icon + */ + public static Drawable getInfoDrawable() { + if (sInfoDrawable != null) { + return sInfoDrawable; + } + sInfoDrawable = ResourceUtils.getDrawable(R.drawable.dev_toast_icon_info_white); + return sInfoDrawable; + } + + /** + * 获取 Warning 样式 icon + * @return {@link Drawable} Warning 样式 icon + */ + public static Drawable getWarningDrawable() { + if (sWarningDrawable != null) { + return sWarningDrawable; + } + sWarningDrawable = ResourceUtils.getDrawable(R.drawable.dev_toast_icon_warning_white); + return sWarningDrawable; + } + + /** + * 获取 Error 样式 icon + * @return {@link Drawable} Error 样式 icon + */ + public static Drawable getErrorDrawable() { + if (sErrorDrawable != null) { + return sErrorDrawable; + } + sErrorDrawable = ResourceUtils.getDrawable(R.drawable.dev_toast_icon_error_white); + return sErrorDrawable; + } + + /** + * 获取 Success 样式 icon + * @return {@link Drawable} Success 样式 icon + */ + public static Drawable getSuccessDrawable() { + if (sSuccessDrawable != null) { + return sSuccessDrawable; + } + sSuccessDrawable = ResourceUtils.getDrawable(R.drawable.dev_toast_icon_success_white); + return sSuccessDrawable; + } + + // ==================== + // = 显示 normal Toast = + // ==================== + + /** + * normal 样式 Toast + * @param text Toast 提示文本 + */ + public static void normal(final String text) { + custom(true, null, sNormalStyle, text, Toast.LENGTH_SHORT, null); + } + + /** + * normal 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void normal( + final String text, + final int duration + ) { + custom(true, null, sNormalStyle, text, duration, null); + } + + /** + * normal 样式 Toast + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void normal( + final String text, + final Drawable icon + ) { + custom(true, null, sNormalStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * normal 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void normal( + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, sNormalStyle, text, duration, icon); + } + + // = + + /** + * normal 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + */ + public static void normal( + final boolean isSingle, + final String text + ) { + custom(isSingle, null, sNormalStyle, text, Toast.LENGTH_SHORT, null); + } + + /** + * normal 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void normal( + final boolean isSingle, + final String text, + final int duration + ) { + custom(isSingle, null, sNormalStyle, text, duration, null); + } + + /** + * normal 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void normal( + final boolean isSingle, + final String text, + final Drawable icon + ) { + custom(isSingle, null, sNormalStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * normal 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void normal( + final boolean isSingle, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, sNormalStyle, text, duration, icon); + } + + // ================== + // = 显示 info Toast = + // ================== + + /** + * info 样式 Toast + * @param text Toast 提示文本 + */ + public static void info(final String text) { + custom(true, null, sInfoStyle, text, Toast.LENGTH_SHORT, getInfoDrawable()); + } + + /** + * info 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void info( + final String text, + final int duration + ) { + custom(true, null, sInfoStyle, text, duration, getInfoDrawable()); + } + + /** + * info 样式 Toast + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void info( + final String text, + final Drawable icon + ) { + custom(true, null, sInfoStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * info 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void info( + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, sInfoStyle, text, duration, icon); + } + + // = + + /** + * info 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + */ + public static void info( + final boolean isSingle, + final String text + ) { + custom(isSingle, null, sInfoStyle, text, Toast.LENGTH_SHORT, getInfoDrawable()); + } + + /** + * info 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void info( + final boolean isSingle, + final String text, + final int duration + ) { + custom(isSingle, null, sInfoStyle, text, duration, getInfoDrawable()); + } + + /** + * info 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void info( + final boolean isSingle, + final String text, + final Drawable icon + ) { + custom(isSingle, null, sInfoStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * info 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void info( + final boolean isSingle, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, sInfoStyle, text, duration, icon); + } + + // ===================== + // = 显示 warning Toast = + // ===================== + + /** + * warning 样式 Toast + * @param text Toast 提示文本 + */ + public static void warning(final String text) { + custom(true, null, sWarningStyle, text, Toast.LENGTH_SHORT, getWarningDrawable()); + } + + /** + * warning 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void warning( + final String text, + final int duration + ) { + custom(true, null, sWarningStyle, text, duration, getWarningDrawable()); + } + + /** + * warning 样式 Toast + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void warning( + final String text, + final Drawable icon + ) { + custom(true, null, sWarningStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * warning 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void warning( + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, sWarningStyle, text, duration, icon); + } + + // = + + /** + * warning 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + */ + public static void warning( + final boolean isSingle, + final String text + ) { + custom(isSingle, null, sWarningStyle, text, Toast.LENGTH_SHORT, getWarningDrawable()); + } + + /** + * warning 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void warning( + final boolean isSingle, + final String text, + final int duration + ) { + custom(isSingle, null, sWarningStyle, text, duration, getWarningDrawable()); + } + + /** + * warning 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void warning( + final boolean isSingle, + final String text, + final Drawable icon + ) { + custom(isSingle, null, sWarningStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * warning 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void warning( + final boolean isSingle, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, sWarningStyle, text, duration, icon); + } + + // =================== + // = 显示 error Toast = + // =================== + + /** + * error 样式 Toast + * @param text Toast 提示文本 + */ + public static void error(final String text) { + custom(true, null, sErrorStyle, text, Toast.LENGTH_SHORT, getErrorDrawable()); + } + + /** + * error 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void error( + final String text, + final int duration + ) { + custom(true, null, sErrorStyle, text, duration, getErrorDrawable()); + } + + /** + * error 样式 Toast + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void error( + final String text, + final Drawable icon + ) { + custom(true, null, sErrorStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * error 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void error( + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, sErrorStyle, text, duration, icon); + } + + // = + + /** + * error 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + */ + public static void error( + final boolean isSingle, + final String text + ) { + custom(isSingle, null, sErrorStyle, text, Toast.LENGTH_SHORT, getErrorDrawable()); + } + + /** + * error 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void error( + final boolean isSingle, + final String text, + final int duration + ) { + custom(isSingle, null, sErrorStyle, text, duration, getErrorDrawable()); + } + + /** + * error 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void error( + final boolean isSingle, + final String text, + final Drawable icon + ) { + custom(isSingle, null, sErrorStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * error 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void error( + final boolean isSingle, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, sErrorStyle, text, duration, icon); + } + + // ===================== + // = 显示 success Toast = + // ===================== + + /** + * success 样式 Toast + * @param text Toast 提示文本 + */ + public static void success(final String text) { + custom(true, null, sSuccessStyle, text, Toast.LENGTH_SHORT, getSuccessDrawable()); + } + + /** + * success 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void success( + final String text, + final int duration + ) { + custom(true, null, sSuccessStyle, text, duration, getSuccessDrawable()); + } + + /** + * success 样式 Toast + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void success( + final String text, + final Drawable icon + ) { + custom(true, null, sSuccessStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * success 样式 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void success( + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, sSuccessStyle, text, duration, icon); + } + + // = + + /** + * success 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + */ + public static void success( + final boolean isSingle, + final String text + ) { + custom(isSingle, null, sSuccessStyle, text, Toast.LENGTH_SHORT, getSuccessDrawable()); + } + + /** + * success 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void success( + final boolean isSingle, + final String text, + final int duration + ) { + custom(isSingle, null, sSuccessStyle, text, duration, getSuccessDrawable()); + } + + /** + * success 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void success( + final boolean isSingle, + final String text, + final Drawable icon + ) { + custom(isSingle, null, sSuccessStyle, text, Toast.LENGTH_SHORT, icon); + } + + /** + * success 样式 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void success( + final boolean isSingle, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, sSuccessStyle, text, duration, icon); + } + + // ==================== + // = 显示 custom Toast = + // ==================== + + /** + * custom Toast + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + */ + public static void custom( + final ToastTintUtils.Style style, + final String text + ) { + custom(true, null, style, text, Toast.LENGTH_SHORT, null); + } + + /** + * custom Toast + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void custom( + final ToastTintUtils.Style style, + final String text, + final int duration + ) { + custom(true, null, style, text, duration, null); + } + + /** + * custom Toast + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void custom( + final ToastTintUtils.Style style, + final String text, + final Drawable icon + ) { + custom(true, null, style, text, Toast.LENGTH_SHORT, icon); + } + + /** + * custom Toast + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void custom( + final ToastTintUtils.Style style, + final String text, + final int duration, + final Drawable icon + ) { + custom(true, null, style, text, duration, icon); + } + + // = + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + */ + public static void custom( + final boolean isSingle, + final ToastTintUtils.Style style, + final String text + ) { + custom(isSingle, null, style, text, Toast.LENGTH_SHORT, null); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void custom( + final boolean isSingle, + final ToastTintUtils.Style style, + final String text, + final int duration + ) { + custom(isSingle, null, style, text, duration, null); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void custom( + final boolean isSingle, + final ToastTintUtils.Style style, + final String text, + final Drawable icon + ) { + custom(isSingle, null, style, text, Toast.LENGTH_SHORT, icon); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void custom( + final boolean isSingle, + final ToastTintUtils.Style style, + final String text, + final int duration, + final Drawable icon + ) { + custom(isSingle, null, style, text, duration, icon); + } + + // = + + /** + * custom Toast + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + */ + public static void custom( + final Context context, + final ToastTintUtils.Style style, + final String text + ) { + custom(true, context, style, text, Toast.LENGTH_SHORT, null); + } + + /** + * custom Toast + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void custom( + final Context context, + final ToastTintUtils.Style style, + final String text, + final int duration + ) { + custom(true, context, style, text, duration, null); + } + + /** + * custom Toast + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void custom( + final Context context, + final ToastTintUtils.Style style, + final String text, + final Drawable icon + ) { + custom(true, context, style, text, Toast.LENGTH_SHORT, icon); + } + + /** + * custom Toast + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void custom( + final Context context, + final ToastTintUtils.Style style, + final String text, + final int duration, + final Drawable icon + ) { + custom(true, context, style, text, duration, icon); + } + + // = + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + */ + public static void custom( + final boolean isSingle, + final Context context, + final ToastTintUtils.Style style, + final String text + ) { + custom(isSingle, context, style, text, Toast.LENGTH_SHORT, null); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void custom( + final boolean isSingle, + final Context context, + final ToastTintUtils.Style style, + final String text, + final int duration + ) { + custom(isSingle, context, style, text, duration, null); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + */ + public static void custom( + final boolean isSingle, + final Context context, + final ToastTintUtils.Style style, + final String text, + final Drawable icon + ) { + custom(isSingle, context, style, text, Toast.LENGTH_SHORT, icon); + } + + /** + * custom Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param icon Toast icon Drawable + */ + public static void custom( + final boolean isSingle, + final Context context, + final ToastTintUtils.Style style, + final String text, + final int duration, + final Drawable icon + ) { + // 获取 View + View view = inflaterView(context, style, text, icon); + // 显示 Toast + showToastView(isSingle, context, view, duration); + } + + // ============= + // = 内部 Toast = + // ============= + + /** + * 显示 View Toast 方法 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + private static void showToastView( + final boolean isSingle, + final Context context, + final View view, + final int duration + ) { + if (view == null) return; + if (sUseHandler) { + sHandler.post(() -> { + try { + Toast toast = newToastView(isSingle, context, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "showToastView"); + } + }); + } else { + try { + Toast toast = newToastView(isSingle, context, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "showToastView"); + } + } + } + + /** + * 获取一个新的 View Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @return {@link Toast} + */ + private static Toast newToastView( + final boolean isSingle, + Context context, + final View view, + final int duration + ) { + context = DevUtils.getContext(context); + // 防止 Context 为 null + if (context == null) { + return null; + } else if (view == null) { // 防止显示的 View 为 null + return null; + } + // 判断是否显示唯一, 单独共用一个 + if (isSingle) { + try { + // 关闭旧的 Toast + if (sToast != null) { + sToast.cancel(); + sToast = null; + } + // 解决 MIUI 会显示应用名称问题 + sToast = new Toast(context); + sToast.setView(view); + sToast.setDuration(duration); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + sToast.setGravity(sGravity, sX, sY); + } + sToast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(sToast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastView"); + } + return sToast; + } else { + Toast toast = null; + try { + // 解决 MIUI 会显示应用名称问题 + toast = new Toast(context); + toast.setView(view); + toast.setDuration(duration); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + toast.setGravity(sGravity, sX, sY); + } + toast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(toast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastView"); + } + return toast; + } + } + + /** + * 实例化 Layout View + * @param context {@link Context} + * @param style Toast 样式 {@link ToastTintUtils.Style} + * @param text Toast 提示文本 + * @param icon Toast icon Drawable + * @return {@link View} inflate dev_toast_layout + */ + private static View inflaterView( + Context context, + final ToastTintUtils.Style style, + final String text, + Drawable icon + ) { + context = DevUtils.getContext(context); + // 如果样式为 null, 则不处理 + if (style == null) { + return null; + } + // 提示文本 + String toastText = text; + // 判断是否过滤 + if (!sPriToastFilter.filter(toastText)) { + return null; + } + // 处理内容 + toastText = sPriToastFilter.handlerContent(toastText); + // 设置为 null, 便于提示排查 + if (TextUtils.isEmpty(toastText)) { + toastText = sNullText; + // 如果还是为 null, 则不处理 + if (TextUtils.isEmpty(toastText)) { + return null; + } + } + if (context != null) { + try { + // 引入 View + final View toastLayout = LayoutInflater.from(context).inflate( + dev.utils.R.layout.dev_toast_layout, null + ); + // 初始化 View + final ImageView toastIcon = toastLayout.findViewById( + dev.utils.R.id.vid_toast_iv + ); + final TextView toastTextView = toastLayout.findViewById( + dev.utils.R.id.vid_toast_tv + ); + + // ================ + // = TextView 相关 = + // ================ + + // 设置文案 + toastTextView.setText(toastText); + // 设置字体颜色 + if (style.getTextColor() != 0) { + toastTextView.setTextColor(style.getTextColor()); + } + // 设置字体大小 + if (style.getTextSize() != 0F) { + toastTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.getTextSize()); + } + // 设置最大行数 + if (style.getMaxLines() >= 1) { + toastTextView.setMaxLines(style.getMaxLines()); + } + // 设置 Ellipsize 效果 + if (style.getEllipsize() != null) { + toastTextView.setEllipsize(style.getEllipsize()); + } + // 设置字体样式 + if (style.getTypeface() != null) { + toastTextView.setTypeface(style.getTypeface()); + } + + // ================= + // = ImageView 相关 = + // ================= + + // 判断是否使用图标 + if (icon != null) { + // 判断是否渲染图标 + if (style.isTintIcon() && style.getTintIconColor() != 0) { + icon = ImageUtils.setColorFilter(icon, style.getTintIconColor()); + } + // 设置 ImageView 图片 + ViewUtils.setBackground(toastIcon, icon); + } else { + // 隐藏图标 + toastIcon.setVisibility(View.GONE); + } + + // ================ + // = 背景 View 相关 = + // ================ + + // 背景图片 + Drawable drawableFrame = style.getBackground(); + // 判断是否为 null + if (drawableFrame == null) { + drawableFrame = ResourceUtils.getNinePatchDrawable( + dev.utils.R.drawable.dev_toast_frame + ); + // 判断是否需要着色 + if (style.getBackgroundTintColor() != 0) { // 根据背景色进行渲染透明图片 + drawableFrame = ImageUtils.setColorFilter( + drawableFrame, style.getBackgroundTintColor() + ); + } + } + // 设置 View 背景 + ViewUtils.setBackground(toastLayout, drawableFrame); + // 返回 View + return toastLayout; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "inflaterView"); + } + } + return null; + } + + // ============================ + // = 解决 Android 7.1.1 崩溃问题 = + // ============================ + + /** + * 反射 Hook Toast 设置 Handler + * @param toast {@link Toast} + */ + private static void reflectToastHandler(final Toast toast) { + if (toast == null) return; + // 反射设置 Toast Handler 解决 Android 7.1.1 Toast 崩溃问题 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + try { + Field field_tn = Toast.class.getDeclaredField("mTN"); + field_tn.setAccessible(true); + + Object mTN = field_tn.get(toast); + Field field_handler = field_tn.getType().getDeclaredField("mHandler"); + field_handler.setAccessible(true); + + Handler handler = (Handler) field_handler.get(mTN); + field_handler.set(mTN, new SafeHandler(handler)); + } catch (Exception ignore) { + } + } + } + + /** + * detail: Toast 安全显示 Handler + * @author Ttt + */ + private static final class SafeHandler + extends Handler { + + private final Handler mHandler; + + SafeHandler(Handler handler) { + mHandler = handler; + } + + @Override + public void handleMessage(Message msg) { + mHandler.handleMessage(msg); + } + + @Override + public void dispatchMessage(Message msg) { + try { + mHandler.dispatchMessage(msg); + } catch (Exception ignore) { + } + } + } + + // ========== + // = 样式相关 = + // ========== + + /** + * detail: Toast 自定义 View 着色等相关 样式配置 + * @author Ttt + */ + public interface Style { + + /** + * 获取文本颜色 + * @return 文本颜色 + */ + int getTextColor(); + + /** + * 获取字体大小 + * @return 字体大小 + */ + float getTextSize(); + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + int getBackgroundTintColor(); + + /** + * 获取背景图片 + * @return 背景图片 + */ + Drawable getBackground(); + + /** + * 获取最大行数 + * @return 最大行数 + */ + int getMaxLines(); + + /** + * 获取 Ellipsize 效果 + * @return Ellipsize 效果 + */ + TextUtils.TruncateAt getEllipsize(); + + /** + * 获取字体样式 + * @return 字体样式 + */ + Typeface getTypeface(); + + /** + * 获取图标着色颜色 + * @return 图标着色颜色 + */ + int getTintIconColor(); + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + boolean isTintIcon(); + } + + // = + + /** + * detail: 默认样式 + * @author Ttt + */ + public static class DefaultStyle + implements Style { + + /** + * 获取获取文本颜色 + * @return 文本颜色 + */ + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 获取字体大小 + * @return 字体大小 + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return 0; + } + + /** + * 获取背景图片 + * @return 背景图片 + */ + @Override + public Drawable getBackground() { + return null; + } + + /** + * 获取最大行数 + * @return 最大行数 + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * 获取 Ellipsize 效果 + * @return Ellipsize 效果 + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 获取字体样式 + * @return 字体样式 + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * 获取图标着色颜色 + * @return 图标着色颜色 + */ + @Override + public int getTintIconColor() { + return Color.WHITE; + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return false; + } + } + + /** + * detail: Normal 样式 ( 灰色 ) + * @author Ttt + */ + public static class NormalStyle + extends DefaultStyle { + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return Color.parseColor("#353A3E"); + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return true; + } + } + + /** + * detail: Info 样式 ( 海洋蓝 ) + * @author Ttt + */ + public static class InfoStyle + extends DefaultStyle { + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return Color.parseColor("#3F51B5"); + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return true; + } + } + + /** + * detail: Warning 样式 ( 橙色 ) + * @author Ttt + */ + public static class WarningStyle + extends DefaultStyle { + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return Color.parseColor("#FFA900"); + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return true; + } + } + + /** + * detail: Error 样式 ( 红色 ) + * @author Ttt + */ + public static class ErrorStyle + extends DefaultStyle { + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return Color.parseColor("#D50000"); + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return true; + } + } + + /** + * detail: Success 样式 ( 绿色 ) + * @author Ttt + */ + public static class SuccessStyle + extends DefaultStyle { + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @Override + public int getBackgroundTintColor() { + return Color.parseColor("#388E3C"); + } + + /** + * 是否渲染图标 ( getTintIconColor() 着色渲染 ) + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isTintIcon() { + return true; + } + } + + // ========== + // = 其他接口 = + // ========== + + /** + * detail: Toast 过滤器 + * @author Ttt + */ + public interface Filter { + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + boolean filter(String content); + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + String handlerContent(String content); + } + + // ================================ + // = ToastTintUtils.Filter 实现方法 = + // ================================ + + /** + * 内部 Toast Filter 实现对象 + */ + private static final ToastTintUtils.Filter sPriToastFilter = new ToastTintUtils.Filter() { + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + @Override + public boolean filter(String content) { + if (sToastFilter != null) { + return sToastFilter.filter(content); + } + return true; + } + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + @Override + public String handlerContent(String content) { + if (sToastFilter != null) { + return sToastFilter.handlerContent(content); + } + return content; + } + }; +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/ToastUtils.java b/lib/DevApp/src/main/java/dev/utils/app/toast/ToastUtils.java new file mode 100644 index 0000000000..80422c3c59 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/ToastUtils.java @@ -0,0 +1,972 @@ +package dev.utils.app.toast; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.StringRes; + +import java.lang.reflect.Field; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; + +/** + * detail: Simple Toast 工具类 ( 简单的 Toast 工具类, 支持子线程弹出 Toast ) + * @author Ttt + *
+ *     支持子线程弹出 Toast, 可通过开关配置
+ *     内部解决 Android 7.1.1 崩溃问题
+ *     但无处理 部分 ROM 如魅族、小米、三星等关闭应用通知, 无法显示 Toast 问题
+ * 
+ */ +public final class ToastUtils { + + private ToastUtils() { + } + + // 日志 TAG + private static final String TAG = ToastUtils.class.getSimpleName(); + + // Toast 判断过滤 + private static ToastUtils.Filter sToastFilter = null; + // 内部持有单个 Toast + private static Toast sToast = null; + // 判断是否使用 Handler + private static boolean sUseHandler = true; + // 内部 Handler + private static final Handler sHandler = new Handler(Looper.getMainLooper()); + // Null 值 ( null 提示值 ) + private static String sNullText = null; + + // ========== + // = 部分配置 = + // ========== + + // 判断是否使用配置 + private static boolean sUseConfig = true; + // Toast 的重心、X、Y 轴偏移 + private static int sGravity, sX, sY; + // 水平边距、垂直边距 + private static float sHorizontalMargin, sVerticalMargin; + + /** + * 重置默认参数 + */ + public static void reset() { + sUseHandler = true; + sUseConfig = true; + sNullText = null; + sGravity = sX = sY = 0; + sHorizontalMargin = sVerticalMargin = 0.0F; + } + + /** + * 设置 Toast 过滤器 + * @param toastFilter {@link ToastUtils.Filter} + */ + public static void setToastFilter(final ToastUtils.Filter toastFilter) { + ToastUtils.sToastFilter = toastFilter; + } + + /** + * 设置是否使用 Handler 显示 Toast + * @param useHandler {@code true} 使用, {@code false} 不使用 + */ + public static void setUseHandler(final boolean useHandler) { + ToastUtils.sUseHandler = useHandler; + } + + /** + * 设置 Text 为 null 的文本 + * @param nullText 显示内容为 null 时, 使用的提示值 + */ + public static void setNullText(final String nullText) { + ToastUtils.sNullText = nullText; + } + + /** + * 设置是否使用配置 + * @param useConfig {@code true} 使用, {@code false} 不使用 + */ + public static void setUseConfig(final boolean useConfig) { + ToastUtils.sUseConfig = useConfig; + } + + /** + * 设置 Toast 显示在屏幕上的位置 + * @param gravity 重心 + * @param xOffset X 轴偏移 + * @param yOffset Y 轴偏移 + */ + public static void setGravity( + final int gravity, + final int xOffset, + final int yOffset + ) { + ToastUtils.sGravity = gravity; + ToastUtils.sX = xOffset; + ToastUtils.sY = yOffset; + } + + /** + * 设置边距 + * @param horizontalMargin 水平边距 + * @param verticalMargin 垂直边距 + */ + public static void setMargin( + final float horizontalMargin, + final float verticalMargin + ) { + ToastUtils.sHorizontalMargin = horizontalMargin; + ToastUtils.sVerticalMargin = verticalMargin; + } + + // ================ + // = 统一显示 Toast = + // ================ + + // ====================== + // = Toast.LENGTH_SHORT = + // ====================== + + /** + * 显示 LENGTH_SHORT Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showShort( + final String text, + final Object... formatArgs + ) { + showShort(null, text, formatArgs); + } + + /** + * 显示 LENGTH_SHORT Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showShort( + final Context context, + final String text, + final Object... formatArgs + ) { + handlerToastStr(true, context, text, Toast.LENGTH_SHORT, formatArgs); + } + + // = + + /** + * 显示 LENGTH_SHORT Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showShort( + @StringRes final int resId, + final Object... formatArgs + ) { + showShort(null, resId, formatArgs); + } + + /** + * 显示 LENGTH_SHORT Toast + * @param context {@link Context} + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showShort( + final Context context, + @StringRes final int resId, + final Object... formatArgs + ) { + handlerToastRes(true, context, resId, Toast.LENGTH_SHORT, formatArgs); + } + + // ===================== + // = Toast.LENGTH_LONG = + // ===================== + + /** + * 显示 LENGTH_LONG Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showLong( + final String text, + final Object... formatArgs + ) { + showLong(null, text, formatArgs); + } + + /** + * 显示 LENGTH_LONG Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showLong( + final Context context, + final String text, + final Object... formatArgs + ) { + handlerToastStr(true, context, text, Toast.LENGTH_LONG, formatArgs); + } + + // = + + /** + * 显示 LENGTH_LONG Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showLong( + @StringRes final int resId, + final Object... formatArgs + ) { + showLong(null, resId, formatArgs); + } + + /** + * 显示 LENGTH_LONG Toast + * @param context {@link Context} + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showLong( + final Context context, + @StringRes final int resId, + final Object... formatArgs + ) { + handlerToastRes(true, context, resId, Toast.LENGTH_LONG, formatArgs); + } + + // ============= + // = Toast 方法 = + // ============= + + /** + * 显示 Toast + * @param resId R.string.id + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToast( + @StringRes final int resId, + final int duration + ) { + showToast(null, resId, duration); + } + + /** + * 显示 Toast + * @param context {@link Context} + * @param resId R.string.id + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToast( + final Context context, + @StringRes final int resId, + final int duration + ) { + handlerToastRes(true, context, resId, duration); + } + + /** + * 显示 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToast( + final String text, + final int duration + ) { + innerShowToastText(true, null, text, duration); + } + + /** + * 显示 Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToast( + final Context context, + final String text, + final int duration + ) { + innerShowToastText(true, context, text, duration); + } + + // ================== + // = 非统一显示 Toast = + // ================== + + // ====================== + // = Toast.LENGTH_SHORT = + // ====================== + + /** + * 显示 new LENGTH_SHORT Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showShortNew( + final String text, + final Object... formatArgs + ) { + showShortNew(null, text, formatArgs); + } + + /** + * 显示 new LENGTH_SHORT Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showShortNew( + final Context context, + final String text, + final Object... formatArgs + ) { + handlerToastStr(false, context, text, Toast.LENGTH_SHORT, formatArgs); + } + + // = + + /** + * 显示 new LENGTH_SHORT Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showShortNew( + @StringRes final int resId, + final Object... formatArgs + ) { + showShortNew(null, resId, formatArgs); + } + + /** + * 显示 new LENGTH_SHORT Toast + * @param context {@link Context} + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showShortNew( + final Context context, + @StringRes final int resId, + final Object... formatArgs + ) { + handlerToastRes(false, context, resId, Toast.LENGTH_SHORT, formatArgs); + } + + // ===================== + // = Toast.LENGTH_LONG = + // ===================== + + /** + * 显示 new LENGTH_LONG Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showLongNew( + final String text, + final Object... formatArgs + ) { + showLongNew(null, text, formatArgs); + } + + /** + * 显示 new LENGTH_LONG Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void showLongNew( + final Context context, + final String text, + final Object... formatArgs + ) { + handlerToastStr(false, context, text, Toast.LENGTH_LONG, formatArgs); + } + + // = + + /** + * 显示 new LENGTH_LONG Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showLongNew( + @StringRes final int resId, + final Object... formatArgs + ) { + showLongNew(null, resId, formatArgs); + } + + /** + * 显示 new LENGTH_LONG Toast + * @param context {@link Context} + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void showLongNew( + final Context context, + @StringRes final int resId, + final Object... formatArgs + ) { + handlerToastRes(false, context, resId, Toast.LENGTH_LONG, formatArgs); + } + + // ============= + // = Toast 方法 = + // ============= + + /** + * 显示新的 Toast + * @param resId R.string.id + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastNew( + @StringRes final int resId, + final int duration + ) { + showToastNew(null, resId, duration); + } + + /** + * 显示新的 Toast + * @param context {@link Context} + * @param resId R.string.id + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastNew( + final Context context, + @StringRes final int resId, + final int duration + ) { + handlerToastRes(false, context, resId, duration); + } + + /** + * 显示新的 Toast + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastNew( + final String text, + final int duration + ) { + innerShowToastText(false, null, text, duration); + } + + /** + * 显示新的 Toast + * @param context {@link Context} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastNew( + final Context context, + final String text, + final int duration + ) { + innerShowToastText(false, context, text, duration); + } + + // ============= + // = 显示 Toast = + // ============= + + /** + * 内部私有方法, 最终显示 Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + private static void innerShowToastText( + final boolean isSingle, + final Context context, + final String text, + final int duration + ) { + if (sUseHandler) { + sHandler.post(() -> { + try { + Toast toast = newToastText(isSingle, context, text, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastText - handler"); + } + }); + } else { + try { + Toast toast = newToastText(isSingle, context, text, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastText"); + } + } + } + + /** + * 获取一个新的 Text Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @return {@link Toast} + */ + public static Toast newToastText( + final boolean isSingle, + Context context, + final String text, + final int duration + ) { + context = DevUtils.getContext(context); + // 提示文本 + String toastText = text; + // 判断是否过滤 + if (!sPriToastFilter.filter(toastText)) { + return null; + } + // 处理内容 + toastText = sPriToastFilter.handlerContent(toastText); + // 设置为 null, 便于提示排查 + if (TextUtils.isEmpty(toastText)) { + toastText = sNullText; + // 如果还是为 null, 则不处理 + if (TextUtils.isEmpty(toastText)) { + return null; + } + } + // 判断是否显示唯一, 单独共用一个 + if (isSingle) { + try { + // 关闭旧的 Toast + if (sToast != null) { + sToast.cancel(); + sToast = null; + } + // 解决 MIUI 会显示应用名称问题 + sToast = Toast.makeText(context, "", duration); + sToast.setText(toastText); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + sToast.setGravity(sGravity, sX, sY); + } + sToast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(sToast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastText"); + } + return sToast; + } else { + Toast toast = null; + try { + // 解决 MIUI 会显示应用名称问题 + toast = Toast.makeText(context, "", duration); + toast.setText(toastText); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + toast.setGravity(sGravity, sX, sY); + } + toast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(toast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastText"); + } + return toast; + } + } + + // ================== + // = 显示 View Toast = + // ================== + + /** + * 显示 View Toast 方法 + * @param view Toast 显示的 View + */ + public static void showToastView(final View view) { + showToastView(true, null, view, Toast.LENGTH_SHORT); + } + + /** + * 显示 View Toast 方法 + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastView( + final View view, + final int duration + ) { + showToastView(true, null, view, duration); + } + + /** + * 显示 View Toast 方法 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param view Toast 显示的 View + */ + public static void showToastView( + final boolean isSingle, + final View view + ) { + showToastView(isSingle, null, view, Toast.LENGTH_SHORT); + } + + /** + * 显示 View Toast 方法 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastView( + final boolean isSingle, + final View view, + final int duration + ) { + showToastView(isSingle, null, view, duration); + } + + /** + * 显示 View Toast 方法 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void showToastView( + final boolean isSingle, + final Context context, + final View view, + final int duration + ) { + if (view == null) return; + if (sUseHandler) { + sHandler.post(() -> { + try { + Toast toast = newToastView(isSingle, context, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "showToastView"); + } + }); + } else { + try { + Toast toast = newToastView(isSingle, context, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "showToastView"); + } + } + } + + /** + * 获取一个新的 View Toast + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @return {@link Toast} + */ + public static Toast newToastView( + final boolean isSingle, + Context context, + final View view, + final int duration + ) { + context = DevUtils.getContext(context); + // 判断是否过滤 + if (!sPriToastFilter.filter(view)) { + return null; + } + // 防止 Context 为 null + if (context == null) { + return null; + } else if (view == null) { // 防止显示的 View 为 null + return null; + } + // 判断是否显示唯一, 单独共用一个 + if (isSingle) { + try { + // 关闭旧的 Toast + if (sToast != null) { + sToast.cancel(); + sToast = null; + } + // 解决 MIUI 会显示应用名称问题 + sToast = new Toast(context); + sToast.setView(view); + sToast.setDuration(duration); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + sToast.setGravity(sGravity, sX, sY); + } + sToast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(sToast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastView"); + } + return sToast; + } else { + Toast toast = null; + try { + // 解决 MIUI 会显示应用名称问题 + toast = new Toast(context); + toast.setView(view); + toast.setDuration(duration); + // 判断是否使用配置 + if (sUseConfig) { + // 设置属性配置 + if (sGravity != 0) { + toast.setGravity(sGravity, sX, sY); + } + toast.setMargin(sHorizontalMargin, sVerticalMargin); + } + // 反射 Hook Toast 解决 Android 7.1.1 崩溃问题 + reflectToastHandler(toast); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastView"); + } + return toast; + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 处理 R.string 资源 Toast 的格式化 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param resId R.string.id + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param formatArgs 格式化参数 + */ + private static void handlerToastRes( + final boolean isSingle, + Context context, + @StringRes final int resId, + final int duration, + final Object... formatArgs + ) { + context = DevUtils.getContext(context); + // 防止 Context 为 null + if (context != null) { + String text = null; + try { + if (formatArgs != null && formatArgs.length != 0) { + text = context.getString(resId, formatArgs); + } else { + text = context.getString(resId); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "handlerToastRes"); + } + innerShowToastText(isSingle, context, text, duration); + } + } + + /** + * 处理字符串 Toast 的格式化 + * @param isSingle 是否单例 Toast ( 全局共用 Toast) + * @param context {@link Context} + * @param text Toast 提示文本 + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @param formatArgs 格式化参数 + */ + private static void handlerToastStr( + final boolean isSingle, + Context context, + final String text, + final int duration, + final Object... formatArgs + ) { + context = DevUtils.getContext(context); + // 防止 Context 为 null + if (context != null) { + // 表示需要格式化字符串, 只是为了减少 format 步骤, 增加判断, 为 null 不影响 + if (formatArgs != null && formatArgs.length != 0) { + if (text != null) { // String.format() 中的 formatArgs 可以为 null, 但是 text 不能为 null + try { + innerShowToastText( + isSingle, context, + String.format(text, formatArgs), duration + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "handlerToastStr"); + innerShowToastText(isSingle, context, e.getMessage(), duration); + } + } else { + innerShowToastText(isSingle, context, null, duration); + } + } else { + innerShowToastText(isSingle, context, text, duration); + } + } + } + + // ============================ + // = 解决 Android 7.1.1 崩溃问题 = + // ============================ + + /** + * 反射 Hook Toast 设置 Handler + * @param toast {@link Toast} + */ + private static void reflectToastHandler(final Toast toast) { + if (toast == null) return; + // 反射设置 Toast Handler 解决 Android 7.1.1 Toast 崩溃问题 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + try { + Field field_tn = Toast.class.getDeclaredField("mTN"); + field_tn.setAccessible(true); + + Object mTN = field_tn.get(toast); + Field field_handler = field_tn.getType().getDeclaredField("mHandler"); + field_handler.setAccessible(true); + + Handler handler = (Handler) field_handler.get(mTN); + field_handler.set(mTN, new SafeHandler(handler)); + } catch (Exception ignore) { + } + } + } + + /** + * detail: Toast 安全显示 Handler + * @author Ttt + */ + private static final class SafeHandler + extends Handler { + + private final Handler mHandler; + + SafeHandler(Handler handler) { + mHandler = handler; + } + + @Override + public void handleMessage(Message msg) { + mHandler.handleMessage(msg); + } + + @Override + public void dispatchMessage(Message msg) { + try { + mHandler.dispatchMessage(msg); + } catch (Exception ignore) { + } + } + } + + // ========== + // = 其他接口 = + // ========== + + /** + * detail: Toast 过滤器 + * @author Ttt + */ + public interface Filter { + + /** + * 判断是否显示 + * @param view Toast 显示的 View + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + boolean filter(View view); + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + boolean filter(String content); + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + String handlerContent(String content); + } + + // ============================ + // = ToastUtils.Filter 实现方法 = + // ============================ + + /** + * 内部 Toast Filter 实现对象 + */ + private static final ToastUtils.Filter sPriToastFilter = new Filter() { + + /** + * 判断是否显示 + * @param view Toast 显示的 View + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + @Override + public boolean filter(View view) { + if (sToastFilter != null) { + return sToastFilter.filter(view); + } + return (view != null); + } + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + @Override + public boolean filter(String content) { + if (sToastFilter != null) { + return sToastFilter.filter(content); + } + return true; + } + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + @Override + public String handlerContent(String content) { + if (sToastFilter != null) { + return sToastFilter.handlerContent(content); + } + return content; + } + }; +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DefaultToastStyle.java b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DefaultToastStyle.java new file mode 100644 index 0000000000..4866f15487 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DefaultToastStyle.java @@ -0,0 +1,158 @@ +package dev.utils.app.toast.toaster; + +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; + +import androidx.annotation.ColorInt; + +/** + * detail: Toast 默认样式 + * @author Ttt + */ +public class DefaultToastStyle + implements IToast.Style { + + /** + * 获取 Toast 的重心 + * @return Toast 的重心 + */ + @Override + public int getGravity() { + return 0; + } + + /** + * 获取 X 轴偏移 + * @return X 轴偏移 + */ + @Override + public int getXOffset() { + return 0; + } + + /** + * 获取 Y 轴偏移 + * @return Y 轴偏移 + */ + @Override + public int getYOffset() { + return 0; + } + + /** + * 获取水平边距 + * @return 水平边距 + */ + @Override + public int getHorizontalMargin() { + return 0; + } + + /** + * 获取垂直边距 + * @return 垂直边距 + */ + @Override + public int getVerticalMargin() { + return 0; + } + + /** + * 获取 Toast Z 轴坐标阴影 + * @return Toast Z 轴坐标阴影 + */ + @Override + public int getZ() { + return 0; + } + + /** + * 获取圆角大小 + * @return 圆角大小 + */ + @Override + public float getCornerRadius() { + return 5F; + } + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @ColorInt + @Override + public int getBackgroundTintColor() { + return 0xB2000000; + } + + /** + * 获取背景图片 + * @return 背景图片 + */ + @Override + public Drawable getBackground() { + return null; + } + + // ================ + // = TextView 相关 = + // ================ + + /** + * 获取文本颜色 + * @return 文本颜色 + */ + @ColorInt + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 获取字体大小 + * @return 字体大小 + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 获取最大行数 + * @return 最大行数 + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * 获取 Ellipsize 效果 + * @return Ellipsize 效果 + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 获取字体样式 + * @return 字体样式 + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * 获取 TextView padding 边距 ( new int[] { left, top, right, bottom } ) + * @return TextView padding 边距 + */ + @Override + public int[] getPadding() { + return new int[]{25, 10, 25, 10}; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DevToast.java b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DevToast.java new file mode 100644 index 0000000000..38abf95cce --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DevToast.java @@ -0,0 +1,187 @@ +package dev.utils.app.toast.toaster; + +import android.content.Context; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.LayoutRes; +import androidx.annotation.StringRes; + +/** + * detail: Toast 工具类 ( 支持子线程弹出 Toast, 处理无通知权限 ) + * @author Ttt + *
+ *     支持子线程弹出 Toast, 可通过开关配置
+ *     内部解决 Android 7.1.1 崩溃问题
+ *     已处理 部分 ROM 如魅族、小米、三星等关闭应用通知, 无法显示 Toast 问题
+ *     

+ * 缺点: 同时间只能显示一个 Toast + *
+ */ +public final class DevToast { + + private DevToast() { + } + + // 包下 IToastImpl 类持有对象 + private static final IToast.Operate sToast = new IToastImpl(); + + /** + * 重置默认参数 + */ + public static void reset() { + sToast.reset(); + } + + /** + * 设置是否使用 Handler 显示 Toast + * @param useHandler {@code true} 使用, {@code false} 不使用 + */ + public static void setUseHandler(final boolean useHandler) { + sToast.setUseHandler(useHandler); + } + + /** + * 设置 Text 为 null 的文本 + * @param nullText 显示内容为 null 时, 使用的提示值 + */ + public static void setNullText(final String nullText) { + sToast.setNullText(nullText); + } + + /** + * 设置 Toast 文案长度转换 显示时间 + * @param textLengthConvertDuration Toast 文案长度转换界限 + */ + public static void setTextLength(final int textLengthConvertDuration) { + sToast.setTextLength(textLengthConvertDuration); + } + + // = + + /** + * 初始化调用 ( 内部已调用 ) + * @param context {@link Context} + */ + public static void initialize(final Context context) { + sToast.initialize(context); + } + + // ========== + // = 配置方法 = + // ========== + + /** + * 使用单次 Toast 样式配置 + * @param toastStyle Toast 样式 + * @return {@link IToast.Operate} + */ + public static IToast.Operate style(final IToast.Style toastStyle) { + return sToast.style(toastStyle); + } + + /** + * 使用默认 Toast 样式 + * @return {@link IToast.Operate} + */ + public static IToast.Operate defaultStyle() { + return sToast.defaultStyle(); + } + + /** + * 获取 Toast 样式配置 + * @return Toast 样式配置 + */ + public static IToast.Style getToastStyle() { + return sToast.getToastStyle(); + } + + /** + * 初始化 Toast 样式配置 + * @param toastStyle Toast 样式配置 + */ + public static void initStyle(final IToast.Style toastStyle) { + sToast.initStyle(toastStyle); + } + + /** + * 初始化 Toast 过滤器 + * @param toastFilter Toast 过滤器 + */ + public static void initToastFilter(final IToast.Filter toastFilter) { + sToast.initToastFilter(toastFilter); + } + + /** + * 设置 Toast 显示的 View + * @param view Toast 显示的 View + */ + public static void setView(final View view) { + sToast.setView(view); + } + + /** + * 设置 Toast 显示的 View + * @param layoutId R.layout.id + */ + public static void setView(@LayoutRes final int layoutId) { + sToast.setView(layoutId); + } + + // ========== + // = 操作方法 = + // ========== + + /** + * 显示 Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + public static void show( + final String text, + final Object... formatArgs + ) { + sToast.show(text, formatArgs); + } + + /** + * 显示 R.string.id Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + public static void show( + @StringRes final int resId, + final Object... formatArgs + ) { + sToast.show(resId, formatArgs); + } + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + */ + public static void show(final View view) { + sToast.show(view); + } + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + public static void show( + final View view, + final int duration + ) { + sToast.show(view, duration); + } + + // = + + /** + * 取消当前显示的 Toast + */ + public static void cancel() { + sToast.cancel(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToast.java b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToast.java new file mode 100644 index 0000000000..0d3c5756db --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToast.java @@ -0,0 +1,291 @@ +package dev.utils.app.toast.toaster; + +import android.content.Context; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.ColorInt; +import androidx.annotation.LayoutRes; +import androidx.annotation.StringRes; + +/** + * detail: Toast 对外提供接口方法 + * @author Ttt + */ +public final class IToast { + + private IToast() { + } + + /** + * detail: Toast 对外公开操作方法 + * @author Ttt + */ + public interface Operate { + + /** + * 重置默认参数 + */ + void reset(); + + /** + * 设置是否使用 Handler 显示 Toast + * @param useHandler {@code true} 使用, {@code false} 不使用 + */ + void setUseHandler(boolean useHandler); + + /** + * 设置 Text 为 null 的文本 + * @param nullText 显示内容为 null 时, 使用的提示值 + */ + void setNullText(String nullText); + + /** + * 设置 Toast 文案长度转换 显示时间 + * @param textLengthConvertDuration Toast 文案长度转换界限 + */ + void setTextLength(int textLengthConvertDuration); + + // = + + /** + * 初始化调用 + * @param context {@link Context} + */ + void initialize(Context context); + + // ========== + // = 配置方法 = + // ========== + + /** + * 使用单次 Toast 样式配置 + * @param toastStyle Toast 样式 + * @return {@link IToast.Operate} + */ + IToast.Operate style(IToast.Style toastStyle); + + /** + * 使用默认 Toast 样式 + * @return {@link IToast.Operate} + */ + IToast.Operate defaultStyle(); + + /** + * 获取 Toast 样式配置 + * @return Toast 样式配置 + */ + IToast.Style getToastStyle(); + + /** + * 初始化 Toast 样式配置 + * @param toastStyle Toast 样式配置 + */ + void initStyle(IToast.Style toastStyle); + + /** + * 初始化 Toast 过滤器 + * @param toastFilter Toast 过滤器 + */ + void initToastFilter(IToast.Filter toastFilter); + + /** + * 设置 Toast 显示的 View + * @param view Toast 显示的 View + */ + void setView(View view); + + /** + * 设置 Toast 显示的 View + * @param layoutId R.layout.id + */ + void setView(@LayoutRes int layoutId); + + // ========== + // = 操作方法 = + // ========== + + /** + * 显示 Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + void show( + String text, + Object... formatArgs + ); + + /** + * 显示 R.string.id Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + void show( + @StringRes int resId, + Object... formatArgs + ); + + // = + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + */ + void show(View view); + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + void show( + View view, + int duration + ); + + // = + + /** + * 取消当前显示的 Toast + */ + void cancel(); + } + + // ========== + // = 其他接口 = + // ========== + + /** + * detail: Toast 样式配置 + * @author Ttt + */ + public interface Style { + + /** + * 获取 Toast 的重心 + * @return Toast 的重心 + */ + int getGravity(); + + /** + * 获取 X 轴偏移 + * @return X 轴偏移 + */ + int getXOffset(); + + /** + * 获取 Y 轴偏移 + * @return Y 轴偏移 + */ + int getYOffset(); + + /** + * 获取水平边距 + * @return 水平边距 + */ + int getHorizontalMargin(); + + /** + * 获取垂直边距 + * @return 垂直边距 + */ + int getVerticalMargin(); + + /** + * 获取 Toast Z 轴坐标阴影 + * @return Toast Z 轴坐标阴影 + */ + int getZ(); + + /** + * 获取圆角大小 + * @return 圆角大小 + */ + float getCornerRadius(); + + /** + * 获取背景着色颜色 + * @return 背景着色颜色 + */ + @ColorInt + int getBackgroundTintColor(); + + /** + * 获取背景图片 + * @return 背景图片 + */ + Drawable getBackground(); + + // ================ + // = TextView 相关 = + // ================ + + /** + * 获取文本颜色 + * @return 文本颜色 + */ + @ColorInt + int getTextColor(); + + /** + * 获取字体大小 + * @return 字体大小 + */ + float getTextSize(); + + /** + * 获取最大行数 + * @return 最大行数 + */ + int getMaxLines(); + + /** + * 获取 Ellipsize 效果 + * @return Ellipsize 效果 + */ + TextUtils.TruncateAt getEllipsize(); + + /** + * 获取字体样式 + * @return 字体样式 + */ + Typeface getTypeface(); + + /** + * 获取 TextView padding 边距 ( new int[] { left, top, right, bottom } ) + * @return TextView padding 边距 + */ + int[] getPadding(); + } + + /** + * detail: Toast 过滤器 + * @author Ttt + */ + public interface Filter { + + /** + * 判断是否显示 + * @param view Toast 显示的 View + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + boolean filter(View view); + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + boolean filter(String content); + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + String handlerContent(String content); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToastImpl.java b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToastImpl.java new file mode 100644 index 0000000000..7f78ad23da --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToastImpl.java @@ -0,0 +1,605 @@ +package dev.utils.app.toast.toaster; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.LayoutRes; +import androidx.annotation.StringRes; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.ResourceUtils; +import dev.utils.app.ViewUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Toast 接口实现方法 ( 处理方法 ) + * @author Ttt + */ +final class IToastImpl + implements IToast.Operate, + IToast.Filter { + + // 日志 TAG + private final String TAG = IToastImpl.class.getSimpleName(); + + // Context + private Context mContext; + // 内部保存配置 Toast + private ToastFactory.BaseToast mConfigToast = null; + // 当前显示的 Toast + private ToastFactory.BaseToast mToast = null; + // Toast 样式信息 + private IToast.Style mToastStyle = null; + // Toast 判断过滤 + private IToast.Filter mToastFilter = null; + // Toast 默认样式 + private final IToast.Style mDefaultStyle = new DefaultToastStyle(); + // 每个线程的 Toast 样式 + private final ThreadLocal LOCAL_TOAST_STYLES = new ThreadLocal<>(); + // 判断是否使用 Handler + private boolean mUseHandler = true; + // 内部 Handler + private final Handler mHandler = new Handler(Looper.getMainLooper()); + // Null 值 ( null 提示值 ) + private String mNullText = null; + // Toast 文案长度转换 显示时间 + private int mTextLengthConvertDuration = 15; + + /** + * 重置默认参数 + */ + @Override + public void reset() { + // 重新初始化 + initialize(mContext); + } + + /** + * 设置是否使用 Handler 显示 Toast + * @param useHandler {@code true} 使用, {@code false} 不使用 + */ + @Override + public void setUseHandler(final boolean useHandler) { + this.mUseHandler = useHandler; + } + + /** + * 设置 Text 为 null 的文本 + * @param nullText 显示内容为 null 时, 使用的提示值 + */ + @Override + public void setNullText(final String nullText) { + this.mNullText = nullText; + } + + /** + * 设置 Toast 文案长度转换 显示时间 + * @param textLengthConvertDuration Toast 文案长度转换界限 + */ + @Override + public void setTextLength(final int textLengthConvertDuration) { + this.mTextLengthConvertDuration = textLengthConvertDuration; + } + + // ======== + // = 初始化 = + // ======== + + /** + * 初始化调用 + * @param context {@link Context} + */ + @Override + public void initialize(final Context context) { + if (context != null) { + this.mContext = context.getApplicationContext(); + // 初始化默认参数 + mUseHandler = true; + mNullText = null; + // 初始化 Toast + mConfigToast = new ToastFactory.BaseToast(mContext); + mConfigToast.setView(createView()); + // 初始化默认样式 + getToastStyle(); + } + } + + // ============================== + // = 实现 IToast 接口, 对外公开方法 = + // ============================== + + /** + * 使用单次 Toast 样式配置 + * @param toastStyle Toast 样式 + * @return {@link IToast.Operate} + */ + @Override + public IToast.Operate style(final IToast.Style toastStyle) { + if (toastStyle != null) { + LOCAL_TOAST_STYLES.set(toastStyle); + } + return this; + } + + /** + * 使用默认 Toast 样式 + * @return {@link IToast.Operate} + */ + @Override + public IToast.Operate defaultStyle() { + return style(mDefaultStyle); + } + + /** + * 获取 Toast 样式配置 + * @return Toast 样式配置 + */ + @Override + public IToast.Style getToastStyle() { + if (mToastStyle == null) { + mToastStyle = mDefaultStyle; + } + return mToastStyle; + } + + /** + * 初始化 Toast 样式配置 + * @param toastStyle Toast 样式配置 + */ + @Override + public void initStyle(final IToast.Style toastStyle) { + mToastStyle = toastStyle; + // 防止样式为 null + getToastStyle(); + } + + /** + * 初始化 Toast 过滤器 + * @param toastFilter Toast 过滤器 + */ + @Override + public void initToastFilter(final IToast.Filter toastFilter) { + mToastFilter = toastFilter; + } + + /** + * 设置 Toast 显示的 View + * @param view Toast 显示的 View + */ + @Override + public void setView(final View view) { + if (mConfigToast != null && view != null) { + mConfigToast.setView(view); + // 如果是 null, 则抛出异常 + if (mConfigToast.isEmptyMessageView()) { + // 如果设置的布局没有包含一个 TextView 则抛出异常, 必须要包含一个 TextView 作为 Message View + throw new IllegalArgumentException("The layout must contain a TextView"); + } + } + } + + /** + * 设置 Toast 显示的 View + * @param layoutId R.layout.id + */ + @Override + public void setView(@LayoutRes final int layoutId) { + if (mConfigToast != null) { + try { + setView( + View.inflate( + mConfigToast.getView().getContext() + .getApplicationContext(), + layoutId, null + ) + ); + } catch (Exception ignored) { + } + // 如果是 null, 则抛出异常 + if (mConfigToast.isEmptyMessageView()) { + // 如果设置的布局没有包含一个 TextView 则抛出异常, 必须要包含一个 TextView 作为 Message View + throw new IllegalArgumentException("The layout must contain a TextView"); + } + } + } + + // ========== + // = 操作方法 = + // ========== + + /** + * 显示 Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + @Override + public void show( + final String text, + final Object... formatArgs + ) { + String context = StringUtils.format(text, formatArgs); + if (filter(context)) { + innerShowToastText(handlerContent(context)); + } + } + + /** + * 显示 R.string.id Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + @Override + public void show( + @StringRes final int resId, + final Object... formatArgs + ) { + String context = ResourceUtils.getString(resId, formatArgs); + if (filter(context)) { + // 获取处理的内容 + innerShowToastText(handlerContent(context)); + } + } + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + */ + @Override + public void show(final View view) { + if (filter(view)) { + innerShowToastView(view, Toast.LENGTH_SHORT); + } + } + + /** + * 通过 View 显示 Toast + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + @Override + public void show( + final View view, + final int duration + ) { + if (filter(view)) { + innerShowToastView(view, duration); + } + } + + // = + + /** + * 取消当前显示的 Toast + */ + @Override + public void cancel() { + if (mToast != null) { + try { + mToast.cancel(); + } catch (Exception ignored) { + } + } + } + + // ======================== + // = IToast.Filter 实现方法 = + // ======================== + + /** + * 判断是否显示 + * @param view Toast 显示的 View + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + @Override + public boolean filter(View view) { + if (mToastFilter != null) { + return mToastFilter.filter(view); + } + return true; + } + + /** + * 判断是否显示 + * @param content Toast 显示文案 + * @return {@code true} 接着执行, {@code false} 过滤不处理 + */ + @Override + public boolean filter(String content) { + if (mToastFilter != null) { + return mToastFilter.filter(content); + } + return true; + } + + /** + * 获取 Toast 显示的文案 + * @param content Toast 显示文案 + * @return 处理后的内容 + */ + @Override + public String handlerContent(String content) { + if (mToastFilter != null) { + return mToastFilter.handlerContent(content); + } + return content; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 返回对应线程的 Toast 样式信息 + * @return Toast 样式 + */ + private IToast.Style getThreadToastStyle() { + // 获取当前线程的线程的 Toast 样式 + IToast.Style toastStyle = LOCAL_TOAST_STYLES.get(); + // 如果等于 null, 则返回默认配置信息 + if (toastStyle == null) { + return getToastStyle(); + } else { + LOCAL_TOAST_STYLES.remove(); + } + // 如果存在当前线程的配置信息, 则返回 + return toastStyle; + } + + /** + * 默认创建 View + * @return {@link TextView} + */ + private TextView createView() { + TextView textView = new TextView(mContext); + textView.setId(android.R.id.message); + textView.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + ); + return textView; + } + + /** + * 内部私有方法, 最终显示 Toast + * @param text Toast 提示文本 + */ + private void innerShowToastText(final String text) { + // 获取样式 + final IToast.Style style = getThreadToastStyle(); + if (mUseHandler) { + mHandler.post(() -> { + try { + Toast toast = newToastText(style, text); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastText - handler"); + } + }); + } else { + try { + Toast toast = newToastText(style, text); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastText"); + } + } + } + + /** + * 获取一个新的 Text Toast + * @param style Toast 样式 {@link IToast.Style} + * @param text Toast 提示文本 + * @return {@link Toast} + */ + private Toast newToastText( + final IToast.Style style, + final String text + ) { + if (style == null) return null; + // 提示文本 + String toastText = text; + // 设置为 null, 便于提示排查 + if (TextUtils.isEmpty(toastText)) { + toastText = mNullText; + // 如果还是为 null, 则不处理 + if (TextUtils.isEmpty(toastText)) { + return null; + } + } + try { + // 关闭旧的 Toast + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + // 如果不存在 TextView, 直接跳过 + if (mConfigToast.isEmptyMessageView()) { + return null; + } + View view = mConfigToast.getView(); + // 获取 Toast TextView + TextView toastTextView = mConfigToast.getMessageView(); + // 设置文案 + toastTextView.setText(toastText); + // 设置字体颜色 + if (style.getTextColor() != 0) { + toastTextView.setTextColor(style.getTextColor()); + } + // 设置字体大小 + if (style.getTextSize() != 0F) { + toastTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, style.getTextSize()); + } + // 设置最大行数 + if (style.getMaxLines() >= 1) { + toastTextView.setMaxLines(style.getMaxLines()); + } + // 设置 Ellipsize 效果 + if (style.getEllipsize() != null) { + toastTextView.setEllipsize(style.getEllipsize()); + } + // 设置字体样式 + if (style.getTypeface() != null) { + toastTextView.setTypeface(style.getTypeface()); + } + // 设置 Z 轴阴影 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // 设置 Z 轴阴影 + toastTextView.setZ(style.getZ()); + } + // 设置边距 + if (style.getPadding() != null && style.getPadding().length == 4) { + int[] padding = style.getPadding(); + toastTextView.setPadding(padding[0], padding[1], padding[2], padding[3]); + } + + // 获取背景图片 + Drawable backgroundDrawable = style.getBackground(); + // 如果等于 null + if (backgroundDrawable != null) { + // 设置背景 + ViewUtils.setBackground(view, backgroundDrawable); + } else { + if (style.getBackgroundTintColor() != 0) { + GradientDrawable drawable = new GradientDrawable(); + // 设置背景色 + drawable.setColor(style.getBackgroundTintColor()); + // 设置圆角大小 + drawable.setCornerRadius(style.getCornerRadius()); + // 设置背景 + ViewUtils.setBackground(view, drawable); + } + } + + // 创建 Toast + mToast = ToastFactory.create(DevUtils.getContext()); + mToast.setView(view); + // 设置属性配置 + if (style.getGravity() != 0) { + // 设置 Toast 的重心、X、Y 轴偏移 + mToast.setGravity(style.getGravity(), style.getXOffset(), style.getYOffset()); + } + // 设置边距 + mToast.setMargin(style.getHorizontalMargin(), style.getVerticalMargin()); + // 设置显示时间 + mToast.setDuration( + (toastText.length() < mTextLengthConvertDuration) + ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastText"); + } + return mToast; + } + + /** + * 显示 View Toast 方法 + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + */ + private void innerShowToastView( + final View view, + final int duration + ) { + if (view == null) return; + // 获取样式 + final IToast.Style style = getThreadToastStyle(); + // = + if (mUseHandler) { + mHandler.post(() -> { + try { + Toast toast = newToastView(style, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastView - handler"); + } + }); + } else { + try { + Toast toast = newToastView(style, view, duration); + if (toast != null) { + toast.show(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "innerShowToastView"); + } + } + } + + /** + * 获取一个新的 View Toast + * @param style Toast 样式 {@link IToast.Style} + * @param view Toast 显示的 View + * @param duration Toast 显示时长 {@link Toast#LENGTH_SHORT}、{@link Toast#LENGTH_LONG} + * @return {@link Toast} + */ + private Toast newToastView( + final IToast.Style style, + final View view, + final int duration + ) { + if (style == null) { + return null; + } else if (view == null) { // 防止显示的 View 为 null + return null; + } + try { + // 关闭旧的 Toast + if (mToast != null) { + mToast.cancel(); + mToast = null; + } + // 获取背景图片 + Drawable backgroundDrawable = style.getBackground(); + // 如果等于 null + if (backgroundDrawable != null) { + // 设置背景 + ViewUtils.setBackground(view, backgroundDrawable); + } else { + if (style.getBackgroundTintColor() != 0) { + GradientDrawable drawable = new GradientDrawable(); + // 设置背景色 + drawable.setColor(style.getBackgroundTintColor()); + // 设置圆角大小 + drawable.setCornerRadius(style.getCornerRadius()); + // 设置背景 + ViewUtils.setBackground(view, drawable); + } + } + + // 创建 Toast + mToast = ToastFactory.create(DevUtils.getContext()); + mToast.setView(view); + // 设置属性配置 + if (style.getGravity() != 0) { + // 设置 Toast 的重心、X、Y 轴偏移 + mToast.setGravity(style.getGravity(), style.getXOffset(), style.getYOffset()); + } + // 设置边距 + mToast.setMargin(style.getHorizontalMargin(), style.getVerticalMargin()); + // 设置显示时间 + mToast.setDuration(duration); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "newToastView"); + } + return mToast; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/ToastFactory.java b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/ToastFactory.java new file mode 100644 index 0000000000..4cdc082872 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/ToastFactory.java @@ -0,0 +1,381 @@ +package dev.utils.app.toast.toaster; + +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; +import android.widget.Toast; + +import java.lang.reflect.Field; + +import dev.DevUtils; +import dev.utils.LogPrintUtils; +import dev.utils.app.NotificationUtils; + +/** + * detail: Toast 工厂模式 + * @author Ttt + */ +final class ToastFactory { + + private ToastFactory() { + } + + // 日志 TAG + private static final String TAG = ToastFactory.class.getSimpleName(); + + /** + * detail: Toast 基类 + * @author Ttt + */ + static class BaseToast + extends Toast { + + // Toast 消息 View + private TextView mMessageView; + + /** + * 构造函数 + * @param context {@link Context} + */ + public BaseToast(Context context) { + super(context); + } + + @Override + public final void setView(View view) { + super.setView(view); + if (view instanceof TextView) { + mMessageView = (TextView) view; + } else if (view.findViewById(android.R.id.message) instanceof TextView) { + mMessageView = view.findViewById(android.R.id.message); + } else if (view instanceof ViewGroup) { + mMessageView = findTextView((ViewGroup) view); + } + } + + @Override + public final void setText(CharSequence text) { + if (mMessageView != null) { + mMessageView.setText(text); + } + } + + /** + * 递归获取 ViewGroup 中的 TextView 对象 + * @param group {@link ViewGroup} + * @return {@link TextView} + */ + private TextView findTextView(final ViewGroup group) { + for (int i = 0; i < group.getChildCount(); i++) { + View view = group.getChildAt(i); + if ((view instanceof TextView)) { + return (TextView) view; + } else if (view instanceof ViewGroup) { + TextView textView = findTextView((ViewGroup) view); + if (textView != null) return textView; + } + } + return null; + } + + /** + * 判断是否 null 的 Message View + * @return {@code true} yes, {@code false} no + */ + public final boolean isEmptyMessageView() { + return mMessageView == null; + } + + /** + * 获取 TextView + * @return {@link TextView} + */ + public TextView getMessageView() { + return mMessageView; + } + +// // = +// +// /** +// * 获取系统 Toast View +// * @param context {@link Context} +// * @return {@link View} +// */ +// public static final View getToastSystemView(Context context) { +// return Toast.makeText(context, "", Toast.LENGTH_SHORT).getView(); +// } +// +// /** +// * 设置系统 Toast View +// * @param context {@link Context} +// * @return {@link BaseToast} +// */ +// public final BaseToast setToastSystemView(Context context) { +// setView(getToastSystemView(context)); +// return this; +// } + } + + /** + * detail: 解决 Android 7.1 Toast 崩溃问题 + * @author Ttt + */ + static final class SafeToast + extends BaseToast { + + /** + * 构造函数 + * @param context {@link Context} + */ + public SafeToast(Context context) { + super(context); + // 反射设置 Toast Handler 解决 Android 7.1.1 Toast 崩溃问题 + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + try { + Field field_tn = Toast.class.getDeclaredField("mTN"); + field_tn.setAccessible(true); + + Object mTN = field_tn.get(this); + Field field_handler = field_tn.getType().getDeclaredField("mHandler"); + field_handler.setAccessible(true); + + Handler handler = (Handler) field_handler.get(mTN); + field_handler.set(mTN, new SafeHandler(handler)); + } catch (Exception ignore) { + } + } + } + + /** + * detail: Toast 安全显示 Handler + * @author Ttt + */ + static final class SafeHandler + extends Handler { + + private final Handler mHandler; + + SafeHandler(Handler handler) { + mHandler = handler; + } + + @Override + public void handleMessage(Message msg) { + mHandler.handleMessage(msg); + } + + @Override + public void dispatchMessage(Message msg) { + try { + mHandler.dispatchMessage(msg); + } catch (Exception ignore) { + } + } + } + } + + /** + * detail: 通知栏显示 Toast + * @author Ttt + */ + static final class NotificationToast + extends BaseToast { + + // Toast Window 显示辅助类 + private final ToastHelper mToastHelper; + + /** + * 构造函数 + * @param context {@link Context} + */ + public NotificationToast(Context context) { + super(context); + // 初始化操作 + mToastHelper = new ToastHelper(this); + } + + @Override + public void show() { + // 显示 Toast + mToastHelper.show(); + } + + @Override + public void cancel() { + // 取消显示 + mToastHelper.cancel(); + } + } + + // = + + /** + * 创建 Toast + * @param context {@link Context} + * @return {@link BaseToast} + */ + public static BaseToast create(final Context context) { + if (NotificationUtils.isNotificationEnabled()) { + return new SafeToast(context); + } + return new NotificationToast(context); + } + + // = + + /** + * detail: Toast Window 显示辅助类 + * @author Ttt + *
+     *     参考 Toast.TN 实现方式
+     * 
+ */ + static final class ToastHelper + extends Handler { + + // 当前 Toast 对象 + private final Toast mToast; + // 判断是否显示中 + private boolean mShow; + + ToastHelper(Toast toast) { + super(Looper.getMainLooper()); + mToast = toast; + } + + @Override + public void handleMessage(Message msg) { + cancel(); + } + + /*** + * 显示 Toast 弹窗 + */ + void show() { + if (!mShow) { + try { + if (mToast == null) { + return; + } + // 获取 View + View view = mToast.getView(); + // 防止 View 为 null + if (view == null) { + return; + } + // 获取 Context + Context context = view.getContext().getApplicationContext(); + if (context == null) { + context = view.getContext(); + } + // 获取包名 + String packageName = context.getPackageName(); + + // = + + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + // 设置参数 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { + params.type = WindowManager.LayoutParams.TYPE_TOAST; + } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) { + params.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + } else { + params.type = WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + 37; + } + + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = android.R.style.Animation_Toast; + params.setTitle(Toast.class.getSimpleName()); + params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + + // Toast 的重心 + int gravity = mToast.getGravity(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Configuration config = context.getResources().getConfiguration(); + gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); + } + if (gravity != 0) { + params.gravity = gravity; + // 判断是否铺满整个方向 + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = 1.0F; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = 1.0F; + } + } + // 设置 X、Y 轴偏移 + params.x = mToast.getXOffset(); + params.y = mToast.getYOffset(); + // 设置水平边距、垂直边距 + params.verticalMargin = mToast.getVerticalMargin(); + params.horizontalMargin = mToast.getHorizontalMargin(); + // 设置包名 + params.packageName = packageName; + + // View 对象不能重复添加, 否则会抛出异常 + getWindowManager(DevUtils.getTopActivity()).addView(mToast.getView(), params); + // 当前已经显示 + mShow = true; + // 添加一个移除 Toast 的任务 + sendEmptyMessageDelayed( + 0, mToast.getDuration() == Toast.LENGTH_LONG ? 3500 : 2000 + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "ToastHelper - show"); + } + } + } + + /** + * 取消 Toast 弹窗 + */ + void cancel() { + // 移除之前移除 Toast 的任务 + removeMessages(0); + // 如果显示中, 则移除 View + if (mShow) { + try { + getWindowManager(DevUtils.getTopActivity()).removeView(mToast.getView()); + } catch (Exception ignore) { + } + // 当前没有显示 + mShow = false; + } + } + } + + // = + + /** + * 获取一个 WindowManager 对象 + * @param activity {@link Activity} + * @return {@link WindowManager} + */ + private static WindowManager getWindowManager(final Activity activity) { + // 如果使用的 WindowManager 对象不是当前 Activity 创建的, 则会抛出异常 + // android.view.WindowManager$BadTokenException: Unable to add window - token null is not for an application + if (activity != null) { + try { + return ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWindowManager"); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiHotUtils.java b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiHotUtils.java new file mode 100644 index 0000000000..69eb5b7a59 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiHotUtils.java @@ -0,0 +1,639 @@ +package dev.utils.app.wifi; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Intent; +import android.net.DhcpInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresPermission; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.lang.reflect.Method; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: Wifi 热点工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ *     
+ *     
+ *     
+ *     
+ * 
+ */ +public final class WifiHotUtils { + + // 日志 TAG + private static final String TAG = WifiHotUtils.class.getSimpleName(); + + // WifiManager 对象 + private final WifiManager mWifiManager; + // Wifi 热点配置 + private WifiConfiguration mAPWifiConfig; + + /** + * 构造函数 + */ + public WifiHotUtils() { + // 初始化 WifiManager 对象 + mWifiManager = AppUtils.getWifiManager(); + } + + // ============ + // = Wifi 操作 = + // ============ + + /** + * 创建 Wifi 热点配置 ( 支持 无密码 / WPA2 PSK ) + * @param ssid Wifi ssid + * @return {@link WifiConfiguration} 热点配置 + */ + public static WifiConfiguration createWifiConfigToAp(final String ssid) { + return createWifiConfigToAp(ssid, null); + } + + /** + * 创建 Wifi 热点配置 ( 支持 无密码 / WPA2 PSK ) + * @param ssid Wifi ssid + * @param pwd 密码 ( 需要大于等于 8 位 ) + * @return {@link WifiConfiguration} + */ + public static WifiConfiguration createWifiConfigToAp( + final String ssid, + final String pwd + ) { + try { + // 创建一个新的网络配置 + WifiConfiguration wifiConfig = new WifiConfiguration(); + wifiConfig.allowedAuthAlgorithms.clear(); + wifiConfig.allowedGroupCiphers.clear(); + wifiConfig.allowedKeyManagement.clear(); + wifiConfig.allowedPairwiseCiphers.clear(); + wifiConfig.allowedProtocols.clear(); + wifiConfig.priority = 0; + // 设置连接的 SSID + wifiConfig.SSID = ssid; + // 判断密码 + if (TextUtils.isEmpty(pwd)) { + wifiConfig.hiddenSSID = true; + wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } else { + wifiConfig.preSharedKey = pwd; + wifiConfig.hiddenSSID = true; + wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); + wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); +// wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + wifiConfig.status = WifiConfiguration.Status.ENABLED; + } + return wifiConfig; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createWifiConfigToAp"); + } + return null; + } + + /** + * 开启 Wifi 热点 + *
+     *     android 8.0 及以上必须要有定位权限
+     *     android 7.0 及以下需要 WRITE_SETTINGS 权限
+     * 
+ * @param wifiConfig Wifi 配置 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(allOf = { + Manifest.permission.CHANGE_WIFI_STATE, + Manifest.permission.ACCESS_FINE_LOCATION + }) + public boolean startWifiAp(final WifiConfiguration wifiConfig) { + this.mAPWifiConfig = wifiConfig; + // 大于 8.0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + // 关闭热点 + if (mReservation != null) { + mReservation.close(); + } + // 清空信息 + mAPWifiSSID = mAPWifiPwd = null; + // Android 8.0 是基于应用开启的, 必须使用固定生成的配置进行创建, 无法进行控制 (APP 关闭后, 热点自动关闭 ) + // 必须有定位权限 + mWifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() { + @Override + public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { + super.onStarted(reservation); + // 保存信息 + mReservation = reservation; + // 获取配置信息 + WifiConfiguration wifiConfiguration = reservation.getWifiConfiguration(); + mAPWifiSSID = wifiConfiguration.SSID; + mAPWifiPwd = wifiConfiguration.preSharedKey; + // 打印信息 + LogPrintUtils.dTag( + TAG, "Android 8.0 onStarted wifiAp ssid: %s, pwd: %s", + mAPWifiSSID, mAPWifiPwd + ); + // 触发回调 + if (mWifiAPListener != null) { + mWifiAPListener.onStarted(wifiConfiguration); + } + } + + @Override + public void onStopped() { + super.onStopped(); + // 打印信息 + LogPrintUtils.dTag(TAG, "Android 8.0 onStopped wifiAp"); + // 触发回调 + if (mWifiAPListener != null) { + mWifiAPListener.onStopped(); + } + } + + @Override + public void onFailed(int reason) { + super.onFailed(reason); + // 打印信息 + LogPrintUtils.eTag(TAG, "Android 8.0 onFailed wifiAp, reason: %s", reason); + // 触发回调 + if (mWifiAPListener != null) { + mWifiAPListener.onFailed(reason); + } + } + }, null); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startWifiAp"); + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { // android 7.1 系统以上不支持自动开启热点, 需要手动开启热点 + try { + // 先设置 Wifi 热点信息, 这样跳转前保存热点信息, 开启热点则是对应设置的信息 + boolean setResult = setWifiApConfiguration(wifiConfig); + // 打印日志 + LogPrintUtils.dTag(TAG, "设置 Wifi 热点信息是否成功: %s", setResult); + // 跳转到便携式热点设置页面 + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_MAIN); + intent.setComponent( + new ComponentName( + "com.android.settings", + "com.android.settings.TetherSettings" + ) + ); + AppUtils.startActivity(intent); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startWifiAp"); + } + } else { + try { + // 需要 android.permission.WRITE_SETTINGS 权限 + // 获取设置 Wifi 热点方法 + Method method = mWifiManager.getClass().getMethod( + "setWifiApEnabled", WifiConfiguration.class, + boolean.class + ); + // 开启 Wifi 热点 + method.invoke(mWifiManager, wifiConfig, true); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startWifiAp"); + } + } + return false; + } + + /** + * 关闭 Wifi 热点 + * @return {@code true} success, {@code false} fail + */ + public boolean closeWifiAp() { + // 大于 8.0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 关闭热点 + if (mReservation != null) { + mReservation.close(); + } + // 清空信息 + mAPWifiSSID = mAPWifiPwd = null; + return true; + } + try { + // 获取设置 Wifi 热点方法 + Method method = mWifiManager.getClass().getMethod( + "setWifiApEnabled", WifiConfiguration.class, + boolean.class + ); + // 创建一个新的网络配置 + WifiConfiguration wifiConfig = new WifiConfiguration(); + wifiConfig.allowedAuthAlgorithms.clear(); + wifiConfig.allowedGroupCiphers.clear(); + wifiConfig.allowedKeyManagement.clear(); + wifiConfig.allowedPairwiseCiphers.clear(); + wifiConfig.allowedProtocols.clear(); + wifiConfig.priority = 0; + // 设置 Wifi ssid + wifiConfig.SSID = "CloseWifiAp"; // formatSSID(ssid,true); + // 设置 Wifi 密码 + wifiConfig.preSharedKey = "CloseWifiAp"; + // 设置 Wifi 属性 + wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); + wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.RSN); + wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + wifiConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + // 开启 Wifi 热点 + method.invoke(mWifiManager, wifiConfig, false); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeWifiAp"); + } + return false; + } + + // ============= + // = 手机热点功能 = + // ============= + + /** + * Wifi 热点正在关闭 ( WifiManager.WIFI_AP_STATE_DISABLING ) + */ + public static final int WIFI_AP_STATE_DISABLING = 10; + /** + * Wifi 热点已关闭 ( WifiManager.WIFI_AP_STATE_DISABLED ) + */ + public static final int WIFI_AP_STATE_DISABLED = 11; + /** + * Wifi 热点正在打开 ( WifiManager.WIFI_AP_STATE_ENABLING ) + */ + public static final int WIFI_AP_STATE_ENABLING = 12; + /** + * Wifi 热点已打开 ( WifiManager.WIFI_AP_STATE_ENABLED ) + */ + public static final int WIFI_AP_STATE_ENABLED = 13; + /** + * Wifi 热点状态未知 ( WifiManager.WIFI_AP_STATE_FAILED ) + */ + public static final int WIFI_AP_STATE_FAILED = 14; + + /** + * 获取 Wifi 热点状态 + * @return Wifi 热点状态 + */ + public int getWifiApState() { + try { + // 反射获取方法 + Method method = mWifiManager.getClass().getMethod("getWifiApState"); + // 调用方法, 获取状态 + int wifiApState = (Integer) method.invoke(mWifiManager); + // 打印状态 + LogPrintUtils.dTag(TAG, "WifiApState: %s", wifiApState); + return wifiApState; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiApState"); + } + return WIFI_AP_STATE_FAILED; + } + + /** + * 获取 Wifi 热点配置信息 + * @return {@link WifiConfiguration} 热点配置 + */ + public WifiConfiguration getWifiApConfiguration() { + try { + // 获取 Wifi 热点方法 + Method method = mWifiManager.getClass().getMethod("getWifiApConfiguration"); + // 获取配置 + return (WifiConfiguration) method.invoke(mWifiManager); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiApConfiguration"); + } + return null; + } + + /** + * 设置 Wifi 热点配置信息 + * @param apWifiConfig Wifi 热点配置 + * @return {@code true} success, {@code false} fail + */ + public boolean setWifiApConfiguration(final WifiConfiguration apWifiConfig) { + try { + // 获取设置 Wifi 热点方法 + Method method = mWifiManager.getClass().getMethod( + "setWifiApConfiguration", WifiConfiguration.class + ); + // 开启 Wifi 热点 + return (boolean) method.invoke(mWifiManager, apWifiConfig); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setWifiApConfiguration"); + } + return false; + } + + // = + + /** + * 判断是否打开 Wifi 热点 + * @return {@code true} yes, {@code false} no + */ + public boolean isOpenWifiAp() { + // 判断是否开启热点 ( 默认未打开 ) + boolean isOpen = false; + // 获取当前 Wifi 热点状态 + int wifiApState = getWifiApState(); + switch (wifiApState) { + case WIFI_AP_STATE_DISABLING: // Wifi 热点正在关闭 + break; + case WIFI_AP_STATE_DISABLED: // Wifi 热点已关闭 + break; + case WIFI_AP_STATE_ENABLING: // Wifi 热点正在打开 + break; + case WIFI_AP_STATE_ENABLED: // Wifi 热点已打开 + isOpen = true; + break; + case WIFI_AP_STATE_FAILED: // Wifi 热点状态未知 + break; + } + return isOpen; + } + + /** + * 关闭 Wifi 热点 ( 判断当前状态 ) + * @param isExecute 是否执行关闭 + * @return {@code true} success, {@code false} fail + */ + public boolean closeWifiApCheck(final boolean isExecute) { + // 判断是否开启热点 ( 默认是 ) + boolean isOpen = true; + // 判断是否执行关闭 + boolean isExecuteClose = isExecute; + // 获取当前 Wifi 热点状态 + int wifiApState = getWifiApState(); + switch (wifiApState) { + case WIFI_AP_STATE_DISABLING: // Wifi 热点正在关闭 + isExecuteClose = false; + break; + case WIFI_AP_STATE_DISABLED: // Wifi 热点已关闭 + isOpen = false; + break; + case WIFI_AP_STATE_ENABLING: // Wifi 热点正在打开 + break; + case WIFI_AP_STATE_ENABLED: // Wifi 热点已打开 + break; + case WIFI_AP_STATE_FAILED: // Wifi 热点状态未知 + break; + } + // 如果属于开启, 则进行关闭 + if (isOpen && isExecuteClose) { + closeWifiAp(); + } + return isOpen; + } + + /** + * 是否有设备连接热点 + * @return {@code true} yes, {@code false} no + */ + public boolean isConnectHot() { + try { + BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); + String line; + while ((line = br.readLine()) != null) { + String[] arrays = line.split(" +"); + if (arrays.length >= 4) { + String ipAddress = arrays[0]; // IP 地址 + // 防止地址为 null, 并且需要以. 拆分存在 4 个长度 255.255.255.255 + if (ipAddress != null && ipAddress.split("\\.").length >= 3) { + return true; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isConnectHot"); + } + return false; + } + + /** + * 获取热点主机 IP 地址 + * @return 热点主机 IP 地址 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public String getHotspotServiceIp() { + try { + // 获取网关信息 + DhcpInfo dhcpInfo = mWifiManager.getDhcpInfo(); + // 获取服务器地址 + return intToString(dhcpInfo.serverAddress); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getHotspotServiceIp"); + } + return null; + } + + /** + * 获取连接上的子网关热点 IP ( 一个 ) + * @return 连接上的子网关热点 IP + */ + public String getHotspotAllotIp() { + try { + BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); + String line; + while ((line = br.readLine()) != null) { + String[] arrays = line.split(" +"); + if (arrays.length >= 4) { + String ipAddress = arrays[0]; // IP 地址 + // 防止地址为 null, 并且需要以. 拆分存在 4 个长度 255.255.255.255 + if (ipAddress != null && ipAddress.split("\\.").length >= 3) { + return ipAddress; + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getHotspotAllotIp"); + } + return null; + } + + /** + * 获取连接的热点信息 + * @return 连接的热点信息 + */ + public String getConnectHotspotMsg() { + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("/proc/net/arp")); + StringBuilder builder = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getHotspotAllotIp"); + } finally { + CloseUtils.closeIOQuietly(br); + } + return null; + } + +// /** +// * 获取连接的热点信息 +// * @return 连接的热点信息 +// */ +// private String getConnectHotspotMsg() { +// try { +// BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp")); +// String line; +// while ((line = br.readLine()) != null) { +// String[] arrays = line.split(" +"); +// if (arrays.length >= 4) { +// String ip = arrays[0]; // IP 地址 +// String mac = arrays[3]; // Mac 地址 +// } +// } +// } catch (Exception e) { +// LogPrintUtils.eTag(TAG, e, "getConnectHotspotMsg"); +// } +// return null; +// } + + /** + * 获取热点拼接后的 IP 网关掩码 + * @param defaultGateway 默认网关掩码 + * @param ipAddress IP 地址 + * @return 网关掩码 + */ + public String getHotspotSplitIpMask( + final String defaultGateway, + final String ipAddress + ) { + // 网关掩码 + String hsMask = defaultGateway; + // 获取网关掩码 + if (ipAddress != null) { + try { + int length = ipAddress.lastIndexOf('.'); + // 进行裁剪 + hsMask = ipAddress.substring(0, length) + ".255"; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getHotspotSplitIpMask"); + } + } + return hsMask; + } + + /** + * 转换 IP 地址 + * @param data 待转换的数据 + * @return 转换后的 IP 地址 + */ + private String intToString(final int data) { + return ((data) & 0xff) + "." + + ((data >> 8) & 0xff) + "." + + ((data >> 16) & 0xff) + "." + + ((data >> 24) & 0xff); + } + + // =================== + // = Android 8.0 相关 = + // =================== + + // Wifi ssid + private String mAPWifiSSID; + // Wifi 密码 + private String mAPWifiPwd; + // Wifi 热点对象 + private WifiManager.LocalOnlyHotspotReservation mReservation; + + /** + * 获取 Wifi 热点名 + * @return Wifi ssid + */ + public String getApWifiSSID() { + // 大于 8.0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return mAPWifiSSID; + } else { + if (mAPWifiConfig != null) { + return mAPWifiConfig.SSID; + } + } + return null; + } + + /** + * 获取 Wifi 热点密码 + * @return Wifi 热点密码 + */ + public String getApWifiPwd() { + // 大于 8.0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return mAPWifiPwd; + } else { + if (mAPWifiConfig != null) { + return mAPWifiConfig.preSharedKey; + } + } + return null; + } + + // = + + // Wifi 热点监听 + private OnWifiAPListener mWifiAPListener; + + /** + * 设置 Wifi 热点监听事件 + * @param wifiAPListener {@link OnWifiAPListener} + * @return {@link WifiHotUtils} + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public WifiHotUtils setOnWifiAPListener(final OnWifiAPListener wifiAPListener) { + this.mWifiAPListener = wifiAPListener; + return this; + } + + /** + * detail: Android Wifi 热点监听 + * @author Ttt + */ + public interface OnWifiAPListener { + + /** + * 开启热点回调 + * @param wifiConfig 热点配置 + */ + void onStarted(WifiConfiguration wifiConfig); + + /** + * 关闭热点回调 + */ + void onStopped(); + + /** + * 失败回调 + * @param reason 失败原因 ( 错误码 ) + */ + void onFailed(int reason); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiUtils.java b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiUtils.java new file mode 100644 index 0000000000..0331cf7bf3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiUtils.java @@ -0,0 +1,1177 @@ +package dev.utils.app.wifi; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.ScanResult; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.text.TextUtils; + +import androidx.annotation.RequiresPermission; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.LogPrintUtils; +import dev.utils.app.AppUtils; +import dev.utils.common.ConvertUtils; + +/** + * detail: Wifi 工具类 + * @author Ttt + *
+ *     所需权限
+ *     
+ *     
+ *     
+ * 
+ */ +public final class WifiUtils { + + // 日志 TAG + private static final String TAG = WifiUtils.class.getSimpleName(); + + // WifiManager 对象 + private final WifiManager mWifiManager; + + // ======= + // = 常量 = + // ======= + + // 没有密码 + public static final int NOPWD = 0; + // wep 加密方式 + public static final int WEP = 1; + // wpa 加密方式 + public static final int WPA = 2; + + /** + * 构造函数 + */ + public WifiUtils() { + // 初始化 WifiManager 对象 + mWifiManager = AppUtils.getWifiManager(); + } + + // ======================= + // = Wifi 开关、连接状态获取 = + // ======================= + + /** + * 判断是否打开 Wifi + * @return {@code true} yes, {@code false} no + */ + public boolean isOpenWifi() { + return mWifiManager.isWifiEnabled(); + } + + /** + * 打开 Wifi + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean openWifi() { + // 如果没有打开 Wifi, 才进行打开 + if (!isOpenWifi()) { + try { + return mWifiManager.setWifiEnabled(true); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "openWifi"); + } + } + return false; + } + + /** + * 关闭 Wifi + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean closeWifi() { + // 如果已经打开了 Wifi, 才进行关闭 + if (isOpenWifi()) { + try { + return mWifiManager.setWifiEnabled(false); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "closeWifi"); + } + } + return false; + } + + /** + * 自动切换 Wifi 开关状态 + *
+     *     如果打开了, 则关闭
+     *     如果关闭了, 则打开
+     * 
+ * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean toggleWifiEnabled() { + try { + return mWifiManager.setWifiEnabled(!isOpenWifi()); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "toggleWifiEnabled"); + } + return false; + } + + /** + * 获取当前 Wifi 连接状态 + * @return Wifi 连接状态 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public int getWifiState() { + // WifiManager.WIFI_STATE_ENABLED: // 已打开 + // WifiManager.WIFI_STATE_ENABLING: // 正在打开 + // WifiManager.WIFI_STATE_DISABLED: // 已关闭 + // WifiManager.WIFI_STATE_DISABLING: // 正在关闭 + // WifiManager.WIFI_STATE_UNKNOWN: // 未知 + try { + return mWifiManager.getWifiState(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiState"); + } + return WifiManager.WIFI_STATE_UNKNOWN; + } + + // =========== + // = get 操作 = + // =========== + + /** + * 开始扫描 Wifi + * @return {@code true} 操作成功, {@code false} 操作失败 + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean startScan() { + try { + return mWifiManager.startScan(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "startScan"); + } + return false; + } + + /** + * 获取已配置 ( 连接过 ) 的 Wifi 配置 + * @return {@link List} 已配置 ( 连接过 ) 的 Wifi 配置 + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE + }) + public List getConfiguration() { + try { + return mWifiManager.getConfiguredNetworks(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getConfiguration"); + } + return null; + } + + /** + * 获取附近的 Wifi 列表 + * @return {@link List} 附近的 Wifi 列表 + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public List getWifiList() { + try { + return mWifiManager.getScanResults(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiList"); + } + return null; + } + + /** + * 获取连接的 WifiInfo + * @return {@link WifiInfo} + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public WifiInfo getWifiInfo() { + try { + return mWifiManager.getConnectionInfo(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getWifiInfo"); + } + return null; + } + + /** + * 获取 MAC 地址 + * @param wifiInfo {@link WifiInfo} + * @return MAC 地址 + */ + @SuppressLint("HardwareIds") + public static String getMacAddress(final WifiInfo wifiInfo) { + if (wifiInfo == null) return null; + return wifiInfo.getMacAddress(); + } + + /** + * 获取连接的 BSSID + * @param wifiInfo {@link WifiInfo} + * @return BSSID + */ + public static String getBSSID(final WifiInfo wifiInfo) { + if (wifiInfo == null) return null; + return wifiInfo.getBSSID(); + } + + /** + * 获取 IP 地址 + * @param wifiInfo {@link WifiInfo} + * @return IP 地址 + */ + public static int getIPAddress(final WifiInfo wifiInfo) { + if (wifiInfo == null) return -1; + return wifiInfo.getIpAddress(); + } + + /** + * 获取连接的 Network Id + * @param wifiInfo {@link WifiInfo} + * @return Network Id + */ + public static int getNetworkId(final WifiInfo wifiInfo) { + if (wifiInfo == null) return -1; + return wifiInfo.getNetworkId(); + } + + /** + * 获取 Wifi SSID + * @param wifiInfo {@link WifiInfo} + * @return Wifi SSID + */ + public static String getSSID(final WifiInfo wifiInfo) { + if (wifiInfo == null) return null; + try { + // 获取 SSID, 并进行处理 + return formatSSID(wifiInfo.getSSID(), false); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSSID"); + } + return null; + } + + /** + * 获取当前连接的 Wifi SSID + * @return Wifi SSID + */ + @RequiresPermission(Manifest.permission.ACCESS_WIFI_STATE) + public static String getSSID() { + try { + // 获取当前连接的 Wifi + WifiInfo wifiInfo = AppUtils.getWifiManager().getConnectionInfo(); + // 获取 Wifi SSID + return formatSSID(wifiInfo.getSSID(), false); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getSSID"); + } + return null; + } + + // = + + /** + * 判断是否存在 \"ssid\", 存在则裁剪返回 + * @param ssid 待处理的 SSID + * @return 处理后的 SSID + */ + public static String formatSSID(final String ssid) { + if (ssid == null) return null; + // 自动去掉 "" + if (ssid.startsWith("\"") && ssid.endsWith("\"")) { + try { + // 裁剪连接的 ssid, 并返回 + return ssid.substring(1, ssid.length() - 1); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "formatSSID"); + } + } + return ssid; + } + + /** + * 格式化处理 SSID + * @param ssid 待处理的 SSID + * @param appendQuotes {@code true} 添加引号, {@code false} 删除引号 + * @return 处理后的 SSID + */ + public static String formatSSID( + final String ssid, + final boolean appendQuotes + ) { + if (ssid == null) return null; + if (appendQuotes) { + return "\"" + ssid + "\""; + } else { + return formatSSID(ssid); + } + } + + /** + * 获取处理后的密码 + * @param pwd 待处理的密码 + * @param isJudge 是否需要判断 + * @return 处理后的密码 + */ + public static String getPassword( + final String pwd, + final boolean isJudge + ) { + if (pwd == null) return null; + if (isJudge && isHexWepKey(pwd)) { + return pwd; + } else { + return "\"" + pwd + "\""; + } + } + + /** + * 判断是否 wep 加密 + * @param wepKey 加密类型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHexWepKey(final String wepKey) { + if (wepKey == null) return false; + // WEP-40, WEP-104, and some vendors using 256-bit WEP (WEP-232?) + int len = wepKey.length(); + if (len != 10 && len != 26 && len != 58) { + return false; + } + return ConvertUtils.isHex(wepKey); + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取加密类型 + * @param typeStr 加密类型 + * @return 加密类型 {@link WifiUtils#NOPWD}、{@link WifiUtils#WPA}、{@link WifiUtils#WEP} + */ + public static int getWifiType(final String typeStr) { + if (typeStr == null) return NOPWD; + if (typeStr.contains("WPA")) { + return WPA; + } else if (typeStr.contains("WEP")) { + return WEP; + } + return NOPWD; + } + + /** + * 获取加密类型 + * @param typeInt 加密类型 + * @return 加密类型 {@link WifiUtils#NOPWD}、{@link WifiUtils#WPA}、{@link WifiUtils#WEP} + */ + public static int getWifiTypeInt(final String typeInt) { + if (typeInt == null) return NOPWD; + if ("2".equals(typeInt)) { + return WPA; + } else if ("1".equals(typeInt)) { + return WEP; + } + return NOPWD; + } + + /** + * 获取加密类型 + * @param type 加密类型 + * @return 加密类型 + */ + public static String getWifiType(final int type) { + switch (type) { + case WPA: + return "2"; + case WEP: + return "1"; + case NOPWD: + return "0"; + } + return "0"; + } + + /** + * 获取加密类型 + * @param type 加密类型 + * @return 加密类型 + */ + public static String getWifiTypeStr(final int type) { + switch (type) { + case WPA: + return "WPA"; + case WEP: + return "WEP"; + default: + return ""; + } + } + + /** + * 判断是否连接为 null ( unknown ssid ) + * @param ssid Wifi ssid + * @return {@code true} yes, {@code false} no + */ + public static boolean isConnNull(final String ssid) { + if (ssid == null) return true; + return ssid.contains("unknown"); // + } + + /** + * 获取连接的 Wifi 热点 SSID + * @return Wifi 热点 SSID + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_NETWORK_STATE, + Manifest.permission.ACCESS_WIFI_STATE + }) + public static String isConnectAPHot() { + try { + // 连接管理 + ConnectivityManager manager = AppUtils.getConnectivityManager(); + // 版本兼容处理 + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + // 连接状态 + NetworkInfo.State state = manager.getNetworkInfo( + ConnectivityManager.TYPE_WIFI + ).getState(); + if ((state == NetworkInfo.State.CONNECTED)) { + // 获取连接的 ssid + return getSSID(); + } + } else { + // 获取当前活跃的网络 ( 连接的网络信息 ) + Network network = manager.getActiveNetwork(); + if (network != null) { + NetworkCapabilities networkCapabilities = manager.getNetworkCapabilities(network); + // 判断是否连接 Wifi + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + // 获取连接的 ssid + return getSSID(); + } + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isConnectAPHot"); + } + return null; + } + + // =============== + // = Wifi 配置操作 = + // =============== + + // 默认没有密码 + public static final int SECURITY_NONE = 0; + // WEP 加密方式 + public static final int SECURITY_WEP = 1; + // PSK 加密方式 + public static final int SECURITY_PSK = 2; + // EAP 加密方式 + public static final int SECURITY_EAP = 3; + + /** + * 获取 Wifi 加密类型 + * @param wifiConfig Wifi 配置信息 + * @return Wifi 加密类型 + */ + public static int getSecurity(final WifiConfiguration wifiConfig) { + if (wifiConfig == null) return SECURITY_NONE; + if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + return SECURITY_PSK; + } + if (wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_EAP) + || wifiConfig.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { + return SECURITY_EAP; + } + return (wifiConfig.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE; + } + + /** + * 判断 Wifi 加密类型, 是否为加密类型 + * @param wifiConfig Wifi 配置信息 + * @return {@code true} yes, {@code false} no + */ + public static boolean isExistsPwd(final WifiConfiguration wifiConfig) { + if (wifiConfig == null) return false; + int wifiSecurity = getSecurity(wifiConfig); + // 判断是否加密 + return (wifiSecurity != SECURITY_NONE); + } + + /** + * 获取指定的 ssid 网络配置 ( 需连接保存过, 才存在 ) + * @param ssid Wifi ssid + * @return {@link WifiConfiguration} + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE + }) + public WifiConfiguration isExists(final String ssid) { + if (ssid == null) return null; + // 获取 Wifi 连接过的配置信息 + List listWifiConfigs = getConfiguration(); + // 防止为 null + if (listWifiConfigs == null) return null; + // 遍历判断是否存在 + for (int i = 0, len = listWifiConfigs.size(); i < len; i++) { + WifiConfiguration wifiConfig = listWifiConfigs.get(i); + if (wifiConfig != null) { + if (("\"" + ssid + "\"").equals(wifiConfig.SSID)) { + return wifiConfig; + } + } + } + return null; + } + + /** + * 获取指定的 network id 网络配置 ( 需连接保存过, 才存在 ) + * @param networkId network id + * @return {@link WifiConfiguration} + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE + }) + public WifiConfiguration isExists(final int networkId) { + // 获取 Wifi 连接过的配置信息 + List listWifiConfigs = getConfiguration(); + // 防止为 null + if (listWifiConfigs == null) return null; + // 遍历判断是否存在 + for (int i = 0, len = listWifiConfigs.size(); i < len; i++) { + WifiConfiguration wConfig = listWifiConfigs.get(i); + if (wConfig != null) { + if (wConfig.networkId == networkId) { + return wConfig; + } + } + } + return null; + } + + // ========== + // = 配置操作 = + // ========== + + /** + * 删除指定的 Wifi ( SSID ) 配置信息 + * @param ssid Wifi ssid + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.CHANGE_WIFI_STATE + }) + public static boolean delWifiConfig(final String ssid) { + if (ssid == null) return false; + try { + // 初始化 WifiManager 对象 + WifiManager wifiManager = AppUtils.getWifiManager(); + // 获取 Wifi 连接过的配置信息 + List listWifiConfigs = wifiManager.getConfiguredNetworks(); + // 防止为 null + if (listWifiConfigs != null) { + // 遍历判断是否存在 + for (int i = 0, len = listWifiConfigs.size(); i < len; i++) { + WifiConfiguration wConfig = listWifiConfigs.get(i); + if (wConfig != null) { + if (("\"" + ssid + "\"").equals(wConfig.SSID)) { + // 删除操作 + wifiManager.removeNetwork(wConfig.networkId); + } + } + } + // 保存操作 + return wifiManager.saveConfiguration(); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "delWifiConfig"); + } + return false; + } + + // = + + /** + * 快速连接 Wifi ( 不使用静态 IP 方式 ) + * @param ssid Wifi ssid + * @param pwd Wifi 密码 + * @param type Wifi 加密类型 + * @return {@link WifiConfiguration} + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.CHANGE_WIFI_STATE + }) + public WifiConfiguration quickConnWifi( + final String ssid, + final String pwd, + final int type + ) { + return quickConnWifi(ssid, pwd, type, false, null); + } + + /** + * 快速连接 Wifi + * @param ssid Wifi ssid + * @param pwd Wifi 密码 + * @param type Wifi 加密类型 + * @param isStatic 是否使用静态 IP 连接 + * @param ip 静态 IP 地址 + * @return {@link WifiConfiguration} + */ + @RequiresPermission(allOf = { + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.CHANGE_WIFI_STATE + }) + public WifiConfiguration quickConnWifi( + final String ssid, + final String pwd, + final int type, + final boolean isStatic, + final String ip + ) { + // 步骤: + // 1. 创建 Wifi 静态 IP 连接配置 + // 2. 创建正常 Wifi 连接配置 + // 3. 查询准备连接的 Wifi SSID 是否存在配置文件, 准备进行删除 + // 4. 查询当前连接的 Wifi SSID 准备进行断开 + // 5. 同步进行断开, 删除操作, 并且进行保存 + // 6. 调用连接方法 + // 7. 返回连接的配置信息 + // = + try { + // 正常的 Wifi 连接配置 + WifiConfiguration connWifiConfig; + // 如果需要通过静态 IP 方式连接, 则进行设置 + if (isStatic && !TextUtils.isEmpty(ip)) { + // 创建 Wifi 静态 IP 连接配置 + WifiConfiguration staticWifiConfig = setStaticWifiConfig( + createWifiConfig(ssid, pwd, type, true), ip + ); + // 如果静态 IP 方式, 配置失败, 则初始化正常连接的 Wifi 配置 + if (staticWifiConfig == null) { + // 创建正常的配置信息 + connWifiConfig = createWifiConfig(ssid, pwd, type, true); + // = + LogPrintUtils.dTag(TAG, "属于正常方式连接 (DHCP)"); + } else { + // 设置静态信息 + connWifiConfig = staticWifiConfig; + // = + LogPrintUtils.dTag(TAG, "属于静态 IP 方式连接"); + } + } else { + // 创建正常的配置信息 + connWifiConfig = createWifiConfig(ssid, pwd, type, true); + // = + LogPrintUtils.dTag(TAG, "属于正常方式连接 (DHCP)"); + } + // 判断当前准备连接的 Wifi, 是否存在配置文件 + WifiConfiguration preWifiConfig = this.isExists(ssid); + // = + if (preWifiConfig != null) { + // 存在则删除 + boolean isRemove = mWifiManager.removeNetwork(preWifiConfig.networkId); + // 打印结果 + LogPrintUtils.dTag( + TAG, "删除旧的配置信息 %s, isRemove: %s", + preWifiConfig.SSID, isRemove + ); + // 保存配置 + mWifiManager.saveConfiguration(); + } + // = + // 连接网络 + int nId = mWifiManager.addNetwork(connWifiConfig); + if (nId != -1) { + try { + // 获取当前连接的 Wifi 对象 + WifiInfo wifiInfo = getWifiInfo(); + // 获取连接的 id + int networkId = wifiInfo.getNetworkId(); + // 禁用网络 + boolean isDisable = mWifiManager.disableNetwork(networkId); + // 断开之前的连接 + boolean isDisConnect = mWifiManager.disconnect(); + // 打印断开连接结果 + LogPrintUtils.dTag( + TAG, "isDisConnect: %s, isDisable: %s", + isDisConnect, isDisable + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "quickConnWifi 关闭连接出错: %s", nId); + } + // 开始连接 + boolean result = mWifiManager.enableNetwork(nId, true); + // = + if (!result) { + result = mWifiManager.enableNetwork(nId, true); + } + // 打印结果 + LogPrintUtils.dTag(TAG, "addNetwork(enableNetwork) result: %s", result); + } else { + // 尝试不带引号 SSID 连接 + connWifiConfig.SSID = formatSSID(connWifiConfig.SSID, false); + // 连接网络 + nId = mWifiManager.addNetwork(connWifiConfig); + if (nId != -1) { + try { + // 获取当前连接的 Wifi 对象 + WifiInfo wifiInfo = getWifiInfo(); + // 获取连接的 id + int networkId = wifiInfo.getNetworkId(); + // 禁用网络 + boolean isDisable = mWifiManager.disableNetwork(networkId); + // 断开之前的连接 + boolean isDisConnect = mWifiManager.disconnect(); + // 打印断开连接结果 + LogPrintUtils.dTag( + TAG, "isDisConnect: %s, isDisable: %s", + isDisConnect, isDisable + ); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "quickConnWifi 关闭连接出错: %s", nId); + } + // 开始连接 + boolean result = mWifiManager.enableNetwork(nId, true); + // = + if (!result) { + result = mWifiManager.enableNetwork(nId, true); + } + // 打印结果 + LogPrintUtils.dTag(TAG, "addNetwork(enableNetwork) result: %s", result); + } + } + // 保存 id + connWifiConfig.networkId = nId; + // 连接的 networkId + LogPrintUtils.dTag(TAG, "连接的 SSID networkId: %s", nId); + // 返回连接的信息 + return connWifiConfig; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "quickConnWifi"); + } + return null; + } + + /** + * 创建 Wifi 配置信息 + * @param ssid Wifi ssid + * @param pwd Wifi 密码 + * @param type Wifi 加密类型 + * @param appendQuotes 是否追加双引号 + * @return {@link WifiConfiguration} + */ + public static WifiConfiguration createWifiConfig( + final String ssid, + final String pwd, + final int type, + final boolean appendQuotes + ) { + try { + // 创建一个新的网络配置 + WifiConfiguration wifiConfig = new WifiConfiguration(); + wifiConfig.allowedAuthAlgorithms.clear(); + wifiConfig.allowedGroupCiphers.clear(); + wifiConfig.allowedKeyManagement.clear(); + wifiConfig.allowedPairwiseCiphers.clear(); + wifiConfig.allowedProtocols.clear(); + wifiConfig.priority = 0; + // 设置连接的 SSID + if (appendQuotes) { + wifiConfig.SSID = formatSSID(ssid, true); + } else { + wifiConfig.SSID = ssid; + } + switch (type) { + case WifiUtils.NOPWD: // 不存在密码 + wifiConfig.hiddenSSID = true; + wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); + break; + case WifiUtils.WEP: // WEP 加密方式 + wifiConfig.hiddenSSID = true; + if (appendQuotes) { + if (isHexWepKey(pwd)) { + wifiConfig.wepKeys[0] = pwd; + } else { + wifiConfig.wepKeys[0] = "\"" + pwd + "\""; + } + } else { + wifiConfig.wepKeys[0] = pwd; + } + wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); + wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); + // wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); + wifiConfig.wepTxKeyIndex = 0; + break; + case WifiUtils.WPA: // WPA 加密方式 + if (appendQuotes) { + wifiConfig.preSharedKey = "\"" + pwd + "\""; + } else { + wifiConfig.preSharedKey = pwd; + } + wifiConfig.hiddenSSID = true; + wifiConfig.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); + wifiConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK); +// wifiConfig.allowedProtocols.set(WifiConfiguration.Protocol.WPA); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); + wifiConfig.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); + wifiConfig.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); + wifiConfig.status = WifiConfiguration.Status.ENABLED; + break; + } + return wifiConfig; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createWifiConfig"); + } + return null; + } + + // ========== + // = 连接操作 = + // ========== + + /** + * 移除 Wifi 配置信息 + * @param wifiConfig Wifi 配置信息 + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean removeWifiConfig(final WifiConfiguration wifiConfig) { + // 如果等于 null 则直接返回 + if (wifiConfig == null) return false; + try { + // 删除配置 + boolean result = mWifiManager.removeNetwork(wifiConfig.networkId); + // 保存操作 + mWifiManager.saveConfiguration(); + // 返回删除结果 + return result; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeWifiConfig"); + } + return false; + } + + /** + * 断开指定 networkId 的网络 + * @param networkId network id + * @return {@code true} success, {@code false} fail + */ + @RequiresPermission(Manifest.permission.CHANGE_WIFI_STATE) + public boolean disconnectWifi(final int networkId) { + try { + mWifiManager.disableNetwork(networkId); + return mWifiManager.disconnect(); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "disconnectWifi"); + } + return false; + } + + // ======================= + // = 设置静态 IP、域名等信息 = + // ======================= + + /** + * 设置静态 Wifi 配置信息 + * @param wifiConfig Wifi 配置信息 + * @param ip 静态 IP + * @return {@link WifiConfiguration} + */ + private WifiConfiguration setStaticWifiConfig( + final WifiConfiguration wifiConfig, + final String ip + ) { + String gateway = null; + String dns; + if (ip != null) { + try { + InetAddress intetAddress = InetAddress.getByName(ip); + int intIp = inetAddressToInt(intetAddress); + dns = (intIp & 0xFF) + "." + ((intIp >> 8) & 0xFF) + "." + ((intIp >> 16) & 0xFF) + ".1"; + gateway = dns; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStaticWifiConfig"); + return null; + } + } + // 暂时不需要设置 DNS, 所以 DNS 参数传入 null + return setStaticWifiConfig(wifiConfig, ip, gateway, null, 24); + } + + /** + * 设置静态 Wifi 配置信息 + * @param wifiConfig Wifi 配置信息 + * @param ip 静态 IP + * @param gateway 网关 + * @param dns DNS + * @param networkPrefixLength 网络前缀长度 + * @return {@link WifiConfiguration} + */ + private WifiConfiguration setStaticWifiConfig( + final WifiConfiguration wifiConfig, + final String ip, + final String gateway, + final String dns, + final int networkPrefixLength + ) { + try { + if (ip == null || gateway == null) return null; + // 设置 InetAddress + InetAddress intetAddress = InetAddress.getByName(ip); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH) { // 旧的版本, 5.0 之前 + // 设置 IP 分配方式, 静态 IP + setEnumField(wifiConfig, "STATIC", "ipAssignment"); + // 设置不用代理 + setEnumField(wifiConfig, "NONE", "proxySettings"); + // 设置 IP 地址 + setIpAddress(intetAddress, networkPrefixLength, wifiConfig); + // 设置网关 + setGateway(InetAddress.getByName(gateway), wifiConfig); + if (dns != null) { // 判断是否需要设置域名 + // 设置 DNS + setDNS(InetAddress.getByName(dns), wifiConfig); + } + } else { // 5.0 新版本改变到其他地方 + Object object = getDeclaredField(wifiConfig, "mIpConfiguration"); + // 设置 IP 分配方式, 静态 IP + setEnumField(object, "STATIC", "ipAssignment"); + // 设置不用代理 + setEnumField(object, "NONE", "proxySettings"); + // 设置 IP 地址、网关、DNS + setStaticIpConfig(ip, gateway, dns, networkPrefixLength, object); + } + return wifiConfig; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "setStaticWifiConfig"); + } + return null; + } + + /** + * 转换 IP 地址 + * @param inetAddress {@link InetAddress} + * @return IPv4 地址 + * @throws Exception 不属于 IPv4 地址 + */ + private int inetAddressToInt(final InetAddress inetAddress) { + byte[] data = inetAddress.getAddress(); + if (data.length != 4) { + throw new IllegalArgumentException("Not an IPv4 address"); + } + return ((data[3] & 0xff) << 24) | ((data[2] & 0xff) << 16) + | ((data[1] & 0xff) << 8) | (data[0] & 0xff); + } + + /** + * 设置 DNS + * @param dns DNS + * @param wifiConfig Wifi 配置信息 + * @throws Exception 设置失败, 抛出异常 + */ + private void setDNS( + final InetAddress dns, + final WifiConfiguration wifiConfig + ) + throws Exception { + Object linkProperties = getField(wifiConfig, "linkProperties"); + if (linkProperties == null) { + throw new NullPointerException(); + } + + List mDnses = (ArrayList) getDeclaredField( + linkProperties, "mDnses" + ); + mDnses.clear(); // or add a new dns address, here I just want to replace DNS1 + mDnses.add(dns); + } + + /** + * 设置网关 + * @param gateway 网关 + * @param wifiConfig Wifi 配置信息 + * @throws Exception 设置失败, 抛出异常 + */ + private void setGateway( + final InetAddress gateway, + final WifiConfiguration wifiConfig + ) + throws Exception { + Object linkProperties = getField(wifiConfig, "linkProperties"); + if (linkProperties == null) { + throw new NullPointerException(); + } + + Class routeInfoClass = Class.forName("android.net.RouteInfo"); + Constructor routeInfoConstructor = routeInfoClass.getConstructor(InetAddress.class); + Object routeInfo = routeInfoConstructor.newInstance(gateway); + ArrayList mRoutes = (ArrayList) getDeclaredField(linkProperties, "mRoutes"); + mRoutes.clear(); + mRoutes.add(routeInfo); + } + + /** + * 设置 IP 地址 + * @param address IP 地址 + * @param prefixLength 网络前缀长度 + * @param wifiConfig Wifi 配置信息 + * @throws Exception 设置失败, 抛出异常 + */ + private void setIpAddress( + final InetAddress address, + final int prefixLength, + final WifiConfiguration wifiConfig + ) + throws Exception { + Object linkProperties = getField(wifiConfig, "linkProperties"); + if (linkProperties == null) { + throw new NullPointerException(); + } + + Class laClass = Class.forName("android.net.LinkAddress"); + Constructor laConstructor = laClass.getConstructor(InetAddress.class, int.class); + Object linkAddress = laConstructor.newInstance(address, prefixLength); + ArrayList mLinkAddresses = (ArrayList) getDeclaredField(linkProperties, "mLinkAddresses"); + mLinkAddresses.clear(); + mLinkAddresses.add(linkAddress); + } + + /** + * 设置 IP 地址、网关、DNS (5.0 之后 ) + * @param ip 静态 IP + * @param gateway 网关 + * @param dns DNS + * @param prefixLength 网络前缀长度 + * @param object Wifi 配置信息 + * @throws Exception 设置失败, 抛出异常 + */ + private void setStaticIpConfig( + final String ip, + final String gateway, + final String dns, + final int prefixLength, + final Object object + ) + throws Exception { + // 从 WifiConfig 成员变量 mIpConfiguration 获取 staticIpConfiguration + // 获取 staticIpConfiguration 变量 + Object staticIpConfigClass = getField(object, "staticIpConfiguration"); + if (staticIpConfigClass == null) { + // 创建静态 IP 配置类 + staticIpConfigClass = Class.forName("android.net.StaticIpConfiguration").newInstance(); + } + // 初始化 LinkAddress 并设置 IP 地址 + Class laClass = Class.forName("android.net.LinkAddress"); + Constructor laConstructor = laClass.getConstructor(InetAddress.class, int.class); + Object linkAddress = laConstructor.newInstance(InetAddress.getByName(ip), prefixLength); + // 设置地址 IP 地址 ipAddress + setValueField(staticIpConfigClass, linkAddress, "ipAddress"); + // 设置网关 gateway + setValueField(staticIpConfigClass, InetAddress.getByName(gateway), "gateway"); + if (dns != null) { // 判断是否需要设置域名 + // 设置 DNS + List mDnses = (ArrayList) getDeclaredField( + staticIpConfigClass, "dnsServers" + ); + mDnses.clear(); // or add a new dns address, here I just want to replace DNS1 + mDnses.add(InetAddress.getByName(dns)); + } + // 设置赋值 staticIpConfiguration 属性 + setValueField(object, staticIpConfigClass, "staticIpConfiguration"); + } + + /** + * 通过反射获取 + * @param object Object + * @param name 字段名 + * @return 对应的字段 + * @throws Exception 获取失败, 抛出异常 + */ + private Object getField( + final Object object, + final String name + ) + throws Exception { + Field field = object.getClass().getField(name); + return field.get(object); + } + + /** + * 通过反射获取 + * @param object Object + * @param name 字段名 + * @return 对应的字段 + * @throws Exception 获取失败, 抛出异常 + */ + private Object getDeclaredField( + final Object object, + final String name + ) + throws Exception { + Field field = object.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(object); + } + + /** + * 通过反射枚举类, 进行设置 + * @param object Object + * @param value 设置参数值 + * @param name 字段名 + * @throws Exception 设置失败, 抛出异常 + */ + private void setEnumField( + final Object object, + final String value, + final String name + ) + throws Exception { + Field field = object.getClass().getField(name); + field.set(object, Enum.valueOf((Class) field.getType(), value)); + } + + /** + * 通过反射, 进行设置 + * @param object Object + * @param val 设置参数值 + * @param name 字段名 + * @throws Exception 设置失败, 抛出异常 + */ + private void setValueField( + final Object object, + final Object val, + final String name + ) + throws Exception { + Field field = object.getClass().getField(name); + field.set(object, val); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiVo.java b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiVo.java new file mode 100644 index 0000000000..2dd8915f85 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiVo.java @@ -0,0 +1,157 @@ +package dev.utils.app.wifi; + +import android.net.wifi.ScanResult; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.Keep; + +import java.util.ArrayList; +import java.util.List; + +import dev.utils.LogPrintUtils; + +/** + * detail: Wifi 信息实体类 + * @author Ttt + */ +public class WifiVo + implements Parcelable { + + // 日志 TAG + private static final String TAG = WifiVo.class.getSimpleName(); + + @Keep // Wifi ssid + public String wifiSSID = null; + @Keep // Wifi 密码 + public String wifiPwd = null; + @Keep // Wifi 加密类型 + public int wifiType = WifiUtils.NOPWD; + @Keep // Wifi 信号等级 + public int wifiLevel = 0; + + public WifiVo() { + } + + /** + * 获取 Wifi 信息 + * @param scanResult 扫描的 Wifi 信息 + * @return {@link WifiVo} + */ + public static WifiVo createWifiVo(final ScanResult scanResult) { + return createWifiVo(scanResult, false); + } + + /** + * 获取 Wifi 信息 + * @param scanResult 扫描的 Wifi 信息 + * @param isAppend {@code true} 添加引号, {@code false} 删除引号 + * @return {@link WifiVo} + */ + public static WifiVo createWifiVo( + final ScanResult scanResult, + final boolean isAppend + ) { + if (scanResult != null) { + try { + // 防止 Wifi 名长度为 0 + if (scanResult.SSID.length() == 0) { + return null; + } + WifiVo wifiVo = new WifiVo(); + // Wifi ssid + wifiVo.wifiSSID = WifiUtils.formatSSID(scanResult.SSID, isAppend); + // Wifi 加密类型 + wifiVo.wifiType = WifiUtils.getWifiType(scanResult.capabilities); + // Wifi 信号等级 + wifiVo.wifiLevel = scanResult.level; + return wifiVo; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "createWifiVo"); + } + } + return null; + } + + /** + * 扫描 Wifi 信息 + * @param listScanResults 扫描返回的数据 + * @return {@link List} + */ + public static List scanWifiVos(final List listScanResults) { + List listWifiVos = new ArrayList<>(); + scanWifiVos(listWifiVos, listScanResults); + return listWifiVos; + } + + /** + * 扫描 Wifi 信息 + * @param listWifiVos 数据源 + * @param listScanResults 扫描返回的数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean scanWifiVos( + final List listWifiVos, + final List listScanResults + ) { + if (listWifiVos == null || listScanResults == null) return false; + // 清空旧数据 + listWifiVos.clear(); + // 遍历 Wifi 列表数据 + for (int i = 0, len = listScanResults.size(); i < len; i++) { + // 如果出现异常、或者失败, 则无视当前的索引 Wifi 信息 + try { + // 获取当前索引的 Wifi 信息 + ScanResult scanResult = listScanResults.get(i); + // 防止 Wifi 名长度为 0 + if (scanResult.SSID.length() == 0) { + continue; + } + // 保存 Wifi 信息 + listWifiVos.add(createWifiVo(scanResult)); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "scanWifiVos"); + } + } + return true; + } + + // ============== + // = Parcelable = + // ============== + + protected WifiVo(Parcel in) { + wifiSSID = in.readString(); + wifiPwd = in.readString(); + wifiType = in.readInt(); + wifiLevel = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public WifiVo createFromParcel(Parcel in) { + return new WifiVo(in); + } + + @Override + public WifiVo[] newArray(int size) { + return new WifiVo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel( + final Parcel dest, + final int flags + ) { + dest.writeString(wifiSSID); + dest.writeString(wifiPwd); + dest.writeInt(wifiType); + dest.writeInt(wifiLevel); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ArrayUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ArrayUtils.java new file mode 100644 index 0000000000..79d9e19ae5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ArrayUtils.java @@ -0,0 +1,4083 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Array 数组工具类 + * @author Ttt + *
+ *     // 升序
+ *     Arrays.sort(arrays);
+ *     // 降序 ( 只能对对象数组降序 )
+ *     Arrays.sort(arrays, Collections.reverseOrder());
+ * 
+ */ +public final class ArrayUtils { + + private ArrayUtils() { + } + + // 日志 TAG + private static final String TAG = ArrayUtils.class.getSimpleName(); + + // ========= + // = Array = + // ========= + + /** + * 判断数组是否为 null + * @param objects object[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object[] objects) { + return objects == null || objects.length == 0; + } + + /** + * 判断数组是否为 null + * @param ints int[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final int[] ints) { + return ints == null || ints.length == 0; + } + + /** + * 判断数组是否为 null + * @param bytes byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final byte[] bytes) { + return bytes == null || bytes.length == 0; + } + + /** + * 判断数组是否为 null + * @param chars char[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final char[] chars) { + return chars == null || chars.length == 0; + } + + /** + * 判断数组是否为 null + * @param shorts short[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final short[] shorts) { + return shorts == null || shorts.length == 0; + } + + /** + * 判断数组是否为 null + * @param longs long[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final long[] longs) { + return longs == null || longs.length == 0; + } + + /** + * 判断数组是否为 null + * @param floats float[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final float[] floats) { + return floats == null || floats.length == 0; + } + + /** + * 判断数组是否为 null + * @param doubles double[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final double[] doubles) { + return doubles == null || doubles.length == 0; + } + + /** + * 判断数组是否为 null + * @param booleans boolean[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final boolean[] booleans) { + return booleans == null || booleans.length == 0; + } + + /** + * 判断数组是否为 null + * @param object Array[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object object) { + return object == null || length(object) == 0; + } + + // = + + /** + * 判断数组是否不为 null + * @param objects object[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object[] objects) { + return objects != null && objects.length != 0; + } + + /** + * 判断数组是否不为 null + * @param ints int[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final int[] ints) { + return ints != null && ints.length != 0; + } + + /** + * 判断数组是否不为 null + * @param bytes byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final byte[] bytes) { + return bytes != null && bytes.length != 0; + } + + /** + * 判断数组是否不为 null + * @param chars char[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final char[] chars) { + return chars != null && chars.length != 0; + } + + /** + * 判断数组是否不为 null + * @param shorts short[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final short[] shorts) { + return shorts != null && shorts.length != 0; + } + + /** + * 判断数组是否不为 null + * @param longs long[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final long[] longs) { + return longs != null && longs.length != 0; + } + + /** + * 判断数组是否不为 null + * @param floats float[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final float[] floats) { + return floats != null && floats.length != 0; + } + + /** + * 判断数组是否不为 null + * @param doubles double[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final double[] doubles) { + return doubles != null && doubles.length != 0; + } + + /** + * 判断数组是否不为 null + * @param booleans boolean[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final boolean[] booleans) { + return booleans != null && booleans.length != 0; + } + + /** + * 判断数组是否不为 null + * @param object Array[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object object) { + return object != null && length(object) != 0; + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取数组长度 + * @param objects object[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final Object[] objects) { + return length(objects, 0); + } + + /** + * 获取数组长度 + * @param ints int[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final int[] ints) { + return length(ints, 0); + } + + /** + * 获取数组长度 + * @param bytes byte[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final byte[] bytes) { + return length(bytes, 0); + } + + /** + * 获取数组长度 + * @param chars char[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final char[] chars) { + return length(chars, 0); + } + + /** + * 获取数组长度 + * @param shorts short[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final short[] shorts) { + return length(shorts, 0); + } + + /** + * 获取数组长度 + * @param longs long[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final long[] longs) { + return length(longs, 0); + } + + /** + * 获取数组长度 + * @param floats float[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final float[] floats) { + return length(floats, 0); + } + + /** + * 获取数组长度 + * @param doubles double[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final double[] doubles) { + return length(doubles, 0); + } + + /** + * 获取数组长度 + * @param booleans boolean[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final boolean[] booleans) { + return length(booleans, 0); + } + + /** + * 获取数组长度 + * @param object Array[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final Object object) { + return length(object, 0); + } + + // = + + /** + * 获取数组长度 + * @param objects object[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final Object[] objects, + final int defaultLength + ) { + return objects != null ? objects.length : defaultLength; + } + + /** + * 获取数组长度 + * @param ints int[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final int[] ints, + final int defaultLength + ) { + return ints != null ? ints.length : defaultLength; + } + + /** + * 获取数组长度 + * @param bytes byte[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final byte[] bytes, + final int defaultLength + ) { + return bytes != null ? bytes.length : defaultLength; + } + + /** + * 获取数组长度 + * @param chars char[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final char[] chars, + final int defaultLength + ) { + return chars != null ? chars.length : defaultLength; + } + + /** + * 获取数组长度 + * @param shorts short[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final short[] shorts, + final int defaultLength + ) { + return shorts != null ? shorts.length : defaultLength; + } + + /** + * 获取数组长度 + * @param longs long[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final long[] longs, + final int defaultLength + ) { + return longs != null ? longs.length : defaultLength; + } + + /** + * 获取数组长度 + * @param floats float[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final float[] floats, + final int defaultLength + ) { + return floats != null ? floats.length : defaultLength; + } + + /** + * 获取数组长度 + * @param doubles double[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final double[] doubles, + final int defaultLength + ) { + return doubles != null ? doubles.length : defaultLength; + } + + /** + * 获取数组长度 + * @param booleans boolean[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final boolean[] booleans, + final int defaultLength + ) { + return booleans != null ? booleans.length : defaultLength; + } + + /** + * 获取数组长度 + * @param object Array[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final Object object, + final int defaultLength + ) { + if (object != null) { + Class clazz = object.getClass(); + // 判断是否数组类型 + if (clazz.isArray()) { + try { + // = 基本数据类型 = + if (clazz.isAssignableFrom(int[].class)) { + return ((int[]) object).length; + } else if (clazz.isAssignableFrom(boolean[].class)) { + return ((boolean[]) object).length; + } else if (clazz.isAssignableFrom(long[].class)) { + return ((long[]) object).length; + } else if (clazz.isAssignableFrom(double[].class)) { + return ((double[]) object).length; + } else if (clazz.isAssignableFrom(float[].class)) { + return ((float[]) object).length; + } else if (clazz.isAssignableFrom(byte[].class)) { + return ((byte[]) object).length; + } else if (clazz.isAssignableFrom(char[].class)) { + return ((char[]) object).length; + } else if (clazz.isAssignableFrom(short[].class)) { + return ((short[]) object).length; + } + return ((Object[]) object).length; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "length"); + } + } + } + return defaultLength; + } + + // = + + /** + * 判断数组长度是否等于期望长度 + * @param objects object[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Object[] objects, + final int length + ) { + return objects != null && objects.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param ints int[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final int[] ints, + final int length + ) { + return ints != null && ints.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param bytes byte[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final byte[] bytes, + final int length + ) { + return bytes != null && bytes.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param chars char[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final char[] chars, + final int length + ) { + return chars != null && chars.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param shorts short[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final short[] shorts, + final int length + ) { + return shorts != null && shorts.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param longs long[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final long[] longs, + final int length + ) { + return longs != null && longs.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param floats float[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final float[] floats, + final int length + ) { + return floats != null && floats.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param doubles double[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final double[] doubles, + final int length + ) { + return doubles != null && doubles.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param booleans boolean[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final boolean[] booleans, + final int length + ) { + return booleans != null && booleans.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param object Array[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Object object, + final int length + ) { + return object != null && length(object) == length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取数组长度总和 + * @param objects Array[] + * @return 数组长度总和 + */ + public static int getCount(final Object... objects) { + if (objects == null) return 0; + int count = 0; + for (Object object : objects) { + count += length(object); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T getByArray( + final Object array, + final int pos + ) { + return getByArray(array, pos, null); + } + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param defaultValue 默认值 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T getByArray( + final Object array, + final int pos, + final T defaultValue + ) { + if (array == null || pos < 0) return defaultValue; + try { + return (T) Array.get(array, pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getByArray"); + } + return defaultValue; + } + + // = + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T get( + final T[] array, + final int pos + ) { + return get(array, pos, null); + } + + /** + * 获取数组对应索引数据 + * @param ints int[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static int get( + final int[] ints, + final int pos + ) { + return get(ints, pos, DevFinal.DEFAULT.ERROR_INT); + } + + /** + * 获取数组对应索引数据 + * @param bytes byte[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static byte get( + final byte[] bytes, + final int pos + ) { + return get(bytes, pos, DevFinal.DEFAULT.ERROR_BYTE); + } + + /** + * 获取数组对应索引数据 + * @param chars char[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static char get( + final char[] chars, + final int pos + ) { + return get(chars, pos, DevFinal.DEFAULT.ERROR_CHAR); + } + + /** + * 获取数组对应索引数据 + * @param shorts short[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static short get( + final short[] shorts, + final int pos + ) { + return get(shorts, pos, DevFinal.DEFAULT.ERROR_SHORT); + } + + /** + * 获取数组对应索引数据 + * @param longs long[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static long get( + final long[] longs, + final int pos + ) { + return get(longs, pos, DevFinal.DEFAULT.ERROR_LONG); + } + + /** + * 获取数组对应索引数据 + * @param floats float[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static float get( + final float[] floats, + final int pos + ) { + return get(floats, pos, DevFinal.DEFAULT.ERROR_FLOAT); + } + + /** + * 获取数组对应索引数据 + * @param doubles double[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static double get( + final double[] doubles, + final int pos + ) { + return get(doubles, pos, DevFinal.DEFAULT.ERROR_DOUBLE); + } + + /** + * 获取数组对应索引数据 + * @param booleans boolean[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static boolean get( + final boolean[] booleans, + final int pos + ) { + return get(booleans, pos, DevFinal.DEFAULT.ERROR_BOOLEAN); + } + + // = + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param defaultValue 默认值 + * @param 泛型 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static T get( + final T[] array, + final int pos, + final T defaultValue + ) { + if (array == null || pos < 0) return defaultValue; + try { + return array[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param ints int[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static int get( + final int[] ints, + final int pos, + final int defaultValue + ) { + if (ints == null || pos < 0) return defaultValue; + try { + return ints[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param bytes byte[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static byte get( + final byte[] bytes, + final int pos, + final byte defaultValue + ) { + if (bytes == null || pos < 0) return defaultValue; + try { + return bytes[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param chars char[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static char get( + final char[] chars, + final int pos, + final char defaultValue + ) { + if (chars == null || pos < 0) return defaultValue; + try { + return chars[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param shorts short[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static short get( + final short[] shorts, + final int pos, + final short defaultValue + ) { + if (shorts == null || pos < 0) return defaultValue; + try { + return shorts[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param longs long[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static long get( + final long[] longs, + final int pos, + final long defaultValue + ) { + if (longs == null || pos < 0) return defaultValue; + try { + return longs[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param floats float[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static float get( + final float[] floats, + final int pos, + final float defaultValue + ) { + if (floats == null || pos < 0) return defaultValue; + try { + return floats[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param doubles double[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static double get( + final double[] doubles, + final int pos, + final double defaultValue + ) { + if (doubles == null || pos < 0) return defaultValue; + try { + return doubles[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param booleans boolean[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static boolean get( + final boolean[] booleans, + final int pos, + final boolean defaultValue + ) { + if (booleans == null || pos < 0) return defaultValue; + try { + return booleans[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + // = + + /** + * 获取数组第一条数据 + * @param array 数组 + * @param 泛型 + * @return 数组索引为 0 的值 + */ + public static T getFirst(final T[] array) { + return get(array, 0); + } + + /** + * 获取数组第一条数据 + * @param ints int[] + * @return 数组索引为 0 的值 + */ + public static int getFirst(final int[] ints) { + return get(ints, 0); + } + + /** + * 获取数组第一条数据 + * @param bytes byte[] + * @return 数组索引为 0 的值 + */ + public static byte getFirst(final byte[] bytes) { + return get(bytes, 0); + } + + /** + * 获取数组第一条数据 + * @param chars char[] + * @return 数组索引为 0 的值 + */ + public static char getFirst(final char[] chars) { + return get(chars, 0); + } + + /** + * 获取数组第一条数据 + * @param shorts short[] + * @return 数组索引为 0 的值 + */ + public static short getFirst(final short[] shorts) { + return get(shorts, 0); + } + + /** + * 获取数组第一条数据 + * @param longs long[] + * @return 数组索引为 0 的值 + */ + public static long getFirst(final long[] longs) { + return get(longs, 0); + } + + /** + * 获取数组第一条数据 + * @param floats float[] + * @return 数组索引为 0 的值 + */ + public static float getFirst(final float[] floats) { + return get(floats, 0); + } + + /** + * 获取数组第一条数据 + * @param doubles double[] + * @return 数组索引为 0 的值 + */ + public static double getFirst(final double[] doubles) { + return get(doubles, 0); + } + + /** + * 获取数组第一条数据 + * @param booleans boolean[] + * @return 数组索引为 0 的值 + */ + public static boolean getFirst(final boolean[] booleans) { + return get(booleans, 0); + } + + // = + + /** + * 获取数组最后一条数据 + * @param array 数组 + * @param 泛型 + * @return 数组索引 length - 1 的值 + */ + public static T getLast(final T[] array) { + return get(array, length(array) - 1); + } + + /** + * 获取数组最后一条数据 + * @param ints int[] + * @return 数组索引 length - 1 的值 + */ + public static int getLast(final int[] ints) { + return get(ints, length(ints) - 1); + } + + /** + * 获取数组最后一条数据 + * @param bytes byte[] + * @return 数组索引 length - 1 的值 + */ + public static byte getLast(final byte[] bytes) { + return get(bytes, length(bytes) - 1); + } + + /** + * 获取数组最后一条数据 + * @param chars char[] + * @return 数组索引 length - 1 的值 + */ + public static char getLast(final char[] chars) { + return get(chars, length(chars) - 1); + } + + /** + * 获取数组最后一条数据 + * @param shorts short[] + * @return 数组索引 length - 1 的值 + */ + public static short getLast(final short[] shorts) { + return get(shorts, length(shorts) - 1); + } + + /** + * 获取数组最后一条数据 + * @param longs long[] + * @return 数组索引 length - 1 的值 + */ + public static long getLast(final long[] longs) { + return get(longs, length(longs) - 1); + } + + /** + * 获取数组最后一条数据 + * @param floats float[] + * @return 数组索引 length - 1 的值 + */ + public static float getLast(final float[] floats) { + return get(floats, length(floats) - 1); + } + + /** + * 获取数组最后一条数据 + * @param doubles double[] + * @return 数组索引 length - 1 的值 + */ + public static double getLast(final double[] doubles) { + return get(doubles, length(doubles) - 1); + } + + /** + * 获取数组最后一条数据 + * @param booleans boolean[] + * @return 数组索引 length - 1 的值 + */ + public static boolean getLast(final boolean[] booleans) { + return get(booleans, length(booleans) - 1); + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (array != null) { + if (notNull && value == null) { + return null; + } + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + T t = array[i]; + // 判断是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (array != null) { + if (notNull && value == null) { + return -1; + } + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + T t = array[i]; + // 判断是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value + ) { + return get(array, value, 0, false, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number + ) { + return get(array, value, number, false, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final boolean notNull + ) { + return get(array, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number, + final boolean notNull + ) { + return get(array, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNotNull( + final T[] array, + final T value + ) { + return get(array, value, 0, true, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNotNull( + final T[] array, + final T value, + final int number + ) { + return get(array, value, number, true, 0); + } + + // = + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value + ) { + return getPosition(array, value, 0, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number + ) { + return getPosition(array, value, number, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final boolean notNull + ) { + return getPosition(array, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number, + final boolean notNull + ) { + return getPosition(array, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final T[] array, + final T value + ) { + return getPosition(array, value, 0, true, 0); + } + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final T[] array, + final T value, + final int number + ) { + return getPosition(array, value, number, true, 0); + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static int get( + final int[] array, + final int value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + int valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final int[] array, + final int value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + int valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static byte get( + final byte[] array, + final byte value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + byte valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (byte) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final byte[] array, + final byte value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + byte valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static char get( + final char[] array, + final char value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + char valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (char) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final char[] array, + final char value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + char valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static short get( + final short[] array, + final short value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + short valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (short) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final short[] array, + final short value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + short valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static long get( + final long[] array, + final long value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + long valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1L; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final long[] array, + final long value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + long valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static float get( + final float[] array, + final float value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + float valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (float) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final float[] array, + final float value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + float valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static double get( + final double[] array, + final double value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + double valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1D; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final double[] array, + final double value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + double valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static boolean get( + final boolean[] array, + final boolean value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + boolean valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return false; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final boolean[] array, + final boolean value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + boolean valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // ========== + // = 转换处理 = + // ========== + + /** + * int[] 转换 Integer[] + * @param ints int[] + * @return {@link Integer[]} + */ + public static Integer[] intsToIntegers(final int[] ints) { + if (ints != null) { + int len = ints.length; + // 创建数组 + Integer[] array = new Integer[len]; + for (int i = 0; i < len; i++) { + array[i] = ints[i]; + } + return array; + } + return null; + } + + /** + * byte[] 转换 Byte[] + * @param bytes byte[] + * @return {@link Byte[]} + */ + public static Byte[] bytesToBytes(final byte[] bytes) { + if (bytes != null) { + int len = bytes.length; + // 创建数组 + Byte[] array = new Byte[len]; + for (int i = 0; i < len; i++) { + array[i] = bytes[i]; + } + return array; + } + return null; + } + + /** + * char[] 转换 Character[] + * @param chars char[] + * @return {@link Character[]} + */ + public static Character[] charsToCharacters(final char[] chars) { + if (chars != null) { + int len = chars.length; + // 创建数组 + Character[] array = new Character[len]; + for (int i = 0; i < len; i++) { + array[i] = chars[i]; + } + return array; + } + return null; + } + + /** + * short[] 转换 Short[] + * @param shorts short[] + * @return {@link Short[]} + */ + public static Short[] shortsToShorts(final short[] shorts) { + if (shorts != null) { + int len = shorts.length; + // 创建数组 + Short[] array = new Short[len]; + for (int i = 0; i < len; i++) { + array[i] = shorts[i]; + } + return array; + } + return null; + } + + /** + * long[] 转换 Long[] + * @param longs long[] + * @return {@link Long[]} + */ + public static Long[] longsToLongs(final long[] longs) { + if (longs != null) { + int len = longs.length; + // 创建数组 + Long[] array = new Long[len]; + for (int i = 0; i < len; i++) { + array[i] = longs[i]; + } + return array; + } + return null; + } + + /** + * float[] 转换 Float[] + * @param floats float[] + * @return {@link Float[]} + */ + public static Float[] floatsToFloats(final float[] floats) { + if (floats != null) { + int len = floats.length; + // 创建数组 + Float[] array = new Float[len]; + for (int i = 0; i < len; i++) { + array[i] = floats[i]; + } + return array; + } + return null; + } + + /** + * double[] 转换 Double[] + * @param doubles double[] + * @return {@link Double[]} + */ + public static Double[] doublesToDoubles(final double[] doubles) { + if (doubles != null) { + int len = doubles.length; + // 创建数组 + Double[] array = new Double[len]; + for (int i = 0; i < len; i++) { + array[i] = doubles[i]; + } + return array; + } + return null; + } + + /** + * boolean[] 转换 Boolean[] + * @param booleans boolean[] + * @return {@link Boolean[]} + */ + public static Boolean[] booleansToBooleans(final boolean[] booleans) { + if (booleans != null) { + int len = booleans.length; + // 创建数组 + Boolean[] array = new Boolean[len]; + for (int i = 0; i < len; i++) { + array[i] = booleans[i]; + } + return array; + } + return null; + } + + // = + + /** + * Integer[] 转换 int[] + * @param integers Integer[] + * @param defaultValue 转换失败使用得默认值 + * @return int[] + */ + public static int[] integersToInts( + final Integer[] integers, + final int defaultValue + ) { + if (integers != null) { + int len = integers.length; + // 创建数组 + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = integers[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Byte[] 转换 byte[] + * @param bytes Byte[] + * @param defaultValue 转换失败使用得默认值 + * @return byte[] + */ + public static byte[] bytesToBytes( + final Byte[] bytes, + final byte defaultValue + ) { + if (bytes != null) { + int len = bytes.length; + // 创建数组 + byte[] array = new byte[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = bytes[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Character[] 转换 char[] + * @param characters Character[] + * @param defaultValue 转换失败使用得默认值 + * @return char[] + */ + public static char[] charactersToChars( + final Character[] characters, + final char defaultValue + ) { + if (characters != null) { + int len = characters.length; + // 创建数组 + char[] array = new char[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = characters[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Short[] 转换 short[] + * @param shorts Short[] + * @param defaultValue 转换失败使用得默认值 + * @return short[] + */ + public static short[] shortsToShorts( + final Short[] shorts, + final short defaultValue + ) { + if (shorts != null) { + int len = shorts.length; + // 创建数组 + short[] array = new short[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = shorts[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Long[] 转换 long[] + * @param longs Long[] + * @param defaultValue 转换失败使用得默认值 + * @return long[] + */ + public static long[] longsToLongs( + final Long[] longs, + final long defaultValue + ) { + if (longs != null) { + int len = longs.length; + // 创建数组 + long[] array = new long[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = longs[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Float[] 转换 float[] + * @param floats Float[] + * @param defaultValue 转换失败使用得默认值 + * @return float[] + */ + public static float[] floatsToFloats( + final Float[] floats, + final float defaultValue + ) { + if (floats != null) { + int len = floats.length; + // 创建数组 + float[] array = new float[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = floats[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Double[] 转换 double[] + * @param doubles Double[] + * @param defaultValue 转换失败使用得默认值 + * @return double[] + */ + public static double[] doublesToDoubles( + final Double[] doubles, + final double defaultValue + ) { + if (doubles != null) { + int len = doubles.length; + // 创建数组 + double[] array = new double[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = doubles[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Boolean[] 转换 boolean[] + * @param booleans Boolean[] + * @param defaultValue 转换失败使用得默认值 + * @return boolean[] + */ + public static boolean[] booleansToBooleans( + final Boolean[] booleans, + final boolean defaultValue + ) { + if (booleans != null) { + int len = booleans.length; + // 创建数组 + boolean[] array = new boolean[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = booleans[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + // ============ + // = 转换 List = + // ============ + + /** + * 转换数组为集合 + * @param array 数组 + * @param 泛型 + * @return {@link List} + */ + public static List asList(final T[] array) { + if (array != null) { + try { + return new ArrayList<>(Arrays.asList(array)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "asList"); + } + } + return null; + } + + /** + * 转换数组为集合 + * @param array 数组 + * @param 泛型 + * @return {@link List} + */ + public static List asListArgs(final T... array) { + if (array != null) { + try { + return new ArrayList<>(Arrays.asList(array)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "asListArgs"); + } + } + return null; + } + + // = + + /** + * 转换数组为集合 + * @param ints int[] + * @return {@link List} + */ + public static List asList(final int[] ints) { + if (ints != null) { + List lists = new ArrayList<>(); + for (int value : ints) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param bytes byte[] + * @return {@link List} + */ + public static List asList(final byte[] bytes) { + if (bytes != null) { + List lists = new ArrayList<>(); + for (byte value : bytes) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param chars char[] + * @return {@link List} + */ + public static List asList(final char[] chars) { + if (chars != null) { + List lists = new ArrayList<>(); + for (char value : chars) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param shorts short[] + * @return {@link List} + */ + public static List asList(final short[] shorts) { + if (shorts != null) { + List lists = new ArrayList<>(); + for (short value : shorts) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param longs long[] + * @return {@link List} + */ + public static List asList(final long[] longs) { + if (longs != null) { + List lists = new ArrayList<>(); + for (long value : longs) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param floats float[] + * @return {@link List} + */ + public static List asList(final float[] floats) { + if (floats != null) { + List lists = new ArrayList<>(); + for (float value : floats) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param doubles double[] + * @return {@link List} + */ + public static List asList(final double[] doubles) { + if (doubles != null) { + List lists = new ArrayList<>(); + for (double value : doubles) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param booleans boolean[] + * @return {@link List} + */ + public static List asList(final boolean[] booleans) { + if (booleans != null) { + List lists = new ArrayList<>(); + for (boolean value : booleans) { + lists.add(value); + } + return lists; + } + return null; + } + + // = + + /** + * 转换数组为集合 + * @param ints int[] + * @return {@link List} + */ + public static List asListArgsInt(final int... ints) { + if (ints != null) { + List lists = new ArrayList<>(); + for (int value : ints) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param bytes byte[] + * @return {@link List} + */ + public static List asListArgsByte(final byte... bytes) { + if (bytes != null) { + List lists = new ArrayList<>(); + for (byte value : bytes) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param chars char[] + * @return {@link List} + */ + public static List asListArgsChar(final char... chars) { + if (chars != null) { + List lists = new ArrayList<>(); + for (char value : chars) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param shorts short[] + * @return {@link List} + */ + public static List asListArgsShort(final short... shorts) { + if (shorts != null) { + List lists = new ArrayList<>(); + for (short value : shorts) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param longs long[] + * @return {@link List} + */ + public static List asListArgsLong(final long... longs) { + if (longs != null) { + List lists = new ArrayList<>(); + for (long value : longs) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param floats float[] + * @return {@link List} + */ + public static List asListArgsFloat(final float... floats) { + if (floats != null) { + List lists = new ArrayList<>(); + for (float value : floats) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param doubles double[] + * @return {@link List} + */ + public static List asListArgsDouble(final double... doubles) { + if (doubles != null) { + List lists = new ArrayList<>(); + for (double value : doubles) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param booleans boolean[] + * @return {@link List} + */ + public static List asListArgsBoolean(final boolean... booleans) { + if (booleans != null) { + List lists = new ArrayList<>(); + for (boolean value : booleans) { + lists.add(value); + } + return lists; + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // = + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @param 泛型 + * @return 拼接后的数组集合 + */ + public static T[] arrayCopy( + final T[] prefix, + final T[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建集合 + List lists = new ArrayList<>(prefixLength + suffixLength); + // 进行判断处理 + if (prefixLength != 0) { + lists.addAll(Arrays.asList(prefix).subList(0, prefixLength)); + } + if (suffixLength != 0) { + lists.addAll(Arrays.asList(suffix).subList(0, suffixLength)); + } + if (prefix != null) { + return (T[]) Arrays.copyOf(lists.toArray(), lists.size(), prefix.getClass()); + } else { + return (T[]) Arrays.copyOf(lists.toArray(), lists.size(), suffix.getClass()); + } + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static int[] arrayCopy( + final int[] prefix, + final int[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + int[] arrays = new int[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static byte[] arrayCopy( + final byte[] prefix, + final byte[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + byte[] arrays = new byte[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static char[] arrayCopy( + final char[] prefix, + final char[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + char[] arrays = new char[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static short[] arrayCopy( + final short[] prefix, + final short[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + short[] arrays = new short[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static long[] arrayCopy( + final long[] prefix, + final long[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + long[] arrays = new long[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static float[] arrayCopy( + final float[] prefix, + final float[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + float[] arrays = new float[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static double[] arrayCopy( + final double[] prefix, + final double[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + double[] arrays = new double[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static boolean[] arrayCopy( + final boolean[] prefix, + final boolean[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + boolean[] arrays = new boolean[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + // = + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @param 泛型 + * @return 指定长度数组 + */ + public static T[] newArray( + final T[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static int[] newArray( + final int[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static byte[] newArray( + final byte[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static char[] newArray( + final char[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static short[] newArray( + final short[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static long[] newArray( + final long[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static float[] newArray( + final float[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static double[] newArray( + final double[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static boolean[] newArray( + final boolean[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + // = + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @param 泛型 + * @return 裁剪后的数组 + */ + public static T[] subArray( + final T[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + List lists = new ArrayList<>(length); + lists.addAll(Arrays.asList(data).subList(off, off + length)); + return (T[]) Arrays.copyOf(lists.toArray(), length, data.getClass()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static int[] subArray( + final int[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + int[] arrays = new int[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static byte[] subArray( + final byte[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + byte[] arrays = new byte[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static char[] subArray( + final char[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + char[] arrays = new char[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static short[] subArray( + final short[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + short[] arrays = new short[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static long[] subArray( + final long[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + long[] arrays = new long[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static float[] subArray( + final float[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + float[] arrays = new float[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static double[] subArray( + final double[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + double[] arrays = new double[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static boolean[] subArray( + final boolean[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + boolean[] arrays = new boolean[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + // = + + /** + * 追加数组内容字符串 + * @param data 数组 + * @param 泛型 + * @return 追加数组内容字符串 + */ + public static String appendToString(final T[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final int[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final byte[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final char[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final short[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final long[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final float[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final double[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final boolean[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + // ============ + // = 最小值索引 = + // ============ + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final int[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + int temp = data[index]; + for (int i = 1; i < len; i++) { + int value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final long[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + long temp = data[index]; + for (int i = 1; i < len; i++) { + long value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final float[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + float temp = data[index]; + for (int i = 1; i < len; i++) { + float value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final double[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + double temp = data[index]; + for (int i = 1; i < len; i++) { + double value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + // ============ + // = 最大值索引 = + // ============ + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final int[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + int temp = data[index]; + for (int i = 1; i < len; i++) { + int value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final long[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + long temp = data[index]; + for (int i = 1; i < len; i++) { + long value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final float[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + float temp = data[index]; + for (int i = 1; i < len; i++) { + float value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final double[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + double temp = data[index]; + for (int i = 1; i < len; i++) { + double value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + // =========== + // = 获取最小值 = + // =========== + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static int getMinimum(final int[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static long getMinimum(final long[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0L; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static float getMinimum(final float[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0F; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static double getMinimum(final double[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0D; + } + + // =========== + // = 获取最大值 = + // =========== + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static int getMaximum(final int[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static long getMaximum(final long[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0L; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static float getMaximum(final float[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0F; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static double getMaximum(final double[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0D; + } + + // ============= + // = 计算数组总和 = + // ============= + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static int sumArray(final int[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int end, + final int extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int start, + final int end, + final int extra + ) { + int total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static long sumArray(final long[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int end, + final long extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int start, + final int end, + final long extra + ) { + long total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static float sumArray(final float[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int end, + final float extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int start, + final int end, + final float extra + ) { + float total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static double sumArray(final double[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int end, + final double extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int start, + final int end, + final double extra + ) { + double total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/BigDecimalUtils.java b/lib/DevApp/src/main/java/dev/utils/common/BigDecimalUtils.java new file mode 100644 index 0000000000..4f048f0cf8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/BigDecimalUtils.java @@ -0,0 +1,1775 @@ +package dev.utils.common; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 资金运算工具类 + * @author Ttt + *
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class BigDecimalUtils { + + private BigDecimalUtils() { + } + + // 日志 TAG + private static final String TAG = BigDecimalUtils.class.getSimpleName(); + + // 小数点位数 + private static int NEW_SCALE = 10; + // 舍入模式 ( 默认向下取 ) + private static int ROUNDING_MODE = BigDecimal.ROUND_DOWN; + + /** + * 设置全局小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + */ + public static void setScale( + final int scale, + final int roundingMode + ) { + NEW_SCALE = scale; + ROUNDING_MODE = roundingMode; + } + + /** + * 获取 BigDecimal + * @param value Value + * @return {@link BigDecimal} + */ + public static BigDecimal getBigDecimal(final Object value) { + if (value == null) return null; + try { + if (value instanceof Double) { + return BigDecimal.valueOf((Double) value); + } else if (value instanceof Long) { + return new BigDecimal((Long) value); + } else if (value instanceof Float) { + return BigDecimal.valueOf((Float) value); + } else if (value instanceof Integer) { + return new BigDecimal((Integer) value); + } else if (value instanceof BigInteger) { + return new BigDecimal((BigInteger) value); + } else if (value instanceof BigDecimal) { + return (BigDecimal) value; + } else { + String strValue = ConvertUtils.newStringNotArrayDecode(value); + if (strValue != null) { + return new BigDecimal(strValue); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBigDecimal"); + } + return null; + } + + /** + * 获取 Operation + * @param value Value + * @return {@link Operation} + */ + public static Operation operation(final Object value) { + return new Operation(value); + } + + /** + * 获取 Operation + * @param value Value + * @param config {@link Config} + * @return {@link Operation} + */ + public static Operation operation( + final Object value, + final Config config + ) { + return new Operation(value, config); + } + + // ======== + // = 包装类 = + // ======== + + /** + * detail: 计算异常 + * @author Ttt + */ + public static class CalculateException + extends RuntimeException { + public CalculateException() { + } + } + + /** + * detail: 配置信息 + * @author Ttt + */ + public static final class Config { + + // 小数点位数 + private final int mScale; + // 舍入模式 + private final int mRoundingMode; + + public Config() { + this(NEW_SCALE, ROUNDING_MODE); + } + + /** + * 初始化小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + */ + public Config( + final int scale, + final int roundingMode + ) { + this.mScale = scale; + this.mRoundingMode = roundingMode; + } + + /** + * 获取小数点保留位数 + * @return 小数点保留位数 + */ + public int getScale() { + return mScale; + } + + /** + * 获取舍入模式 + * @return 舍入模式 + */ + public int getRoundingMode() { + return mRoundingMode; + } + } + + /** + * detail: BigDecimal 操作包装类 + * @author Ttt + */ + public static final class Operation { + + // 计算数值 + private BigDecimal mValue; + // 配置信息 + private Config mConfig; + // 是否抛出异常 + private boolean mThrowError = false; + + public Operation(final Object value) { + this(value, null); + } + + public Operation( + final Object value, + final Config config + ) { + this.mValue = BigDecimalUtils.getBigDecimal(value); + this.mConfig = config; + } + + // = + + /** + * 检查 Value 是否为 null, 为 null 则抛出异常 + * @return {@link Operation} + * @throws NullPointerException null 异常 + */ + public Operation requireNonNull() { + if (mValue != null) return this; + throw new NullPointerException("mValue is null"); + } + + /** + * 内部抛出异常方法 + */ + private void throwException() { + if (mThrowError) throw new CalculateException(); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取 Value + * @return {@link BigDecimal} + */ + public BigDecimal getBigDecimal() { + return mValue; + } + + /** + * 设置 Value + * @param value {@link BigDecimal} + * @return {@link Operation} + */ + public Operation setBigDecimal(final Object value) { + this.mValue = BigDecimalUtils.getBigDecimal(value); + return this; + } + + /** + * 获取配置信息 + * @return {@link Config} + */ + public Config getConfig() { + return mConfig; + } + + /** + * 设置配置信息 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation setConfig(final Config config) { + return setConfig(config, true); + } + + /** + * 设置配置信息 + * @param config {@link Config} + * @param set 是否进行设置 + * @return {@link Operation} + */ + public Operation setConfig( + final Config config, + final boolean set + ) { + this.mConfig = config; + if (set) setScaleByConfig(); + return this; + } + + /** + * 移除配置信息 + * @return {@link Operation} + */ + public Operation removeConfig() { + return setConfig(null, false); + } + + // = + + /** + * 设置小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation setScale( + final int scale, + final int roundingMode + ) { + return setScale(new Config(scale, roundingMode)); + } + + /** + * 设置小数点保留位数、舍入模式 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation setScale(final Config config) { + if (mValue != null && config != null) { + try { + mValue = mValue.setScale( + config.getScale(), + config.getRoundingMode() + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "setScale"); + throwException(); + } + } else { + throwException(); + } + return this; + } + + /** + * 设置小数点保留位数、舍入模式 + * @return {@link Operation} + */ + public Operation setScaleByConfig() { + return setScale(mConfig); + } + + // = + + /** + * 是否抛出异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isThrowError() { + return mThrowError; + } + + /** + * 设置是否抛出异常 + * @param throwError 是否抛出异常 + * @return {@link Operation} + */ + public Operation setThrowError(final boolean throwError) { + this.mThrowError = throwError; + return this; + } + + // ========== + // = 获取方法 = + // ========== + + /** + * 克隆对象 + * @return {@link Operation} + */ + public Operation clone() { + return new Operation(mValue, mConfig); + } + + /** + * 获取此 BigDecimal 的字符串表示形式科学记数法 + * @return 此 BigDecimal 的字符串表示形式科学记数法 + */ + public String toString() { + return (mValue != null) ? mValue.toString() : null; + } + + /** + * 获取此 BigDecimal 的字符串表示形式不带指数字段 + * @return 此 BigDecimal 的字符串表示形式不带指数字段 + */ + public String toPlainString() { + return (mValue != null) ? mValue.toPlainString() : null; + } + + /** + * 获取此 BigDecimal 的字符串表示形式工程计数法 + * @return 此 BigDecimal 的字符串表示形式工程计数法 + */ + public String toEngineeringString() { + return (mValue != null) ? mValue.toEngineeringString() : null; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public int intValue() { + return (mValue != null) ? mValue.intValue() : 0; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public float floatValue() { + return (mValue != null) ? mValue.floatValue() : 0F; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public long longValue() { + return (mValue != null) ? mValue.longValue() : 0L; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public double doubleValue() { + return (mValue != null) ? mValue.doubleValue() : 0D; + } + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 + * @param value 加数 + * @return {@link Operation} + */ + public Operation add(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.add(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 + * @param value 减数 + * @return {@link Operation} + */ + public Operation subtract(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.subtract(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 + * @param value 乘数 + * @return {@link Operation} + */ + public Operation multiply(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.multiply(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 + * @param value 除数 + * @return {@link Operation} + */ + public Operation divide(final Object value) { + return divide(value, mConfig); + } + + /** + * 提供精确的除法运算 + * @param value 除数 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation divide( + final Object value, + final Config config + ) { + if (config != null) { + return divide(value, config.getScale(), config.getRoundingMode()); + } else { + return divide(value, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的除法运算 + * @param value 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation divide( + final Object value, + final int scale, + final int roundingMode + ) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + try { + mValue = mValue.divide(bigDecimal, scale, roundingMode); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "divide"); + throwException(); + } + } else { + throwException(); + } + return this; + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 + * @param value 除数 + * @return {@link Operation} + */ + public Operation remainder(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.remainder(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 + * @return {@link Operation} + */ + public Operation round() { + return round(mConfig); + } + + /** + * 提供精确的小数位四舍五入处理 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation round(final Config config) { + return divide(new BigDecimal("1"), config); + } + + /** + * 提供精确的小数位四舍五入处理 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation round( + final int scale, + final int roundingMode + ) { + return divide(new BigDecimal("1"), scale, roundingMode); + } + + // ========== + // = 比较大小 = + // ========== + + /** + * 比较大小 + * @param value 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2]、[-2 = error] + */ + public int compareTo(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + try { + return mValue.compareTo(bigDecimal); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "compareTo"); + throwException(); + } + } else { + throwException(); + } + return -2; + } + + // ========== + // = 金额分割 = + // ========== + + /** + * 金额分割, 四舍五入金额 + * @return 指定格式处理的字符串 + */ + public String formatMoney() { + return formatMoney(mConfig); + } + + /** + * 金额分割, 四舍五入金额 + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final int splitNumber, + final String splitSymbol + ) { + return formatMoney(mConfig, splitNumber, splitSymbol); + } + + // = + + /** + * 金额分割, 四舍五入金额 + * @param config {@link Config} + * @return 指定格式处理的字符串 + */ + public String formatMoney(final Config config) { + return formatMoney(config, 3, ","); + } + + /** + * 金额分割, 四舍五入金额 + * @param config {@link Config} + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final Config config, + final int splitNumber, + final String splitSymbol + ) { + if (config != null) { + return formatMoney(config.getScale(), config.getRoundingMode(), splitNumber, splitSymbol); + } else { + return formatMoney(NEW_SCALE, ROUNDING_MODE, splitNumber, splitSymbol); + } + } + + /** + * 金额分割, 四舍五入金额 + * @param scale 小数点后保留几位 + * @param mode 处理模式 + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final int scale, + final int mode, + final int splitNumber, + final String splitSymbol + ) { + if (mValue == null) return null; + try { + // 如果等于 0, 直接返回 + if (mValue.doubleValue() == 0) { + return mValue.setScale(scale, mode).toPlainString(); + } + // 获取原始值字符串 ( 非科学计数法 ) + String valuePlain = mValue.toPlainString(); + // 判断是否负数 + boolean isNegative = valuePlain.startsWith("-"); + // 处理后的数据 + BigDecimal bigDecimal = new BigDecimal(isNegative ? valuePlain.substring(1) : valuePlain); + // 范围处理 + valuePlain = bigDecimal.setScale(scale, mode).toPlainString(); + // 进行拆分小数点处理 + String[] values = valuePlain.split("\\."); + // 判断是否存在小数点 + boolean isDecimal = (values.length == 2); + + // 拼接符号 + String symbol = (splitSymbol != null) ? splitSymbol : ""; + // 防止出现负数 + int number = Math.max(splitNumber, 0); + // 格式化数据 ( 拼接处理 ) + StringBuilder builder = new StringBuilder(); + // 进行处理小数点前的数值 + for (int len = values[0].length() - 1, i = len, splitPos = 1; i >= 0; i--) { + char ch = values[0].charAt(i); + builder.append(ch); + // 判断是否需要追加符号 + if (number > 0 && splitPos % number == 0 && i != 0) { + builder.append(symbol); + } + splitPos++; + } + // 倒序处理 + builder.reverse(); + // 存在小数点, 则进行拼接 + if (isDecimal) { + builder.append(".").append(values[1]); + } + // 判断是否负数 + return isNegative ? "-" + builder.toString() : builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatMoney"); + } + return null; + } + } + + // ================== + // = 获取指定格式字符串 = + // ================== + + /** + * 获取自己想要的数据格式 + * @param value 需处理的数据 + * @param numOfIntPart 整数位数 + * @param numOfDecimalPart 小数位数 + * @return 处理过的数据 + */ + public static String adjustDouble( + final String value, + final int numOfIntPart, + final int numOfDecimalPart + ) { + if (value == null) return null; + try { + // 按小数点的位置分割成整数部分和小数部分 + String[] array = value.split("\\."); + char[] tempA = new char[numOfIntPart]; + char[] tempB = new char[numOfDecimalPart]; + // 整数部分满足精度要求 + if (array[0].length() == numOfIntPart) { + // 直接获取整数部分长度字符 + for (int i = 0; i < array[0].length(); i++) { + tempA[i] = array[0].charAt(i); + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + if (numOfDecimalPart == 0) { + return String.valueOf(tempA) + String.valueOf(tempB); + } + return String.valueOf(tempA) + "." + String.valueOf(tempB); + } + // 整数部分位数大于精度要求 + if (array[0].length() > numOfIntPart) { + // 先倒序获取指定位数的整数 + for (int i = array[0].length() - 1, j = 0; (i >= array[0].length() - numOfIntPart) && (j < numOfIntPart); i--, j++) { + tempA[j] = array[0].charAt(i); + } + char[] tempA1 = new char[numOfIntPart]; + // 调整顺序 + for (int j = 0, k = tempA.length - 1; j < numOfIntPart && (k >= 0); j++, k--) { + tempA1[j] = tempA[k]; + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + return String.valueOf(tempA1) + "." + String.valueOf(tempB); + } + // 先倒序获取指定位数的整数 + char[] tempA1 = new char[numOfIntPart]; + for (int i = array[0].length() - 1, j = 0; (i >= 0) && (j < numOfIntPart); i--, j++) { + tempA1[j] = array[0].charAt(i); + } + // 补 0 + for (int i = array[0].length(); i < array[0].length() + numOfIntPart - array[0].length(); i++) { + tempA1[i] = '0'; + } + + char[] tempA2 = new char[numOfIntPart]; + // 调整顺序 + for (int j = 0, k = tempA1.length - 1; j < numOfIntPart && (k >= 0); j++) { + tempA2[j] = tempA1[k]; + k--; + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + if (numOfDecimalPart == 0) { + return String.valueOf(tempA2) + String.valueOf(tempB); + } + return String.valueOf(tempA2) + "." + String.valueOf(tempB); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "adjustDouble"); + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + // ========== + // = 比较大小 = + // ========== + + /** + * 比较大小 + * @param v1 输入的数值 + * @param v2 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2]、[-2 = error] + */ + public static int compareTo( + final Object v1, + final Object v2 + ) { + try { + return compareToThrow(v1, v2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "compareTo"); + } + return -2; + } + + /** + * 比较大小 ( 抛出异常 ) + * @param v1 输入的数值 + * @param v2 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2] + */ + public static int compareToThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return operation(v1).setThrowError(true).compareTo(v2); + } + + // ========== + // = 捕获异常 = + // ========== + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2 + ) { + try { + return addThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final int scale + ) { + try { + return addThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param config {@link Config} + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final Config config + ) { + try { + return addThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return addThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2 + ) { + try { + return subtractThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final int scale + ) { + try { + return subtractThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param config {@link Config} + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final Config config + ) { + try { + return subtractThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return subtractThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2 + ) { + try { + return multiplyThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final int scale + ) { + try { + return multiplyThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param config {@link Config} + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final Config config + ) { + try { + return multiplyThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return multiplyThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2 + ) { + try { + return divideThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final int scale + ) { + try { + return divideThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final Config config + ) { + try { + return divideThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return divideThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2 + ) { + try { + return remainderThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final int scale + ) { + try { + return remainderThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final Config config + ) { + try { + return remainderThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return remainderThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @return 四舍五入后的结果 + */ + public static double round(final Object v1) { + try { + return roundThrow(v1); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final int scale + ) { + try { + return roundThrow(v1, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param config {@link Config} + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final Config config + ) { + try { + return roundThrow(v1, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final int scale, + final int roundingMode + ) { + try { + return roundThrow(v1, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ========== + // = 抛出异常 = + // ========== + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return addThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return addThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param config {@link Config} + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return addThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return addThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .add(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return subtractThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return subtractThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param config {@link Config} + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return subtractThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return subtractThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .subtract(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return multiplyThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return multiplyThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param config {@link Config} + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return multiplyThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return multiplyThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .multiply(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return divideThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return divideThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return divideThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return divideThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .divide(v2, scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return remainderThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return remainderThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return remainderThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return remainderThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .remainder(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @return 四舍五入后的结果 + */ + public static double roundThrow(final Object v1) + throws Exception { + return roundThrow(v1, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final int scale + ) + throws Exception { + return roundThrow(v1, scale, ROUNDING_MODE); + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param config {@link Config} + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final Config config + ) + throws Exception { + if (config != null) { + return roundThrow(v1, config.getScale(), config.getRoundingMode()); + } else { + return roundThrow(v1, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .round(scale, roundingMode) + .requireNonNull().doubleValue(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/CalendarUtils.java b/lib/DevApp/src/main/java/dev/utils/common/CalendarUtils.java new file mode 100644 index 0000000000..63a023c761 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/CalendarUtils.java @@ -0,0 +1,1057 @@ +package dev.utils.common; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 日历工具类 + * @author Ttt + *
+ *     公历、农历转换
+ *     @see 
+ *     关于日历实现代码里 0x04bd8, 0x04ae0, 0x0a570 的解释
+ *     @see 
+ *     Android 农历和节气相关工具类 ( 记录 )
+ *     @see 
+ * 
+ */ +public final class CalendarUtils { + + private CalendarUtils() { + } + + // 日志 TAG + private static final String TAG = CalendarUtils.class.getSimpleName(); + + // 支持转换的最小农历年份 + public static final int MIN_LUNAR_YEAR = 1900; + // 支持转换的最小公历年份 + public static final int MIN_SOLAR_YEAR = 1900; // 1901 + // 支持转换的最大公历、农历年份 + public static final int MAX_YEAR = 2099; + + /** + * 是否支持农历年份计算 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportLunar(final int year) { + return year <= MAX_YEAR && year >= MIN_LUNAR_YEAR; + } + + /** + * 是否支持公历年份计算 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportSolar(final int year) { + return year <= MAX_YEAR && year >= MIN_SOLAR_YEAR; + } + + // ========== + // = 农历转换 = + // ========== + + /** + * 公历转农历 + * @param year 公历年 + * @param month 公历月 + * @param day 公历日 + * @return [0] 农历年 [1] 农历月 [2] 农历日 [3] 是否闰月 0 false、1 true + */ + public static int[] solarToLunar( + final int year, + final int month, + final int day + ) { + // 不支持的公历年份, 则返回 null + if (!isSupportSolar(year)) return null; + + int[] lunarInt = new int[4]; + int index = year - SOLAR[0]; + int data = (year << 9) | (month << 5) | (day); + int solar11; + if (SOLAR[index] > data) { + index--; + } + solar11 = SOLAR[index]; + int y = getBitInt(solar11, 12, 9); + int m = getBitInt(solar11, 4, 5); + int d = getBitInt(solar11, 5, 0); + long offset = solarToInt(year, month, day) - solarToInt(y, m, d); + + int days = LUNAR_MONTH_DAYS[index]; + int leap = getBitInt(days, 4, 13); + + int lunarY = index + SOLAR[0]; + int lunarM = 1; + int lunarD; + offset += 1; + + for (int i = 0; i < 13; i++) { + int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; + if (offset > dm) { + lunarM++; + offset -= dm; + } else { + break; + } + } + lunarD = (int) (offset); + lunarInt[0] = lunarY; + lunarInt[1] = lunarM; + lunarInt[3] = 0; + + if (leap != 0 && lunarM > leap) { + lunarInt[1] = lunarM - 1; + if (lunarM == leap + 1) { + lunarInt[3] = 1; + } + } + lunarInt[2] = lunarD; + return lunarInt; + } + + /** + * 农历转公历 + * @param lunarYear 农历年 + * @param lunarMonth 农历月 + * @param lunarDay 农历日 + * @param isLeap 是否闰月 + * @return [0] 公历年 [1] 公历月 [2] 公历日 + */ + public static int[] lunarToSolar( + final int lunarYear, + final int lunarMonth, + final int lunarDay, + final boolean isLeap + ) { + // 不支持的农历年份, 则返回 null + if (!isSupportLunar(lunarYear)) return null; + + int days = LUNAR_MONTH_DAYS[lunarYear - LUNAR_MONTH_DAYS[0]]; + int leap = getBitInt(days, 4, 13); + int offset = 0; + int loop = leap; + if (!isLeap) { + if (lunarMonth <= leap || leap == 0) { + loop = lunarMonth - 1; + } else { + loop = lunarMonth; + } + } + for (int i = 0; i < loop; i++) { + offset += getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; + } + offset += lunarDay; + + int solar11 = SOLAR[lunarYear - SOLAR[0]]; + + int y = getBitInt(solar11, 12, 9); + int m = getBitInt(solar11, 4, 5); + int d = getBitInt(solar11, 5, 0); + + return solarFromInt(solarToInt(y, m, d) + offset - 1); + } + + // = + + /** + * 获取农历年份总天数 + * @param year 农历年 + * @return 农历年份总天数 + */ + public static int getLunarYearDays(final int year) { + if (!isSupportLunar(year)) return 0; + int i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) { + if ((LUNAR_INFO[year - 1900] & i) != 0) { + sum += 1; + } + } + return (sum + getLunarLeapDays(year)); + } + + /** + * 获取农历年份闰月天数 + * @param year 农历年 + * @return 农历年份闰月天数 + */ + public static int getLunarLeapDays(final int year) { + if (!isSupportLunar(year)) return 0; + if (getLunarLeapMonth(year) != 0) { + if ((LUNAR_INFO[year - 1899] & 0xf) != 0) { + return 30; + } else { + return 29; + } + } else { + return 0; + } + } + + /** + * 获取农历年份哪个月是闰月 + *
+     *     返回 1 - 12 无闰月返回 0
+     * 
+ * @param year 农历年 + * @return 农历年份哪个月是闰月 + */ + public static int getLunarLeapMonth(final int year) { + if (!isSupportLunar(year)) return 0; + long var = LUNAR_INFO[year - 1900] & 0xf; + return (int) (var == 0xf ? 0 : var); + } + + /** + * 获取农历年份与月份总天数 + * @param year 农历年 + * @param month 农历月 + * @return 农历年份与月份总天数 + */ + public static int getLunarMonthDays( + final int year, + final int month + ) { + if (!isSupportLunar(year)) return 0; + if ((LUNAR_INFO[year - 1900] & (0x10000 >> month)) == 0) { + return 29; + } else { + return 30; + } + } + + /** + * 获取干支历 + * @param year 年份 + * @return 干支历 + */ + public static String getLunarGanZhi(final int year) { + int y = Math.abs(year - 4); + return TIAN_GAN[y % 10] + DI_ZHI[y % 12]; + } + + /** + * 获取农历中文月份 + * @param month 农历月 + * @param isLeap 是否闰月 + * @return 农历中文月份 + */ + public static String getLunarMonthChinese( + final int month, + final boolean isLeap + ) { + if (month > 12 || month < 1) return null; + return isLeap ? "闰" + CHINESE_NUMBER[month - 1] + "月" : CHINESE_NUMBER[month - 1] + "月"; + } + + /** + * 获取农历中文天数 + * @param day 天数 + * @return 农历中文天数 + */ + public static String getLunarDayChinese(final int day) { + if (day > 30 || day < 1) return null; + if (day == 10) return "初十"; + if (day == 20) return "二十"; + int dayIndex = (day % 10 == 0) ? 9 : day % 10 - 1; + return CHINESE_TEN[day / 10] + CHINESE_DAY[dayIndex]; + } + + /** + * 获取二十四节气 ( 公历 ) 索引 + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) 索引 + */ + public static int getSolarTermsIndex( + final int month, + final int day + ) { + if (month > 12 || month < 1) return -1; + int start = (month - 2) >= 0 ? month - 2 : 11; + int leftIndex = start * 2; // 左边节气索引 + int[] dates = solarTermsDateSplit(leftIndex); + if (dates != null && day >= dates[0] && day <= dates[1]) return leftIndex; + int rightIndex = leftIndex + 1; // 右边节气索引 + dates = solarTermsDateSplit(rightIndex); + if (dates != null && day >= dates[0] && day <= dates[1]) return rightIndex; + return -1; + } + + /** + * 获取二十四节气 ( 公历 ) + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) + */ + public static String getSolarTerms( + final int month, + final int day + ) { + int index = getSolarTermsIndex(month, day); + return index != -1 ? SOLAR_TERMS[index] : null; + } + + /** + * 获取二十四节气 ( 公历 ) 时间 + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) 时间 + */ + public static String getSolarTermsDate( + final int month, + final int day + ) { + int index = getSolarTermsIndex(month, day); + return index != -1 ? SOLAR_TERMS_DATE[index] : null; + } + + /** + * 校验是否相同节日 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFestival( + final Festival festival, + final int year, + final int month, + final int day + ) { + return isFestival(festival, year, month, day, sFestivalHook); + } + + /** + * 校验是否相同节日 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @param festivalHook 节日 Hook 接口 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFestival( + final Festival festival, + final int year, + final int month, + final int day, + final FestivalHook festivalHook + ) { + if (festival == null) return false; + List list = new ArrayList<>(); + list.add(festival); + return getFestival(list, year, month, day, festivalHook) != null; + } + + /** + * 获取符合条件的节日信息 + * @param list 节日集合 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@link Festival} + */ + public static Festival getFestival( + final List list, + final int year, + final int month, + final int day + ) { + return getFestival(list, year, month, day, sFestivalHook); + } + + /** + * 获取符合条件的节日信息 + *
+     *     list 不能混合公历、农历节日防止判断出错
+     * 
+ * @param list 节日集合 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @param festivalHook 节日 Hook 接口 + * @return {@link Festival} + */ + public static Festival getFestival( + final List list, + final int year, + final int month, + final int day, + final FestivalHook festivalHook + ) { + if (list == null) return null; + for (Festival festival : list) { + if (festival != null) { + if (festivalHook != null) { + Festival hook = festivalHook.hook(festival, year, month, day); + if (hook != null) return hook; + } + if (festival.isFestival(month, day)) return festival; + } + } + return null; + } + + /** + * 获取公历符合条件的节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@link Festival} + */ + public static Festival getSolarFestival( + final int year, + final int month, + final int day + ) { + return getFestival(SOLAR_FESTIVAL_LIST, year, month, day); + } + + /** + * 获取农历符合条件的节日信息 + * @param year 农历年 + * @param month 农历月 + * @param day 农历日 + * @return {@link Festival} + */ + public static Festival getLunarFestival( + final int year, + final int month, + final int day + ) { + return getFestival(LUNAR_FESTIVAL_LIST, year, month, day); + } + + // ======= + // = 常量 = + // ======= + + // 天干 + private static final String[] TIAN_GAN = { + "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" + }; + // 地支 + private static final String[] DI_ZHI = { + "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" + }; + // 中文年份 + private static final String[] CHINESE_NUMBER = { + "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊" + }; + // 中文十位数 + private static final String[] CHINESE_TEN = { + "初", "十", "廿", "三" + }; + // 中文一到十 + private static final String[] CHINESE_DAY = { + "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" + }; + // 二十四节气 ( 公历 ) + private static final String[] SOLAR_TERMS = { + // 春季 2-4 + "立春", "雨水", + "惊蛰", "春分", + "清明", "谷雨", + // 夏季 5-7 + "立夏", "小满", + "芒种", "夏至", + "小暑", "大暑", + // 秋季 8-10 + "立秋", "处暑", + "白露", "秋分", + "寒露", "霜降", + // 冬季 11-1 + "立冬", "小雪", + "大雪", "冬至", + "小寒", "大寒" + }; + // 二十四节气 ( 公历 ) 时间 + private static final String[] SOLAR_TERMS_DATE = { + // 春季 2-4 + "2.3-5", "2.18-20", + "3.5-7", "3.20-22", + "4.4-6", "4.19-21", + // 夏季 5-7 + "5.5-7", "5.20-22", + "6.5-7", "6.21-22", + "7.6-8", "7.22-24", + // 秋季 8-10 + "8.7-9", "8.22-24", + "9.7-9", "9.22-24", + "10.8-9", "10.23-24", + // 冬季 11-1 + "11.7-8", "11.22-23", + "12.6-8", "12.21-23", + "1.5-7", "1.20-21" + }; + // 部分公历节日集合 + private static final List SOLAR_FESTIVAL_LIST = new ArrayList<>(); + // 部分农历节日集合 + private static final List LUNAR_FESTIVAL_LIST = new ArrayList<>(); + + private static final long[] LUNAR_INFO = { + 0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, 0xd950, 0x5554, 0x56af, 0x9ad0, 0x55d2, + 0x4ae0, 0xa5b6, 0xa4d0, 0xd250, 0xd255, 0xb54f, 0xd6a0, 0xada2, 0x95b0, 0x4977, + 0x497f, 0xa4b0, 0xb4b5, 0x6a50, 0x6d40, 0xab54, 0x2b6f, 0x9570, 0x52f2, 0x4970, + 0x6566, 0xd4a0, 0xea50, 0x6a95, 0x5adf, 0x2b60, 0x86e3, 0x92ef, 0xc8d7, 0xc95f, + 0xd4a0, 0xd8a6, 0xb55f, 0x56a0, 0xa5b4, 0x25df, 0x92d0, 0xd2b2, 0xa950, 0xb557, + 0x6ca0, 0xb550, 0x5355, 0x4daf, 0xa5b0, 0x4573, 0x52bf, 0xa9a8, 0xe950, 0x6aa0, + 0xaea6, 0xab50, 0x4b60, 0xaae4, 0xa570, 0x5260, 0xf263, 0xd950, 0x5b57, 0x56a0, + 0x96d0, 0x4dd5, 0x4ad0, 0xa4d0, 0xd4d4, 0xd250, 0xd558, 0xb540, 0xb6a0, 0x95a6, + 0x95bf, 0x49b0, 0xa974, 0xa4b0, 0xb27a, 0x6a50, 0x6d40, 0xaf46, 0xab60, 0x9570, + 0x4af5, 0x4970, 0x64b0, 0x74a3, 0xea50, 0x6b58, 0x5ac0, 0xab60, 0x96d5, 0x92e0, + 0xc960, 0xd954, 0xd4a0, 0xda50, 0x7552, 0x56a0, 0xabb7, 0x25d0, 0x92d0, 0xcab5, + 0xa950, 0xb4a0, 0xbaa4, 0xad50, 0x55d9, 0x4ba0, 0xa5b0, 0x5176, 0x52bf, 0xa930, + 0x7954, 0x6aa0, 0xad50, 0x5b52, 0x4b60, 0xa6e6, 0xa4e0, 0xd260, 0xea65, 0xd530, + 0x5aa0, 0x76a3, 0x96d0, 0x4afb, 0x4ad0, 0xa4d0, 0xd0b6, 0xd25f, 0xd520, 0xdd45, + 0xb5a0, 0x56d0, 0x55b2, 0x49b0, 0xa577, 0xa4b0, 0xaa50, 0xb255, 0x6d2f, 0xada0, + 0x4b63, 0x937f, 0x49f8, 0x4970, 0x64b0, 0x68a6, 0xea5f, 0x6b20, 0xa6c4, 0xaaef, + 0x92e0, 0xd2e3, 0xc960, 0xd557, 0xd4a0, 0xda50, 0x5d55, 0x56a0, 0xa6d0, 0x55d4, + 0x52d0, 0xa9b8, 0xa950, 0xb4a0, 0xb6a6, 0xad50, 0x55a0, 0xaba4, 0xa5b0, 0x52b0, + 0xb273, 0x6930, 0x7337, 0x6aa0, 0xad50, 0x4b55, 0x4b6f, 0xa570, 0x54e4, 0xd260, + 0xe968, 0xd520, 0xdaa0, 0x6aa6, 0x56df, 0x4ae0, 0xa9d4, 0xa4d0, 0xd150, 0xf252, + 0xd520 + }; + + private static final int[] LUNAR_MONTH_DAYS = { + 1887, 0x1694, 0x16aa, 0x4ad5, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, + 0xd54, 0x75aa, 0x156a, 0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4, 0x135a, 0x495d, 0x95c, + 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8, 0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a, 0xda8, + 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94, 0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, + 0x192a, 0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b, 0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, + 0x9b4, 0x14b6, 0x6a57, 0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae, 0x14ae, 0xa4c, 0x7d26, + 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d, 0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da, + 0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4, 0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, + 0x1d4a, 0x10d65, 0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a, 0x4b55, 0xad4, 0xf55b, + 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694, 0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526, + 0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, 0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, + 0x1695d, 0x95a, 0x149a, 0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936, 0xf497, 0x1496, + 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e, 0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c, + 0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4, 0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, + 0x1694, 0xd6aa, 0x15aa, 0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa, 0xa9b5, 0x96c, + 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54, 0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, + 0x1aa4, 0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a, 0x136a5, 0xda4, 0x15ac + }; + + private static final int[] SOLAR = { + 1887, 0xec04c, 0xec23f, 0xec435, 0xec649, 0xec83e, 0xeca51, 0xecc46, 0xece3a, + 0xed04d, 0xed242, 0xed436, 0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244, 0xee439, 0xee64d, + 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052, 0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41, + 0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d, 0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, + 0xf1846, 0xf1a3a, 0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848, 0xf2a3b, 0xf2c4f, 0xf2e45, + 0xf3039, 0xf324d, 0xf3442, 0xf3636, 0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443, 0xf4638, + 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f, 0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, + 0xf603e, 0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b, 0xf703f, 0xf7252, 0xf7447, 0xf763c, + 0xf7850, 0xf7a45, 0xf7c39, 0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46, 0xf8c3b, 0xf8e4f, + 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853, 0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641, + 0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e, 0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, + 0xfc048, 0xfc23c, 0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a, 0xfd23d, 0xfd451, 0xfd646, + 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37, 0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44, 0xfee38, + 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51, 0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, + 0x10064b, 0x100841, 0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438, 0x10164c, 0x101842, + 0x101a35, 0x101c49, 0x101e3d, 0x102051, 0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b, + 0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845, 0x103a38, 0x103c4c, 0x103e42, 0x104036, + 0x104249, 0x10443d, 0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038, 0x10524a, 0x10543e, + 0x105652, 0x105847, 0x105a3b, 0x105c4f, 0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849, + 0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444, 0x107638, 0x10784c, 0x107a3f, 0x107c53, + 0x107e48 + }; + + static { + // 部分公历节日集合 + SOLAR_FESTIVAL_LIST.clear(); + SOLAR_FESTIVAL_LIST.add(new Festival("元旦", 1, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国人民警察节", 1, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("情人节", 2, 14)); + SOLAR_FESTIVAL_LIST.add(new Festival("妇女节", 3, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("植树节", 3, 12)); + SOLAR_FESTIVAL_LIST.add(new Festival("消费者权益日", 3, 15)); + SOLAR_FESTIVAL_LIST.add(new Festival("愚人节", 4, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("清明节", 4, 4)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界卫生日", 4, 7)); + SOLAR_FESTIVAL_LIST.add(new Festival("地球日", 4, 22)); + SOLAR_FESTIVAL_LIST.add(new Festival("劳动节", 5, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("青年节", 5, 4)); + SOLAR_FESTIVAL_LIST.add(new Festival("护士节", 5, 12)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国科技工作者日", 5, 30)); + SOLAR_FESTIVAL_LIST.add(new Festival("儿童节", 6, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国爱眼日", 6, 6)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际禁毒日", 6, 26)); + SOLAR_FESTIVAL_LIST.add(new Festival("建党节", 7, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国航海日", 7, 11)); + SOLAR_FESTIVAL_LIST.add(new Festival("建军节", 8, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国医师节", 8, 19)); + SOLAR_FESTIVAL_LIST.add(new Festival("教师节", 9, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("九一八纪念日", 9, 18)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界阿尔茨海默病日", 9, 21)); + SOLAR_FESTIVAL_LIST.add(new Festival("烈士纪念日", 9, 30)); + SOLAR_FESTIVAL_LIST.add(new Festival("国庆节", 10, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国高血压日", 10, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("万圣节", 10, 31)); + SOLAR_FESTIVAL_LIST.add(new Festival("记者节", 11, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国消防日", 11, 9)); + SOLAR_FESTIVAL_LIST.add(new Festival("光棍节", 11, 11)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际大学生节", 11, 17)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界艾滋病日", 12, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际残疾人日", 12, 3)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界人权日", 12, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("平安夜", 12, 24)); + SOLAR_FESTIVAL_LIST.add(new Festival("圣诞节", 12, 25)); + + // 部分农历节日集合 + LUNAR_FESTIVAL_LIST.clear(); + LUNAR_FESTIVAL_LIST.add(new Festival("春节", 1, 1, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("元宵节", 1, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("龙抬头", 2, 2, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("上巳节", 3, 3, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("端午节", 5, 5, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("七夕节", 7, 7, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("中元节", 7, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("地藏节", 7, 30, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("灶君诞", 8, 2, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("中秋节", 8, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("先师诞", 8, 27, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("重阳节", 9, 9, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("寒衣节", 10, 1, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("下元节", 10, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("腊八节", 12, 8, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("小年", 12, 23, false)); + // LUNAR_FESTIVAL_LIST.add(new Festival("除夕", 12, 30, false)); // 除夕得判断是 29 还是 30 需要特殊判断 + } + + // ========== + // = 内部方法 = + // ========== + + private static int getBitInt( + int data, + int length, + int shift + ) { + return (data & (((1 << length) - 1) << shift)) >> shift; + } + + private static long solarToInt( + int y, + int m, + int d + ) { + m = (m + 9) % 12; + y = y - m / 10; + return 365L * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 + (d - 1); + } + + private static int[] solarFromInt(long g) { + long y = (10000 * g + 14780) / 3652425; + long ddd = g - (365 * y + y / 4 - y / 100 + y / 400); + if (ddd < 0) { + y--; + ddd = g - (365 * y + y / 4 - y / 100 + y / 400); + } + long mi = (100 * ddd + 52) / 3060; + long mm = (mi + 2) % 12 + 1; + y = y + (mi + 2) / 12; + long dd = ddd - (mi * 306 + 5) / 10 + 1; + int[] solar = new int[3]; + solar[0] = (int) y; + solar[1] = (int) mm; + solar[2] = (int) dd; + return solar; + } + + /** + * 拆分二十四节气 ( 公历 ) 时间 + * @param index 二十四节气索引 + * @return [0] 开始日 [1] 结束日 + */ + private static int[] solarTermsDateSplit(final int index) { + try { + String date = SOLAR_TERMS_DATE[index]; + String[] splits = date.substring(date.indexOf('.') + 1).split("-"); + return new int[]{ + ConvertUtils.toInt(splits[0]), + ConvertUtils.toInt(splits[1]) + }; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "solarTermsDateSplit"); + } + return null; + } + + // ======== + // = 实体类 = + // ======== + + /** + * detail: 公历农历实体类 + * @author Ttt + */ + public static class SolarLunar { + + // 公历年 + public final int year; + // 公历月 + public final int month; + // 公历日 + public final int day; + // 公历农历互转是否成功 + public final boolean result; + // 农历年 + public final int lunarYear; + // 农历月 + public final int lunarMonth; + // 农历日 + public final int lunarDay; + // 农历月是否闰月 + public final boolean isLunarLeap; + + /** + * 公历转农历 + * @param year 公历年 + * @param month 公历月 + * @param day 公历日 + */ + public SolarLunar( + int year, + int month, + int day + ) { + this.year = year; + this.month = month; + this.day = day; + // 公历转农历 + int[] lunars = CalendarUtils.solarToLunar(year, month, day); + result = lunars != null; + if (result) { + lunarYear = lunars[0]; + lunarMonth = lunars[1]; + lunarDay = lunars[2]; + isLunarLeap = lunars[3] == 1; + } else { + lunarYear = 0; + lunarMonth = 0; + lunarDay = 0; + isLunarLeap = false; + } + } + + /** + * 农历转公历 + * @param lunarYear 农历年 + * @param lunarMonth 农历月 + * @param lunarDay 农历日 + * @param isLunarLeap 是否闰月 + */ + public SolarLunar( + int lunarYear, + int lunarMonth, + int lunarDay, + boolean isLunarLeap + ) { + this.lunarYear = lunarYear; + this.lunarMonth = lunarMonth; + this.lunarDay = lunarDay; + this.isLunarLeap = isLunarLeap; + // 农历转公历 + int[] solars = CalendarUtils.lunarToSolar(lunarYear, lunarMonth, lunarDay, isLunarLeap); + result = solars != null; + if (result) { + year = solars[0]; + month = solars[1]; + day = solars[2]; + } else { + year = 0; + month = 0; + day = 0; + } + } + + /** + * 获取农历年份总天数 + * @return 农历年份总天数 + */ + public int getLunarYearDays() { + return CalendarUtils.getLunarYearDays(lunarYear); + } + + /** + * 获取农历年份闰月天数 + * @return 农历年份闰月天数 + */ + public int getLunarLeapDays() { + return CalendarUtils.getLunarLeapDays(lunarYear); + } + + /** + * 获取农历闰月 + * @return 农历闰月 + */ + public int getLunarLeapMonth() { + return isLunarLeap ? lunarMonth : 0; + } + + /** + * 获取农历年份与月份总天数 + * @return 农历年份与月份总天数 + */ + public int getLunarMonthDays() { + return CalendarUtils.getLunarMonthDays(lunarYear, lunarMonth); + } + + /** + * 获取干支历 + * @return 干支历 + */ + public String getLunarGanZhi() { + return CalendarUtils.getLunarGanZhi(lunarYear); + } + + /** + * 获取农历中文月份 + * @return 农历中文月份 + */ + public String getLunarMonthChinese() { + return CalendarUtils.getLunarMonthChinese(lunarMonth, isLunarLeap); + } + + /** + * 获取农历中文天数 + * @return 农历中文天数 + */ + public String getLunarDayChinese() { + return CalendarUtils.getLunarDayChinese(lunarDay); + } + + /** + * 获取二十四节气 ( 公历 ) 索引 + * @return 二十四节气 ( 公历 ) 索引 + */ + public int getSolarTermsIndex() { + return CalendarUtils.getSolarTermsIndex(month, day); + } + + /** + * 获取二十四节气 ( 公历 ) + * @return 二十四节气 ( 公历 ) + */ + public String getSolarTerms() { + return CalendarUtils.getSolarTerms(month, day); + } + + /** + * 获取二十四节气 ( 公历 ) 时间 + * @return 二十四节气 ( 公历 ) 时间 + */ + public String getSolarTermsDate() { + return CalendarUtils.getSolarTermsDate(month, day); + } + } + + // = + + /** + * detail: 节日实体类 + * @author Ttt + */ + public static class Festival + implements Comparable { + + // 节日名 + public final String name; + // 节日月份 + public final int month; + // 节日天数 + public final int day; + // 是否公历节日 + public final boolean isSolarFestival; + // 内部排序值 + private final int compareValue; + + public Festival( + String name, + int month, + int day + ) { + this(name, month, day, true); + } + + public Festival( + String name, + int month, + int day, + boolean isSolarFestival + ) { + this.name = name; + this.month = month; + this.day = day; + this.isSolarFestival = isSolarFestival; + this.compareValue = ConvertUtils.toInt(month + NumberUtils.addZero(day)); + } + + /** + * 校验是否相同节日 + * @param festival {@link Festival} + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival(final Festival festival) { + if (festival == null) return false; + return isFestival(festival.month, festival.day, festival.isSolarFestival); + } + + /** + * 校验是否相同节日 + * @param month 月份 + * @param day 天数 + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival( + final int month, + final int day + ) { + return this.month == month && this.day == day; + } + + /** + * 校验是否相同节日 + * @param month 月份 + * @param day 天数 + * @param solarFestival 是否公历节日 + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival( + final int month, + final int day, + final boolean solarFestival + ) { + return this.month == month && this.day == day && this.isSolarFestival == solarFestival; + } + + @Override + public int compareTo(Festival festival) { + return Integer.compare(festival.compareValue, compareValue); + } + + @Override + public String toString() { + return StringUtils.checkValue(name) + " " + NumberUtils.addZero(month) + + NumberUtils.addZero(day); + } + } + + // ======= + // = 接口 = + // ======= + + // 节日 Hook 接口 + private static FestivalHook sFestivalHook = new FestivalHook() { + @Override + public Festival hook( + Festival festival, + int year, + int month, + int day + ) { + if (festival != null) { + if (festival.isSolarFestival) { // 公历节日 + Calendar calendar; + int monthDay; // 该月存在天数 + int sundays = 0; // 星期天累加次数 + // 月份判断 + switch (month) { + case 5: + calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + monthDay = calendar.getActualMaximum(Calendar.DATE); + for (int i = 1; i <= monthDay; i++) { + calendar.set(Calendar.DATE, i); + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) { + sundays++; + if (sundays == 2) { + if (i == day) { // 母亲节每年 5 月的第二个星期日 + return new Festival("母亲节", month, day, true); + } + return null; + } + } + } + break; + case 6: + calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + monthDay = calendar.getActualMaximum(Calendar.DATE); + for (int i = 1; i <= monthDay; i++) { + calendar.set(Calendar.DATE, i); + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) { + sundays++; + if (sundays == 3) { + if (i == day) { // 父亲节最广泛的日期在每年 6 月的第三个星期日 + return new Festival("父亲节", month, day, true); + } + return null; + } + } + } + break; + } + } else { // 农历节日 + if (month == 12) { // 腊月 + if (CalendarUtils.getLunarMonthDays(year, month) == day && day != 0) { + return new Festival("除夕", month, day, false); + } + } + } + } + return null; + } + }; + + /** + * detail: 节日 Hook 接口 + * @author Ttt + *
+     *     主要用于特殊节日判断, 如 除夕 非确定天数无法直接进行添加
+     * 
+ */ + public interface FestivalHook { + + /** + * 节日 hook 判断 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return 如果特殊判断成功则返回节日 + */ + Festival hook( + Festival festival, + int year, + int month, + int day + ); + } + + /** + * 获取节日 Hook 接口 + * @return {@link FestivalHook} + */ + public static FestivalHook getFestivalHook() { + return sFestivalHook; + } + + /** + * 设置节日 Hook 接口 + * @param festivalHook {@link FestivalHook} + */ + public static void setFestivalHook(final FestivalHook festivalHook) { + CalendarUtils.sFestivalHook = festivalHook; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ChineseUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ChineseUtils.java new file mode 100644 index 0000000000..dd3d97b90c --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ChineseUtils.java @@ -0,0 +1,311 @@ +package dev.utils.common; + +import java.math.BigDecimal; +import java.util.Random; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 中文工具类 + * @author Ttt + */ +public final class ChineseUtils { + + private ChineseUtils() { + } + + // 日志 TAG + private static final String TAG = ChineseUtils.class.getSimpleName(); + + /** + * 随机生成汉字 + * @param number 字数 + * @return 随机汉字 + */ + public static String randomWord(final int number) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < number; i++) { + builder.append(randomWord()); + } + return builder.toString(); + } + + /** + * 随机生成汉字 + * @return 一个随机汉字 + */ + public static String randomWord() { + Random random = new Random(); + int heightPos = 176 + Math.abs(random.nextInt(39)); + int lowPos = 161 + Math.abs(random.nextInt(93)); + byte[] bytes = new byte[2]; + bytes[0] = Integer.valueOf(heightPos).byteValue(); + bytes[1] = Integer.valueOf(lowPos).byteValue(); + try { + return new String(bytes, DevFinal.ENCODE.GBK); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "randomWord"); + } + return ""; + } + + /** + * 随机生成名字 + * @param surnames 姓氏数组 + * @param names 名字数组 + * @return 随机名字 + */ + public static String randomName( + final String[] surnames, + final String[] names + ) { + return randomName(surnames, names, 1); + } + + /** + * 随机生成名字 + * @param surnames 姓氏数组 + * @param names 名字数组 + * @param nameLength 名字长度 + * @return 随机名字 + */ + public static String randomName( + final String[] surnames, + final String[] names, + final int nameLength + ) { + if (surnames != null && surnames.length != 0 && names != null && names.length != 0) { + return RandomUtils.getRandom(surnames, 1) + + RandomUtils.getRandom(names, nameLength); + } + return null; + } + + // =============== + // = 数字、中文互转 = + // =============== + + // 零索引 + private static final int ZERO = 0; + // 十位数索引 + private static final int TEN_POS = 10; + // 中文 ( 数字单位 ) + private static final String[] CHN_NUMBER_UNITS = { + "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "百", "千", "万", "亿", "兆" + }; + // 中文大写 ( 数字单位 ) + private static final String[] CHN_NUMBER_UPPER_UNITS = { + "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾", "佰", "仟", "万", "亿", "兆" + }; + // 数字单位对应数值 + private static final double[] NUMBER_UNITS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000, 10000, 100000000, 1000000000000D + }; + + // ============= + // = 对外公开方法 = + // ============= + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final double number, + final boolean isUpper + ) { + try { + return numberToCHNNumber( + BigDecimal.valueOf(number), + isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "numberToCHN"); + } + return null; + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final String number, + final boolean isUpper + ) { + if (number != null) { + try { + return numberToCHNNumber( + new BigDecimal(number), + isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "numberToCHN"); + } + } + return null; + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final BigDecimal number, + final boolean isUpper + ) { + return numberToCHNNumber(number, isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS); + } + + // ========== + // = 内部方法 = + // ========== + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param bigDecimal 数值 + * @param chnUnits 中文数字单位数组 + * @return 数字中文化字符串 + */ + private static String numberToCHNNumber( + final BigDecimal bigDecimal, + final String[] chnUnits + ) { + // 防止为 null + if (bigDecimal == null) return null; + // 去除小数点 + BigDecimal number = bigDecimal.setScale(0, BigDecimal.ROUND_DOWN); + // 防止小于 1, 直接返回 0 + if (number.doubleValue() < 1) return chnUnits[ZERO]; + + StringBuilder builder = new StringBuilder(); + // 索引记录 + int unitIndex = 0; // 当前数字单位 + int beforeUnitIndex = 0; // 之前的数字单位 + // 循环处理 + for (int i = NUMBER_UNITS.length - 1; i > 0; i--) { + if (number.doubleValue() >= NUMBER_UNITS[i]) { + final int multiple = (int) (number.doubleValue() / NUMBER_UNITS[i]); // 倍数 + number = number.subtract(BigDecimal.valueOf(multiple * NUMBER_UNITS[i])); // 递减 + // 如果倍数大于万倍, 则直接递归 + if (multiple >= 10000) { + // 拼接数值 + builder.append(numberToCHNNumber(new BigDecimal(multiple), chnUnits)); + builder.append(chnUnits[i]); // 数字单位 + // 判断是否需要补零 + if (unitIndex > i) { + builder.append(chnUnits[ZERO]); // 补零 + } + } else { + // 判断 兆级与亿级、亿级与万级 补零操作 + if ((i == 14 && unitIndex == 15) || (i == 13 && unitIndex == 14)) { + if (multiple < 1000) { + builder.append(chnUnits[ZERO]); // 补零 + } + } else if (unitIndex > i) { // 跨数字单位处理 + builder.append(chnUnits[ZERO]); // 补零 + } + // 拼接数值 + builder.append(thousandConvertCHN(multiple, chnUnits)); + builder.append(chnUnits[i]); // 数字单位 + } + // 保存旧的数字单位索引 + beforeUnitIndex = unitIndex; + // 保存新的数字单位索引 + unitIndex = i; + } + + double numberValue = number.doubleValue(); + // 如果位数小于万位 ( 属于千位 ), 则进行处理 + if (numberValue < 10000) { + // 判断是否需要补零 + if (unitIndex >= (TEN_POS + 3)) { + // 是否大于 1 ( 结尾零, 则不补充数字单位 ) + if (numberValue >= 1) { + if (beforeUnitIndex == 0) { + beforeUnitIndex = unitIndex; + } + // 如果旧的索引, 大于当前索引, 则补零 + if (unitIndex != 13 && (beforeUnitIndex == 14 || beforeUnitIndex == 15) + && beforeUnitIndex >= unitIndex) { // 属于亿、兆级别, 都需要补零 + builder.append(chnUnits[ZERO]); // 补零 + } else { // 当前数字单位属于万级 + if (numberValue < 1000) { + builder.append(chnUnits[ZERO]); // 补零 + } + } + } + } + // 拼接数值 + builder.append(thousandConvertCHN((int) numberValue, chnUnits)); + return builder.toString(); + } + } + return builder.toString(); + } + + /** + * 万位以下 ( 千位 ) 级别转换 + * @param value 数值 + * @param chnUnits 中文数字单位数组 + * @return 数字中文化字符串 + */ + private static String thousandConvertCHN( + final int value, + final String[] chnUnits + ) { + StringBuilder builder = new StringBuilder(); + // 转换数字 + int number = value; + // 对应数值 + int[] units = {10, 100, 1000}; + // 判断是否需要补零 + boolean[] zeros = new boolean[3]; + zeros[0] = number >= 10; // 大于等于十位, 才需要补零 + zeros[1] = number >= 100; // 大于等于百位, 才需要补零 + zeros[2] = false; // 千位数, 不需要补零 + // 循环处理 + for (int i = 2; i >= 0; i--) { + if (number >= units[i]) { + int multiple = (number / units[i]); + number -= multiple * units[i]; + // 拼接数值 + builder.append(chnUnits[multiple]); // 个位数 + builder.append(chnUnits[TEN_POS + i]); // 数字单位 + // 进行改变处理 + zeros[i] = false; + } + // 补零判断处理 + if (number >= 1 && zeros[i]) { + if (i == 1) { // 属于百位 + builder.append(chnUnits[ZERO]); // 补零 + } else { // 属于十位 + // 如果百位, 补零了, 个位数则不用补零 + if (!zeros[i + 1]) { + builder.append(chnUnits[ZERO]); // 补零 + } + } + } + } + // 判断最后值, 是否大于 1 ( 结尾零, 则不补充数字单位 ) + if (number >= 1) { + builder.append(chnUnits[number]); + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ClassUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ClassUtils.java new file mode 100644 index 0000000000..ef052d57de --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ClassUtils.java @@ -0,0 +1,331 @@ +package dev.utils.common; + +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collection; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 类 ( Class ) 工具类 + * @author Ttt + */ +public final class ClassUtils { + + private ClassUtils() { + } + + // 日志 TAG + private static final String TAG = ClassUtils.class.getSimpleName(); + + /** + * 根据类获取对象, 不再必须一个无参构造 + * @param clazz {@link Class} + * @param 泛型 + * @return 初始化后的对象 + */ + public static T newInstance(final Class clazz) { + if (clazz == null) return null; + try { + Constructor[] cons = clazz.getDeclaredConstructors(); + for (Constructor c : cons) { + Class[] cls = c.getParameterTypes(); + if (cls.length == 0) { + c.setAccessible(true); + return (T) c.newInstance(); + } else { + Object[] objects = new Object[cls.length]; + for (int i = 0; i < cls.length; i++) { + objects[i] = getDefaultPrimitiveValue(cls[i]); + } + c.setAccessible(true); + return (T) c.newInstance(objects); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + } + return null; + } + + /** + * 获取 Class 原始类型值 + * @param clazz {@link Class} + * @return 原始类型值 + */ + public static Object getDefaultPrimitiveValue(final Class clazz) { + if (clazz != null && clazz.isPrimitive()) { + return clazz == boolean.class ? false : 0; + } + return null; + } + + // = + + /** + * 获取 Object Class + * @param object {@link Object} + * @return Object Class + */ + public static Class getClass(final Object object) { + return (object != null) ? object.getClass() : null; + } + + /** + * 获取 Type Class + * @param type {@link Type} + * @return Type Class + */ + public static Class getClass(final Type type) { + if (type == null) return null; + try { + if (type.getClass() == Class.class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + return getClass(((ParameterizedType) type).getRawType()); + } + if (type instanceof TypeVariable) { + Type boundType = ((TypeVariable) type).getBounds()[0]; + return (Class) boundType; + } + if (type instanceof WildcardType) { + Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + if (upperBounds.length == 1) { + return getClass(upperBounds[0]); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getClass"); + } + return Object.class; + } + + /** + * 判断 Class 是否为原始类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrimitive(final Class clazz) { + return (clazz != null && clazz.isPrimitive()); + } + + /** + * 判断是否 Collection 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isCollection(final Class clazz) { + return (clazz != null && Collection.class.isAssignableFrom(clazz)); + } + + /** + * 判断是否 Map 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isMap(final Class clazz) { + return (clazz != null && Map.class.isAssignableFrom(clazz)); + } + + /** + * 判断是否 Array 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isArray(final Class clazz) { + return (clazz != null && clazz.isArray()); + } + + /** + * 判断是否参数类型 + * @param type {@link Type} + * @return {@code true} yes, {@code false} no + */ + public static boolean isGenericParamType(final Type type) { + if (type != null) { + if (type instanceof ParameterizedType) { + return true; + } + if (type instanceof Class) { + try { + Type superType = ((Class) type).getGenericSuperclass(); + return superType != Object.class && isGenericParamType(superType); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isGenericParamType"); + } + } + } + return false; + } + + /** + * 获取参数类型 + * @param type {@link Type} + * @return {@link Type} + */ + public static Type getGenericParamType(final Type type) { + if (type != null) { + if (type instanceof ParameterizedType) { + return type; + } + if (type instanceof Class) { + try { + return getGenericParamType(((Class) type).getGenericSuperclass()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericParamType"); + } + } + } + return type; + } + + // = + + /** + * 获取父类泛型类型 + * @param object Object + * @return 泛型类型 + */ + public static Type getGenericSuperclass(final Object object) { + return getGenericSuperclass(object, 0); + } + + /** + * 获取父类泛型类型 + * @param object Object + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericSuperclass( + final Object object, + final int pos + ) { + return getGenericSuperclass(getClass(object), pos); + } + + // = + + /** + * 获取父类泛型类型 + * @param clazz {@link Class} + * @return 泛型类型 + */ + public static Type getGenericSuperclass(final Class clazz) { + return getGenericSuperclass(clazz, 0); + } + + /** + * 获取父类泛型类型 + * @param clazz {@link Class} + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericSuperclass( + final Class clazz, + final int pos + ) { + if (clazz != null && pos >= 0) { + try { + return ((ParameterizedType) clazz.getGenericSuperclass()) + .getActualTypeArguments()[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericSuperclass"); + } + } + return null; + } + + // = + + /** + * 获取接口泛型类型 + * @param object Object + * @param interfaceClazz 接口 Class + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Object object, + final Class interfaceClazz + ) { + return getGenericInterfaces(object, interfaceClazz, 0); + } + + /** + * 获取接口泛型类型 + * @param object Object + * @param interfaceClazz 接口 Class + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Object object, + final Class interfaceClazz, + final int pos + ) { + return getGenericInterfaces(getClass(object), interfaceClazz, pos); + } + + // = + + /** + * 获取接口泛型类型 + * @param clazz {@link Class} + * @param interfaceClazz 接口 Class + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Class clazz, + final Class interfaceClazz + ) { + return getGenericInterfaces(clazz, interfaceClazz, 0); + } + + /** + * 获取接口泛型类型 + * @param clazz {@link Class} + * @param interfaceClazz 接口 Class + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Class clazz, + final Class interfaceClazz, + final int pos + ) { + if (clazz != null && interfaceClazz != null && pos >= 0) { + try { + // 获取接口类名 + String iName = interfaceClazz.getName(); + if ("".equals(iName)) return null; + // 获取接口泛型类型数组 + Type[] types = clazz.getGenericInterfaces(); + // 循环类型 + for (Type type : types) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + // 判断是否属于该接口 + String rawType = parameterizedType.getRawType().toString(); + // 判断接口包名是否一致 + if (rawType.startsWith("interface ")) { + if (rawType.equals("interface " + iName)) { + return parameterizedType.getActualTypeArguments()[pos]; + } + } else { + if (rawType.equals(iName)) { + return parameterizedType.getActualTypeArguments()[pos]; + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericInterfaces"); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/CloneUtils.java b/lib/DevApp/src/main/java/dev/utils/common/CloneUtils.java new file mode 100644 index 0000000000..1f3a3cd549 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/CloneUtils.java @@ -0,0 +1,124 @@ +package dev.utils.common; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 克隆工具类 + * @author Ttt + */ +public final class CloneUtils { + + private CloneUtils() { + } + + // 日志 TAG + private static final String TAG = CloneUtils.class.getSimpleName(); + + /** + * 进行克隆 + * @param data Object implements {@link Serializable} + * @param 泛型 + * @return 克隆后的对象 + */ + public static T deepClone(final Serializable data) { + if (data == null) return null; + return (T) ConvertUtils.bytesToObject(serializableToBytes(data)); + } + + /** + * 通过序列化实体类, 获取对应的 byte[] 数据 + * @param serializable Object implements {@link Serializable} + * @return 克隆后 byte[] + */ + public static byte[] serializableToBytes(final Serializable serializable) { + if (serializable == null) return null; + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(serializable); + return baos.toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "serializableToBytes"); + return null; + } finally { + CloseUtils.closeIOQuietly(oos); + } + } + + // = + + /** + * 进行克隆 + * @param map 存储集合 + * @param values 需要克隆的数据源 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean deepClone( + final Map map, + final Map values + ) { + if (map != null && values != null && values.size() > 0) { + Iterator> iterator = values.entrySet().iterator(); + while (iterator.hasNext()) { + try { + Map.Entry entry = iterator.next(); + // 获取 key + K key = entry.getKey(); + // 克隆对象 + V cloneObj = (V) ConvertUtils.bytesToObject( + serializableToBytes((Serializable) entry.getValue()) + ); + if (cloneObj != null) { + // 保存到集合 + map.put(key, cloneObj); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deepClone"); + } + } + return true; + } + return false; + } + + /** + * 进行克隆 + * @param collection 存储集合 + * @param values 需要克隆的数据源 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean deepClone( + final Collection collection, + final Collection values + ) { + if (collection != null && values != null && values.size() > 0) { + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + try { + // 克隆对象 + T cloneObj = (T) ConvertUtils.bytesToObject( + serializableToBytes((Serializable) iterator.next()) + ); + if (cloneObj != null) { + collection.add(cloneObj); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deepClone"); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/CloseUtils.java b/lib/DevApp/src/main/java/dev/utils/common/CloseUtils.java new file mode 100644 index 0000000000..03637fccf9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/CloseUtils.java @@ -0,0 +1,159 @@ +package dev.utils.common; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.OutputStream; +import java.io.Writer; + +import dev.utils.JCLogUtils; + +/** + * detail: 关闭 ( IO 流 ) 工具类 + * @author Ttt + */ +public final class CloseUtils { + + private CloseUtils() { + } + + // 日志 TAG + private static final String TAG = CloseUtils.class.getSimpleName(); + + /** + * 关闭 IO + * @param closeables Closeable[] + */ + public static void closeIO(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "closeIO"); + } + } + } + } + + /** + * 安静关闭 IO + * @param closeables Closeable[] + */ + public static void closeIOQuietly(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignore) { + } + } + } + } + + // = + + /** + * 将缓冲区数据输出 + * @param flushables Flushable[] + */ + public static void flush(final Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flush"); + } + } + } + } + + /** + * 安静将缓冲区数据输出 + * @param flushables Flushable[] + */ + public static void flushQuietly(final Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (Exception ignore) { + } + } + } + } + + // = + + /** + * 将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + */ + public static void flushCloseIO(final OutputStream outputStream) { + if (outputStream == null) return; + try { + outputStream.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + try { + outputStream.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + */ + public static void flushCloseIOQuietly(final OutputStream outputStream) { + if (outputStream == null) return; + try { + outputStream.flush(); + } catch (Exception ignored) { + } + try { + outputStream.close(); + } catch (Exception ignored) { + } + } + + /** + * 将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + */ + public static void flushCloseIO(final Writer writer) { + if (writer == null) return; + try { + writer.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + try { + writer.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + */ + public static void flushCloseIOQuietly(final Writer writer) { + if (writer == null) return; + try { + writer.flush(); + } catch (Exception ignored) { + } + try { + writer.close(); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/CollectionUtils.java b/lib/DevApp/src/main/java/dev/utils/common/CollectionUtils.java new file mode 100644 index 0000000000..beee45faac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/CollectionUtils.java @@ -0,0 +1,2183 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import dev.utils.JCLogUtils; + +/** + * detail: 集合工具类 ( Collection - List、Set、Queue ) 等 + * @author Ttt + *
+ *     // 升序
+ *     Collections.sort(list);
+ *     // 降序
+ *     Collections.sort(list, Collections.reverseOrder());
+ *     // 逆序
+ *     Collections.reverse(list);
+ *     // 创建不可修改集合
+ *     Collections.unmodifiableList()
+ *     Arrays.asList()
+ * 
+ */ +public final class CollectionUtils { + + private CollectionUtils() { + } + + // 日志 TAG + private static final String TAG = CollectionUtils.class.getSimpleName(); + + // ============== + // = Collection = + // ============== + + /** + * 判断 Collection 是否为 null + * @param collection {@link Collection} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Collection collection) { + return (collection == null || collection.size() == 0); + } + + /** + * 判断 Collection 是否不为 null + * @param collection {@link Collection} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Collection collection) { + return (collection != null && collection.size() != 0); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取 Collection 长度 + * @param collection {@link Collection} + * @return 如果 Collection 为 null, 则返回默认长度, 如果不为 null, 则返回 collection.size() + */ + public static int length(final Collection collection) { + return length(collection, 0); + } + + /** + * 获取 Collection 长度 + * @param collection {@link Collection} + * @param defaultLength 集合为 null 默认长度 + * @return 如果 Collection 为 null, 则返回 defaultLength, 如果不为 null, 则返回 collection.size() + */ + public static int length( + final Collection collection, + final int defaultLength + ) { + return collection != null ? collection.size() : defaultLength; + } + + // = + + /** + * 获取长度 Collection 是否等于期望长度 + * @param collection {@link Collection} + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Collection collection, + final int length + ) { + return collection != null && collection.size() == length; + } + + // = + + /** + * 判断 Collection 长度是否大于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThan( + final Collection collection, + final int length + ) { + return collection != null && collection.size() > length; + } + + /** + * 判断 Collection 长度是否大于等于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThanOrEqual( + final Collection collection, + final int length + ) { + return collection != null && collection.size() >= length; + } + + // = + + /** + * 判断 Collection 长度是否小于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThan( + final Collection collection, + final int length + ) { + return collection != null && collection.size() < length; + } + + /** + * 判断 Collection 长度是否小于等于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThanOrEqual( + final Collection collection, + final int length + ) { + return collection != null && collection.size() <= length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取 Collection 数组长度总和 + * @param collections Collection[] + * @return Collection 数组长度总和 + */ + public static int getCount(final Collection... collections) { + if (collections == null) return 0; + int count = 0; + for (Collection collection : collections) { + count += length(collection); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取数据 + * @param collection {@link Collection} + * @param pos 索引 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final Collection collection, + final int pos + ) { + if (collection != null) { + // 防止索引为负数 + if (pos < 0) { + return null; + } + if (collection instanceof List) { + try { + return (T) ((List) collection).get(pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + } else { + try { + return (T) collection.toArray()[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + } + } + return null; + } + + /** + * 获取第一条数据 + * @param collection {@link Collection} + * @param 泛型 + * @return 索引为 0 的值 + */ + public static T getFirst(final Collection collection) { + return get(collection, 0); + } + + /** + * 获取最后一条数据 + * @param collection {@link Collection} + * @param 泛型 + * @return 索引 length - 1 的值 + */ + public static T getLast(final Collection collection) { + return get(collection, length(collection) - 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final Collection collection, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (collection != null) { + if (notNull && value == null) { + return null; + } + try { + // 保存当前临时次数 + int temp = number; + // 转换数组 + T[] arrays = (T[]) collection.toArray(); + // 进行循环判断 + for (int i = 0, len = arrays.length; i < len; i++) { + T t = arrays[i]; + // 判断值是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return arrays[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (collection != null) { + if (notNull && value == null) { + return -1; + } + try { + // 保存当前临时次数 + int temp = number; + // 转换数组 + T[] arrays = (T[]) collection.toArray(); + // 进行循环判断 + for (int i = 0, len = arrays.length; i < len; i++) { + T t = arrays[i]; + // 判断值是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value + ) { + return getPosition(collection, value, 0, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number + ) { + return getPosition(collection, value, number, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final boolean notNull + ) { + return getPosition(collection, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return getPosition(collection, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final Collection collection, + final T value + ) { + return getPosition(collection, value, 0, true, 0); + } + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final Collection collection, + final T value, + final int number + ) { + return getPosition(collection, value, number, true, 0); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value + ) { + return get(collection, value, 0, false, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, false, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final boolean notNull + ) { + return get(collection, value, 0, notNull, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return get(collection, value, number, notNull, 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNextNotNull( + final Collection collection, + final T value + ) { + return get(collection, value, 0, true, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNextNotNull( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, true, 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value + ) { + return get(collection, value, 0, false, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, false, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final boolean notNull + ) { + return get(collection, value, 0, notNull, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return get(collection, value, number, notNull, -1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPreviousNotNull( + final Collection collection, + final T value + ) { + return get(collection, value, 0, true, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPreviousNotNull( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, true, -1); + } + + // ========== + // = 添加数据 = + // ========== + + /** + * 添加一条数据 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final Collection collection, + final T value + ) { + return add(collection, value, false); + } + + /** + * 添加一条数据 ( value 不允许为 null ) + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addNotNull( + final Collection collection, + final T value + ) { + return add(collection, value, true); + } + + /** + * 添加一条数据 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许添加 null 数据 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final Collection collection, + final T value, + final boolean notNull + ) { + if (collection != null) { + if (notNull) { + if (value != null) { + try { + return collection.add(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add notNull"); + } + } + } else { + try { + return collection.add(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add"); + } + } + } + return false; + } + + // = + + /** + * 添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final Collection collection, + final Collection values + ) { + return addAll(collection, values, false); + } + + /** + * 添加集合数据 ( values 内的值不允许为 null ) + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAllNotNull( + final Collection collection, + final Collection values + ) { + return addAll(collection, values, true); + } + + /** + * 添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final Collection collection, + final Collection values, + final boolean notNull + ) { + if (collection != null && values != null) { + if (notNull) { + try { + for (T value : values) { + if (value != null) { + collection.add(value); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll notNull"); + } + } else { + try { + return collection.addAll(values); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll"); + } + } + } + return false; + } + + // = + + /** + * 移除全部数据并添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAll( + final Collection collection, + final Collection values + ) { + return clearAndAddAll(collection, values, false); + } + + /** + * 移除全部数据并添加集合数据 ( values 内的值不允许为 null ) + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAllNotNull( + final Collection collection, + final Collection values + ) { + return clearAndAddAll(collection, values, true); + } + + /** + * 移除全部数据并添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAll( + final Collection collection, + final Collection values, + final boolean notNull + ) { + clearAll(collection); + return addAll(collection, values, notNull); + } + + // ========================= + // = 添加数据到指定索引 (List) = + // ========================= + + /** + * 添加一条数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final int index, + final List list, + final T value + ) { + return add(index, list, value, false); + } + + /** + * 添加一条数据到指定索引后 ( value 不允许为 null ) + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addNotNull( + final int index, + final List list, + final T value + ) { + return add(index, list, value, true); + } + + /** + * 添加一条数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param notNull 是否不允许添加 null 数据 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final int index, + final List list, + final T value, + final boolean notNull + ) { + if (list != null) { + if (notNull) { + if (value != null) { + try { + list.add(index, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add notNull"); + } + } + } else { + try { + list.add(index, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add"); + } + } + } + return false; + } + + // = + + /** + * 添加集合数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final int index, + final List list, + final List values + ) { + return addAll(index, list, values, false); + } + + /** + * 添加集合数据到指定索引后 ( values 内的值不允许为 null ) + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAllNotNull( + final int index, + final List list, + final List values + ) { + return addAll(index, list, values, true); + } + + /** + * 添加集合数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final int index, + final List list, + final List values, + final boolean notNull + ) { + if (list != null && values != null) { + if (notNull) { + try { + List tempList = new ArrayList<>(); + for (T value : values) { + if (value != null) { + tempList.add(value); + } + } + // 添加到集合中 + list.addAll(index, tempList); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll notNull"); + } + } else { + try { + list.addAll(index, values); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll"); + } + } + } + return false; + } + + // ========== + // = 删除数据 = + // ========== + + /** + * 移除一条数据 + * @param collection {@link Collection} + * @param value 准备删除的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + return collection.remove(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param list 集合 + * @param pos 准备删除的索引 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static T remove( + final List list, + final int pos + ) { + if (list != null) { + // 防止索引为负数 + if (pos < 0) { + return null; + } + try { + return list.remove(pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return null; + } + + /** + * 移除集合数据 + * @param collection {@link Collection} + * @param values 准备删除的集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean removeAll( + final Collection collection, + final Collection values + ) { + if (collection != null && values != null) { + try { + return collection.removeAll(values); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeAll"); + } + } + return false; + } + + // = + + /** + * 清空集合中符合指定 value 的全部数据 + * @param collection {@link Collection} + * @param value 准备对比移除的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clear( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + T t = iterator.next(); + // 判断值是否一样 + if (equals(t, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clear"); + } + } + return false; + } + + /** + * 保留集合中符合指定 value 的全部数据 + * @param collection {@link Collection} + * @param value 准备对比保留的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearNotBelong( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + T t = iterator.next(); + // 判断值是否不一样 ( 移除不一样的 ) + if (!equals(t, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearNotBelong"); + } + } + return false; + } + + /** + * 清空集合全部数据 + * @param collection {@link Collection} + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAll(final Collection collection) { + if (collection != null) { + try { + collection.clear(); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearAll"); + } + } + return false; + } + + /** + * 清空集合中为 null 的值 + * @param collection {@link Collection} + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearNull(final Collection collection) { + return clear(collection, null); + } + + // ================= + // = 判断集合是否相同 = + // ================= + + /** + * 判断两个集合是否相同 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEqualCollection( + final Collection collection1, + final Collection collection2 + ) { + // 数据长度 + int len; + // 判断数据是否相同 + if (collection1 != null && collection2 != null && (len = collection1.size()) == collection2.size()) { + if (len == 0) return true; + + // 进行判断类型, 如果不同, 则直接跳过不处理 + if (!collection1.getClass().getName().equals(collection2.getClass().getName())) { + return false; + } + + // 如果集合相等, 直接跳过 + if (collection1.equals(collection2)) { + return true; + } + + T[] arrays1, arrays2; + try { + // 转换数组, 防止异常 + arrays1 = (T[]) collection1.toArray(); + arrays2 = (T[]) collection2.toArray(); + } catch (Exception e) { + return false; + } + for (int i = 0; i < len; i++) { + // 判断两个值是否一样 + boolean equals = equals(arrays1[i], arrays2[i]); + // 如果不一样, 直接 return + if (!equals) { + return false; + } + } + return true; + } + // 如果不符合条件, 防止两个集合都是为 null + return (collection1 == null && collection2 == null); + } + + /** + * 判断多个集合是否相同 + * @param collections 集合数组 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEqualCollections(final Collection... collections) { + if (collections != null && collections.length >= 2) { + // 获取长度 + int len = collections.length; + // 设置临时值为第一个 + Collection temp = collections[0]; + // 进行判断 + for (int i = 1; i < len; i++) { + // 判断是否一样 + boolean equalCollection = isEqualCollection(temp, collections[i]); + // 如果不一样, 直接返回 + if (!equalCollection) { + return false; + } + } + return true; + } + return false; + } + + // ========== + // = 集合处理 = + // ========== + + // ======= + // = 并集 = + // ======= + + /** + * 两个集合并集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 并集集合 + */ + public static Collection union( + final Collection collection1, + final Collection collection2 + ) { + if (collection1 != null && collection2 != null) { + try { + // 初始化新的集合, 默认保存第一个集合的数据 + Set sets = new LinkedHashSet<>(collection1); + sets.addAll(collection2); + // 返回集合 + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "union"); + } + return null; + } else if (collection1 != null) { + return collection1; + } + return collection2; + } + + /** + * 多个集合并集处理 + * @param collections 集合数组 + * @param 泛型 + * @return 并集集合 + */ + public static Collection unions(final Collection... collections) { + if (collections != null) { + int len = collections.length; + if (len >= 2) { + try { + // 保存第一个集合 + Set sets = new LinkedHashSet<>(); + // 防止集合为 null + if (collections[0] != null) { + sets.addAll(collections[0]); + } + // 进行循环处理 + for (int i = 1; i < len; i++) { + // 获取集合值 + Collection value = collections[i]; + if (value != null) { + sets.addAll(value); + } + } + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unions"); + } + return null; + } + return collections[0]; + } + return null; + } + + // ======= + // = 交集 = + // ======= + + /** + * 两个集合交集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 交集集合 + */ + public static Collection intersection( + final Collection collection1, + final Collection collection2 + ) { + if (collection1 != null && collection2 != null) { + try { + // 专门用于返回中转的集合 + Set sets = new LinkedHashSet<>(); + + // 初始化新的集合, 默认保存第一个集合的数据 + Set setsTemp = new LinkedHashSet<>(collection1); + // 循环第二个集合 + for (T value : collection2) { + // 判断是否存在, 存在则保存 + if (setsTemp.contains(value)) { + sets.add(value); + } + } + // 返回集合 + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intersection"); + } + return null; + } else if (collection1 != null) { + return collection1; + } + return collection2; + } + + /** + * 两个集合交集的补集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 交集集合 + */ + public static Collection disjunction( + final Collection collection1, + final Collection collection2 + ) { + try { + // 先进行并集处理 + Collection unionC = union(collection1, collection2); + // 在进行交集处理 + Collection intersectionC = intersection(collection1, collection2); + // 再进行移除处理 + if (unionC != null && intersectionC != null) { + try { + // 移除数据 + unionC.removeAll(intersectionC); + return unionC; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "disjunction"); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "disjunction"); + } + return null; + } + + /** + * 两个集合差集 ( 扣除 ) 处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 差集 ( 扣除 ) 集合 + */ + public static Collection subtract( + final Collection collection1, + final Collection collection2 + ) { + try { + // 先进行交集处理 + Collection intersectionC = intersection(collection1, collection2); + if (intersectionC != null) { + // 保存到新的集合中 + Set sets = new LinkedHashSet<>(collection1); + // 进行移除 + sets.removeAll(intersectionC); + // 返回集合 + return sets; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subtract"); + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // ========== + // = 转换处理 = + // ========== + + /** + * 转换数组 to Object + * @param collection {@link Collection} + * @param 泛型 + * @return 转换后的数组 + */ + public static Object[] toArray(final Collection collection) { + if (collection != null) { + try { + return collection.toArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toArray"); + } + } + return null; + } + + /** + * 转换数组 to T + * @param collection {@link Collection} + * @param 泛型 + * @return 转换后的泛型数组 + */ + public static T[] toArrayT(final Collection collection) { + if (collection != null) { + try { + return new ArrayWithTypeToken(collection).create(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toArrayT"); + } + } + return null; + } + + /** + * 集合翻转处理 + * @param collection {@link Collection} + * @param 泛型 + * @return 翻转后的集合 + */ + public static Collection reverse(final Collection collection) { + try { + // 返回集合 + List lists = new ArrayList<>(); + // 转换数据 + T[] arrays = (T[]) collection.toArray(); + // 循环处理 + for (int i = arrays.length - 1; i >= 0; i--) { + lists.add(arrays[i]); + } + // 保存新的数据 + return lists; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "reverse"); + } + return null; + } + + // ============ + // = 最小值索引 = + // ============ + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexI(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Integer temp = list.get(index); + for (int i = 1; i < len; i++) { + Integer value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexL(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Long temp = list.get(index); + for (int i = 1; i < len; i++) { + Long value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexF(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Float temp = list.get(index); + for (int i = 1; i < len; i++) { + Float value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexD(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Double temp = list.get(index); + for (int i = 1; i < len; i++) { + Double value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + // ============ + // = 最大值索引 = + // ============ + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexI(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Integer temp = list.get(index); + for (int i = 1; i < len; i++) { + Integer value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexL(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Long temp = list.get(index); + for (int i = 1; i < len; i++) { + Long value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexF(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Float temp = list.get(index); + for (int i = 1; i < len; i++) { + Float value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexD(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Double temp = list.get(index); + for (int i = 1; i < len; i++) { + Double value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + // ============ + // = 获取最小值 = + // ============ + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static int getMinimumI(final List list) { + try { + return list.get(getMinimumIndexI(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumI"); + } + return 0; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static long getMinimumL(final List list) { + try { + return list.get(getMinimumIndexL(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumL"); + } + return 0L; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static float getMinimumF(final List list) { + try { + return list.get(getMinimumIndexF(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumF"); + } + return 0F; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static double getMinimumD(final List list) { + try { + return list.get(getMinimumIndexD(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumD"); + } + return 0D; + } + + // ============ + // = 获取最大值 = + // ============ + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static int getMaximumI(final List list) { + try { + return list.get(getMaximumIndexI(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumI"); + } + return 0; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static long getMaximumL(final List list) { + try { + return list.get(getMaximumIndexL(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumL"); + } + return 0L; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static float getMaximumF(final List list) { + try { + return list.get(getMaximumIndexF(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumF"); + } + return 0F; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static double getMaximumD(final List list) { + try { + return list.get(getMaximumIndexD(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumD"); + } + return 0D; + } + + // ============= + // = 计算集合总和 = + // ============= + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static int sumlistI(final List lists) { + return sumlistI(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int end + ) { + return sumlistI(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int end, + final int extra + ) { + return sumlistI(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int start, + final int end, + final int extra + ) { + int total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistI"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static long sumlistL(final List lists) { + return sumlistL(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int end + ) { + return sumlistL(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int end, + final long extra + ) { + return sumlistL(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int start, + final int end, + final long extra + ) { + long total = 0L; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistL"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static float sumlistF(final List lists) { + return sumlistF(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int end + ) { + return sumlistF(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int end, + final float extra + ) { + return sumlistF(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int start, + final int end, + final float extra + ) { + float total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistF"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static double sumlistD(final List lists) { + return sumlistD(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int end + ) { + return sumlistD(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int end, + final double extra + ) { + return sumlistD(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int start, + final int end, + final double extra + ) { + double total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistD"); + } + } + } + return total; + } + + // ============ + // = 内部实现类 = + // ============ + + /** + * detail: 持有数组 TypeToken 实体类 + * @author Ttt + */ + public static class ArrayWithTypeToken { + + // 泛型数组 + private T[] array; + + public ArrayWithTypeToken(Collection collection) { + newInstance(collection); + } + + public ArrayWithTypeToken( + Class type, + int size + ) { + newInstance(type, size); + } + + /** + * 添加数据 + * @param index 索引 + * @param item 数据 + */ + public void put( + final int index, + final T item + ) { + array[index] = item; + } + + /** + * 获取对应索引的数据 + * @param index 索引 + * @return 对应索引的数据 + */ + public T get(final int index) { + return array[index]; + } + + /** + * 获取数组 + * @return 泛型数组 + */ + public T[] create() { + return array; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 创建数组方法 + * @param type 数组类型 + * @param size 数组长度 + */ + private void newInstance( + final Class type, + final int size + ) { + array = (T[]) Array.newInstance(type, size); + } + + /** + * 创建数组方法 + * @param collection 集合 + */ + private void newInstance(final Collection collection) { + // 泛型实体类 + T value = null; + // 数组 + Object[] objects = collection.toArray(); + // 获取不为 null 的泛型实体类 + for (Object object : objects) { + if (object != null) { + value = (T) object; + break; + } + } + newInstance((Class) value.getClass(), objects.length); + // 保存数据 + for (int i = 0, len = objects.length; i < len; i++) { + Object object = objects[i]; + put(i, (object != null) ? (T) object : null); + } + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ColorUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ColorUtils.java new file mode 100644 index 0000000000..13c1bb5e59 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ColorUtils.java @@ -0,0 +1,1275 @@ +package dev.utils.common; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 颜色工具类 ( 包括常用的色值 ) + * @author Ttt + *
+ *     颜色信息和转换工具
+ *     @see 
+ *     RGB 颜色空间、色调、饱和度、亮度、HSV 颜色空间详解
+ *     @see 
+ * 
+ */ +public final class ColorUtils { + + private ColorUtils() { + } + + // 日志 TAG + private static final String TAG = ColorUtils.class.getSimpleName(); + + // 透明 + public static final int TRANSPARENT = 0x00000000; + // 白色 + public static final int WHITE = 0xffffffff; + // 白色 ( 半透明 ) + public static final int WHITE_TRANSLUCENT = 0x80ffffff; + // 黑色 + public static final int BLACK = 0xff000000; + // 黑色 ( 半透明 ) + public static final int BLACK_TRANSLUCENT = 0x80000000; + // 红色 + public static final int RED = 0xffff0000; + // 红色 ( 半透明 ) + public static final int RED_TRANSLUCENT = 0x80ff0000; + // 绿色 + public static final int GREEN = 0xff00ff00; + // 绿色 ( 半透明 ) + public static final int GREEN_TRANSLUCENT = 0x8000ff00; + // 蓝色 + public static final int BLUE = 0xff0000ff; + // 蓝色 ( 半透明 ) + public static final int BLUE_TRANSLUCENT = 0x800000ff; + // 灰色 + public static final int GRAY = 0xff969696; + // 灰色 ( 半透明 ) + public static final int GRAY_TRANSLUCENT = 0x80969696; + // 天蓝 + public static final int SKYBLUE = 0xff87ceeb; + // 橙色 + public static final int ORANGE = 0xffffa500; + // 金色 + public static final int GOLD = 0xffffd700; + // 粉色 + public static final int PINK = 0xffffc0cb; + // 紫红色 + public static final int FUCHSIA = 0xffff00ff; + // 灰白色 + public static final int GRAYWHITE = 0xfff2f2f2; + // 紫色 + public static final int PURPLE = 0xff800080; + // 青色 + public static final int CYAN = 0xff00ffff; + // 黄色 + public static final int YELLOW = 0xffffff00; + // 巧克力色 + public static final int CHOCOLATE = 0xffd2691e; + // 番茄色 + public static final int TOMATO = 0xffff6347; + // 橙红色 + public static final int ORANGERED = 0xffff4500; + // 银白色 + public static final int SILVER = 0xffc0c0c0; + // 深灰色 + public static final int DKGRAY = 0xFF444444; + // 亮灰色 + public static final int LTGRAY = 0xFFCCCCCC; + // 洋红色 + public static final int MAGENTA = 0xFFFF00FF; + // 高光 + public static final int HIGHLIGHT = 0x33ffffff; + // 低光 + public static final int LOWLIGHT = 0x33000000; + + /* + * 0-255 十进值转换成十六进制, 如 255 就是 ff + * 255 * 0.x = 十进制 转 十六进制 + *

+ * 透明度 0 - 100 + * 00、19、33、4C、66、7F、99、B2、CC、E5、FF + * 透明度 5 - 95 + * 0D、26、40、59、73、8C、A6、BF、D9、F2 + */ + + static { + // 设置 Color 解析器 + setParser(new ColorInfo.ColorParser()); + } + + /** + * 获取十六进制透明度字符串 + * @param alpha 0-255 + * @return 透明度 ( 十六进制 ) 值 + */ + public static String hexAlpha(final int alpha) { + try { + if (alpha >= 0 && alpha <= 255) { + return Integer.toHexString(alpha); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hexAlpha"); + } + return null; + } + + // = + + /** + * 返回一个颜色 ARGB 色值数组 ( 返回十进制 ) + * @param color argb/rgb color + * @return ARGB 色值数组 { alpha, red, green, blue } + */ + public static int[] getARGB(final int color) { + int[] argb = new int[4]; + if (ColorUtils.isARGB(color)) { + argb[0] = alpha(color); + } else { + argb[0] = 255; + } + argb[1] = red(color); + argb[2] = green(color); + argb[3] = blue(color); + return argb; + } + + // = + + /** + * 返回一个颜色中的透明度值 ( 返回十进制 ) + * @param color argb color + * @return alpha 值 + */ + public static int alpha(final int color) { + return color >>> 24; + } + + /** + * 返回一个颜色中的透明度百分比值 + * @param color argb color + * @return alpha 百分比值 + */ + public static float alphaPercent(final int color) { + return NumberUtils.percentF(alpha(color), 255); + } + + // = + + /** + * 返回一个颜色中红色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return red 值 + */ + public static int red(final int color) { + return (color >> 16) & 0xFF; + } + + /** + * 返回一个颜色中红色的百分比值 + * @param color argb/rgb color + * @return red 百分比值 + */ + public static float redPercent(final int color) { + return NumberUtils.percentF(red(color), 255); + } + + // = + + /** + * 返回一个颜色中绿色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return green 值 + */ + public static int green(final int color) { + return (color >> 8) & 0xFF; + } + + /** + * 返回一个颜色中绿色的百分比值 + * @param color argb/rgb color + * @return green 百分比值 + */ + public static float greenPercent(final int color) { + return NumberUtils.percentF(green(color), 255); + } + + // = + + /** + * 返回一个颜色中蓝色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return blue 值 + */ + public static int blue(final int color) { + return color & 0xFF; + } + + /** + * 返回一个颜色中蓝色的百分比值 + * @param color argb/rgb color + * @return blue 百分比值 + */ + public static float bluePercent(final int color) { + return NumberUtils.percentF(blue(color), 255); + } + + // = + + /** + * 根据对应的 red、green、blue 生成一个颜色值 + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return rgb 颜色值 + */ + public static int rgb( + final int red, + final int green, + final int blue + ) { + return 0xff000000 | (red << 16) | (green << 8) | blue; + } + + /** + * 根据对应的 red、green、blue 生成一个颜色值 + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return rgb 颜色值 + */ + public static int rgb( + final float red, + final float green, + final float blue + ) { + return 0xff000000 | + ((int) (red * 255.0F + 0.5F) << 16) | + ((int) (green * 255.0F + 0.5F) << 8) | + (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) + * @param alpha 透明度 [0-255] + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return argb 颜色值 + */ + public static int argb( + final int alpha, + final int red, + final int green, + final int blue + ) { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + /** + * 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) + * @param alpha 透明度 [0-255] + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return argb 颜色值 + */ + public static int argb( + final float alpha, + final float red, + final float green, + final float blue + ) { + return ((int) (alpha * 255.0F + 0.5F) << 24) | + ((int) (red * 255.0F + 0.5F) << 16) | + ((int) (green * 255.0F + 0.5F) << 8) | + (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 判断颜色 RGB 是否有效 + * @param color rgb color + * @return {@code true} yes, {@code false} no + */ + public static boolean isRGB(final int color) { + int red = red(color); + int green = green(color); + int blue = blue(color); + return (red <= 255 && red >= 0) && + (green <= 255 && green >= 0) && + (blue <= 255 && blue >= 0); + } + + /** + * 判断颜色 ARGB 是否有效 + * @param color argb color + * @return {@code true} yes, {@code false} no + */ + public static boolean isARGB(final int color) { + int alpha = alpha(color); + int red = red(color); + int green = green(color); + int blue = blue(color); + return (alpha <= 255 && alpha >= 0) && + (red <= 255 && red >= 0) && + (green <= 255 && green >= 0) && + (blue <= 255 && blue >= 0); + } + + // = + + /** + * 设置透明度 + * @param color argb/rgb color + * @param alpha 透明度 [0-255] + * @return argb 颜色值 + */ + public static int setAlpha( + final int color, + final int alpha + ) { + return (color & 0x00ffffff) | (alpha << 24); + } + + /** + * 设置透明度 + * @param color argb/rgb color + * @param alpha 透明度 [0-255] + * @return argb 颜色值 + */ + public static int setAlpha( + final int color, + final float alpha + ) { + return (color & 0x00ffffff) | ((int) (alpha * 255.0F + 0.5F) << 24); + } + + /** + * 改变颜色值中的红色色值 + * @param color argb/rgb color + * @param red 红色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setRed( + final int color, + final int red + ) { + return (color & 0xff00ffff) | (red << 16); + } + + /** + * 改变颜色值中的红色色值 + * @param color argb/rgb color + * @param red 红色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setRed( + final int color, + final float red + ) { + return (color & 0xff00ffff) | ((int) (red * 255.0F + 0.5F) << 16); + } + + /** + * 改变颜色值中的绿色色值 + * @param color argb/rgb color + * @param green 绿色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setGreen( + final int color, + final int green + ) { + return (color & 0xffff00ff) | (green << 8); + } + + /** + * 改变颜色值中的绿色色值 + * @param color argb/rgb color + * @param green 绿色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setGreen( + final int color, + final float green + ) { + return (color & 0xffff00ff) | ((int) (green * 255.0F + 0.5F) << 8); + } + + /** + * 改变颜色值中的蓝色色值 + * @param color argb/rgb color + * @param blue 蓝色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setBlue( + final int color, + final int blue + ) { + return (color & 0xffffff00) | blue; + } + + /** + * 改变颜色值中的蓝色色值 + * @param color argb/rgb color + * @param blue 蓝色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setBlue( + final int color, + final float blue + ) { + return (color & 0xffffff00) | (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 解析颜色字符串, 返回对应的颜色值 + * @param colorStr argb/rgb color String + * @return argb/rgb 颜色值 + */ + private static int innerParseColor(final String colorStr) { + if (colorStr.charAt(0) == '#') { + // Use a long to avoid rollovers on #ffXXXXXX + long color = Long.parseLong(colorStr.substring(1), 16); + if (colorStr.length() == 7) { + // Set the alpha value + color |= 0x00000000ff000000; + } else if (colorStr.length() != 9) { + throw new IllegalArgumentException("Unknown color"); + } + return (int) color; + } else { + Integer color = sColorNameMaps.get(colorStr.toLowerCase(Locale.ROOT)); + if (color != null) { + return color; + } + } + throw new IllegalArgumentException("Unknown color"); + } + + /** + * 解析颜色字符串, 返回对应的颜色值 + *
+     *     支持的格式:
+     *     #RRGGBB
+     *     #AARRGGBB
+     *     'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', 'lightgray', 'darkgray'
+     * 
+ * @param colorStr argb/rgb color String + * @return argb/rgb 颜色值 + */ + public static int parseColor(final String colorStr) { + if (colorStr != null) { + try { + return innerParseColor(colorStr); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseColor"); + } + } + return Integer.MAX_VALUE; + } + + /** + * 颜色值 转换 RGB 颜色字符串 + * @param colorInt rgb int color + * @return rgb color String + */ + public static String intToRgbString(final int colorInt) { + try { + int color = colorInt; + color = color & 0x00ffffff; + String colorStr = Integer.toHexString(color); + while (colorStr.length() < 6) { + colorStr = "0" + colorStr; + } + return "#" + colorStr; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intToRgbString"); + } + return null; + } + + /** + * 颜色值 转换 ARGB 颜色字符串 + * @param colorInt argb int color + * @return argb color String + */ + public static String intToArgbString(final int colorInt) { + try { + String colorString = Integer.toHexString(colorInt); + while (colorString.length() < 6) { + colorString = "0" + colorString; + } + while (colorString.length() < 8) { + colorString = "f" + colorString; + } + return "#" + colorString; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intToArgbString"); + } + return null; + } + + // = + + /** + * 获取随机颜色值 + * @return 随机颜色值 + */ + public static int getRandomColor() { + return getRandomColor(true); + } + + /** + * 获取随机颜色值 + * @param supportAlpha 是否支持透明度 + * @return argb/rgb 颜色值 + */ + public static int getRandomColor(final boolean supportAlpha) { + int high = supportAlpha ? (int) (Math.random() * 0x100) << 24 : 0xFF000000; + return high | (int) (Math.random() * 0x1000000); + } + + /** + * 获取随机颜色值字符串 + * @return 随机颜色值 + */ + public static String getRandomColorString() { + return getRandomColorString(true); + } + + /** + * 获取随机颜色值字符串 + * @param supportAlpha 是否支持透明度 + * @return 随机颜色值 + */ + public static String getRandomColorString(final boolean supportAlpha) { + if (supportAlpha) { + return intToArgbString(getRandomColor(supportAlpha)); + } else { + return intToRgbString(getRandomColor(supportAlpha)); + } + } + + // = + + /** + * 判断是否为 ARGB 格式的十六进制颜色, 例如: FF990587 + * @param colorStr color String + * @return {@code true} yes, {@code false} no + */ + public static boolean judgeColorString(final String colorStr) { + if (colorStr != null && colorStr.length() == 8) { + char cc = colorStr.charAt(0); + return !(cc != '0' && cc != '1' && cc != '2' && cc != '3' && cc != '4' + && cc != '5' && cc != '6' && cc != '7' && cc != '8' && cc != '9' + && cc != 'A' && cc != 'B' && cc != 'C' && cc != 'D' && cc != 'E' + && cc != 'F' && cc != 'a' && cc != 'b' && cc != 'c' && cc != 'd' + && cc != 'e' && cc != 'f'); + } + return false; + } + + // = + + /** + * 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) + * @param colorStr color String + * @param darkValue 加深值 + * @return 加深后的颜色值 + */ + public static int setDark( + final String colorStr, + final int darkValue + ) { + int color = parseColor(colorStr); + return setDark(color, darkValue); + } + + /** + * 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) + * @param color int color + * @param darkValue 加深值 + * @return 加深后的颜色值 + */ + public static int setDark( + final int color, + final int darkValue + ) { + int red = red(color); + int green = green(color); + int blue = blue(color); + // 进行加深 ( 累减 ) + red -= darkValue; + green -= darkValue; + blue -= darkValue; + // 颜色值 + int colorTemp = color; + // 进行设置 + colorTemp = setRed(colorTemp, NumberUtils.clamp(red, 255, 0)); + colorTemp = setGreen(colorTemp, NumberUtils.clamp(green, 255, 0)); + colorTemp = setBlue(colorTemp, NumberUtils.clamp(blue, 255, 0)); + return colorTemp; + } + + /** + * 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) + * @param colorStr color String + * @param lightValue 变亮 ( 变浅 ) 值 + * @return 变亮 ( 变浅 ) 后的颜色值 + */ + public static int setLight( + final String colorStr, + final int lightValue + ) { + int color = parseColor(colorStr); + return setLight(color, lightValue); + } + + /** + * 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) + * @param color int color + * @param lightValue 变亮 ( 变浅 ) 值 + * @return 变亮 ( 变浅 ) 后的颜色值 + */ + public static int setLight( + final int color, + final int lightValue + ) { + int red = red(color); + int green = green(color); + int blue = blue(color); + // 进行变浅, 变亮 ( 累加 ) + red += lightValue; + green += lightValue; + blue += lightValue; + // 颜色值 + int colorTemp = color; + // 进行设置 + colorTemp = setRed(colorTemp, NumberUtils.clamp(red, 255, 0)); + colorTemp = setGreen(colorTemp, NumberUtils.clamp(green, 255, 0)); + colorTemp = setBlue(colorTemp, NumberUtils.clamp(blue, 255, 0)); + return colorTemp; + } + + /** + * 设置透明度加深 + * @param colorStr color String + * @param darkValue 加深值 + * @return 透明度加深后的颜色值 + */ + public static int setAlphaDark( + final String colorStr, + final int darkValue + ) { + int color = parseColor(colorStr); + return setAlphaDark(color, darkValue); + } + + /** + * 设置透明度加深 + * @param color int color + * @param darkValue 加深值 + * @return 透明度加深后的颜色值 + */ + public static int setAlphaDark( + final int color, + final int darkValue + ) { + int alpha = alpha(color); + // 透明度加深 + alpha += darkValue; + // 进行设置 + return setAlpha(color, NumberUtils.clamp(alpha, 255, 0)); + } + + /** + * 设置透明度变浅 + * @param colorStr color String + * @param lightValue 变浅值 + * @return 透明度变浅后的颜色值 + */ + public static int setAlphaLight( + final String colorStr, + final int lightValue + ) { + int color = parseColor(colorStr); + return setAlphaLight(color, lightValue); + } + + /** + * 设置透明度变浅 + * @param color int color + * @param lightValue 变浅值 + * @return 透明度变浅后的颜色值 + */ + public static int setAlphaLight( + final int color, + final int lightValue + ) { + int alpha = alpha(color); + // 透明度变浅 + alpha -= lightValue; + // 进行设置 + return setAlpha(color, NumberUtils.clamp(alpha, 255, 0)); + } + + // = + + /** + * 获取灰度值 + * @param colorStr color String + * @return 灰度值 + */ + public static int grayLevel(final String colorStr) { + int color = parseColor(colorStr); + int[] argb = getARGB(color); + return (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + } + + /** + * 获取灰度值 + * @param color int color + * @return 灰度值 + */ + public static int grayLevel(final int color) { + // [] { alpha, red, green, blue } + int[] argb = getARGB(color); + return (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + } + + // = + + // 颜色字典集合 + private static final Map sColorNameMaps; + + static { + sColorNameMaps = new HashMap<>(); + sColorNameMaps.put("transparent", TRANSPARENT); + sColorNameMaps.put("white", WHITE); + sColorNameMaps.put("black", BLACK); + sColorNameMaps.put("red", RED); + sColorNameMaps.put("green", GREEN); + sColorNameMaps.put("blue", BLUE); + sColorNameMaps.put("gray", GRAY); + sColorNameMaps.put("grey", GRAY); + sColorNameMaps.put("skyblue", SKYBLUE); + sColorNameMaps.put("orange", ORANGE); + sColorNameMaps.put("gold", GOLD); + sColorNameMaps.put("pink", PINK); + sColorNameMaps.put("fuchsia", FUCHSIA); + sColorNameMaps.put("graywhite", GRAYWHITE); + sColorNameMaps.put("purple", PURPLE); + sColorNameMaps.put("cyan", CYAN); + sColorNameMaps.put("yellow", YELLOW); + sColorNameMaps.put("chocolate", CHOCOLATE); + sColorNameMaps.put("tomato", TOMATO); + sColorNameMaps.put("orangered", ORANGERED); + sColorNameMaps.put("silver", SILVER); + sColorNameMaps.put("darkgray", DKGRAY); + sColorNameMaps.put("lightgray", LTGRAY); + sColorNameMaps.put("lightgrey", LTGRAY); + sColorNameMaps.put("magenta", MAGENTA); + sColorNameMaps.put("highlight", HIGHLIGHT); + sColorNameMaps.put("lowlight", LOWLIGHT); + sColorNameMaps.put("aqua", 0xFF00FFFF); + sColorNameMaps.put("lime", 0xFF00FF00); + sColorNameMaps.put("maroon", 0xFF800000); + sColorNameMaps.put("navy", 0xFF000080); + sColorNameMaps.put("olive", 0xFF808000); + sColorNameMaps.put("teal", 0xFF008080); + } + + // ========== + // = 颜色信息 = + // ========== + + // 内部解析器 + private static ColorInfo.Parser sParser; + + /** + * 设置 Color 解析器 + * @param parser {@link ColorInfo.Parser} + */ + public static void setParser(final ColorInfo.Parser parser) { + ColorUtils.sParser = parser; + } + + /** + * detail: 颜色信息 + * @author Ttt + */ + public static class ColorInfo { + + // key + private final String key; + // value ( 如: #000000 ) + private final String value; + // value 解析后的值 ( 如: #000 => #000000 ) + private String valueParser; + // ARGB/RGB color + private long valueColor; + // A、R、G、B + private int alpha = 255, red = 0, green = 0, blue = 0; + // 灰度值 + private int grayLevel; + // H、S、B ( V ) + private float hue, saturation, brightness; + + /** + * 构造函数 + * @param key Key + * @param value Value ( 如: #000000 ) + */ + public ColorInfo( + final String key, + final String value + ) { + this.key = key; + this.value = value; + innerConvert(); + } + + /** + * 构造函数 + * @param key Key + * @param valueColor ARGB/RGB color + */ + public ColorInfo( + final String key, + final int valueColor + ) { + this(key, ColorUtils.intToArgbString(valueColor)); + } + + /** + * 获取 Key + * @return key String + */ + public String getKey() { + return key; + } + + /** + * 获取 Value + * @return value String + */ + public String getValue() { + return value; + } + + /** + * 获取 Value 解析后的值 ( 如: #000 => #000000 ) + * @return value 解析后的值 ( 如: #000 => #000000 ) + */ + public String getValueParser() { + return valueParser; + } + + /** + * 获取 ARGB/RGB color + * @return ARGB/RGB color + */ + public long getValueColor() { + return valueColor; + } + + /** + * 返回颜色中的透明度值 ( 返回十进制 ) + * @return alpha 值 + */ + public int getAlpha() { + return alpha; + } + + /** + * 返回颜色中红色的色值 ( 返回十进制 ) + * @return red 值 + */ + public int getRed() { + return red; + } + + /** + * 返回颜色中绿色的色值 ( 返回十进制 ) + * @return green 值 + */ + public int getGreen() { + return green; + } + + /** + * 返回颜色中蓝色的色值 ( 返回十进制 ) + * @return blue 值 + */ + public int getBlue() { + return blue; + } + + /** + * 获取灰度值 + * @return 灰度值 + */ + public int getGrayLevel() { + return grayLevel; + } + + /** + * 获取颜色色调 + * @return 颜色色调 + */ + public float getHue() { + return hue; + } + + /** + * 获取颜色饱和度 + * @return 颜色饱和度 + */ + public float getSaturation() { + return saturation; + } + + /** + * 获取颜色亮度 + * @return 颜色亮度 + */ + public float getBrightness() { + return brightness; + } + + @Override + public String toString() { + return "key : " + key + + "\nvalue : " + value + + "\nvalueParser : " + valueParser + + "\nalpha : " + alpha + + "\nred : " + red + + "\ngreen : " + green + + "\nblue : " + blue + + "\ngrayLevel : " + grayLevel + + "\nintToRgbString : " + ColorUtils.intToRgbString((int) valueColor) + + "\nintToArgbString : " + ColorUtils.intToArgbString((int) valueColor); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 内部转换处理 + */ + private void innerConvert() { + String temp = value; + if (sParser != null) { + temp = sParser.handleColor(value); + // 保存解析后的值 + valueParser = temp; + } + if (temp == null) return; + // 转换 long 颜色值 + valueColor = ColorUtils.parseColor(temp); + + int[] argb = ColorUtils.getARGB((int) valueColor); + // 获取 ARGB + alpha = argb[0]; + red = argb[1]; + green = argb[2]; + blue = argb[3]; + // 获取灰度值 + grayLevel = (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + // 获取 HSB + float[] hsbArrays = RGBtoHSB(red, green, blue, null); + hue = hsbArrays[0]; // 色调 + saturation = hsbArrays[1]; // 饱和度 + brightness = hsbArrays[2]; // 亮度 + } + + // ============ + // = 解析器相关 = + // ============ + + /** + * detail: Color 解析器 + * @author Ttt + */ + public interface Parser { + + /** + * 处理 color + * @param value 如: #000000 + * @return 处理后的 value + */ + String handleColor(String value); + } + + /** + * detail: Color 解析器 + * @author Ttt + */ + public static class ColorParser + implements Parser { + @Override + public String handleColor(String value) { + if (value == null) return null; + String color = StringUtils.clearSpace(value); + char[] chars = color.toCharArray(); + int length = chars.length; + if (length != 0 && chars[0] == '#') { + if (length == 4) { + String colorSub = color.substring(1); + // #000 => #000000 + return color + colorSub; + } else if (length == 5) { + String colorSub = color.substring(3); + // #0011 => #00111111 + return color + colorSub + colorSub; + } + } + return color; + } + } + + // ========== + // = 转换处理 = + // ========== + + /** + * RGB 转换 HSB + *
+         *     HSB 等于 HSV, 不同的叫法
+         *     java.awt.Color#RGBtoHSB
+         *     {@link android.graphics.Color#RGBToHSV}
+         * 
+ * @param r 红色值 [0-255] + * @param g 绿色值 [0-255] + * @param b 蓝色值 [0-255] + * @param hsbArrays HSB 数组 + * @return [] { hue, saturation, brightness } + */ + private static float[] RGBtoHSB( + int r, + int g, + int b, + float[] hsbArrays + ) { + float hue, saturation, brightness; + if (hsbArrays == null) { + hsbArrays = new float[3]; + } + int cmax = Math.max(r, g); + if (b > cmax) cmax = b; + int cmin = Math.min(r, g); + if (b < cmin) cmin = b; + + brightness = ((float) cmax) / 255.0F; + if (cmax != 0) { + saturation = ((float) (cmax - cmin)) / ((float) cmax); + } else { + saturation = 0; + } + if (saturation == 0) { + hue = 0; + } else { + float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); + float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); + float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); + if (r == cmax) { + hue = bluec - greenc; + } else if (g == cmax) { + hue = 2.0F + redc - bluec; + } else { + hue = 4.0F + greenc - redc; + } + hue = hue / 6.0F; + if (hue < 0) { + hue = hue + 1.0F; + } + } + hsbArrays[0] = hue; + hsbArrays[1] = saturation; + hsbArrays[2] = brightness; + return hsbArrays; + } + } + + // ========== + // = 颜色排序 = + // ========== + + /** + * 灰度值排序 + * @param lists 待排序颜色集合 + */ + public static void sortGray(final List lists) { + Collections.sort(lists, (c1, c2) -> { + long diff = c1.getGrayLevel() - c2.getGrayLevel(); + if (diff < 0) { + return 1; + } else if (diff > 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) HUE 色相排序 + * @param lists 待排序颜色集合 + */ + public static void sortHUE(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getHue() - c2.getHue(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) Saturation 饱和度排序 + * @param lists 待排序颜色集合 + */ + public static void sortSaturation(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getSaturation() - c2.getSaturation(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) Brightness 亮度排序 + * @param lists 待排序颜色集合 + */ + public static void sortBrightness(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getBrightness() - c2.getBrightness(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + // ========== + // = 混合颜色 = + // ========== + + /** + * 使用给定的比例在两种 ARGB 颜色之间进行混合 + * @param color1 第一种 ARGB 颜色 + * @param color2 第二种 ARGB 颜色 + * @param ratio 两种颜色混合比例 + * @return 混合后的颜色 + */ + public static int blendColor( + final String color1, + final String color2, + final float ratio + ) { + return blendColor(parseColor(color1), parseColor(color2), ratio); + } + + /** + * 使用给定的比例在两种 ARGB 颜色之间进行混合 + *
+     *     android.support.v4.graphics.ColorUtils#blendARGB
+     *     混合比:
+     *     0.0 将产生 color1
+     *     0.5 将产生均匀的混合
+     *     1.0 将产生 color2
+     * 
+ * @param color1 第一种 ARGB 颜色 + * @param color2 第二种 ARGB 颜色 + * @param ratio 两种颜色混合比例 + * @return 混合后的颜色 + */ + public static int blendColor( + final int color1, + final int color2, + final float ratio + ) { + int[] color1Argb = getARGB(color1); + int[] color2Argb = getARGB(color2); + + final float inverseRatio = 1 - ratio; + float a = color1Argb[0] * inverseRatio + color2Argb[0] * ratio; + float r = color1Argb[1] * inverseRatio + color2Argb[1] * ratio; + float g = color1Argb[2] * inverseRatio + color2Argb[2] * ratio; + float b = color1Argb[3] * inverseRatio + color2Argb[3] * ratio; + return argb((int) a, (int) r, (int) g, (int) b); + } + + // ========== + // = 颜色过渡 = + // ========== + + /** + * 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 + * @param startColor 开始颜色值 + * @param endColor 结束颜色值 + * @param ratio 过渡百分比 + * @return 计算后颜色值 + */ + public static int transitionColor( + final String startColor, + final String endColor, + final float ratio + ) { + return transitionColor(parseColor(startColor), parseColor(endColor), ratio); + } + + /** + * 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 + * @param startColor 开始颜色值 + * @param endColor 结束颜色值 + * @param ratio 过渡百分比 + * @return 计算后颜色值 + */ + public static int transitionColor( + final int startColor, + final int endColor, + final float ratio + ) { + int[] startArgb = getARGB(startColor); + int[] endArgb = getARGB(endColor); + + int startAlpha = startArgb[0]; + int startRed = startArgb[1]; + int startGreen = startArgb[2]; + int startBlue = startArgb[3]; + + int endAlpha = endArgb[0]; + int endRed = endArgb[1]; + int endGreen = endArgb[2]; + int endBlue = endArgb[3]; + + float a = (endAlpha - startAlpha) * ratio + startAlpha; + float r = (endRed - startRed) * ratio + startRed; + float g = (endGreen - startGreen) * ratio + startGreen; + float b = (endBlue - startBlue) * ratio + startBlue; + return argb((int) a, (int) r, (int) g, (int) b); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ConvertUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ConvertUtils.java new file mode 100644 index 0000000000..9cf43e15e2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ConvertUtils.java @@ -0,0 +1,2052 @@ +package dev.utils.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 转换工具类 ( Byte、Hex 等 ) + * @author Ttt + *
+ *     byte 是字节数据类型、有符号型的、占 1 个字节、大小范围为 [ -128 - 127]
+ *     当大于 127 时则开始缩进 127 = 127, 128 = -128, 129 = -127
+ *     char 是字符数据类型、无符号型的、占 2 个字节 (unicode 码 )、大小范围为 [0 - 65535]
+ *     

+ * Binary( 二进制 ) toBinaryString + * Oct( 八进制 ) + * Dec( 十进制 ) + * Hex( 十六进制 ) 以 0x 开始的数据表示十六进制 + *

+ * 位移加密: bytesBitwiseAND(byte[] bytes) + * @see
+ *
+ */ +public final class ConvertUtils { + + private ConvertUtils() { + } + + // 日志 TAG + private static final String TAG = ConvertUtils.class.getSimpleName(); + + // 用于建立十六进制字符的输出的小写字符数组 + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // 用于建立十六进制字符的输出的大写字符数组 + private static final char[] HEX_DIGITS_UPPER = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + /** + * Object 转换所需类型对象 + * @param object Object + * @param 泛型 + * @return Object convert T object + */ + public static T convert(final Object object) { + if (object == null) return null; + try { + return (T) object; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert"); + } + return null; + } + + /** + * Object 转 String + * @param value Value + * @return {@link String} + */ + public static String newString(final Object value) { + return newString(value, null, true); + } + + /** + * Object 转 String + * @param value Value + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newString( + final Object value, + final String defaultStr + ) { + return newString(value, defaultStr, true); + } + + /** + * Object 转 String + * @param value Value + * @param defaultStr 默认字符串 + * @param arrayDecode 是否 byte、char 数组进行 String 接码 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newString( + final Object value, + final String defaultStr, + final boolean arrayDecode + ) { + if (value != null) { + try { + if (arrayDecode) { + if (value instanceof byte[]) { + return new String((byte[]) value); + } + if (value instanceof char[]) { + return new String((char[]) value); + } + } + if (value instanceof String) { + return (String) value; + } + if (value instanceof StringBuffer) { + return new String((StringBuffer) value); + } + if (value instanceof StringBuilder) { + return new String((StringBuilder) value); + } + if (value instanceof CharSequence) { + return ((CharSequence) value).toString(); + } + throw new Exception("can not new string, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newString"); + } + } + return defaultStr; + } + + /** + * Object 转 String ( 不进行 Array 解码转 String ) + * @param value Value + * @return {@link String} + */ + public static String newStringNotArrayDecode(final Object value) { + return newString(value, null, false); + } + + /** + * Object 转 String ( 不进行 Array 解码转 String ) + * @param value Value + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newStringNotArrayDecode( + final Object value, + final String defaultStr + ) { + return newString(value, defaultStr, false); + } + + // = + + /** + * Object 转 String + * @param object Object + * @return {@link String} + */ + public static String toString(final Object object) { + return toString(object, null); + } + + /** + * Object 转 String + * @param object Object + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String toString( + final Object object, + final String defaultStr + ) { + if (object != null) { + try { + String strValue = newStringNotArrayDecode(object); + if (strValue != null) { + return strValue; + } + if (object instanceof Integer) { + return Integer.toString((Integer) object); + } + if (object instanceof Boolean) { + return Boolean.toString((Boolean) object); + } + if (object instanceof Long) { + return Long.toString((Long) object); + } + if (object instanceof Double) { + return Double.toString((Double) object); + } + if (object instanceof Float) { + return Float.toString((Float) object); + } + if (object instanceof Byte) { + return Byte.toString((Byte) object); + } + if (object instanceof Character) { + return Character.toString((Character) object); + } + if (object instanceof Short) { + return Short.toString((Short) object); + } + Class clazz = object.getClass(); + // 判断是否数组类型 + if (clazz.isArray()) { + // = 基本数据类型 = + if (clazz.isAssignableFrom(int[].class)) { + return Arrays.toString((int[]) object); + } else if (clazz.isAssignableFrom(boolean[].class)) { + return Arrays.toString((boolean[]) object); + } else if (clazz.isAssignableFrom(long[].class)) { + return Arrays.toString((long[]) object); + } else if (clazz.isAssignableFrom(double[].class)) { + return Arrays.toString((double[]) object); + } else if (clazz.isAssignableFrom(float[].class)) { + return Arrays.toString((float[]) object); + } else if (clazz.isAssignableFrom(byte[].class)) { + return Arrays.toString((byte[]) object); + } else if (clazz.isAssignableFrom(char[].class)) { + return Arrays.toString((char[]) object); + } else if (clazz.isAssignableFrom(short[].class)) { + return Arrays.toString((short[]) object); + } + return Arrays.toString((Object[]) object); + } + return object.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toString"); + } + } + return defaultStr; + } + + /** + * Object 转 Integer + * @param value Value + * @return Integer + */ + public static Integer toInt(final Object value) { + return toInt(value, DevFinal.DEFAULT.INT); + } + + /** + * Object 转 Integer + * @param value Value + * @param defaultValue 默认值 + * @return Integer, 如果转换失败则返回 defaultValue + */ + public static Integer toInt( + final Object value, + final Integer defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof Boolean) { + return (Boolean) value ? 1 : 0; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Integer.parseInt(strValue); + } + throw new Exception("can not cast to int, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toInt"); + } + return defaultValue; + } + + /** + * Object 转 Boolean + * @param value Value + * @return Boolean + */ + public static Boolean toBoolean(final Object value) { + return toBoolean(value, DevFinal.DEFAULT.BOOLEAN); + } + + /** + * Object 转 Boolean + * @param value Value + * @param defaultValue 默认值 + * @return Boolean, 如果转换失败则返回 defaultValue + */ + public static Boolean toBoolean( + final Object value, + final Boolean defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof Number) { + return ((Number) value).intValue() == 1; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + strValue = strValue.toLowerCase(); + // true、t、yes、y、1 + if ("true".equals(strValue) + || "t".equals(strValue) + || "yes".equals(strValue) + || "y".equals(strValue) + || "1".equals(strValue) + ) { + return Boolean.TRUE; + } + // false、f、no、n、0 + if ("false".equals(strValue) + || "f".equals(strValue) + || "no".equals(strValue) + || "n".equals(strValue) + || "0".equals(strValue) + ) { + return Boolean.FALSE; + } + } + throw new Exception("can not cast to boolean, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBoolean"); + } + return defaultValue; + } + + /** + * Object 转 Float + * @param value Value + * @return Float + */ + public static Float toFloat(final Object value) { + return toFloat(value, DevFinal.DEFAULT.FLOAT); + } + + /** + * Object 转 Float + * @param value Value + * @param defaultValue 默认值 + * @return Float, 如果转换失败则返回 defaultValue + */ + public static Float toFloat( + final Object value, + final Float defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Float) { + return (Float) value; + } + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Float.parseFloat(strValue); + } + throw new Exception("can not cast to float, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toFloat"); + } + return defaultValue; + } + + /** + * Object 转 Double + * @param value Value + * @return Double + */ + public static Double toDouble(final Object value) { + return toDouble(value, DevFinal.DEFAULT.DOUBLE); + } + + /** + * Object 转 Double + * @param value Value + * @param defaultValue 默认值 + * @return Double, 如果转换失败则返回 defaultValue + */ + public static Double toDouble( + final Object value, + final Double defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Double.parseDouble(strValue); + } + throw new Exception("can not cast to double, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toDouble"); + } + return defaultValue; + } + + /** + * Object 转 Long + * @param value Value + * @return Long + */ + public static Long toLong(final Object value) { + return toLong(value, DevFinal.DEFAULT.LONG); + } + + /** + * Object 转 Long + * @param value Value + * @param defaultValue 默认值 + * @return Long, 如果转换失败则返回 defaultValue + */ + public static Long toLong( + final Object value, + final Long defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Long.parseLong(strValue); + } + throw new Exception("can not cast to long, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toLong"); + } + return defaultValue; + } + + /** + * Object 转 Short + * @param value Value + * @return Short + */ + public static Short toShort(final Object value) { + return toShort(value, DevFinal.DEFAULT.SHORT); + } + + /** + * Object 转 Short + * @param value Value + * @param defaultValue 默认值 + * @return Short, 如果转换失败则返回 defaultValue + */ + public static Short toShort( + final Object value, + final Short defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Short) { + return (Short) value; + } + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return Short.parseShort(strValue); + } + throw new Exception("can not cast to short, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toShort"); + } + return defaultValue; + } + + /** + * Object 转 Character + * @param value Value + * @return Character + */ + public static Character toChar(final Object value) { + return toChar(value, DevFinal.DEFAULT.CHAR); + } + + /** + * Object 转 Character + * @param value Value + * @param defaultValue 默认值 + * @return Character, 如果转换失败则返回 defaultValue + */ + public static Character toChar( + final Object value, + final Character defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Character) { + return (Character) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.length() == 1) { + return strValue.charAt(0); + } + } + throw new Exception("can not cast to char, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toChar"); + } + return defaultValue; + } + + /** + * Object 转 Byte + * @param value Value + * @return Byte + */ + public static byte toByte(final Object value) { + return toByte(value, DevFinal.DEFAULT.BYTE); + } + + /** + * Object 转 Byte + * @param value Value + * @param defaultValue 默认值 + * @return Byte, 如果转换失败则返回 defaultValue + */ + public static byte toByte( + final Object value, + final Byte defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Byte) { + return (Byte) value; + } + if (value instanceof Number) { + return ((Number) value).byteValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return Byte.parseByte(strValue); + } + throw new Exception("can not cast to byte, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toByte"); + } + return defaultValue; + } + + /** + * Object 转 BigDecimal + * @param value Value + * @return BigDecimal + */ + public static BigDecimal toBigDecimal(final Object value) { + return toBigDecimal(value, DevFinal.DEFAULT.BIG_DECIMAL); + } + + /** + * Object 转 BigDecimal + * @param value Value + * @param defaultValue 默认值 + * @return BigDecimal, 如果转换失败则返回 defaultValue + */ + public static BigDecimal toBigDecimal( + final Object value, + final BigDecimal defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + if (value instanceof BigInteger) { + return new BigDecimal((BigInteger) value); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return new BigDecimal(strValue); + } + throw new Exception("can not cast to BigDecimal, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBigDecimal"); + } + return defaultValue; + } + + /** + * Object 转 BigInteger + * @param value Value + * @return BigInteger + */ + public static BigInteger toBigInteger(final Object value) { + return toBigInteger(value, DevFinal.DEFAULT.BIG_INTEGER); + } + + /** + * Object 转 BigInteger + * @param value Value + * @param defaultValue 默认值 + * @return BigInteger, 如果转换失败则返回 defaultValue + */ + public static BigInteger toBigInteger( + final Object value, + final BigInteger defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof BigInteger) { + return (BigInteger) value; + } + if (value instanceof Float || value instanceof Double) { + return BigInteger.valueOf(((Number) value).longValue()); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return new BigInteger(strValue); + } + throw new Exception("can not cast to BigInteger, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBigInteger"); + } + return defaultValue; + } + + /** + * Object 获取 char[] + * @param value Value + * @return char[] + */ + public static char[] toChars(final Object value) { + try { + if (value instanceof char[]) { + return (char[]) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return strValue.toCharArray(); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toChars"); + } + return null; + } + + /** + * Object 获取 byte[] + * @param value Value + * @return byte[] + */ + public static byte[] toBytes(final Object value) { + try { + if (value instanceof byte[]) { + return (byte[]) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return strValue.getBytes(); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBytes"); + } + return null; + } + + /** + * char 转换 unicode 编码 + * @param value char + * @return int + */ + public static int toCharInt(final char value) { + return value; + } + + /** + * Object 获取 char ( 默认第一位 ) + * @param value Value + * @param defaultValue 默认值 + * @return 第一位值, 如果获取失败则返回 defaultValue + */ + public static char charAt( + final Object value, + final char defaultValue + ) { + return charAt(value, 0, defaultValue); + } + + /** + * Object 获取 char + * @param value Value + * @param pos 索引 + * @param defaultValue 默认值 + * @return 指定索引的值, 如果获取失败则返回 defaultValue + */ + public static char charAt( + final Object value, + final int pos, + final char defaultValue + ) { + if (value == null || pos < 0) return defaultValue; + try { + return toChars(value)[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "charAt"); + } + return defaultValue; + } + + // = + + /** + * 字符串转换对应的进制 + *
+     *     如: parseInt("1f603", 16) = 128515
+     * 
+ * @param str 待处理字符串 + * @param radix 进制 + * @return 对应进制的值 + */ + public static int parseInt( + final String str, + final int radix + ) { + if (str == null) return -1; + try { + return Integer.parseInt(str, radix); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseInt"); + } + return -1; + } + + /** + * 字符串转换对应的进制 + * @param str 待处理字符串 + * @param radix 进制 + * @return 对应进制的值 + */ + public static long parseLong( + final String str, + final int radix + ) { + if (str == null) return -1L; + try { + return Long.parseLong(str, radix); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseLong"); + } + return -1L; + } + + // = + + /** + * 将 short 转换成字节数组 + * @param data short + * @return byte[] + */ + public static byte[] valueOf(final short data) { + try { + byte[] bytes = new byte[2]; + for (int i = 0; i < 2; i++) { + int offset = (bytes.length - 1 - i) * 8; + bytes[i] = (byte) ((data >>> offset) & 0xff); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "valueOf"); + } + return null; + } + + /** + * 将 int 转换成字节数组 + * @param data int + * @return byte[] + */ + public static byte[] valueOf(final int data) { + try { + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) { + int offset = (bytes.length - 1 - i) * 8; + bytes[i] = (byte) ((data >>> offset) & 0xFF); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "valueOf"); + } + return null; + } + + // = + + /** + * byte[] 转为 Object + * @param bytes byte[] + * @return {@link Object} + */ + public static Object bytesToObject(final byte[] bytes) { + if (bytes == null) return null; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return ois.readObject(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToObject"); + } finally { + CloseUtils.closeIOQuietly(ois); + } + return null; + } + + /** + * Object 转为 byte[] + * @param object Object + * @return byte[] + */ + public static byte[] objectToBytes(final Object object) { + if (object == null) return null; + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(object); + return baos.toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "objectToBytes"); + } finally { + CloseUtils.closeIOQuietly(oos); + } + return null; + } + + // = + + /** + * byte[] 转换 char[], 并且进行补码 + * @param data byte[] + * @return char[] + */ + public static char[] bytesToChars(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + char[] chars = new char[len]; + for (int i = 0; i < len; i++) { + chars[i] = (char) (data[i] & 0xff); + } + return chars; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToChars"); + } + return null; + } + + /** + * char[] 转换 byte[] + * @param data char[] + * @return byte[] + */ + public static byte[] charsToBytes(final char[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + byte[] bytes = new byte[len]; + for (int i = 0; i < len; i++) { + bytes[i] = (byte) (data[i]); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "charsToBytes"); + } + return null; + } + + // ============================================ + // = (int、double、long、float)[] 转换 String[] = + // ============================================ + + /** + * int[] 转换 string[] + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings(final int[] datas) { + return intsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 string[] + * @param off 起始值 + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings( + final int off, + final int[] datas + ) { + return intsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * double[] 转换 string[] + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings(final double[] datas) { + return doublesToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 string[] + * @param off 起始值 + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings( + final int off, + final double[] datas + ) { + return doublesToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings( + final int off, + final int length, + final double[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * long[] 转换 string[] + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings(final long[] datas) { + return longsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 string[] + * @param off 起始值 + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings( + final int off, + final long[] datas + ) { + return longsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings( + final int off, + final int length, + final long[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * float[] 转换 string[] + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings(final float[] datas) { + return floatsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 string[] + * @param off 起始值 + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings( + final int off, + final float[] datas + ) { + return floatsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings( + final int off, + final int length, + final float[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // ==================================== + // = int[] 转换 (double、long、float)[] = + // ==================================== + + /** + * int[] 转换 double[] + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles(final int[] datas) { + return intsToDoubles(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 double[] + * @param off 起始值 + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles( + final int off, + final int[] datas + ) { + return intsToDoubles(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + double[] doubles = new double[length - off]; + for (int i = 0, len = doubles.length; i < len; i++) { + doubles[i] = datas[off + i]; + } + return doubles; + } + + // = + + /** + * int[] 转换 long[] + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs(final int[] datas) { + return intsToLongs(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 long[] + * @param off 起始值 + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs( + final int off, + final int[] datas + ) { + return intsToLongs(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + long[] longs = new long[length - off]; + for (int i = 0, len = longs.length; i < len; i++) { + longs[i] = datas[off + i]; + } + return longs; + } + + // = + + /** + * int[] 转换 float[] + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats(final int[] datas) { + return intsToFloats(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 float[] + * @param off 起始值 + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats( + final int off, + final int[] datas + ) { + return intsToFloats(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + float[] floats = new float[length - off]; + for (int i = 0, len = floats.length; i < len; i++) { + floats[i] = datas[off + i]; + } + return floats; + } + + // ============================================ + // = String[] 转换 (int、double、long、float)[] = + // ============================================ + + /** + * string[] 转换 int[] + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts(final String... datas) { + return stringsToInts(0, (datas != null) ? datas.length : 0, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final String... datas + ) { + return stringsToInts(off, (datas != null) ? datas.length : 0, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final int length, + final String... datas + ) { + return stringsToInts(off, length, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final int length, + final int errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = Integer.parseInt(datas[off + i]); + } catch (Exception e) { + ints[i] = errorValue; + } + } + return ints; + } + + // = + + /** + * string[] 转换 double[] + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles(final String... datas) { + return stringsToDoubles(0, (datas != null) ? datas.length : 0, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final String... datas + ) { + return stringsToDoubles(off, (datas != null) ? datas.length : 0, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final int length, + final String... datas + ) { + return stringsToDoubles(off, length, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final int length, + final double errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + double[] doubles = new double[length - off]; + for (int i = 0, len = doubles.length; i < len; i++) { + try { + doubles[i] = Double.parseDouble(datas[off + i]); + } catch (Exception e) { + doubles[i] = errorValue; + } + } + return doubles; + } + + // = + + /** + * string[] 转换 long[] + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs(final String... datas) { + return stringsToLongs(0, (datas != null) ? datas.length : 0, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final String... datas + ) { + return stringsToLongs(off, (datas != null) ? datas.length : 0, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final int length, + final String... datas + ) { + return stringsToLongs(off, length, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final int length, + final long errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + long[] longs = new long[length - off]; + for (int i = 0, len = longs.length; i < len; i++) { + try { + longs[i] = Long.parseLong(datas[off + i]); + } catch (Exception e) { + longs[i] = errorValue; + } + } + return longs; + } + + // = + + /** + * string[] 转换 float[] + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats(final String... datas) { + return stringsToFloats(0, (datas != null) ? datas.length : 0, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final String... datas + ) { + return stringsToFloats(off, (datas != null) ? datas.length : 0, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final int length, + final String... datas + ) { + return stringsToFloats(off, length, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final int length, + final float errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + float[] floats = new float[length - off]; + for (int i = 0, len = floats.length; i < len; i++) { + try { + floats[i] = Float.parseFloat(datas[off + i]); + } catch (Exception e) { + floats[i] = errorValue; + } + } + return floats; + } + + // ==================================== + // = (double、long、float)[] 转换 int[] = + // ==================================== + + /** + * double[] 转换 int[] + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts(final double[] datas) { + return doublesToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 int[] + * @param off 起始值 + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts( + final int off, + final double[] datas + ) { + return doublesToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts( + final int off, + final int length, + final double[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // = + + /** + * long[] 转换 int[] + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts(final long[] datas) { + return longsToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 int[] + * @param off 起始值 + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts( + final int off, + final long[] datas + ) { + return longsToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts( + final int off, + final int length, + final long[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // = + + /** + * float[] 转换 int[] + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts(final float[] datas) { + return floatsToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 int[] + * @param off 起始值 + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts( + final int off, + final float[] datas + ) { + return floatsToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts( + final int off, + final int length, + final float[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // =================== + // = Binary ( 二进制 ) = + // =================== + + /** + * 将 字节转换 为 二进制字符串 + * @param bytes byte[] + * @return 二进制字符串 + */ + public static String toBinaryString(final byte... bytes) { + if (bytes == null || bytes.length == 0) return null; + try { + StringBuilder builder = new StringBuilder(); + for (byte value : bytes) { + for (int j = 7; j >= 0; --j) { + builder.append(((value >> j) & 0x01) == 0 ? '0' : '1'); + } + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBinaryString"); + } + return null; + } + + /** + * 二进制字符串 转换 byte[] 解码 + *
+     *     例: "011000010111001101100100" 传入 decodeBinary
+     *     返回 byte[], 通过 new String(byte()) 获取配合 toBinaryString 使用
+     * 
+ * @param str 待处理字符串 + * @return 解码后的 byte[] + */ + public static byte[] decodeBinary(final String str) { + if (str == null) return null; + try { + String data = str; + int lenMod = data.length() % 8; + int byteLen = data.length() / 8; + // add "0" until length to 8 times + if (lenMod != 0) { + for (int i = lenMod; i < 8; i++) { + data = "0" + data; + } + byteLen++; + } + byte[] bytes = new byte[byteLen]; + for (int i = 0; i < byteLen; ++i) { + for (int j = 0; j < 8; ++j) { + bytes[i] <<= 1; + bytes[i] |= data.charAt(i * 8 + j) - '0'; + } + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decodeBinary"); + } + return null; + } + + // ================== + // = Hex ( 十六进制 ) = + // ================== + + /** + * 判断是否十六进制数据 + * @param data 待检验数据 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHex(final String data) { + if (data == null) return false; + // 获取数据长度 + int len = data.length(); + if (len > 0) { + for (int i = len - 1; i >= 0; i--) { + char c = data.charAt(i); + if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) { + return false; + } + } + return true; + } + return false; + } + + /** + * 将十六进制字节数组解码 + * @param data 十六进制 byte[] + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final byte[] data) { + return decodeHex((ArrayUtils.length(data) == 0) ? null : bytesToChars(data)); + } + + /** + * 将十六进制字符串解码 + * @param str 十六进制字符串 + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final String str) { + return decodeHex(StringUtils.isEmpty(str) ? null : str.toCharArray()); + } + + /** + * 将十六进制字符数组解码 + * @param data 十六进制 char[] + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final char[] data) { + if (data == null) return null; + try { + int len = data.length; + byte[] out = new byte[len >> 1]; + // 十六进制由两个字符组成 + for (int i = 0, j = 0; j < len; i++) { + int d = toDigit(data[j], j) << 4; + j++; + d = d | toDigit(data[j], j); + j++; + out[i] = (byte) (d & 0xFF); + } + return out; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decodeHex"); + } + return null; + } + + /** + * 十六进制 char 转换 int + * @param hexChar 十六进制 char + * @return 十六进制转 ( 解 ) 码后的整数 + */ + public static int hexToInt(final char hexChar) { + if (hexChar >= '0' && hexChar <= '9') { + return hexChar - '0'; + } else if (hexChar >= 'A' && hexChar <= 'F') { + return hexChar - 'A' + 10; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * 将十六进制字符转换成一个整数 + * @param ch 十六进制 char + * @param index 十六进制字符在字符数组中的位置 + * @return 一个整数 + * @throws Exception 当 ch 不是一个合法的十六进制字符时, 抛出运行时异常 + */ + private static int toDigit( + final char ch, + final int index + ) + throws Exception { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new Exception( + String.format( + "Illegal hexadecimal character %s at index %s", + ch, index + ) + ); + } + return digit; + } + + // = + + // toHexString(0x1f603) = 1f603 + // parseInt("1f603", 16) = 128515 + // toHexString(128515) = 1f603 + + /** + * int 转换十六进制 + *
+     *     如: toHexString(0x1f603) 返回: 1f603
+     * 
+ * @param value int + * @return 十六进制字符串 + */ + public static String toHexString(final int value) { + try { + return Integer.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * long 转换十六进制 + * @param value long + * @return 十六进制字符串 + */ + public static String toHexString(final long value) { + try { + return Long.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * double 转换十六进制 + * @param value double + * @return 十六进制字符串 + */ + public static String toHexString(final double value) { + try { + return Double.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * float 转换十六进制 + * @param value float + * @return 十六进制字符串 + */ + public static String toHexString(final float value) { + try { + return Float.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + // = + + /** + * 将 string 转换为 十六进制 char[] + * @param str 待处理字符串 + * @return 十六进制 char[] + */ + public static char[] toHexChars(final String str) { + return toHexChars(str, true); + } + + /** + * 将 string 转换为 十六进制 char[] + * @param str 待处理字符串 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制 char[] + */ + public static char[] toHexChars( + final String str, + final boolean toLowerCase + ) { + return toHexChars( + StringUtils.isEmpty(str) ? null : str.getBytes(), + toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER + ); + } + + // = + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @return 十六进制 char[] + */ + public static char[] toHexChars(final byte[] data) { + return toHexChars(data, true); + } + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制 char[] + */ + public static char[] toHexChars( + final byte[] data, + final boolean toLowerCase + ) { + return toHexChars(data, toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER); + } + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @param hexDigits {@link #HEX_DIGITS}、{@link #HEX_DIGITS_UPPER} + * @return 十六进制 char[] + */ + private static char[] toHexChars( + final byte[] data, + final char[] hexDigits + ) { + if (data == null || hexDigits == null) return null; + try { + return toHexString(data, hexDigits).toCharArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexChars"); + } + return null; + } + + // = + + /** + * 将 string 转换 十六进制字符串 + * @param str 待转换数据 + * @return 十六进制字符串 + */ + public static String toHexString(final String str) { + return toHexString(str, true); + } + + /** + * 将 string 转换 十六进制字符串 + * @param str 待转换数据 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制字符串 + */ + public static String toHexString( + final String str, + final boolean toLowerCase + ) { + return toHexString( + StringUtils.isEmpty(str) ? null : str.getBytes(), + toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER + ); + } + + // = + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @return 十六进制字符串 + */ + public static String toHexString(final byte[] data) { + return toHexString(data, true); + } + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制字符串 + */ + public static String toHexString( + final byte[] data, + final boolean toLowerCase + ) { + return toHexString(data, toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER); + } + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @param hexDigits {@link #HEX_DIGITS}、{@link #HEX_DIGITS_UPPER} + * @return 十六进制字符串 + */ + private static String toHexString( + final byte[] data, + final char[] hexDigits + ) { + if (data == null || hexDigits == null) return null; + try { + int len = data.length; + StringBuilder builder = new StringBuilder(len); + for (byte value : data) { + builder.append(hexDigits[(value & 0xf0) >>> 4]); + builder.append(hexDigits[value & 0x0f]); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + // = + +// String data = "test"; +// // 转换二进制字符串 +// String result = toBinaryString(data.getBytes()); +// // 获取二进制数据 +// byte[] bytes = result.getBytes(); +// // 位移编码 +// bytesBitwiseAND(bytes); +// // = +// // 位移解码 +// bytesBitwiseAND(bytes); +// // 二进制数据解码 +// byte[] byteResult = decodeBinary(new String(bytes)); +// // 转换为原始数据 +// String data1 = new String(byteResult); +// // 判断是否一致 +// boolean equals = data.equals(data1); + + /** + * 按位求补 byte[] 位移编解码 ( 共用同一个方法 ) + * @param data byte[] + */ + public static void bytesBitwiseAND(final byte[] data) { + if (data == null) return; + for (int i = 0, len = data.length; i < len; i++) { + int d = data[i]; + d = ~d; // 按位补运算符, 翻转操作数的每一位, 即 0 变成 1, 1 变成 0, 再通过反转后的二进制初始化回十六进制 + data[i] = (byte) d; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/CoordinateUtils.java b/lib/DevApp/src/main/java/dev/utils/common/CoordinateUtils.java new file mode 100644 index 0000000000..c77b647312 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/CoordinateUtils.java @@ -0,0 +1,378 @@ +package dev.utils.common; + +import static java.lang.Math.PI; + +/** + * detail: 坐标 ( GPS 纠偏 ) 相关工具类 + * @author Ttt + *
+ *     地球坐标系 (WGS-84)
+ *     火星坐标系 (GCJ-02)
+ *     百度坐标系 (BD09)
+ *     

+ * @see
+ * @see + * 根据两点经纬度计算距离 + * @see + * 根据经纬度计算两点之间的距离的公式推导过程以及 google.maps 的测距函数 + * @see + *

+ * 1. WGS84 坐标系: 即地球坐标系, 国际上通用的坐标系, 设备一般包含 GPS 芯片或者北斗芯片获取的经纬度为 WGS84 地理坐标系 + * 谷歌地图采用的是 WGS84 地理坐标系 ( 中国范围除外 ) GPS 设备得到的经纬度就是在 WGS84 坐标系下的经纬度, 通常通过底层接口得到的定位信息都是 WGS84 坐标系 + *

+ * 2. GCJ02 坐标系: 即火星坐标系, 是由中国国家测绘局制订的地理信息系统的坐标系统, 由 WGS84 坐标系经加密后的坐标系 + * 国家规定, 中国大陆所有公开地理数据都需要至少用 GCJ-02 进行加密, 也就是说我们从国内公司的产品中得到的数据, 一定是经过了加密的 + * 绝大部分国内互联网地图提供商都是使用 GCJ-02 坐标系, 包括高德地图, 谷歌地图中国区等 + *

+ * 3. BD09 坐标系: 即百度坐标系, 其在 GCJ-02 上多增加了一次变换, 用来保护用户隐私, 从百度产品中得到的坐标都是 BD-09 坐标系 + *
+ */ +public final class CoordinateUtils { + + private CoordinateUtils() { + } + + private static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + private static final double A = 6378245.0; + private static final double EE = 0.00669342162296594323; + + /** + * BD09 坐标转 GCJ02 坐标 + * @param lng BD09 坐标纬度 + * @param lat BD09 坐标经度 + * @return GCJ02 坐标 [ 经度, 纬度 ] + */ + public static double[] bd09ToGcj02( + final double lng, + final double lat + ) { + double x = lng - 0.0065; + double y = lat - 0.006; + double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + double gg_lng = z * Math.cos(theta); + double gg_lat = z * Math.sin(theta); + return new double[]{gg_lng, gg_lat}; + } + + /** + * GCJ02 坐标转 BD09 坐标 + * @param lng GCJ02 坐标经度 + * @param lat GCJ02 坐标纬度 + * @return BD09 坐标 [ 经度, 纬度 ] + */ + public static double[] gcj02ToBd09( + final double lng, + final double lat + ) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new double[]{bd_lng, bd_lat}; + } + + /** + * GCJ02 坐标转 WGS84 坐标 + * @param lng GCJ02 坐标经度 + * @param lat GCJ02 坐标纬度 + * @return WGS84 坐标 [ 经度, 纬度 ] + */ + public static double[] gcj02ToWGS84( + final double lng, + final double lat + ) { + if (outOfChina(lng, lat)) return new double[]{lng, lat}; + double dlat = transformLat(lng - 105.0, lat - 35.0); + double dlng = transformLng(lng - 105.0, lat - 35.0); + double radlat = lat / 180.0 * PI; + double magic = Math.sin(radlat); + magic = 1 - EE * magic * magic; + double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); + double mglat = lat + dlat; + double mglng = lng + dlng; + return new double[]{lng * 2 - mglng, lat * 2 - mglat}; + } + + /** + * WGS84 坐标转 GCJ02 坐标 + * @param lng WGS84 坐标经度 + * @param lat WGS84 坐标纬度 + * @return GCJ02 坐标 [ 经度, 纬度 ] + */ + public static double[] wgs84ToGcj02( + final double lng, + final double lat + ) { + if (outOfChina(lng, lat)) return new double[]{lng, lat}; + double dlat = transformLat(lng - 105.0, lat - 35.0); + double dlng = transformLng(lng - 105.0, lat - 35.0); + double radlat = lat / 180.0 * PI; + double magic = Math.sin(radlat); + magic = 1 - EE * magic * magic; + double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); + double mglat = lat + dlat; + double mglng = lng + dlng; + return new double[]{mglng, mglat}; + } + + /** + * BD09 坐标转 WGS84 坐标 + * @param lng BD09 坐标经度 + * @param lat BD09 坐标纬度 + * @return WGS84 坐标 [ 经度, 纬度 ] + */ + public static double[] bd09ToWGS84( + final double lng, + final double lat + ) { + double[] gcj = bd09ToGcj02(lng, lat); + return gcj02ToWGS84(gcj[0], gcj[1]); + } + + /** + * WGS84 坐标转 BD09 坐标 + * @param lng WGS84 坐标经度 + * @param lat WGS84 坐标纬度 + * @return BD09 坐标 [ 经度, 纬度 ] + */ + public static double[] wgs84ToBd09( + final double lng, + final double lat + ) { + double[] gcj = wgs84ToGcj02(lng, lat); + return gcj02ToBd09(gcj[0], gcj[1]); + } + + /** + * 转换经度 + * @param lng 经度 + * @param lat 纬度 + * @return 转换后的经度 + */ + private static double transformLat( + final double lng, + final double lat + ) { + double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat + * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + /** + * 转换纬度 + * @param lng 经度 + * @param lat 纬度 + * @return 转换后的纬度 + */ + private static double transformLng( + final double lng, + final double lat + ) { + double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng + * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 判断是否中国境外 + * @param lng 经度 + * @param lat 纬度 + * @return {@code true} yes, {@code false} no + */ + public static boolean outOfChina( + final double lng, + final double lat + ) { + return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271; + } + + // ========== + // = 计算坐标 = + // ========== + + // 赤道半径 + private static final double EARTH_RADIUS = 6378.137; + + /** + * 计算弧度角 + * @param degree 度数 + * @return 弧度角 + */ + private static double rad(final double degree) { + return degree * Math.PI / 180.0; + } + + /** + * 计算两个坐标相距距离 ( 单位: 米 ) + *
+     *     计算点与点直线间距离
+     * 
+ * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标相距距离 ( 单位: 米 ) + */ + public static double getDistance( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double radLat1 = rad(originLat); + double radLat2 = rad(targetLat); + double a = radLat1 - radLat2; + double b = rad(originLng) - rad(targetLng); + double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); + s = s * EARTH_RADIUS; + // 保留两位小数 + s = Math.round(s * 100D) / 100D; + s = s * 1000; + return s; + } + + /** + * 计算两个坐标的方向角度 + *
+     *     以 origin 为参考点坐标, 获取目标坐标位于参考点坐标方向
+     * 
+ * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标的方向角度 + */ + public static double getAngle( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double radLat1 = rad(originLat); + double radLng1 = rad(originLng); + double radLat2 = rad(targetLat); + double radLng2 = rad(targetLng); + double ret; + if (radLng1 == radLng2) { + if (radLat1 > radLat2) { + return 270; // 北半球的情况, 南半球忽略 + } else if (radLat1 < radLat2) { + return 90; + } else { + return Integer.MAX_VALUE; // 位置完全相同 + } + } + ret = 4 * Math.pow(Math.sin((radLat1 - radLat2) / 2), 2) + - Math.pow(Math.sin((radLng1 - radLng2) / 2) + * (Math.cos(radLat1) - Math.cos(radLat2)), 2); + ret = Math.sqrt(ret); + ret = ret / Math.sin(Math.abs(radLng1 - radLng2) / 2) + * (Math.cos(radLat1) + Math.cos(radLat2)); + ret = Math.atan(ret) / Math.PI * 180; + if (radLng1 > radLng2) { // 以 origin 为参考点坐标 + if (radLat1 > radLat2) { + ret += 180; + } else { + ret = 180 - ret; + } + } else if (radLat1 > radLat2) { + ret = 360 - ret; + } + return ret; + } + + /** + * 计算两个坐标的方向 + * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标的方向 + */ + public static Direction getDirection( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double angle = getAngle(originLng, originLat, targetLng, targetLat); + return getDirection(angle); + } + + /** + * 通过角度获取方向 + * @param angle 角度 + * @return 方向 + */ + public static Direction getDirection(final double angle) { + if (angle == Integer.MAX_VALUE) return Direction.SAME; + if ((angle <= 10) || (angle > 350)) { + return Direction.RIGHT; + } + if ((angle > 10) && (angle <= 80)) { + return Direction.RIGHT_TOP; + } + if ((angle > 80) && (angle <= 100)) { + return Direction.TOP; + } + if ((angle > 100) && (angle <= 170)) { + return Direction.LEFT_TOP; + } + if ((angle > 170) && (angle <= 190)) { + return Direction.LEFT; + } + if ((angle > 190) && (angle <= 260)) { + return Direction.LEFT_BOTTOM; + } + if ((angle > 260) && (angle <= 280)) { + return Direction.BOTTOM; + } + if ((angle > 280) && (angle <= 350)) { + return Direction.RIGHT_BOTTOM; + } + return Direction.SAME; + } + + /** + * detail: 坐标方向 + * @author Ttt + */ + public enum Direction { + + SAME("相同"), // 坐标相同 + TOP("北"), // 上 ( 北 ) + BOTTOM("南"), // 下 ( 南 ) + LEFT("西"), // 左 ( 西 ) + RIGHT("东"), // 右 ( 东 ) + LEFT_TOP("西北"), // 左上 ( 西北 ) + LEFT_BOTTOM("西南"), // 左下 ( 西南 ) + RIGHT_TOP("东北"), // 右上 ( 东北 ) + RIGHT_BOTTOM("东南"); // 右下 ( 东南 ) + + private final String value; + + Direction(String value) { + this.value = value; + } + + /** + * 获取中文方向值 + * @return 中文方向值 + */ + public String getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/DateUtils.java b/lib/DevApp/src/main/java/dev/utils/common/DateUtils.java new file mode 100644 index 0000000000..b70ec63e02 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/DateUtils.java @@ -0,0 +1,2921 @@ +package dev.utils.common; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 日期工具类 + * @author Ttt + */ +public final class DateUtils { + + private DateUtils() { + } + + // 日志 TAG + private static final String TAG = DateUtils.class.getSimpleName(); + + // 线程安全 SimpleDateFormat Map + private static final ThreadLocal> SDEF_THREAD_LOCAL + = new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap<>(); + } + }; + + /** + * 获取默认 SimpleDateFormat ( yyyy-MM-dd HH:mm:ss ) + * @return {@link SimpleDateFormat} + */ + public static SimpleDateFormat getDefaultFormat() { + return getSafeDateFormat(DevFinal.TIME.yyyyMMddHHmmss_HYPHEN); + } + + /** + * 获取对应时间格式线程安全 SimpleDateFormat + * @param pattern 时间格式 + * @return {@link SimpleDateFormat} + */ + public static SimpleDateFormat getSafeDateFormat(final String pattern) { + if (pattern == null) return null; + Map sdfMap = SDEF_THREAD_LOCAL.get(); + SimpleDateFormat format = sdfMap.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern); + sdfMap.put(pattern, format); + } + return format; + } + + // = + + /** + * 获取 Calendar + * @return {@link Calendar} + */ + public static Calendar getCalendar() { + return Calendar.getInstance(); + } + + /** + * 获取 Calendar + * @param millis 时间毫秒 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final long millis) { + if (millis == -1L) return null; + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(millis); + return calendar; + } + + /** + * 获取 Calendar + * @param date 日期 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final Date date) { + if (date == null) return null; + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } + + /** + * 获取 Calendar + * @param time 时间 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final String time) { + return getCalendar(parseLong(time, getDefaultFormat())); + } + + /** + * 获取 Calendar + * @param time 时间 + * @param pattern 时间格式 + * @return {@link Calendar} + */ + public static Calendar getCalendar( + final String time, + final String pattern + ) { + return getCalendar(parseLong(time, getSafeDateFormat(pattern))); + } + + /** + * 获取 Calendar + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@link Calendar} + */ + public static Calendar getCalendar( + final String time, + final SimpleDateFormat format + ) { + return getCalendar(parseLong(time, format)); + } + + // = + + /** + * 获取当前时间 Date + * @return 当前时间 Date + */ + public static Date getCurrentTime() { + return Calendar.getInstance().getTime(); + } + + /** + * 获取当前时间毫秒 + * @return 当前时间毫秒 + */ + public static long getCurrentTimeMillis() { + return getDateTime(Calendar.getInstance().getTime()); + } + + /** + * 获取 Date Time + * @param date 日期 + * @return Date Time + */ + public static long getDateTime(final Date date) { + if (date != null) return date.getTime(); + return -1L; + } + + // = + + /** + * 获取当前时间的字符串 + * @return 当前时间的字符串 + */ + public static String getDateNow() { + return formatTime(getCurrentTimeMillis(), getDefaultFormat()); + } + + /** + * 获取当前时间的字符串 + * @param pattern 时间格式 + * @return 当前时间的字符串 + */ + public static String getDateNow(final String pattern) { + return formatTime(getCurrentTimeMillis(), getSafeDateFormat(pattern)); + } + + /** + * 获取当前时间的字符串 + * @param format {@link SimpleDateFormat} + * @return 当前时间的字符串 + */ + public static String getDateNow(final SimpleDateFormat format) { + return formatTime(getCurrentTimeMillis(), format); + } + + // = + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @return 按照指定格式的日期字符串 + */ + public static String formatDate(final Date date) { + return formatTime(getDateTime(date), getDefaultFormat()); + } + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @param pattern 时间格式 + * @return 按照指定格式的日期字符串 + */ + public static String formatDate( + final Date date, + final String pattern + ) { + return formatTime(getDateTime(date), getSafeDateFormat(pattern)); + } + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @param format {@link SimpleDateFormat} + * @return 按照指定格式的日期字符串 + */ + public static String formatDate( + final Date date, + final SimpleDateFormat format + ) { + return formatTime(getDateTime(date), format); + } + + // = + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @return 按照指定格式的日期字符串 + */ + public static String formatTime(final long millis) { + return formatTime(millis, getDefaultFormat()); + } + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @param pattern 时间格式 + * @return 按照指定格式的日期字符串 + */ + public static String formatTime( + final long millis, + final String pattern + ) { + return formatTime(millis, getSafeDateFormat(pattern)); + } + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @param format {@link SimpleDateFormat} + * @return 按照指定格式的日期字符串 + */ + public static String formatTime( + final long millis, + final SimpleDateFormat format + ) { + if (millis == -1L || format == null) return null; + try { + return format.format(millis); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatTime"); + } + return null; + } + + // = + + /** + * 将时间毫秒转换成 Date + * @param millis 时间毫秒 + * @return {@link Date} + */ + public static Date parseDate(final long millis) { + if (millis == -1L) return null; + return new Date(millis); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @return {@link Date} + */ + public static Date parseDate(final String time) { + return parseDate(parseLong(time, getDefaultFormat())); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @param pattern 时间格式 + * @return {@link Date} + */ + public static Date parseDate( + final String time, + final String pattern + ) { + return parseDate(parseLong(time, getSafeDateFormat(pattern))); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@link Date} + */ + public static Date parseDate( + final String time, + final SimpleDateFormat format + ) { + return parseDate(parseLong(time, format)); + } + + // = + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @return 毫秒时间 + */ + public static long parseLong(final String time) { + return parseLong(time, getDefaultFormat()); + } + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @param pattern 时间格式 + * @return 毫秒时间 + */ + public static long parseLong( + final String time, + final String pattern + ) { + return parseLong(time, getSafeDateFormat(pattern)); + } + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 毫秒时间 + */ + public static long parseLong( + final String time, + final SimpleDateFormat format + ) { + if (time == null || format == null) return -1L; + try { + return format.parse(time).getTime(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseLong"); + } + return -1L; + } + + // = + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param pattern 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseStringDefault( + final String time, + final String pattern + ) { + return parseString( + time, getDefaultFormat(), + getSafeDateFormat(pattern) + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param format 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseStringDefault( + final String time, + final SimpleDateFormat format + ) { + return parseString( + time, getDefaultFormat(), format + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param timePattern time 的时间格式 + * @param pattern 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseString( + final String time, + final String timePattern, + final String pattern + ) { + return parseString( + time, getSafeDateFormat(timePattern), + getSafeDateFormat(pattern) + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param timeFormat time 的时间格式 + * @param format 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseString( + final String time, + final SimpleDateFormat timeFormat, + final SimpleDateFormat format + ) { + if (time != null && timeFormat != null && format != null) { + try { + long timeLong = parseLong(time, timeFormat); + // 把时间转为所需格式字符串 + return formatTime(timeLong, format); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseString"); + } + } + return null; + } + + // ========== + // = 获取时间 = + // ========== + + // ===== + // = 年 = + // ===== + + /** + * 获取年份 + * @param calendar {@link Calendar} + * @return 年份 + */ + public static int getYear(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.YEAR); + return -1; + } + + // = + + /** + * 获取年份 + * @return 年份 + */ + public static int getYear() { + return getYear(getCalendar()); + } + + /** + * 获取年份 + * @param millis 时间毫秒 + * @return 年份 + */ + public static int getYear(final long millis) { + return getYear(getCalendar(millis)); + } + + /** + * 获取年份 + * @param date 日期 + * @return 年份 + */ + public static int getYear(final Date date) { + return getYear(getCalendar(date)); + } + + /** + * 获取年份 + * @param time 时间 + * @return 年份 + */ + public static int getYear(final String time) { + return getYear(parseLong(time)); + } + + /** + * 获取年份 + * @param time 时间 + * @param pattern 时间格式 + * @return 年份 + */ + public static int getYear( + final String time, + final String pattern + ) { + return getYear(parseLong(time, pattern)); + } + + /** + * 获取年份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 年份 + */ + public static int getYear( + final String time, + final SimpleDateFormat format + ) { + return getYear(parseLong(time, format)); + } + + // ===== + // = 月 = + // ===== + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param calendar {@link Calendar} + * @return 月份 + */ + public static int getMonth(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.MONTH) + 1; + return -1; + } + + // = + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @return 月份 + */ + public static int getMonth() { + return getMonth(getCalendar()); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param millis 时间毫秒 + * @return 月份 + */ + public static int getMonth(final long millis) { + return getMonth(getCalendar(millis)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param date 日期 + * @return 月份 + */ + public static int getMonth(final Date date) { + return getMonth(getCalendar(date)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @return 月份 + */ + public static int getMonth(final String time) { + return getMonth(parseLong(time)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @param pattern 时间格式 + * @return 月份 + */ + public static int getMonth( + final String time, + final String pattern + ) { + return getMonth(parseLong(time, pattern)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 月份 + */ + public static int getMonth( + final String time, + final SimpleDateFormat format + ) { + return getMonth(parseLong(time, format)); + } + + // ======= + // = 天数 = + // ======= + + /** + * 获取天数 + * @param calendar {@link Calendar} + * @return 天数 + */ + public static int getDay(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.DAY_OF_MONTH); + return -1; + } + + // = + + /** + * 获取天数 + * @return 天数 + */ + public static int getDay() { + return getDay(getCalendar()); + } + + /** + * 获取天数 + * @param millis 时间毫秒 + * @return 天数 + */ + public static int getDay(final long millis) { + return getDay(getCalendar(millis)); + } + + /** + * 获取天数 + * @param date 日期 + * @return 天数 + */ + public static int getDay(final Date date) { + return getDay(getCalendar(date)); + } + + /** + * 获取天数 + * @param time 时间 + * @return 天数 + */ + public static int getDay(final String time) { + return getDay(parseLong(time)); + } + + /** + * 获取天数 + * @param time 时间 + * @param pattern 时间格式 + * @return 天数 + */ + public static int getDay( + final String time, + final String pattern + ) { + return getDay(parseLong(time, pattern)); + } + + /** + * 获取天数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 天数 + */ + public static int getDay( + final String time, + final SimpleDateFormat format + ) { + return getDay(parseLong(time, format)); + } + + // ======= + // = 星期 = + // ======= + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param calendar {@link Calendar} + * @return 星期数 + */ + public static int getWeek(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.DAY_OF_WEEK); + return -1; + } + + // = + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @return 星期数 + */ + public static int getWeek() { + return getWeek(getCalendar()); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param millis 时间毫秒 + * @return 星期数 + */ + public static int getWeek(final long millis) { + return getWeek(getCalendar(millis)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param date 日期 + * @return 星期数 + */ + public static int getWeek(final Date date) { + return getWeek(getCalendar(date)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @return 星期数 + */ + public static int getWeek(final String time) { + return getWeek(parseLong(time)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 星期数 + */ + public static int getWeek( + final String time, + final String pattern + ) { + return getWeek(parseLong(time, pattern)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 星期数 + */ + public static int getWeek( + final String time, + final SimpleDateFormat format + ) { + return getWeek(parseLong(time, format)); + } + + // ======= + // = 24H = + // ======= + + /** + * 获取小时 ( 24 ) + * @param calendar {@link Calendar} + * @return 小时 + */ + public static int get24Hour(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.HOUR_OF_DAY); + return -1; + } + + // = + + /** + * 获取小时 ( 24 ) + * @return 小时 + */ + public static int get24Hour() { + return get24Hour(getCalendar()); + } + + /** + * 获取小时 ( 24 ) + * @param millis 时间毫秒 + * @return 小时 + */ + public static int get24Hour(final long millis) { + return get24Hour(getCalendar(millis)); + } + + /** + * 获取小时 ( 24 ) + * @param date 日期 + * @return 小时 + */ + public static int get24Hour(final Date date) { + return get24Hour(getCalendar(date)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @return 小时 + */ + public static int get24Hour(final String time) { + return get24Hour(parseLong(time)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 小时 + */ + public static int get24Hour( + final String time, + final String pattern + ) { + return get24Hour(parseLong(time, pattern)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 小时 + */ + public static int get24Hour( + final String time, + final SimpleDateFormat format + ) { + return get24Hour(parseLong(time, format)); + } + + // ======= + // = 12H = + // ======= + + /** + * 获取小时 ( 12 ) + * @param calendar {@link Calendar} + * @return 小时 + */ + public static int get12Hour(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.HOUR); + return -1; + } + + // = + + /** + * 获取小时 ( 12 ) + * @return 小时 + */ + public static int get12Hour() { + return get12Hour(getCalendar()); + } + + /** + * 获取小时 ( 12 ) + * @param millis 时间毫秒 + * @return 小时 + */ + public static int get12Hour(final long millis) { + return get12Hour(getCalendar(millis)); + } + + /** + * 获取小时 ( 12 ) + * @param date 日期 + * @return 小时 + */ + public static int get12Hour(final Date date) { + return get12Hour(getCalendar(date)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @return 小时 + */ + public static int get12Hour(final String time) { + return get12Hour(parseLong(time)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 小时 + */ + public static int get12Hour( + final String time, + final String pattern + ) { + return get12Hour(parseLong(time, pattern)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 小时 + */ + public static int get12Hour( + final String time, + final SimpleDateFormat format + ) { + return get12Hour(parseLong(time, format)); + } + + // ======= + // = 分钟 = + // ======= + + /** + * 获取分钟 + * @param calendar {@link Calendar} + * @return 分钟 + */ + public static int getMinute(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.MINUTE); + return -1; + } + + // = + + /** + * 获取分钟 + * @return 分钟 + */ + public static int getMinute() { + return getMinute(getCalendar()); + } + + /** + * 获取分钟 + * @param millis 时间毫秒 + * @return 分钟 + */ + public static int getMinute(final long millis) { + return getMinute(getCalendar(millis)); + } + + /** + * 获取分钟 + * @param date 日期 + * @return 分钟 + */ + public static int getMinute(final Date date) { + return getMinute(getCalendar(date)); + } + + /** + * 获取分钟 + * @param time 时间 + * @return 分钟 + */ + public static int getMinute(final String time) { + return getMinute(parseLong(time)); + } + + /** + * 获取分钟 + * @param time 时间 + * @param pattern 时间格式 + * @return 分钟 + */ + public static int getMinute( + final String time, + final String pattern + ) { + return getMinute(parseLong(time, pattern)); + } + + /** + * 获取分钟 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 分钟 + */ + public static int getMinute( + final String time, + final SimpleDateFormat format + ) { + return getMinute(parseLong(time, format)); + } + + // ======= + // = 秒数 = + // ======= + + /** + * 获取秒数 + * @param calendar {@link Calendar} + * @return 秒数 + */ + public static int getSecond(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.SECOND); + return -1; + } + + // = + + /** + * 获取秒数 + * @return 秒数 + */ + public static int getSecond() { + return getSecond(getCalendar()); + } + + /** + * 获取秒数 + * @param millis 时间毫秒 + * @return 秒数 + */ + public static int getSecond(final long millis) { + return getSecond(getCalendar(millis)); + } + + /** + * 获取秒数 + * @param date 日期 + * @return 秒数 + */ + public static int getSecond(final Date date) { + return getSecond(getCalendar(date)); + } + + /** + * 获取秒数 + * @param time 时间 + * @return 秒数 + */ + public static int getSecond(final String time) { + return getSecond(parseLong(time)); + } + + /** + * 获取秒数 + * @param time 时间 + * @param pattern 时间格式 + * @return 秒数 + */ + public static int getSecond( + final String time, + final String pattern + ) { + return getSecond(parseLong(time, pattern)); + } + + /** + * 获取秒数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 秒数 + */ + public static int getSecond( + final String time, + final SimpleDateFormat format + ) { + return getSecond(parseLong(time, format)); + } + + // ======= + // = 上午 = + // ======= + + /** + * 是否上午 + * @param calendar {@link Calendar} + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final Calendar calendar) { + if (calendar != null) { + return calendar.get(GregorianCalendar.AM_PM) == Calendar.AM; + } + return false; + } + + // = + + /** + * 是否上午 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM() { + return isAM(getCalendar()); + } + + /** + * 是否上午 + * @param millis 时间毫秒 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final long millis) { + return isAM(getCalendar(millis)); + } + + /** + * 是否上午 + * @param date 日期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final Date date) { + return isAM(getCalendar(date)); + } + + /** + * 是否上午 + * @param time 时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final String time) { + return isAM(parseLong(time)); + } + + /** + * 是否上午 + * @param time 时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM( + final String time, + final String pattern + ) { + return isAM(parseLong(time, pattern)); + } + + /** + * 是否上午 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM( + final String time, + final SimpleDateFormat format + ) { + return isAM(parseLong(time, format)); + } + + // ======= + // = 下午 = + // ======= + + /** + * 是否下午 + * @param calendar {@link Calendar} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final Calendar calendar) { + if (calendar != null) { + return calendar.get(GregorianCalendar.AM_PM) == Calendar.PM; + } + return false; + } + + // = + + /** + * 是否下午 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM() { + return isPM(getCalendar()); + } + + /** + * 是否下午 + * @param millis 时间毫秒 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final long millis) { + return isPM(getCalendar(millis)); + } + + /** + * 是否下午 + * @param date 日期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final Date date) { + return isPM(getCalendar(date)); + } + + /** + * 是否下午 + * @param time 时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final String time) { + return isPM(parseLong(time)); + } + + /** + * 是否下午 + * @param time 时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM( + final String time, + final String pattern + ) { + return isPM(parseLong(time, pattern)); + } + + /** + * 是否下午 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM( + final String time, + final SimpleDateFormat format + ) { + return isPM(parseLong(time, format)); + } + + // ========== + // = 年份判断 = + // ========== + + /** + * 是否对应年份 + * @param calendar {@link Calendar} + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final Calendar calendar, + final int year + ) { + return year != -1 && year == getYear(calendar); + } + + // = + + /** + * 是否对应年份 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear(final int year) { + return isYear(getCalendar(), year); + } + + /** + * 是否对应年份 + * @param millis 时间毫秒 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final long millis, + final int year + ) { + return isYear(getCalendar(millis), year); + } + + /** + * 是否对应年份 + * @param date 日期 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final Date date, + final int year + ) { + return isYear(getCalendar(date), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final int year + ) { + return isYear(parseLong(time), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param pattern 时间格式 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final String pattern, + final int year + ) { + return isYear(parseLong(time, pattern), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final SimpleDateFormat format, + final int year + ) { + return isYear(parseLong(time, format), year); + } + + // ========== + // = 月份判断 = + // ========== + + /** + * 是否对应月份 + * @param calendar {@link Calendar} + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final Calendar calendar, + final int month + ) { + return month != -1 && month == getMonth(calendar); + } + + // = + + /** + * 是否对应月份 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth(final int month) { + return isMonth(getCalendar(), month); + } + + /** + * 是否对应月份 + * @param millis 时间毫秒 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final long millis, + final int month + ) { + return isMonth(getCalendar(millis), month); + } + + /** + * 是否对应月份 + * @param date 日期 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final Date date, + final int month + ) { + return isMonth(getCalendar(date), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final int month + ) { + return isMonth(parseLong(time), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param pattern 时间格式 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final String pattern, + final int month + ) { + return isMonth(parseLong(time, pattern), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final SimpleDateFormat format, + final int month + ) { + return isMonth(parseLong(time, format), month); + } + + // ========== + // = 天数判断 = + // ========== + + /** + * 是否对应天数 + * @param calendar {@link Calendar} + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final Calendar calendar, + final int day + ) { + return day != -1 && day == getDay(calendar); + } + + // = + + /** + * 是否对应天数 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay(final int day) { + return isDay(getCalendar(), day); + } + + /** + * 是否对应天数 + * @param millis 时间毫秒 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final long millis, + final int day + ) { + return isDay(getCalendar(millis), day); + } + + /** + * 是否对应天数 + * @param date 日期 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final Date date, + final int day + ) { + return isDay(getCalendar(date), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final int day + ) { + return isDay(parseLong(time), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param pattern 时间格式 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final String pattern, + final int day + ) { + return isDay(parseLong(time, pattern), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final SimpleDateFormat format, + final int day + ) { + return isDay(parseLong(time, format), day); + } + + // ========== + // = 星期判断 = + // ========== + + /** + * 是否对应星期 + * @param calendar {@link Calendar} + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final Calendar calendar, + final int week + ) { + return week != -1 && week == getWeek(calendar); + } + + // = + + /** + * 是否对应星期 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek(final int week) { + return isWeek(getCalendar(), week); + } + + /** + * 是否对应星期 + * @param millis 时间毫秒 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final long millis, + final int week + ) { + return isWeek(getCalendar(millis), week); + } + + /** + * 是否对应星期 + * @param date 日期 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final Date date, + final int week + ) { + return isWeek(getCalendar(date), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final int week + ) { + return isWeek(parseLong(time), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param pattern 时间格式 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final String pattern, + final int week + ) { + return isWeek(parseLong(time, pattern), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final SimpleDateFormat format, + final int week + ) { + return isWeek(parseLong(time, format), week); + } + + // ========== + // = 小时判断 = + // ========== + + /** + * 是否对应小时 + * @param calendar {@link Calendar} + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final Calendar calendar, + final int hour + ) { + return hour != -1 && hour == get24Hour(calendar); + } + + // = + + /** + * 是否对应小时 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour(final int hour) { + return isHour(getCalendar(), hour); + } + + /** + * 是否对应小时 + * @param millis 时间毫秒 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final long millis, + final int hour + ) { + return isHour(getCalendar(millis), hour); + } + + /** + * 是否对应小时 + * @param date 日期 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final Date date, + final int hour + ) { + return isHour(getCalendar(date), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final int hour + ) { + return isHour(parseLong(time), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param pattern 时间格式 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final String pattern, + final int hour + ) { + return isHour(parseLong(time, pattern), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final SimpleDateFormat format, + final int hour + ) { + return isHour(parseLong(time, format), hour); + } + + // ========== + // = 分钟判断 = + // ========== + + /** + * 是否对应分钟 + * @param calendar {@link Calendar} + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final Calendar calendar, + final int minute + ) { + return minute != -1 && minute == getMinute(calendar); + } + + // = + + /** + * 是否对应分钟 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute(final int minute) { + return isMinute(getCalendar(), minute); + } + + /** + * 是否对应分钟 + * @param millis 时间毫秒 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final long millis, + final int minute + ) { + return isMinute(getCalendar(millis), minute); + } + + /** + * 是否对应分钟 + * @param date 日期 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final Date date, + final int minute + ) { + return isMinute(getCalendar(date), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final int minute + ) { + return isMinute(parseLong(time), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param pattern 时间格式 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final String pattern, + final int minute + ) { + return isMinute(parseLong(time, pattern), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final SimpleDateFormat format, + final int minute + ) { + return isMinute(parseLong(time, format), minute); + } + + // ========== + // = 秒数判断 = + // ========== + + /** + * 是否对应秒数 + * @param calendar {@link Calendar} + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final Calendar calendar, + final int second + ) { + return second != -1 && second == getSecond(calendar); + } + + // = + + /** + * 是否对应秒数 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond(final int second) { + return isSecond(getCalendar(), second); + } + + /** + * 是否对应秒数 + * @param millis 时间毫秒 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final long millis, + final int second + ) { + return isSecond(getCalendar(millis), second); + } + + /** + * 是否对应秒数 + * @param date 日期 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final Date date, + final int second + ) { + return isSecond(getCalendar(date), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final int second + ) { + return isSecond(parseLong(time), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param pattern 时间格式 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final String pattern, + final int second + ) { + return isSecond(parseLong(time, pattern), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final SimpleDateFormat format, + final int second + ) { + return isSecond(parseLong(time, format), second); + } + + // = + + /** + * 获取秒数倍数 + * @param millis 时间毫秒 + * @return 秒数倍数 + */ + public static int getSecondMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.SECOND_MS); + } + + /** + * 获取分钟倍数 + * @param millis 时间毫秒 + * @return 分钟倍数 + */ + public static int getMinuteMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.MINUTE_MS); + } + + /** + * 获取小时倍数 + * @param millis 时间毫秒 + * @return 小时倍数 + */ + public static int getHourMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.HOUR_MS); + } + + /** + * 获取天数倍数 + * @param millis 时间毫秒 + * @return 天数倍数 + */ + public static int getDayMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.DAY_MS); + } + + /** + * 获取周数倍数 + * @param millis 时间毫秒 + * @return 周数倍数 + */ + public static int getWeekMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.WEEK_MS); + } + + /** + * 获取对应单位倍数 + * @param millis 时间毫秒 + * @param unit 毫秒单位 ( 除数 ) + * @return 对应单位倍数 + */ + public static int getMillisMultiple( + final long millis, + final long unit + ) { + if (millis == -1L) return -1; + return NumberUtils.multipleI(millis, unit); + } + + // ============ + // = 时间差计算 = + // ============ + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param millis 时间毫秒 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final long millis) { + if (millis == -1L) return -1L; + return millis - System.currentTimeMillis(); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param date 日期 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final Date date) { + return getTimeDiffByCurrent(getDateTime(date)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final String time) { + return getTimeDiffByCurrent(parseLong(time)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @param pattern 把 time 转换成需要的格式 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent( + final String time, + final String pattern + ) { + return getTimeDiffByCurrent(parseLong(time, pattern)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @param format 把 time 转换成需要的格式 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent( + final String time, + final SimpleDateFormat format + ) { + return getTimeDiffByCurrent(parseLong(time, format)); + } + + // = + + /** + * 获取时间差 + * @param time1 时间 + * @param time2 对比时间 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final String time2 + ) { + return getTimeDiff( + time1, getDefaultFormat(), + time2, getDefaultFormat() + ); + } + + /** + * 获取时间差 + * @param time1 时间 + * @param pattern1 时间格式 + * @param time2 对比时间 + * @param pattern2 对比时间格式 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final String pattern1, + final String time2, + final String pattern2 + ) { + return getTimeDiff( + time1, getSafeDateFormat(pattern1), + time2, getSafeDateFormat(pattern2) + ); + } + + /** + * 获取时间差 + * @param time1 时间 + * @param timeFormat1 时间格式 + * @param time2 对比时间 + * @param timeFormat2 对比时间格式 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final SimpleDateFormat timeFormat1, + final String time2, + final SimpleDateFormat timeFormat2 + ) { + long timeLong1 = parseLong(time1, timeFormat1); + if (timeLong1 == -1L) return -1L; + long timeLong2 = parseLong(time2, timeFormat2); + if (timeLong2 == -1L) return -1L; + return timeLong1 - timeLong2; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断是否闰年 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLeapYear(final int year) { + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + } + + /** + * 根据年份、月份, 获取对应的天数 ( 完整天数, 无判断是否属于未来日期 ) + * @param year 年份 + * @param month 月份 + * @return 指定年份所属的月份的天数 + */ + public static int getMonthDayNumberAll( + final int year, + final int month + ) { + int number = 31; + // 判断返回的标识数字 + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + number = 31; + break; + case 2: + if (isLeapYear(year)) { + number = 29; + } else { + number = 28; + } + break; + case 4: + case 6: + case 9: + case 11: + number = 30; + break; + } + return number; + } + + /** + * 根据年份, 获取对应的月份 + *
+     *     传入历史、以及未来年份都返回对应年月份
+     *     传入当前年份则返回当前月份
+     * 
+ * @param year 年份 + * @return 内部判断是否相同一年, 不能超过限制未来的月份 + */ + public static int getYearMonthNumber(final int year) { + if (year == getYear()) return getMonth(); + return 12; + } + + /** + * 根据年份、月份, 获取对应的天数 + *
+     *     传入历史、以及未来年月份都返回对应年月份的天数
+     *     传入当前年月份则返回当前天数
+     * 
+ * @param year 年份 + * @param month 月份 + * @return 内部判断是否相同一年、月份, 不能超过限制未来的天数 + */ + public static int getMonthDayNumber( + final int year, + final int month + ) { + if (year == getYear() && month == getMonth()) { + return getDay(); + } + return getMonthDayNumberAll(year, month); + } + + // = + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero(final int time) { + return timeAddZero(time, true); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @param appendZero 是否自动补 0 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero( + final int time, + final boolean appendZero + ) { + return NumberUtils.addZero(time, appendZero); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero(final long time) { + return timeAddZero(time, true); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @param appendZero 是否自动补 0 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero( + final long time, + final boolean appendZero + ) { + return NumberUtils.addZero(time, appendZero); + } + + // = + + /** + * 生成 HH 按时间排序数组 + * @param appendZero 是否自动补 0 + * @return 按小时排序的数组 + */ + public static String[] getArrayToHH(final boolean appendZero) { + List lists = getListToHH(appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 HH 按时间排序集合 + * @param appendZero 是否自动补 0 + * @return 按小时排序的集合 + */ + public static List getListToHH(final boolean appendZero) { + List lists = new ArrayList<>(); + for (int i = 0; i < 24; i++) { + lists.add(timeAddZero(i, appendZero)); + } + return lists; + } + + // = + + /** + * 生成 MM 按时间排序数组 + * @param appendZero 是否自动补 0 + * @return 按分钟排序的数组 + */ + public static String[] getArrayToMM(final boolean appendZero) { + List lists = getListToMM(appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 MM 按时间排序集合 + * @param appendZero 是否自动补 0 + * @return 按分钟排序的集合 + */ + public static List getListToMM(final boolean appendZero) { + List lists = new ArrayList<>(); + for (int i = 0; i < 60; i++) { + lists.add(timeAddZero(i, appendZero)); + } + return lists; + } + + // = + + /** + * 生成 HH:mm 按间隔时间排序数组 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @param appendZero 是否自动补 0 + * @return 指定格式的数组 + */ + public static String[] getArrayToHHMM( + final int type, + final boolean appendZero + ) { + List lists = getListToHHMM(type, appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 HH:mm 按间隔时间排序集合 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @param appendZero 是否自动补 0 + * @return 指定格式的集合 + */ + public static List getListToHHMM( + final int type, + final boolean appendZero + ) { + List lists = new ArrayList<>(); + switch (type) { + case 0: + for (int i = 0; i < 24; i++) { + lists.add(timeAddZero(i, appendZero) + ":00"); + } + break; + case 1: + for (int i = 0; i < 96; i++) { // 00 15 30 45 = 4 (24 * 4) + if (i % 2 == 0) { // 判断是否偶数 00、30 + // 小时数 + String hour = timeAddZero(i / 4, appendZero); + // 分钟数 + String minute = i % 4 == 0 ? "00" : "30"; + // 累加时间 + lists.add(hour + ":" + minute); + } else { // 15、45 + // 小时数 + String hour = timeAddZero(i / 4, appendZero); + // 分钟数 + String minute = (i - 1) % 4 == 0 ? "15" : "45"; + // 累加时间 + lists.add(hour + ":" + minute); + } + } + break; + case 2: + for (int i = 0; i < 48; i++) { // 00 30 = 2 (24 * 2) + // 小时处理 + int hour = i / 2; + // 属于偶数 + if (i % 2 == 0) { + lists.add(timeAddZero(hour, appendZero) + ":00"); + } else { + lists.add(timeAddZero(hour, appendZero) + ":30"); + } + } + break; + } + return lists; + } + + /** + * 获取 HH:mm 按间隔时间排序的集合中, 指定时间所在索引 + * @param time HH:mm 格式 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @return 指定数据, 在对应格式类型内的索引 + */ + public static int getListToHHMMPosition( + final String time, + final int type + ) { + if (time != null && time.length() != 0) { + // 进行拆分 + String[] timeSplit = time.split(":"); + if (timeSplit.length == 2) { + // 转换小时 + int hour = ConvertUtils.toInt(timeSplit[0], -1); + // 判断是否小于 0 + if (hour < 0) { + return -1; + } else if (hour > 24) { + return -1; + } + + // 判断格式, 进行格式处理 + switch (type) { + case 0: + return hour; + case 1: + case 2: + // 转换分钟 + int minute = ConvertUtils.toInt(timeSplit[1], -1); + // 判断是否小于 0 + if (minute < 0) { + return -1; + } else if (minute > 59) { + return -1; + } + // 判断间隔 + if (type == 1) { + if (minute < 15) { + return hour * 4; + } else if (minute < 30) { + return hour * 4 + 1; + } else if (minute < 45) { + return hour * 4 + 2; + } else { + return hour * 4 + 3; + } + } else { // 30 分钟一个间隔 + if (minute >= 30) { // 属于 30, 需要加 1 + return hour * 2 + 1; + } else { + return hour * 2; + } + } + } + } + } + return -1; + } + + // ============= + // = Unit Span = + // ============= + + // 毫秒格式化范围 + private static final long[] MILLIS_UNIT_SPANS = { + 86400000, 3600000, 60000, 1000, 1 + }; + // 毫秒格式化单位 + private static final String[] MILLIS_UNITS = { + "天", "小时", "分钟", "秒", "毫秒" + }; + + /** + * 转换时间 + * @param millis 时间毫秒 + * @param precision precision = 1, return 天 + * precision = 2, return 天, 小时 + * precision = 3, return 天, 小时, 分钟 + * precision = 4, return 天, 小时, 分钟, 秒 + * precision = 5, return 天, 小时, 分钟, 秒, 毫秒 + * @param appendZero 是否自动补 0 + * @return 转换指定格式的时间字符串 + */ + public static String millisToFitTimeSpan( + final long millis, + final int precision, + final boolean appendZero + ) { + if (precision >= 1 && precision <= 5) { + return FormatUtils.unitSpanOf( + precision, appendZero, "" + ).formatBySpan( + millis, MILLIS_UNIT_SPANS, MILLIS_UNITS + ); + } + return ""; + } + + /** + * 转换时间为数组 + * @param millis 时间毫秒 + * @return int[5] { 天, 小时, 分钟, 秒, 毫秒 } + */ + public static int[] millisToTimeArrays(final long millis) { + if (millis > 0) { + long[] values = NumberUtils.calculateUnitL(millis, MILLIS_UNIT_SPANS); + return ConvertUtils.longsToInts(values); + } + return new int[5]; + } + + // = + + /** + * 传入时间毫秒, 获取 00:00:00 格式 ( 不处理大于一天 ) + * @param millis 时间毫秒 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertByMillis(final long millis) { + return timeConvertByMillis(millis, false); + } + + /** + * 传入时间毫秒, 获取 00:00:00 格式 + *
+     *     小时:分钟:秒
+     * 
+ * @param millis 时间毫秒 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertByMillis( + final long millis, + final boolean handlerMoreThanDay + ) { + int[] result = millisToTimeArrays(millis); + // 如果大于一天但不处理大于一天情况则返回 null + if (result[0] > 0 && !handlerMoreThanDay) { + return null; + } + return timeAddZero(result[0] * 24 + result[1]) + + ":" + timeAddZero(result[2]) + + ":" + timeAddZero(result[3]); + } + + // = + + /** + * 传入时间秒, 获取 00:00:00 格式 ( 不处理大于一天 ) + * @param second 时间 ( 秒 ) + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertBySecond(final long second) { + return timeConvertBySecond(second, false); + } + + /** + * 传入时间秒, 获取 00:00:00 格式 + * @param second 时间 ( 秒 ) + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertBySecond( + final long second, + final boolean handlerMoreThanDay + ) { + return timeConvertByMillis(second * 1000L, handlerMoreThanDay); + } + + // ================== + // = 判断是否在区间范围 = + // ================== + + /** + * 判断时间是否在 [startTime, endTime] 区间 + * @param time 待判断时间 ( 毫秒 ) + * @param startTime 开始时间 ( 毫秒 ) + * @param endTime 结束时间 ( 毫秒 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTime( + final long time, + final long startTime, + final long endTime + ) { + if (time == -1L || startTime == -1L || endTime == -1L) return false; + // 待校验时间 + Calendar check = Calendar.getInstance(); + check.setTimeInMillis(time); + // 开始时间 + Calendar begin = Calendar.getInstance(); + begin.setTimeInMillis(startTime); + // 结束时间 + Calendar end = Calendar.getInstance(); + end.setTimeInMillis(endTime); + // 判断是否在 begin 之后的时间, 并且在 end 之前的时间 + if (check.after(begin) && check.before(end)) { + return true; + } + // 判断时间相同情况 + return time == startTime || time == endTime; + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTime( + final Date time, + final Date startTime, + final Date endTime + ) { + return isInTime(getDateTime(time), getDateTime(startTime), getDateTime(endTime)); + } + + // = + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final String pattern + ) { + return isInTimeFormat(time, startTime, endTime, pattern, false); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param pattern 时间格式 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final String pattern, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + getSafeDateFormat(pattern), + handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final SimpleDateFormat format + ) { + return isInTimeFormat(time, startTime, endTime, format, false); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + *
+     *     handlerMoreThanDay 参数注意事项
+     *     用于 {@link DevFinal.TIME#HHmm_COLON}、{@link DevFinal.TIME#HHmmss_COLON} 判断, 只有该格式判断可传入 true
+     *     其他都用于 false
+     * 
+ * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format {@link SimpleDateFormat} + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final SimpleDateFormat format, + final boolean handlerMoreThanDay + ) { + if (time == null || startTime == null || endTime == null) return false; + long check = parseLong(time, format); + long start = parseLong(startTime, format); + long end = parseLong(endTime, format); + if (check == -1L || start == -1L || end == -1L) return false; + // 大于一天的情况 ( 指的是结束时间在开始时间之前 ) + if (handlerMoreThanDay && end < start) { + // 结束属于第二天区域 + return check >= start || check <= end; + } + // 时间是否在 [startTime, endTime] 区间 + return check >= start && check <= end; + } + + // ======== + // = HHmm = + // ======== + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String startTime, + final String endTime + ) { + return isInTimeHHmm(startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + getDateNow(DevFinal.TIME.HHmm_COLON), startTime, endTime, + DevFinal.TIME.HHmm_COLON, handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String time, + final String startTime, + final String endTime + ) { + return isInTimeHHmm(time, startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String time, + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + DevFinal.TIME.HHmm_COLON, handlerMoreThanDay + ); + } + + // ========== + // = HHmmss = + // ========== + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String startTime, + final String endTime + ) { + return isInTimeHHmmss(startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + getDateNow(DevFinal.TIME.HHmmss_COLON), startTime, endTime, + DevFinal.TIME.HHmmss_COLON, handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String time, + final String startTime, + final String endTime + ) { + return isInTimeHHmmss(time, startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String time, + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + DevFinal.TIME.HHmmss_COLON, handlerMoreThanDay + ); + } + + // = + + /** + * 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) + * @param endTime 结束时间 HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiffHHmm(final String endTime) { + return getEndTimeDiff(System.currentTimeMillis(), endTime, DevFinal.TIME.HHmm_COLON); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) + * @param startTime 开始时间 + * @param endTime 结束时间 HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiffHHmm( + final long startTime, + final String endTime + ) { + return getEndTimeDiff(startTime, endTime, DevFinal.TIME.HHmm_COLON); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) + * @param endTime 结束时间 + * @param format 格式 如: HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiff( + final String endTime, + final String format + ) { + return getEndTimeDiff(System.currentTimeMillis(), endTime, format); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) + *
+     *     如当前时间 2018-12-07 15:27:23, 判断距离 14:39:20 (endTime) 有多久
+     *     如果过了这个时间段, 则返回 2018-12-08 14:39:20 ( 明天的这个时间段时间 )
+     *     如果没有过这个时间段 ( 如: 17:39:20) 则返回当天时间段 2018-12-07 17:39:20 (2018-12-07 + endTime)
+     * 
+ * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format 格式 如: HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiff( + final long startTime, + final String endTime, + final String format + ) { + if (startTime < 1 || endTime == null || format == null) return -1L; + try { + // 判断格式是否加了秒 + boolean isSecond = format.endsWith(":ss"); + // 获取开始时间 + String start = formatTime(startTime, format); + // 转换时间 + int startNumber = Integer.parseInt(start.replace(":", "")); + // 获取结束时间转换 + int endNumber = Integer.parseInt(endTime.replace(":", "")); + // 时间处理 + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(startTime)); // 设置当前时间 + // 如果当前时间大于结束时间, 表示非第二天 + if (startNumber > endNumber) { + // 时间累加一天 + calendar.add(Calendar.DATE, 1); // 当前日期加一天 + } + // 获取天数时间 + String yyyyMMddDate = formatDate(calendar.getTime(), DevFinal.TIME.yyyyMMdd_HYPHEN); + // 累加时间 + String yyyyMMddHHmmssDate = yyyyMMddDate + " " + endTime + (isSecond ? "" : ":00"); + // 返回转换后的时间 + return parseLong(yyyyMMddHHmmssDate, DevFinal.TIME.yyyyMMddHHmmss_HYPHEN); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getEndTimeDiff"); + } + return -1L; + } + + // ============ + // = 生肖、星座 = + // ============ + + // 生肖数组 + private static final String[] ZODIAC = { + "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊" + }; + + // 星座截止天数 + private static final int[] CONSTELLATION_DAY = { + 20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22 + }; + + // 星座对应日期 + private static final String[] CONSTELLATION_DATE = { + "01.20-02.18", "02.19-03.20", "03.21-04.19", "04.20-05.20", "05.21-06.21", "06.22-07.22", + "07.23-08.22", "08.23-09.22", "09.23-10.23", "10.24-11.22", "11.23-12.21", "12.22-01.19" + }; + + // 星座数组 + private static final String[] CONSTELLATION = { + "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", + "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座" + }; + + /** + * 获取生肖 + * @param year 年份 + * @return 生肖 + */ + public static String getZodiac(final int year) { + return ZODIAC[Math.abs(year) % 12]; + } + + /** + * 获取星座 + * @param month 月份 + * @param day 天数 + * @return 星座 + */ + public static String getConstellation( + final int month, + final int day + ) { + if (month > 12 || month < 1) return null; + return CONSTELLATION[day >= CONSTELLATION_DAY[month - 1] ? month - 1 : (month + 10) % 12]; + } + + /** + * 获取星座日期 + * @param month 月份 + * @param day 天数 + * @return 星座日期 + */ + public static String getConstellationDate( + final int month, + final int day + ) { + if (month > 12 || month < 1) return null; + return CONSTELLATION_DATE[day >= CONSTELLATION_DAY[month - 1] ? month - 1 : (month + 10) % 12]; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/DevCommonUtils.java b/lib/DevApp/src/main/java/dev/utils/common/DevCommonUtils.java new file mode 100644 index 0000000000..f4b093e32e --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/DevCommonUtils.java @@ -0,0 +1,253 @@ +package dev.utils.common; + +import java.util.Random; +import java.util.UUID; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.encrypt.MD5Utils; + +/** + * detail: 开发常用方法工具类 + * @author Ttt + */ +public final class DevCommonUtils { + + private DevCommonUtils() { + } + + // 日志 TAG + private static final String TAG = DevCommonUtils.class.getSimpleName(); + + // ============= + // = 计时相关处理 = + // ============= + + /** + * 耗时时间记录 + * @param builder 拼接 Builder + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@link StringBuilder} + */ + public static StringBuilder timeRecord( + final StringBuilder builder, + final long startTime, + final long endTime + ) { + return timeRecord(builder, null, startTime, endTime); + } + + /** + * 耗时时间记录 + * @param builder 拼接 Builder + * @param title 标题 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@link StringBuilder} + */ + public static StringBuilder timeRecord( + final StringBuilder builder, + final String title, + final long startTime, + final long endTime + ) { + if (builder == null) return builder; + // 使用时间 + long diffTime = endTime - startTime; + // 计算时间 + if (!StringUtils.isEmpty(title)) { + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append(title); + } + // 计算时间 + builder.append(DevFinal.SYMBOL.NEW_LINE).append("开始时间: ") + .append(DateUtils.formatTime(startTime)) + .append(DevFinal.SYMBOL.NEW_LINE).append("结束时间: ") + .append(DateUtils.formatTime(endTime)) + .append(DevFinal.SYMBOL.NEW_LINE).append("所用时间(毫秒): ") + .append(diffTime) + .append(DevFinal.SYMBOL.NEW_LINE).append("所用时间(秒): ") + .append(diffTime / 1000); + return builder; + } + + // = + + /** + * 获取操作时间 + * @param operateTime 操作时间 ( 毫秒 ) + * @return 操作时间 + */ + public static long getOperateTime(final long operateTime) { + return getOperateTime(operateTime, -1); + } + + /** + * 获取操作时间 + * @param operateTime 操作时间 ( 毫秒 ) + * @param randomTime 随机时间范围 ( 毫秒 ) + * @return 操作时间 + */ + public static long getOperateTime( + final long operateTime, + final int randomTime + ) { + int random = 0; + // 大于 2 才处理 + if (randomTime >= 2) { + // 随机时间 + random = RandomUtils.getRandom(randomTime); + } + // 返回操作时间 + return Math.max(0, operateTime) + random; + } + + /** + * 堵塞操作 + * @param sleepTime 堵塞时间 ( 毫秒 ) + */ + public static void sleepOperate(final long sleepTime) { + sleepOperate(sleepTime, -1); + } + + /** + * 堵塞操作 + * @param sleepTime 堵塞时间 ( 毫秒 ) + * @param randomTime 随机时间范围 ( 毫秒 ) + */ + public static void sleepOperate( + final long sleepTime, + final int randomTime + ) { + long time = getOperateTime(sleepTime, randomTime); + if (time > 0) { + try { + Thread.sleep(time); + } catch (Throwable throwable) { + JCLogUtils.eTag(TAG, throwable, "sleepOperate"); + } + } + } + + // = + + /** + * 判断是否网络资源 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHttpRes(final String str) { + if (!StringUtils.isEmpty(str)) { + // 属于第一位开始, 才是属于网络资源 + return str.toLowerCase().startsWith("http:") || + str.toLowerCase().startsWith("https:"); + } + return false; + } + + /** + * 循环 MD5 加密处理 + * @param str 待处理数据 + * @param number MD5 加密次数 + * @param isUppercase 是否大写处理 + * @param salts 特殊 salt 拼接 + * @return 循环加密后的字符串 + */ + public static String whileMD5( + final String str, + final int number, + final boolean isUppercase, + final String... salts + ) { + if (str != null && number >= 1) { + int saltLen = (salts != null) ? salts.length : 0; + // 临时字符串 + String tempString = str; + // 判断是否大写 + if (isUppercase) { + // 循环加密 + for (int i = 0; i < number; i++) { + if (saltLen > i) { + String salt = salts[i]; + if (salt != null) { + tempString = MD5Utils.md5Upper(tempString + salt); + } else { + tempString = MD5Utils.md5Upper(tempString); + } + } else { + tempString = MD5Utils.md5Upper(tempString); + } + } + } else { + // 循环加密 + for (int i = 0; i < number; i++) { + if (saltLen > i) { + String salt = salts[i]; + if (salt != null) { + tempString = MD5Utils.md5(tempString + salt); + } else { + tempString = MD5Utils.md5(tempString); + } + } else { + tempString = MD5Utils.md5(tempString); + } + } + } + return tempString; + } + return str; + } + + // ============ + // = 获取唯一数 = + // ============ + + /** + * 获取随机唯一数 + * @return {@link UUID} + */ + public static UUID randomUUID() { + return UUID.randomUUID(); + } + + /** + * 获取随机唯一数 HashCode + * @return 随机 UUID hashCode + */ + public static int randomUUIDToHashCode() { + return UUID.randomUUID().hashCode(); + } + + /** + * 获取随机唯一数 HashCode + * @param uuid {@link UUID} + * @return 随机 UUID hashCode + */ + public static int randomUUIDToHashCode(final UUID uuid) { + return (uuid != null) ? uuid.hashCode() : 0; + } + + /** + * 获取随机规则生成 UUID + * @return 随机规则生成 UUID + */ + public static UUID getRandomUUID() { + // 获取随机数 + String random1 = String.valueOf(900000 + new Random().nextInt(10000)); + // 获取随机数 + String random2 = String.valueOf(900000 + new Random().nextInt(10000)); + // 获取当前时间 + String time = System.currentTimeMillis() + random1 + random2; + // 生成唯一随机 UUID + return new UUID(time.hashCode(), ((long) random1.hashCode() << 32) | random2.hashCode()); + } + + /** + * 获取随机规则生成 UUID 字符串 + * @return 随机规则生成 UUID 字符串 + */ + public static String getRandomUUIDToString() { + return getRandomUUID().toString(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/EncodeUtils.java b/lib/DevApp/src/main/java/dev/utils/common/EncodeUtils.java new file mode 100644 index 0000000000..13d2abdff6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/EncodeUtils.java @@ -0,0 +1,260 @@ +package dev.utils.common; + +import dev.utils.common.cipher.Base64; + +/** + * detail: 编码工具类 + * @author Ttt + *
+ *     Base64 flags
+ *     

+ * CRLF 这个参数看起来比较眼熟, 它就是 Win 风格的换行符, 意思就是使用 CR LF 这一对作为一行的结尾而不是 Unix 风格的 LF + * DEFAULT 这个参数是默认, 使用默认的方法来加密 + * NO_PADDING 这个参数是略去加密字符串最后的 = + * NO_WRAP 这个参数意思是略去所有的换行符 ( 设置后 CRLF 就没用了 ) + * URL_SAFE 这个参数意思是加密时不使用对 URL 和文件名有特殊意义的字符来作为加密字符, 具体就是以 - 和 _ 取代 + 和 / + *
+ */ +public final class EncodeUtils { + + private EncodeUtils() { + } + + // ========== + // = Base64 = + // ========== + + // ============== + // = Base64 编码 = + // ============== + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode(final String input) { + return base64Encode(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode( + final String input, + final int flags + ) { + return base64Encode(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode(final byte[] input) { + return base64Encode(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return Base64.encode(input, flags); + } + + // = + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString(final String input) { + return base64EncodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString( + final String input, + final int flags + ) { + return base64EncodeToString(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString(final byte[] input) { + return base64EncodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return ConvertUtils.newString(Base64.encode(input, flags)); + } + + // ============== + // = Base64 解码 = + // ============== + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode(final String input) { + return base64Decode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode( + final String input, + final int flags + ) { + return base64Decode(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode(final byte[] input) { + return base64Decode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return Base64.decode(input, flags); + } + + // = + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString(final String input) { + return base64DecodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString( + final String input, + final int flags + ) { + return base64DecodeToString(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString(final byte[] input) { + return base64DecodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return ConvertUtils.newString(Base64.decode(input, flags)); + } + + // ======== + // = Html = + // ======== + + /** + * Html 字符串编码 + * @param input 待处理数据 + * @return Html 编码后的数据 + */ + public static String htmlEncode(final CharSequence input) { + if (input == null) return null; + StringBuilder builder = new StringBuilder(); + char ch; + for (int i = 0, len = input.length(); i < len; i++) { + ch = input.charAt(i); + switch (ch) { + case '<': + builder.append("<"); //$NON-NLS-1$ + break; + case '>': + builder.append(">"); //$NON-NLS-1$ + break; + case '&': + builder.append("&"); //$NON-NLS-1$ + break; + case '\'': + //http://www.w3.org/TR/xhtml1 + // The named character reference ' (the apostrophe, U+0027) was introduced in + // XML 1.0 but does not appear in HTML. Authors should therefore use ' instead + // of ' to work as expected in HTML 4 user agents. + builder.append("'"); //$NON-NLS-1$ + break; + case '"': + builder.append("""); //$NON-NLS-1$ + break; + default: + builder.append(ch); + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/FieldUtils.java b/lib/DevApp/src/main/java/dev/utils/common/FieldUtils.java new file mode 100644 index 0000000000..1aee8c40f0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/FieldUtils.java @@ -0,0 +1,417 @@ +package dev.utils.common; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 变量字段工具类 + * @author Ttt + */ +public final class FieldUtils { + + private FieldUtils() { + } + + // 日志 TAG + private static final String TAG = FieldUtils.class.getSimpleName(); + + // = + + /** + * 获取变量对象 + * @param object {@link Object} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getField( + final Object object, + final String name + ) { + return getField(ClassUtils.getClass(object), name); + } + + /** + * 获取变量对象 + *
+     *     public 成员变量, 包括基类
+     * 
+ * @param clazz {@link Class} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getField( + final Class clazz, + final String name + ) { + if (clazz != null && name != null) { + try { + return clazz.getField(name); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getField"); + } + } + return null; + } + + // = + + /** + * 获取变量对象 + * @param object {@link Object} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getDeclaredField( + final Object object, + final String name + ) { + return getDeclaredField(ClassUtils.getClass(object), name); + } + + /** + * 获取变量对象 + *
+     *     所有成员变量, 不包括基类
+     * 
+ * @param clazz {@link Class} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getDeclaredField( + final Class clazz, + final String name + ) { + if (clazz != null && name != null) { + try { + return clazz.getDeclaredField(name); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredField"); + } + } + return null; + } + + // = + + /** + * 获取变量对象数组 + * @param object {@link Object} + * @return Field[] + */ + public static Field[] getFields(final Object object) { + return getFields(ClassUtils.getClass(object)); + } + + /** + * 获取变量对象数组 + *
+     *     public 成员变量, 包括基类
+     * 
+ * @param clazz {@link Class} + * @return Field[] + */ + public static Field[] getFields(final Class clazz) { + if (clazz != null) { + try { + return clazz.getFields(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFields"); + } + } + return null; + } + + // = + + /** + * 获取变量对象数组 + * @param object {@link Object} + * @return Field[] + */ + public static Field[] getDeclaredFields(final Object object) { + return getDeclaredFields(ClassUtils.getClass(object)); + } + + /** + * 获取变量对象数组 + *
+     *     所有成员变量, 不包括基类
+     * 
+ * @param clazz {@link Class} + * @return Field[] + */ + public static Field[] getDeclaredFields(final Class clazz) { + if (clazz != null) { + try { + return clazz.getDeclaredFields(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredFields"); + } + } + return null; + } + + // = + + /** + * 设置字段的值 + * @param field {@link Field} + * @param object Object + * @param value Object-Value + * @return 对应的 Object + */ + public static Object set( + final Field field, + final Object object, + final Object value + ) { + if (field == null || object == null) return null; + try { + field.setAccessible(true); + field.set(object, value); + return field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "set"); + } + return null; + } + + /** + * 获取字段的值 + * @param field {@link Field} + * @param object Object + * @return 对应的 Object + */ + public static Object get( + final Field field, + final Object object + ) { + if (field == null || object == null) return null; + try { + field.setAccessible(true); + return field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + return null; + } + + // = + + /** + * 是否 long/Long 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isLong(final Field field) { + return field != null && (field.getType() == long.class || field.getType() == Long.class); + } + + /** + * 是否 float/Float 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFloat(final Field field) { + return field != null && (field.getType() == float.class || field.getType() == Float.class); + } + + /** + * 是否 double/Double 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isDouble(final Field field) { + return field != null && (field.getType() == double.class || field.getType() == Double.class); + } + + /** + * 是否 int/Integer 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInteger(final Field field) { + return field != null && (field.getType() == int.class || field.getType() == Integer.class); + } + + /** + * 是否 boolean/Boolean 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isBoolean(final Field field) { + return field != null && (field.getType() == boolean.class || field.getType() == Boolean.class); + } + + /** + * 是否 char/Character 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isCharacter(final Field field) { + return field != null && (field.getType() == char.class || field.getType() == Character.class); + } + + /** + * 是否 byte/Byte 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isByte(final Field field) { + return field != null && (field.getType() == byte.class || field.getType() == Byte.class); + } + + /** + * 是否 short/Short 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShort(final Field field) { + return field != null && (field.getType() == short.class || field.getType() == Short.class); + } + + /** + * 是否 String 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isString(final Field field) { + return field != null && (field.getType() == String.class); + } + + // = + + /** + * 判断是否序列化 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSerializable(final Field field) { + if (field == null) return false; + try { + Class[] clazzs = field.getType().getInterfaces(); + for (Class clazz : clazzs) { + if (Serializable.class == clazz) { + return true; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isSerializable"); + } + return false; + } + + /** + * 是否静态常量或者内部结构属性 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInvalid(final Field field) { + return isStaticFinal(field) || isSynthetic(field); + } + + /** + * 是否静态变量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStatic(final Field field) { + return field != null && Modifier.isStatic(field.getModifiers()); + } + + /** + * 是否常量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFinal(final Field field) { + return field != null && Modifier.isFinal(field.getModifiers()); + } + + /** + * 是否静态变量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStaticFinal(final Field field) { + return field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()); + } + + /** + * 是否内部结构属性 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSynthetic(final Field field) { + return field != null && field.isSynthetic(); + } + + // = + + /** + * 获取字段的泛型类型, 如果不带泛型返回 null + * @param field {@link Field} + * @param 未知类型 + * @return 泛型类型 + */ + public static Class getGenericType(final Field field) { + if (field == null) return null; + try { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getActualTypeArguments()[0]; + } + if (type instanceof Class) { + return (Class) type; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericType"); + } + return null; + } + + /** + * 获取数组的类型 + * @param field {@link Field} + * @param 未知类型 + * @return 数组类型 + */ + public static Class getComponentType(final Field field) { + if (field == null) return null; + return field.getType().getComponentType(); + } + + /** + * 获取全部 Field, 包括父类 + * @param clazz {@link Class} + * @return {@link List} + */ + public static List getAllDeclaredFields(final Class clazz) { + if (clazz == null) return null; + try { + Class clazzTemp = clazz; + // find all field. + LinkedList fieldList = new LinkedList<>(); + while (clazzTemp != null && clazzTemp != Object.class) { + Field[] fields = clazzTemp.getDeclaredFields(); + for (Field field : fields) { + if (!isInvalid(field)) { + fieldList.addLast(field); + } + } + clazzTemp = clazzTemp.getSuperclass(); + } + return fieldList; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAllDeclaredFields"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/FileIOUtils.java b/lib/DevApp/src/main/java/dev/utils/common/FileIOUtils.java new file mode 100644 index 0000000000..88ca5064c4 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/FileIOUtils.java @@ -0,0 +1,820 @@ +package dev.utils.common; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 文件 ( IO 流 ) 工具类 + * @author Ttt + */ +public final class FileIOUtils { + + private FileIOUtils() { + } + + // 日志 TAG + private static final String TAG = FileIOUtils.class.getSimpleName(); + + // 缓存大小 + private static int sBufferSize = 8192; + // 无数据读取 + public static final int EOF = -1; + + /** + * 设置缓冲区的大小, 默认大小等于 8192 字节 + * @param bufferSize 缓冲 Buffer 大小 + */ + public static void setBufferSize(final int bufferSize) { + sBufferSize = bufferSize; + } + + /** + * 获取输入流 + * @param filePath 文件路径 + * @return {@link FileInputStream} + */ + public static FileInputStream getFileInputStream(final String filePath) { + return getFileInputStream(FileUtils.getFile(filePath)); + } + + /** + * 获取输入流 + * @param file 文件 + * @return {@link FileInputStream} + */ + public static FileInputStream getFileInputStream(final File file) { + if (file == null) return null; + try { + return new FileInputStream(file); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileInputStream"); + } + return null; + } + + /** + * 获取输出流 + * @param filePath 文件路径 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream(final String filePath) { + return getFileOutputStream(FileUtils.getFile(filePath)); + } + + /** + * 获取输出流 + * @param filePath 文件路径 + * @param append 是否追加到结尾 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream( + final String filePath, + final boolean append + ) { + return getFileOutputStream(FileUtils.getFile(filePath), append); + } + + /** + * 获取输出流 + * @param file 文件 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream(final File file) { + return getFileOutputStream(file, false); + } + + /** + * 获取输出流 + * @param file 文件 + * @param append 是否追加到结尾 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream( + final File file, + final boolean append + ) { + if (file == null) return null; + try { + return new FileOutputStream(file, append); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileOutputStream"); + } + return null; + } + + // = + + /** + * 通过输入流写入文件 + * @param filePath 文件路径 + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final String filePath, + final InputStream inputStream + ) { + return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, false); + } + + /** + * 通过输入流写入文件 + * @param filePath 文件路径 + * @param inputStream {@link InputStream} + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final String filePath, + final InputStream inputStream, + final boolean append + ) { + return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, append); + } + + /** + * 通过输入流写入文件 + * @param file 文件 + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final File file, + final InputStream inputStream + ) { + return writeFileFromIS(file, inputStream, false); + } + + /** + * 通过输入流写入文件 + * @param file 文件 + * @param inputStream {@link InputStream} + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final File file, + final InputStream inputStream, + final boolean append + ) { + if (inputStream == null || !FileUtils.createOrExistsFile(file)) return false; + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file, append)); + byte[] data = new byte[sBufferSize]; + int len; + while ((len = inputStream.read(data, 0, sBufferSize)) != EOF) { + os.write(data, 0, len); + } + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromIS"); + return false; + } finally { + CloseUtils.closeIOQuietly(inputStream, os); + } + } + + /** + * 通过字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final String filePath, + final byte[] bytes + ) { + return writeFileFromBytesByStream(FileUtils.getFileByPath(filePath), bytes, false); + } + + /** + * 通过字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final String filePath, + final byte[] bytes, + final boolean append + ) { + return writeFileFromBytesByStream(FileUtils.getFileByPath(filePath), bytes, append); + } + + /** + * 通过字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final File file, + final byte[] bytes + ) { + return writeFileFromBytesByStream(file, bytes, false); + } + + /** + * 通过字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final File file, + final byte[] bytes, + final boolean append + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + BufferedOutputStream bos = null; + try { + bos = new BufferedOutputStream(new FileOutputStream(file, append)); + bos.write(bytes); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByStream"); + return false; + } finally { + CloseUtils.closeIOQuietly(bos); + } + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final String filePath, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByChannel( + FileUtils.getFileByPath(filePath), bytes, false, isForce + ); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final String filePath, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + return writeFileFromBytesByChannel( + FileUtils.getFileByPath(filePath), bytes, append, isForce + ); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final File file, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByChannel(file, bytes, false, isForce); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final File file, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + FileChannel fc = null; + try { + fc = new FileOutputStream(file, append).getChannel(); + fc.position(fc.size()); + fc.write(ByteBuffer.wrap(bytes)); + if (isForce) fc.force(true); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByChannel"); + return false; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final String filePath, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByMap(filePath, bytes, false, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final String filePath, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + return writeFileFromBytesByMap(FileUtils.getFileByPath(filePath), bytes, append, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final File file, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByMap(file, bytes, false, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final File file, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + FileChannel fc = null; + try { + fc = new FileOutputStream(file, append).getChannel(); + MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length); + mbb.put(bytes); + if (isForce) mbb.force(); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByMap"); + return false; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过字符串写入文件 + * @param filePath 文件路径 + * @param content 写入内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final String filePath, + final String content + ) { + return writeFileFromString(FileUtils.getFileByPath(filePath), content, false); + } + + /** + * 通过字符串写入文件 + * @param filePath 文件路径 + * @param content 写入内容 + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final String filePath, + final String content, + final boolean append + ) { + return writeFileFromString(FileUtils.getFileByPath(filePath), content, append); + } + + /** + * 通过字符串写入文件 + * @param file 文件 + * @param content 写入内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final File file, + final String content + ) { + return writeFileFromString(file, content, false); + } + + /** + * 通过字符串写入文件 + * @param file 文件 + * @param content 写入内容 + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final File file, + final String content, + final boolean append + ) { + if (content == null || !FileUtils.createOrExistsFile(file)) return false; + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter(file, append)); + bw.write(content); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromString"); + return false; + } finally { + CloseUtils.closeIOQuietly(bw); + } + } + + // ============ + // = 读写分界线 = + // ============ + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @return 换行 {@link List} + */ + public static List readFileToList(final String filePath) { + return readFileToList(FileUtils.getFileByPath(filePath), null); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final String charsetName + ) { + return readFileToList(FileUtils.getFileByPath(filePath), charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @return 换行 {@link List} + */ + public static List readFileToList(final File file) { + return readFileToList(file, 0, Integer.MAX_VALUE, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final String charsetName + ) { + return readFileToList(file, 0, Integer.MAX_VALUE, charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param start 开始位置 + * @param end 结束位置 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final int start, + final int end + ) { + return readFileToList(FileUtils.getFileByPath(filePath), start, end, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param start 开始位置 + * @param end 结束位置 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final int start, + final int end, + final String charsetName + ) { + return readFileToList(FileUtils.getFileByPath(filePath), start, end, charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param start 开始位置 + * @param end 结束位置 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final int start, + final int end + ) { + return readFileToList(file, start, end, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param start 开始位置 + * @param end 结束位置 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final int start, + final int end, + final String charsetName + ) { + if (!FileUtils.isFileExists(file)) return null; + if (start > end) return null; + BufferedReader br = null; + try { + String line; + int curLine = 1; + List list = new ArrayList<>(); + if (StringUtils.isEmpty(charsetName)) { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + } else { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName)); + } + while ((line = br.readLine()) != null) { + if (curLine > end) break; + if (start <= curLine && curLine <= end) list.add(line); + ++curLine; + } + return list; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToList"); + return null; + } finally { + CloseUtils.closeIOQuietly(br); + } + } + + // = + + /** + * 读取文件内容, 返回字符串 + * @param filePath 文件路径 + * @return 文件内容字符串 + */ + public static String readFileToString(final String filePath) { + return readFileToString(FileUtils.getFileByPath(filePath), null); + } + + /** + * 读取文件内容, 返回字符串 + * @param filePath 文件路径 + * @param charsetName 字符编码 + * @return 文件内容字符串 + */ + public static String readFileToString( + final String filePath, + final String charsetName + ) { + return readFileToString(FileUtils.getFileByPath(filePath), charsetName); + } + + /** + * 读取文件内容, 返回字符串 + * @param file 文件 + * @return 文件内容字符串 + */ + public static String readFileToString(final File file) { + return readFileToString(file, null); + } + + /** + * 读取文件内容, 返回字符串 + * @param file 文件 + * @param charsetName 字符编码 + * @return 文件内容字符串 + */ + public static String readFileToString( + final File file, + final String charsetName + ) { + if (!FileUtils.isFileExists(file)) return null; + BufferedReader br = null; + try { + StringBuilder builder = new StringBuilder(); + if (StringUtils.isEmpty(charsetName)) { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + } else { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName)); + } + String line; + if ((line = br.readLine()) != null) { + builder.append(line); + while ((line = br.readLine()) != null) { + builder.append(DevFinal.SYMBOL.NEW_LINE).append(line); + } + } + return builder.toString(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToString"); + return null; + } finally { + CloseUtils.closeIOQuietly(br); + } + } + + /** + * 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByStream(final String filePath) { + return readFileToBytesByStream(FileUtils.getFileByPath(filePath)); + } + + /** + * 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByStream(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileInputStream fis = null; + ByteArrayOutputStream baos = null; + try { + fis = new FileInputStream(file); + baos = new ByteArrayOutputStream(); + byte[] b = new byte[sBufferSize]; + int len; + while ((len = fis.read(b, 0, sBufferSize)) != EOF) { + baos.write(b, 0, len); + } + return baos.toByteArray(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(fis, baos); + } + } + + /** + * 通过 FileChannel, 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByChannel(final String filePath) { + return readFileToBytesByChannel(FileUtils.getFileByPath(filePath)); + } + + /** + * 通过 FileChannel, 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByChannel(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileChannel fc = null; + try { + fc = new RandomAccessFile(file, "r").getChannel(); + ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size()); + while (true) { + if (!((fc.read(byteBuffer)) > 0)) break; + } + return byteBuffer.array(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByChannel"); + return null; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByMap(final String filePath) { + return readFileToBytesByMap(FileUtils.getFileByPath(filePath)); + } + + /** + * 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByMap(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileChannel fc = null; + try { + fc = new RandomAccessFile(file, "r").getChannel(); + int size = (int) fc.size(); + MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + byte[] result = new byte[size]; + mbb.get(result, 0, size); + return result; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByMap"); + return null; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + // = + + /** + * 复制 InputStream 到 OutputStream + * @param inputStream {@link InputStream} 读取流 + * @param outputStream {@link OutputStream} 写入流 + * @return bytes number + */ + public static long copyLarge( + final InputStream inputStream, + final OutputStream outputStream + ) { + if (inputStream == null || outputStream == null) return -1L; + try { + byte[] data = new byte[sBufferSize]; + long count = 0; + int n; + while (EOF != (n = inputStream.read(data))) { + outputStream.write(data, 0, n); + count += n; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "copyLarge"); + } finally { + CloseUtils.closeIOQuietly(inputStream, outputStream); + } + return -1L; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/FileUtils.java b/lib/DevApp/src/main/java/dev/utils/common/FileUtils.java new file mode 100644 index 0000000000..2d238b58e2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/FileUtils.java @@ -0,0 +1,2526 @@ +package dev.utils.common; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.encrypt.MD5Utils; + +/** + * detail: 文件操作工具类 + * @author Ttt + */ +public final class FileUtils { + + private FileUtils() { + } + + // 日志 TAG + private static final String TAG = FileUtils.class.getSimpleName(); + + /** + * 获取文件 + * @param filePath 文件路径 + * @return 文件 {@link File} + */ + public static File getFile(final String filePath) { + return getFileByPath(filePath); + } + + /** + * 获取文件 + * @param filePath 文件路径 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFile( + final String filePath, + final String fileName + ) { + return (filePath != null && fileName != null) ? new File(filePath, fileName) : null; + } + + /** + * 获取文件 + * @param parent 文件路径 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFile( + final File parent, + final String fileName + ) { + return (parent != null && fileName != null) ? new File(parent, fileName) : null; + } + + /** + * 获取文件 + * @param filePath 文件路径 + * @return 文件 {@link File} + */ + public static File getFileByPath(final String filePath) { + return filePath != null ? new File(filePath) : null; + } + + /** + * 获取路径, 并且进行创建目录 + * @param filePath 存储目录 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFileCreateFolder( + final String filePath, + final String fileName + ) { + // 防止不存在目录文件, 自动创建 + createFolder(filePath); + // 返回处理过后的 File + return getFile(filePath, fileName); + } + + /** + * 获取路径, 并且进行创建目录 + * @param filePath 存储目录 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static String getFilePathCreateFolder( + final String filePath, + final String fileName + ) { + // 防止不存在目录文件, 自动创建 + createFolder(filePath); + // 返回处理过后的 File + File file = getFile(filePath, fileName); + // 返回文件路径 + return getAbsolutePath(file); + } + + /** + * 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) + * @param dirPath 文件夹路径 ( 无文件名字. 后缀 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolder(final String dirPath) { + return createFolder(getFileByPath(dirPath)); + } + + /** + * 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) + * @param file 文件夹路径 ( 无文件名字. 后缀 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolder(final File file) { + if (file != null) { + try { + // 当这个文件夹不存在的时候则创建文件夹 + if (!file.exists()) { + // 允许创建多级目录 + return file.mkdirs(); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "createFolder"); + } + } + return false; + } + + /** + * 创建文件夹目录 ( 可以传入文件名 ) + * @param filePath 文件路径 + 文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPath(final String filePath) { + return createFolderByPath(getFileByPath(filePath)); + } + + /** + * 创建文件夹目录 ( 可以传入文件名 ) + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPath(final File file) { + // 创建文件夹 ( 如果失败才创建 ) + if (file != null) { + if (file.exists()) { + return true; + } else if (!file.getParentFile().mkdirs()) { + return createFolder(file.getParent()); + } + } + return false; + } + + /** + * 创建多个文件夹, 如果不存在则创建 + * @param filePaths 文件路径数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPaths(final String... filePaths) { + if (filePaths != null && filePaths.length != 0) { + for (String filePath : filePaths) { + createFolder(filePath); + } + return true; + } + return false; + } + + /** + * 创建多个文件夹, 如果不存在则创建 + * @param files 文件数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPaths(final File... files) { + if (files != null && files.length != 0) { + for (File file : files) { + createFolder(file); + } + return true; + } + return false; + } + + // = + + /** + * 判断目录是否存在, 不存在则判断是否创建成功 + * @param dirPath 目录路径 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsDir(final String dirPath) { + return createOrExistsDir(getFileByPath(dirPath)); + } + + /** + * 判断目录是否存在, 不存在则判断是否创建成功 + * @param file 文件 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsDir(final File file) { + // 如果存在, 是目录则返回 true, 是文件则返回 false, 不存在则返回是否创建成功 + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + /** + * 判断文件是否存在, 不存在则判断是否创建成功 + * @param filePath 文件路径 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsFile(final String filePath) { + return createOrExistsFile(getFileByPath(filePath)); + } + + /** + * 判断文件是否存在, 不存在则判断是否创建成功 + * @param file 文件 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsFile(final File file) { + if (file == null) return false; + // 如果存在, 是文件则返回 true, 是目录则返回 false + if (file.exists()) return file.isFile(); + // 判断文件是否存在, 不存在则直接返回 + if (!createOrExistsDir(file.getParentFile())) return false; + try { + // 存在, 则返回新的路径 + return file.createNewFile(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "createOrExistsFile"); + return false; + } + } + + /** + * 判断文件是否存在, 存在则在创建之前删除 + * @param filePath 文件路径 + * @return {@code true} 创建成功, {@code false} 创建失败 + */ + public static boolean createFileByDeleteOldFile(final String filePath) { + return createFileByDeleteOldFile(getFileByPath(filePath)); + } + + /** + * 判断文件是否存在, 存在则在创建之前删除 + * @param file 文件 + * @return {@code true} 创建成功, {@code false} 创建失败 + */ + public static boolean createFileByDeleteOldFile(final File file) { + if (file == null) return false; + // 文件存在并且删除失败返回 false + if (file.exists() && !file.delete()) return false; + // 创建目录失败返回 false + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "createFileByDeleteOldFile"); + return false; + } + } + + /** + * 通过文件后缀创建时间戳文件名 + * @param extension 文件后缀 ( 有无 . 都行 ) + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileName(final String extension) { + // 临时后缀名 + String temp = StringUtils.clearSpace(extension); + if (StringUtils.isNotEmpty(temp)) { + temp = StringUtils.clearSEWiths(temp, "."); + if (StringUtils.isNotEmpty(temp)) { + return System.currentTimeMillis() + "." + temp; + } + } + return null; + } + + /** + * 通过文件名创建时间戳文件名 + * @param fileName 文件名 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByName(final String fileName) { + return createTimestampFileName(FileUtils.getFileExtension(fileName)); + } + + /** + * 通过文件创建时间戳文件名 + * @param file 文件 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByFile(final File file) { + return createTimestampFileName(FileUtils.getFileExtension(file)); + } + + /** + * 通过文件路径创建时间戳文件名 + * @param filePath 文件路径 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByPath(final String filePath) { + return createTimestampFileName(FileUtils.getFileExtension(filePath)); + } + + // = + + /** + * Path List 转 File List + * @param paths Path List + * @return File List + */ + public static List convertFiles(final List paths) { + return convertFiles(paths, true); + } + + /** + * Path List 转 File List + * @param paths Path List + * @param ignore 是否忽略 null + * @return File List + */ + public static List convertFiles( + final List paths, + final boolean ignore + ) { + List files = new ArrayList<>(); + if (paths != null && !paths.isEmpty()) { + for (int i = 0, len = paths.size(); i < len; i++) { + String path = paths.get(i); + if (path == null) { + if (!ignore) files.add(null); + continue; + } + files.add(new File(path)); + } + } + return files; + } + + /** + * File List 转 Path List + * @param files File List + * @return Path List + */ + public static List convertPaths(final List files) { + return convertPaths(files, true); + } + + /** + * File List 转 Path List + * @param files File List + * @param ignore 是否忽略 null + * @return Path List + */ + public static List convertPaths( + final List files, + final boolean ignore + ) { + List paths = new ArrayList<>(); + if (files != null && !files.isEmpty()) { + for (int i = 0, len = files.size(); i < len; i++) { + File file = files.get(i); + if (file == null) { + if (!ignore) paths.add(null); + continue; + } + paths.add(file.getAbsolutePath()); + } + } + return paths; + } + + // = + + /** + * 获取文件路径 + * @param file 文件 + * @return 文件路径 + */ + public static String getPath(final File file) { + return file != null ? file.getPath() : null; + } + + /** + * 获取文件绝对路径 + * @param file 文件 + * @return 文件绝对路径 + */ + public static String getAbsolutePath(final File file) { + return file != null ? file.getAbsolutePath() : null; + } + + // = + + /** + * 获取文件名 + * @param file 文件 + * @return 文件名 + */ + public static String getName(final File file) { + return file != null ? file.getName() : null; + } + + /** + * 获取文件名 + * @param filePath 文件路径 + * @return 文件名 + */ + public static String getName(final String filePath) { + return getName(filePath, ""); + } + + /** + * 获取文件名 + * @param filePath 文件路径 + * @param defaultStr 默认字符串 + * @return 文件名, 如果文件路径为 null 时, 返回默认字符串 + */ + public static String getName( + final String filePath, + final String defaultStr + ) { + return StringUtils.isEmpty(filePath) ? defaultStr : new File(filePath).getName(); + } + + /** + * 获取文件后缀名 ( 无 "." 单独后缀 ) + * @param file 文件 + * @return 文件后缀名 ( 无 "." 单独后缀 ) + */ + public static String getFileSuffix(final File file) { + return getFileSuffix(getAbsolutePath(file)); + } + + /** + * 获取文件后缀名 ( 无 "." 单独后缀 ) + * @param filePath 文件路径或文件名 + * @return 文件后缀名 ( 无 "." 单独后缀 ) + */ + public static String getFileSuffix(final String filePath) { + // 获取最后的索引 + int lastIndexOf; + // 判断是否存在 + if (filePath != null && (lastIndexOf = filePath.lastIndexOf('.')) != -1) { + String result = filePath.substring(lastIndexOf); + if (result.startsWith(".")) { + return result.substring(1); + } + return result; + } + return null; + } + + /** + * 获取文件名 ( 无后缀 ) + * @param file 文件 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffix(final File file) { + return getFileNotSuffix(getName(file)); + } + + /** + * 获取文件名 ( 无后缀 ) + * @param filePath 文件路径 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffixToPath(final String filePath) { + return getFileNotSuffix(getName(filePath)); + } + + /** + * 获取文件名 ( 无后缀 ) + * @param fileName 文件名 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffix(final String fileName) { + if (fileName != null) { + if (fileName.lastIndexOf('.') != -1) { + return fileName.substring(0, fileName.lastIndexOf('.')); + } else { + return fileName; + } + } + return null; + } + + /** + * 获取路径中的不带扩展名的文件名 + * @param file 文件 + * @return 不带扩展名的文件名 + */ + public static String getFileNameNoExtension(final File file) { + if (file == null) return null; + return getFileNameNoExtension(file.getPath()); + } + + /** + * 获取路径中的不带扩展名的文件名 + * @param filePath 文件路径 + * @return 不带扩展名的文件名 + */ + public static String getFileNameNoExtension(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + /** + * 获取路径中的文件扩展名 + * @param file 文件 + * @return 文件扩展名 + */ + public static String getFileExtension(final File file) { + if (file == null) return null; + return getFileExtension(file.getPath()); + } + + /** + * 获取路径中的文件扩展名 + * @param filePath 文件路径 + * @return 文件扩展名 + */ + public static String getFileExtension(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + // = + + /** + * 检查是否存在某个文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + /** + * 检查是否存在某个文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists(final String filePath) { + return isFileExists(getFileByPath(filePath)); + } + + /** + * 检查是否存在某个文件 + * @param filePath 文件路径 + * @param fileName 文件名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists( + final String filePath, + final String fileName + ) { + return filePath != null && fileName != null && new File(filePath, fileName).exists(); + } + + /** + * 判断是否文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFile(final String filePath) { + return isFile(getFileByPath(filePath)); + } + + /** + * 判断是否文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFile(final File file) { + return file != null && file.exists() && file.isFile(); + } + + /** + * 判断是否文件夹 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDirectory(final String filePath) { + return isDirectory(getFileByPath(filePath)); + } + + /** + * 判断是否文件夹 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDirectory(final File file) { + return file != null && file.exists() && file.isDirectory(); + } + + /** + * 判断是否隐藏文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHidden(final String filePath) { + return isHidden(getFileByPath(filePath)); + } + + /** + * 判断是否隐藏文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHidden(final File file) { + return file != null && file.exists() && file.isHidden(); + } + + /** + * 文件是否可读 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canRead(final String filePath) { + return canRead(getFileByPath(filePath)); + } + + /** + * 文件是否可读 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canRead(final File file) { + return file != null && file.exists() && file.canRead(); + } + + /** + * 文件是否可写 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canWrite(final String filePath) { + return canWrite(getFileByPath(filePath)); + } + + /** + * 文件是否可写 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canWrite(final File file) { + return file != null && file.exists() && file.canWrite(); + } + + /** + * 文件是否可读写 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canReadWrite(final String filePath) { + return canReadWrite(getFileByPath(filePath)); + } + + /** + * 文件是否可读写 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canReadWrite(final File file) { + return file != null && file.exists() && file.canRead() && file.canWrite(); + } + + // = + + /** + * 获取文件最后修改的毫秒时间戳 + * @param filePath 文件路径 + * @return 文件最后修改的毫秒时间戳 + */ + public static long getFileLastModified(final String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + /** + * 获取文件最后修改的毫秒时间戳 + * @param file 文件 + * @return 文件最后修改的毫秒时间戳 + */ + public static long getFileLastModified(final File file) { + if (file == null) return 0L; + return file.lastModified(); + } + + /** + * 获取文件编码格式 + * @param filePath 文件路径 + * @return 文件编码格式 + */ + public static String getFileCharsetSimple(final String filePath) { + return getFileCharsetSimple(getFileByPath(filePath)); + } + + /** + * 获取文件编码格式 + * @param file 文件 + * @return 文件编码格式 + */ + public static String getFileCharsetSimple(final File file) { + if (!isFileExists(file)) return null; + int pos = 0; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + pos = (is.read() << 8) + is.read(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "getFileCharsetSimple"); + } finally { + CloseUtils.closeIOQuietly(is); + } + switch (pos) { + case 0xefbb: + return DevFinal.ENCODE.UTF_8; + case 0xfffe: + return DevFinal.ENCODE.UNICODE; + case 0xfeff: + return DevFinal.ENCODE.UTF_16BE; + default: + return DevFinal.ENCODE.GBK; + } + } + + /** + * 获取文件行数 + * @param filePath 文件路径 + * @return 文件行数 + */ + public static int getFileLines(final String filePath) { + return getFileLines(getFileByPath(filePath)); + } + + /** + * 获取文件行数 ( 比 readLine 要快很多 ) + * @param file 文件 + * @return 文件行数 + */ + public static int getFileLines(final File file) { + if (!isFileExists(file)) return 0; + int lineCount = 1; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[1024]; + int readChars; + if (DevFinal.SYMBOL.NEW_LINE.endsWith("\n")) { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\n') ++lineCount; + } + } + } else { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\r') ++lineCount; + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileLines"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return lineCount; + } + + // = + + /** + * 获取文件大小 + * @param filePath 文件路径 + * @return 文件大小 + */ + public static String getFileSize(final String filePath) { + return getFileSize(getFileByPath(filePath)); + } + + /** + * 获取文件大小 + * @param file 文件 + * @return 文件大小 + */ + public static String getFileSize(final File file) { + return formatByteMemorySize(getFileLength(file)); + } + + /** + * 获取目录大小 + * @param dirPath 目录路径 + * @return 文件大小 + */ + public static String getDirSize(final String dirPath) { + return getDirSize(getFileByPath(dirPath)); + } + + /** + * 获取目录大小 + * @param dir 目录 + * @return 文件大小 + */ + public static String getDirSize(final File dir) { + return formatByteMemorySize(getDirLength(dir)); + } + + /** + * 获取文件大小 + * @param filePath 文件路径 + * @return 文件大小 + */ + public static long getFileLength(final String filePath) { + return getFileLength(getFileByPath(filePath)); + } + + /** + * 获取文件大小 + * @param file 文件 + * @return 文件大小 + */ + public static long getFileLength(final File file) { + return file != null ? file.length() : 0L; + } + + /** + * 获取目录全部文件大小 + * @param dirPath 目录路径 + * @return 目录全部文件大小 + */ + public static long getDirLength(final String dirPath) { + return getDirLength(getFileByPath(dirPath)); + } + + /** + * 获取目录全部文件大小 + * @param dir 目录 + * @return 目录全部文件大小 + */ + public static long getDirLength(final File dir) { + if (!isDirectory(dir)) return 0L; + long len = 0; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + + /** + * 获取文件大小 ( 网络资源 ) + * @param httpUri 文件网络链接 + * @return 文件大小 + */ + public static long getFileLengthNetwork(final String httpUri) { + if (StringUtils.isEmpty(httpUri)) return 0L; + // 判断是否网络资源 + boolean isHttpRes = httpUri.toLowerCase().startsWith("http:") || httpUri.toLowerCase().startsWith("https:"); + if (isHttpRes) { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(httpUri).openConnection(); + conn.setRequestProperty("Accept-Encoding", "identity"); + conn.connect(); + if (conn.getResponseCode() == 200) { + return conn.getContentLength(); + } + return 0L; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileLengthNetwork"); + } + } + return 0L; + } + + /** + * 获取路径中的文件名 + * @param file 文件 + * @return 文件名 + */ + public static String getFileName(final File file) { + if (file == null) return null; + return getFileName(file.getPath()); + } + + /** + * 获取路径中的文件名 + * @param filePath 文件路径 + * @return 文件名 + */ + public static String getFileName(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + + /** + * 获取路径中的最长目录地址 + * @param file 文件 + * @return 最长目录地址 + */ + public static String getDirName(final File file) { + if (file == null) return null; + return getDirName(file.getPath()); + } + + /** + * 获取全路径中的最长目录地址 + * @param filePath 文件路径 + * @return 最长目录地址 + */ + public static String getDirName(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); + } + + // = + + /** + * 重命名文件 ( 同个目录下, 修改文件名 ) + * @param filePath 文件路径 + * @param newFileName 文件新名称 + * @return {@code true} yes, {@code false} no + */ + public static boolean rename( + final String filePath, + final String newFileName + ) { + return rename(getFileByPath(filePath), newFileName); + } + + /** + * 重命名文件 ( 同个目录下, 修改文件名 ) + * @param file 文件 + * @param newFileName 文件新名称 + * @return {@code true} yes, {@code false} no + */ + public static boolean rename( + final File file, + final String newFileName + ) { + if (StringUtils.isEmpty(newFileName)) return false; + // 文件为空返回 false + if (file == null) return false; + // 文件不存在返回 false + if (!file.exists()) return false; + // 如果文件名没有改变返回 true + if (newFileName.equals(file.getName())) return true; + // 拼接新的文件路径 + File newFile = new File(file.getParent() + File.separator + newFileName); + // 如果重命名的文件已存在返回 false + return !newFile.exists() && file.renameTo(newFile); + } + + // ============= + // = 文件大小处理 = + // ============= + + /** + * 传入文件路径, 返回对应的文件大小 + * @param filePath 文件路径 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final String filePath) { + File file = getFileByPath(filePath); + return formatFileSize(file != null ? file.length() : 0); + } + + /** + * 传入文件路径, 返回对应的文件大小 + * @param file 文件 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final File file) { + return formatFileSize(file != null ? file.length() : 0); + } + + /** + * 传入对应的文件大小, 返回转换后文件大小 + * @param fileSize 文件大小 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final double fileSize) { + // 转换文件大小 + DecimalFormat df = new DecimalFormat("#.00"); + String fileSizeStr; + if (fileSize <= 0) { + fileSizeStr = "0B"; + } else if (fileSize < 1024) { + fileSizeStr = df.format(fileSize) + "B"; + } else if (fileSize < 1048576) { + fileSizeStr = df.format(fileSize / 1024) + "KB"; + } else if (fileSize < 1073741824) { + fileSizeStr = df.format(fileSize / 1048576) + "MB"; + } else if (fileSize < 1099511627776D) { + fileSizeStr = df.format(fileSize / 1073741824) + "GB"; + } else { + fileSizeStr = df.format(fileSize / 1099511627776D) + "TB"; + } + return fileSizeStr; + } + + /** + * 字节数转合适内存大小 保留 3 位小数 + * @param byteSize 字节数 + * @return 合适内存大小字符串 + */ + public static String formatByteMemorySize(final double byteSize) { + return formatByteMemorySize(3, byteSize); + } + + /** + * 字节数转合适内存大小 保留 number 位小数 + * @param number 保留小数位数 + * @param byteSize 字节数 + * @return 合适内存大小字符串 + */ + public static String formatByteMemorySize( + final int number, + final double byteSize + ) { + if (byteSize < 0D) { + return "0B"; + } else if (byteSize < 1024D) { + return String.format("%." + number + "fB", byteSize); + } else if (byteSize < 1048576D) { + return String.format("%." + number + "fKB", byteSize / 1024D); + } else if (byteSize < 1073741824D) { + return String.format("%." + number + "fMB", byteSize / 1048576D); + } else if (byteSize < 1099511627776D) { + return String.format("%." + number + "fGB", byteSize / 1073741824D); + } else { + return String.format("%." + number + "fTB", byteSize / 1099511627776D); + } + } + + // ========== + // = 文件操作 = + // ========== + + /** + * 删除文件 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFile(final String filePath) { + return deleteFile(getFileByPath(filePath)); + } + + /** + * 删除文件 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFile(final File file) { + // 文件存在, 并且不是目录文件, 则直接删除 + if (file != null && file.exists() && !file.isDirectory()) { + return file.delete(); + } + return false; + } + + /** + * 删除多个文件 + * @param filePaths 文件路径数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFiles(final String... filePaths) { + if (filePaths != null && filePaths.length != 0) { + for (String filePath : filePaths) { + deleteFile(filePath); + } + return true; + } + return false; + } + + /** + * 删除多个文件 + * @param files 文件数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFiles(final File... files) { + if (files != null && files.length != 0) { + for (File file : files) { + deleteFile(file); + } + return true; + } + return false; + } + + // = + + /** + * 删除文件夹 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFolder(final String filePath) { + return deleteFolder(getFileByPath(filePath)); + } + + /** + * 删除文件夹 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFolder(final File file) { + if (file != null) { + try { + // 文件存在, 并且不是目录文件, 则直接删除 + if (file.exists()) { + if (file.isDirectory()) { // 属于文件目录 + File[] files = file.listFiles(); + if (files != null) { + for (File deleteFile : files) { + deleteFolder(deleteFile.getPath()); + } + } + return file.delete(); + } else { // 属于文件 + return deleteFile(file); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deleteFolder"); + } + } + return false; + } + + /** + * 保存文件 + * @param filePath 文件路径 + * @param data 待存储数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveFile( + final String filePath, + final byte[] data + ) { + return saveFile(FileUtils.getFile(filePath), data); + } + + /** + * 保存文件 + * @param file 文件 + * @param data 待存储数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveFile( + final File file, + final byte[] data + ) { + if (file != null && data != null) { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + // 防止文件夹没创建 + createFolder(getDirName(file)); + // 写入文件 + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos); + bos.write(data); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "saveFile"); + } finally { + CloseUtils.closeIOQuietly(bos, fos); + } + } + return false; + } + + /** + * 追加文件 + * @param filePath 文件路径 + * @param data 待追加数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean appendFile( + final String filePath, + final byte[] data + ) { + return appendFile(FileUtils.getFile(filePath), data); + } + + /** + * 追加文件 + *
+     *     如果未创建文件, 则会创建并写入数据 ( 等同 {@link #saveFile} )
+     *     如果已创建文件, 则在结尾追加数据
+     * 
+ * @param file 文件 + * @param data 待追加数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean appendFile( + final File file, + final byte[] data + ) { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + // 防止文件夹没创建 + createFolder(getDirName(file)); + // 写入文件 + fos = new FileOutputStream(file, true); + bos = new BufferedOutputStream(fos); + bos.write(data); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "appendFile"); + } finally { + CloseUtils.closeIOQuietly(bos, fos); + } + return false; + } + + // = + + /** + * 读取文件 + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final String filePath) { + return readFileBytes(getFileByPath(filePath)); + } + + /** + * 读取文件 + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final File file) { + if (file != null && file.exists()) { + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + int length = fis.available(); + byte[] buffer = new byte[length]; + fis.read(buffer); + return buffer; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFileBytes"); + } finally { + CloseUtils.closeIOQuietly(fis); + } + } + return null; + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final InputStream inputStream) { + if (inputStream != null) { + try { + int length = inputStream.available(); + byte[] buffer = new byte[length]; + inputStream.read(buffer); + return buffer; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFileBytes"); + } finally { + CloseUtils.closeIOQuietly(inputStream); + } + } + return null; + } + + /** + * 读取文件 + *
+     *     获取换行内容可以通过
+     *     {@link FileIOUtils#readFileToList(File)}
+     *     {@link FileIOUtils#readFileToString(File)}
+     * 
+ * @param filePath 文件路径 + * @return 文件内容字符串 + */ + public static String readFile(final String filePath) { + return readFile(getFileByPath(filePath)); + } + + /** + * 读取文件 + * @param file 文件 + * @return 文件内容字符串 + */ + public static String readFile(final File file) { + if (file != null && file.exists()) { + try { + return readFile(new FileInputStream(file)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFile"); + } + } + return null; + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} new FileInputStream(path) + * @return 文件内容字符串 + */ + public static String readFile(final InputStream inputStream) { + return readFile(inputStream, null); + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} new FileInputStream(path) + * @param encode 编码格式 + * @return 文件内容字符串 + */ + public static String readFile( + final InputStream inputStream, + final String encode + ) { + if (inputStream != null) { + BufferedReader br = null; + try { + InputStreamReader isr; + if (encode != null) { + isr = new InputStreamReader(inputStream, encode); + } else { + isr = new InputStreamReader(inputStream); + } + br = new BufferedReader(isr); + StringBuilder builder = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFile"); + } finally { + CloseUtils.closeIOQuietly(br); + } + } + return null; + } + + // = + + /** + * 复制单个文件 + * @param inputStream 文件流 ( 被复制 ) + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFile( + final InputStream inputStream, + final String destFilePath, + final boolean overlay + ) { + if (inputStream == null || destFilePath == null) { + return false; + } + File destFile = new File(destFilePath); + // 如果属于文件夹则跳过 + if (destFile.isDirectory()) { + return false; + } + if (destFile.exists()) { + // 如果目标文件存在并允许覆盖 + if (overlay) { + // 删除已经存在的目标文件, 无论目标文件是目录还是单个文件 + destFile.delete(); + } else { // 如果文件存在, 但是不覆盖, 则返回 false 表示失败 + return false; + } + } else { + // 如果目标文件所在目录不存在, 则创建目录 + if (!destFile.getParentFile().exists()) { + // 目标文件所在目录不存在 + if (!destFile.getParentFile().mkdirs()) { + // 复制文件失败: 创建目标文件所在目录失败 + return false; + } + } + } + // 复制文件 + int len; // 读取的字节数 + InputStream is = inputStream; + OutputStream os = null; + try { + os = new FileOutputStream(destFile); + byte[] buffer = new byte[1024]; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "copyFile"); + return false; + } finally { + CloseUtils.closeIOQuietly(os, is); + } + } + + /** + * 复制单个文件 + * @param srcFilePath 待复制的文件地址 + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFile( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + if (destFilePath == null) return false; + if (!FileUtils.isFile(srcFilePath)) return false; + return copyFile( + FileIOUtils.getFileInputStream(srcFilePath), + destFilePath, overlay + ); + } + + /** + * 复制文件夹 + * @param srcFolderPath 待复制的文件夹地址 + * @param destFolderPath 存储目标文件夹地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFolder( + final String srcFolderPath, + final String destFolderPath, + final boolean overlay + ) { + return copyFolder(srcFolderPath, destFolderPath, srcFolderPath, overlay); + } + + /** + * 复制文件夹 + * @param srcFolderPath 待复制的文件夹地址 + * @param destFolderPath 存储目标文件夹地址 + * @param sourcePath 源文件地址 ( 用于保递归留记录 ) + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + private static boolean copyFolder( + final String srcFolderPath, + final String destFolderPath, + final String sourcePath, + final boolean overlay + ) { + if (srcFolderPath == null || destFolderPath == null || sourcePath == null) { + return false; + } + File srcFile = new File(srcFolderPath); + // 判断源文件是否存在 + if (!srcFile.exists()) { + return false; + } else if (!srcFile.isDirectory()) { // 不属于文件夹则跳过 + return false; + } + // 判断目标文件是否存在 + File destFile = new File(destFolderPath); + // 如果文件夹没创建, 则创建 + if (!destFile.exists()) { + // 允许创建多级目录 + destFile.mkdirs(); + } + // 判断是否属于文件夹 ( 不属于文件夹则跳过 ) + if (!destFile.isDirectory()) { + return false; + } + // 判断是否存在 + if (srcFile.exists()) { + // 获取文件路径 + File[] files = srcFile.listFiles(); + // 防止不存在文件 + if (files != null && files.length != 0) { + // 进行遍历 + for (File file : files) { + // 文件存在才进行处理 + if (file.exists()) { + // 属于文件夹 + if (file.isDirectory()) { + copyFolder(file.getAbsolutePath(), destFolderPath, sourcePath, overlay); + } else { // 属于文件 + // 复制的文件地址 + String filePath = file.getAbsolutePath(); + // 获取源文件地址并且进行判断 + String dealSource = new File(sourcePath).getAbsolutePath(); + // 属于最前才进行处理 + if (filePath.indexOf(dealSource) == 0) { + // 获取处理后的地址 + dealSource = filePath.substring(dealSource.length()); + // 获取需要复制保存的地址 + String savePath = new File(destFolderPath, dealSource).getAbsolutePath(); + // 进行复制文件 + boolean result = copyFile(filePath, savePath, overlay); + } + } + } + } + } + } + return true; + } + + // = + + /** + * 移动 ( 剪切 ) 文件 + * @param srcFilePath 待移动的文件地址 + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean moveFile( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + // 复制文件 + if (copyFile(srcFilePath, destFilePath, overlay)) { + // 删除文件 + return deleteFile(srcFilePath); + } + return false; + } + + /** + * 移动 ( 剪切 ) 文件夹 + * @param srcFilePath 待移动的文件夹地址 + * @param destFilePath 存储目标文件夹地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean moveFolder( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + // 复制文件夹 + if (copyFolder(srcFilePath, destFilePath, overlay)) { + // 删除文件夹 + return deleteFolder(srcFilePath); + } + return false; + } + + // = + + /** + * detail: 覆盖 / 替换事件 + * @author Ttt + */ + public interface OnReplaceListener { + + /** + * 是否覆盖 / 替换文件 + * @return {@code true} yes, {@code false} no + */ + boolean onReplace(); + } + + /** + * 复制或移动目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener, + final boolean isMove + ) { + return copyOrMoveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener, isMove); + } + + /** + * 复制或移动目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener, + final boolean isMove + ) { + if (srcDir == null || destDir == null || listener == null) return false; + // 为防止以上这种情况出现出现误判, 需分别在后面加个路径分隔符 + String srcPath = srcDir.getPath() + File.separator; + String destPath = destDir.getPath() + File.separator; + if (destPath.contains(srcPath)) return false; + // 源文件不存在或者不是目录则返回 false + if (!srcDir.exists() || !srcDir.isDirectory()) return false; + if (destDir.exists()) { + if (listener.onReplace()) { // 需要覆盖则删除旧目录 + if (!deleteAllInDir(destDir)) { // 删除文件失败的话返回 false + return false; + } + } else { // 不需要覆盖直接返回即可 true + return true; + } + } + // 目标目录不存在返回 false + if (!createOrExistsDir(destDir)) return false; + File[] files = srcDir.listFiles(); + if (files == null) return false; + for (File file : files) { + File oneDestFile = new File(destPath + file.getName()); + if (file.isFile()) { + // 如果操作失败返回 false + if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; + } else if (file.isDirectory()) { + // 如果操作失败返回 false + if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; + } + } + return !isMove || deleteDir(srcDir); + } + + /** + * 复制或移动文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener, + final boolean isMove + ) { + return copyOrMoveFile( + getFileByPath(srcFilePath), + getFileByPath(destFilePath), + listener, isMove + ); + } + + /** + * 复制或移动文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener, + final boolean isMove + ) { + if (srcFile == null || destFile == null || listener == null) return false; + // 如果源文件和目标文件相同则返回 false + if (srcFile.equals(destFile)) return false; + // 源文件不存在或者不是文件则返回 false + if (!srcFile.exists() || !srcFile.isFile()) return false; + if (destFile.exists()) { // 目标文件存在 + if (listener.onReplace()) { // 需要覆盖则删除旧文件 + if (!destFile.delete()) { // 删除文件失败的话返回 false + return false; + } + } else { // 不需要覆盖直接返回即可 true + return true; + } + } + // 目标目录不存在返回 false + if (!createOrExistsDir(destFile.getParentFile())) return false; + try { + return FileIOUtils.writeFileFromIS( + destFile, new FileInputStream(srcFile), false + ) && !(isMove && !deleteFile(srcFile)); + } catch (FileNotFoundException e) { + JCLogUtils.eTag(TAG, e, "copyOrMoveFile"); + return false; + } + } + + /** + * 复制目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener + ) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * 复制目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener + ) { + return copyOrMoveDir(srcDir, destDir, listener, false); + } + + /** + * 复制文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener + ) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * 复制文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener + ) { + return copyOrMoveFile(srcFile, destFile, listener, false); + } + + /** + * 移动目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener + ) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * 移动目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener + ) { + return copyOrMoveDir(srcDir, destDir, listener, true); + } + + /** + * 移动文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener + ) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * 移动文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener + ) { + return copyOrMoveFile(srcFile, destFile, listener, true); + } + + /** + * 删除目录 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteDir(final String dirPath) { + return deleteDir(getFileByPath(dirPath)); + } + + /** + * 删除目录 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteDir(final File dir) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + return dir.delete(); + } + + /** + * 删除目录下所有文件 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteAllInDir(final String dirPath) { + return deleteAllInDir(getFileByPath(dirPath)); + } + + /** + * 删除目录下所有文件 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteAllInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, pathname -> true); + } + + /** + * 删除目录下所有文件 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDir(final String dirPath) { + return deleteFilesInDir(getFileByPath(dirPath)); + } + + /** + * 删除目录下所有文件 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, pathname -> pathname.isFile()); + } + + /** + * 删除目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDirWithFilter( + final String dirPath, + final FileFilter filter + ) { + return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); + } + + /** + * 删除目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDirWithFilter( + final File dir, + final FileFilter filter + ) { + if (filter == null) return false; + if (dir == null) return false; + if (!dir.exists()) return true; + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + } + return true; + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @return 文件链表 + */ + public static List listFilesInDir(final String dirPath) { + return listFilesInDir(dirPath, false); + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dir 目录 + * @return 文件链表 + */ + public static List listFilesInDir(final File dir) { + return listFilesInDir(dir, false); + } + + /** + * 获取目录下所有文件 + * @param dirPath 目录路径 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDir( + final String dirPath, + final boolean isRecursive + ) { + return listFilesInDir(getFileByPath(dirPath), isRecursive); + } + + /** + * 获取目录下所有文件 + * @param dir 目录 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDir( + final File dir, + final boolean isRecursive + ) { + return listFilesInDirWithFilter(dir, pathname -> true, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final String dirPath, + final FileFilter filter + ) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dir 目录 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final File dir, + final FileFilter filter + ) { + return listFilesInDirWithFilter(dir, filter, false); + } + + /** + * 获取目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final String dirPath, + final FileFilter filter, + final boolean isRecursive + ) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final File dir, + final FileFilter filter, + final boolean isRecursive + ) { + if (!isDirectory(dir) || filter == null) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + list.add(file); + } + if (isRecursive && file.isDirectory()) { + List fileLists = listFilesInDirWithFilter(file, filter, true); + if (fileLists != null) { + list.addAll(fileLists); + } + } + } + } + return list; + } + + // = + + /** + * detail: 文件列表 + * @author Ttt + */ + public static class FileList { + + // 当前文件夹 + private final File mFile; + // 文件夹内子文件列表 + private final List mSubFiles; + + /** + * 构造函数 + * @param file 当前文件夹 + */ + public FileList(File file) { + this(file, new ArrayList<>(0)); + } + + /** + * 构造函数 + * @param file 当前文件夹 + * @param lists 文件夹内子文件列表 + */ + public FileList( + File file, + List lists + ) { + this.mFile = file; + this.mSubFiles = lists; + } + + // = + + /** + * 获取当前文件夹 + * @return {@link File} + */ + public File getFile() { + return mFile; + } + + /** + * 获取文件夹内子文件列表 + * @return {@link List} + */ + public List getSubFiles() { + return mSubFiles; + } + } + + // = + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @return 文件链表 + */ + public static List listFilesInDirBean(final String dirPath) { + return listFilesInDirBean(dirPath, false); + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dir 目录 + * @return 文件链表 + */ + public static List listFilesInDirBean(final File dir) { + return listFilesInDirBean(dir, false); + } + + /** + * 获取目录下所有文件 + * @param dirPath 目录路径 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirBean( + final String dirPath, + final boolean isRecursive + ) { + return listFilesInDirBean(getFileByPath(dirPath), isRecursive); + } + + /** + * 获取目录下所有文件 + * @param dir 目录 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirBean( + final File dir, + final boolean isRecursive + ) { + return listFilesInDirWithFilterBean(dir, pathname -> true, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final String dirPath, + final FileFilter filter + ) { + return listFilesInDirWithFilterBean(getFileByPath(dirPath), filter, false); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dir 目录 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final File dir, + final FileFilter filter + ) { + return listFilesInDirWithFilterBean(dir, filter, false); + } + + /** + * 获取目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final String dirPath, + final FileFilter filter, + final boolean isRecursive + ) { + return listFilesInDirWithFilterBean(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final File dir, + final FileFilter filter, + final boolean isRecursive + ) { + if (!isDirectory(dir) || filter == null) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + FileList fileList; + if (isRecursive && file.isDirectory()) { + List subs = listFilesInDirWithFilterBean(file, filter, true); + fileList = new FileList(file, subs); + } else { + fileList = new FileList(file); + } + list.add(fileList); + } + } + } + return list; + } + + // = + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @return 文件目录列表 + */ + public static List listOrEmpty(final String dirPath) { + return listOrEmpty(getFile(dirPath)); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @return 文件目录列表 + */ + public static List listOrEmpty(final File dir) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.list()); + if (list != null) return list; + } + return new ArrayList<>(); + } + + // = + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty(final String dirPath) { + return listFilesOrEmpty(getFile(dirPath)); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty(final File dir) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.listFiles()); + if (list != null) return list; + } + return new ArrayList<>(); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @param filter 文件过滤 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty( + final String dirPath, + final FilenameFilter filter + ) { + return listFilesOrEmpty(getFile(dirPath), filter); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @param filter 文件过滤 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty( + final File dir, + final FilenameFilter filter + ) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.listFiles(filter)); + if (list != null) return list; + } + return new ArrayList<>(); + } + + // ============= + // = 图片类型判断 = + // ============= + + // 图片格式 + private static final String[] IMAGE_FORMATS = { + ".PNG", ".JPG", ".JPEG", ".BMP", ".GIF", ".WEBP" + }; + + /** + * 根据文件名判断文件是否为图片 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final File file) { + return file != null && isImageFormats(file.getPath(), IMAGE_FORMATS); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final String filePath) { + return isImageFormats(filePath, IMAGE_FORMATS); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // ============= + // = 音频类型判断 = + // ============= + + // 音频格式 + private static final String[] AUDIO_FORMATS = { + ".MP3", ".AAC", ".OGG", ".WMA", ".APE", ".FLAC", ".RA" + }; + + /** + * 根据文件名判断文件是否为音频 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats(final File file) { + return file != null && isAudioFormats(file.getPath(), AUDIO_FORMATS); + } + + /** + * 根据文件名判断文件是否为音频 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats(final String filePath) { + return isAudioFormats(filePath, AUDIO_FORMATS); + } + + /** + * 根据文件名判断文件是否为音频 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // ============= + // = 视频类型判断 = + // ============= + + // 视频格式 + private static final String[] VIDEO_FORMATS = { + ".MP4", ".AVI", ".MOV", ".ASF", ".MPG", ".MPEG", ".WMV", ".RM", ".RMVB", ".3GP", ".MKV" + }; + + /** + * 根据文件名判断文件是否为视频 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats(final File file) { + return file != null && isVideoFormats(file.getPath(), VIDEO_FORMATS); + } + + /** + * 根据文件名判断文件是否为视频 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats(final String filePath) { + return isVideoFormats(filePath, VIDEO_FORMATS); + } + + /** + * 根据文件名判断文件是否为视频 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // = + + /** + * 根据文件名判断文件是否为指定格式 + * @param file 文件 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileFormats( + final File file, + final String[] fileFormats + ) { + return file != null && isFileFormats(file.getPath(), fileFormats); + } + + /** + * 根据文件名判断文件是否为指定格式 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileFormats( + final String filePath, + final String[] fileFormats + ) { + if (filePath == null || fileFormats == null || fileFormats.length == 0) return false; + String path = filePath.toUpperCase(); + for (String format : fileFormats) { + if (format != null) { + if (path.endsWith(format.toUpperCase())) { + return true; + } + } + } + return false; + } + + // ============ + // = MD5Utils = + // ============ + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] getFileMD5(final String filePath) { + return MD5Utils.getFileMD5(filePath); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final String filePath) { + return MD5Utils.getFileMD5ToHexString(filePath); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final File file) { + return MD5Utils.getFileMD5ToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] getFileMD5(final File file) { + return MD5Utils.getFileMD5(file); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ForUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ForUtils.java new file mode 100644 index 0000000000..13f5f58856 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ForUtils.java @@ -0,0 +1,627 @@ +package dev.utils.common; + +/** + * detail: 循环工具类 + * @author Ttt + */ +public final class ForUtils { + + private ForUtils() { + } + + // 日志 TAG + private static final String TAG = ForUtils.class.getSimpleName(); + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface Consumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + T value + ); + } + + // ========== + // = 可变数组 = + // ========== + + // ========== + // = 对象类型 = + // ========== + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forArgs( + final Consumer action, + final T... args + ) { + return forArgs(action, false, args); + } + + /** + * 循环可变数组 + *
+     *     基础类型需要传入包装类型, 如 int 传入为 Integer 为泛型类型
+     * 
+ * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forArgs( + final Consumer action, + final boolean checkLength, + final T... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + T value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========== + // = Simple = + // ========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ConsumerSimple { + + /** + * 循环消费方法 + * @param value 对应索引值 + */ + void accept(T value); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forSimpleArgs( + final ConsumerSimple action, + final T... args + ) { + return forSimpleArgs(action, false, args); + } + + /** + * 循环可变数组 + *
+     *     基础类型需要传入包装类型, 如 int 传入为 Integer 为泛型类型
+     * 
+ * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forSimpleArgs( + final ConsumerSimple action, + final boolean checkLength, + final T... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (T value : args) { + action.accept(value); + } + return true; + } + return false; + } + + // ========== + // = 基础类型 = + // ========== + + // ======= + // = Int = + // ======= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface IntConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + int value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forInts( + final IntConsumer action, + final int... args + ) { + return forInts(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forInts( + final IntConsumer action, + final boolean checkLength, + final int... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + int value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========== + // = Double = + // ========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface DoubleConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + double value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forDoubles( + final DoubleConsumer action, + final double... args + ) { + return forDoubles(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forDoubles( + final DoubleConsumer action, + final boolean checkLength, + final double... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + double value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========= + // = Float = + // ========= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface FloatConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + float value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forFloats( + final FloatConsumer action, + final float... args + ) { + return forFloats(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forFloats( + final FloatConsumer action, + final boolean checkLength, + final float... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + float value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Long = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface LongConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + long value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forLongs( + final LongConsumer action, + final long... args + ) { + return forLongs(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forLongs( + final LongConsumer action, + final boolean checkLength, + final long... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + long value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // =========== + // = Boolean = + // =========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface BooleanConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + boolean value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBooleans( + final BooleanConsumer action, + final boolean... args + ) { + return forBooleans(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBooleans( + final BooleanConsumer action, + final boolean checkLength, + final boolean... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + boolean value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Byte = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ByteConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + byte value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBytes( + final ByteConsumer action, + final byte... args + ) { + return forBytes(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBytes( + final ByteConsumer action, + final boolean checkLength, + final byte... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + byte value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Char = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface CharConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + char value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forChars( + final CharConsumer action, + final char... args + ) { + return forChars(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forChars( + final CharConsumer action, + final boolean checkLength, + final char... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + char value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========= + // = Short = + // ========= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ShortConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + short value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forShorts( + final ShortConsumer action, + final short... args + ) { + return forShorts(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forShorts( + final ShortConsumer action, + final boolean checkLength, + final short... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + short value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/FormatUtils.java b/lib/DevApp/src/main/java/dev/utils/common/FormatUtils.java new file mode 100644 index 0000000000..309be894ee --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/FormatUtils.java @@ -0,0 +1,142 @@ +package dev.utils.common; + +import dev.utils.common.format.ArgsFormatter; +import dev.utils.common.format.UnitSpanFormatter; + +/** + * detail: 格式化工具类 + * @author Ttt + */ +public final class FormatUtils { + + private FormatUtils() { + } + + // 日志 TAG + private static final String TAG = FormatUtils.class.getSimpleName(); + + /** + * 字符串格式化 + * @param format 待格式化字符串 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String format( + final String format, + final Object... args + ) { + return StringUtils.format(format, args); + } + + // ============= + // = Unit Span = + // ============= + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision + ) { + return UnitSpanFormatter.get(precision); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final String defaultValue + ) { + return UnitSpanFormatter.get(precision, defaultValue); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final boolean appendZero + ) { + return UnitSpanFormatter.get(precision, appendZero); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + return UnitSpanFormatter.get(precision, appendZero, defaultValue); + } + + // ======== + // = args = + // ======== + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier + ) { + return ArgsFormatter.get(startSpecifier, middleSpecifier); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier + ) { + return ArgsFormatter.get( + startSpecifier, middleSpecifier, endSpecifier + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + return ArgsFormatter.get( + startSpecifier, middleSpecifier, endSpecifier, + throwError, defaultValue + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/HtmlUtils.java b/lib/DevApp/src/main/java/dev/utils/common/HtmlUtils.java new file mode 100644 index 0000000000..9cea7aee20 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/HtmlUtils.java @@ -0,0 +1,248 @@ +package dev.utils.common; + +/** + * detail: Html 工具类 + * @author Ttt + */ +public final class HtmlUtils { + + private HtmlUtils() { + } + + // 移除 padding、margin style + public static final String REMOVE_PADDING_MARGIN_STYLE = ""; + + /** + * 为给定的 Html 移除 padding、margin + * @param html HTML 字符串 + * @return Html 内容字符串 + */ + public static String addRemovePaddingMargin(final String html) { + return REMOVE_PADDING_MARGIN_STYLE + html; + } + + /** + * 为给定的字符串添加 HTML 颜色标记 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColor( + final String content, + final String color + ) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 颜色标记 + * @param format 格式化字符串 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColor( + final String format, + final String content, + final String color + ) { + return StringUtils.format(format, addHtmlColor(content, color)); + } + + /** + * 为给定的字符串添加 HTML 加粗标记 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlBold(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 颜色标记并加粗 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColorAndBold( + final String content, + final String color + ) { + return addHtmlBold(addHtmlColor(content, color)); + } + + /** + * 为给定的字符串添加 HTML 下划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlUnderline(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 中划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlStrikeThruLine(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 上划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlOverLine(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 字体倾斜 + *
+     *     如果需要倾斜自定义角度, 需要自定义 TextView, 在 onDraw 里面加上倾斜度, 上下左右居中
+     *     canvas.rotate( 倾斜角度, getMeasuredWidth() / 3, getMeasuredHeight() / 3);
+     * 
+ * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlIncline(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML SPAN 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlSPAN(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML P 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlP(final String content) { + return "

" + content + "

"; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @return Html 内容字符串 + */ + public static String addHtmlIMG(final String url) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param width 图片宽度 + * @param height 图片高度 + * @return Html 内容字符串 + */ + public static String addHtmlIMG( + final String url, + final String width, + final String height + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param width 图片宽度 + * @return Html 内容字符串 + */ + public static String addHtmlIMGByWidth( + final String url, + final String width + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param height 图片高度 + * @return Html 内容字符串 + */ + public static String addHtmlIMGByHeight( + final String url, + final String height + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlDIV(final String content) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param margin margin 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByMargin( + final String content, + final String margin + ) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param padding padding 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByPadding( + final String content, + final String padding + ) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param margin margin 边距 + * @param padding padding 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByMarginPadding( + final String content, + final String margin, + final String padding + ) { + return "
" + content + "
"; + } + + // = + + /** + * 将给定的字符串中所有给定的关键字标色 + * @param content 给定的字符串 + * @param keyword 给定的关键字 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String keywordReplaceHtmlColor( + final String content, + final String keyword, + final String color + ) { + return StringUtils.replaceAll(content, keyword, addHtmlColor(keyword, color)); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/HttpParamsUtils.java b/lib/DevApp/src/main/java/dev/utils/common/HttpParamsUtils.java new file mode 100644 index 0000000000..1b22c3ddd0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/HttpParamsUtils.java @@ -0,0 +1,352 @@ +package dev.utils.common; + +import java.net.URLEncoder; +import java.util.LinkedHashMap; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Http 参数工具类 + * @author Ttt + */ +public final class HttpParamsUtils { + + private HttpParamsUtils() { + } + + // 日志 TAG + private static final String TAG = HttpParamsUtils.class.getSimpleName(); + + /** + * 获取 Url 携带参数 + * @param url URL 链接 + * @return Url 携带参数 + */ + public static String getUrlParams(final String url) { + return getUrlParamsArray(url)[1]; + } + + /** + * 获取 Url、携带参数 数组 + * @param url URL 链接 + * @return 0 = url, 1 = params + */ + public static String[] getUrlParamsArray(final String url) { + String[] result = new String[2]; + if (StringUtils.isNotEmpty(url)) { + // 清除掉前后空格 + String urlClean = StringUtils.clearSEWiths(url, " "); + // 清除掉结尾的 ? + urlClean = StringUtils.clearEndsWith(urlClean, "?"); + // 进行拆分 + int index = urlClean.indexOf("?"); + if (index != -1) { + result[0] = urlClean.substring(0, index); + result[1] = urlClean.substring(index + 1); + } else { + result[0] = urlClean; + } + } + return result; + } + + /** + * 判断是否存在参数 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean existsParams(final String params) { + return splitParams(params).size() != 0; + } + + /** + * 通过 Url 判断是否存在参数 + * @param url URL 链接 + * @return {@code true} yes, {@code false} no + */ + public static boolean existsParamsByURL(final String url) { + return splitParams(getUrlParams(url)).size() != 0; + } + + /** + * 拼接 Url 及携带参数 + * @param url URL 链接 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static String joinUrlParams( + final String url, + final String params + ) { + if (StringUtils.isEmpty(params)) return url; + // 获取拼接符号 + String symbol = getUrlParamsJoinSymbol(url, params); + return url + symbol + params; + } + + /** + * 获取 Url 及携带参数 拼接符号 + * @param url URL 链接 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static String getUrlParamsJoinSymbol( + final String url, + final String params + ) { + if (StringUtils.isEmpty(params)) return ""; + // 判断是否存在参数 + if (existsParamsByURL(url)) { + return "&"; + } else { + return "?"; + } + } + + // = + + /** + * 通过 Url 拆分参数 + * @param url URL 链接 + * @return 拆分后的参数 Map + */ + public static Map splitParamsByUrl(final String url) { + return splitParamsByUrl(url, false); + } + + /** + * 通过 Url 拆分参数 + * @param url URL 链接 + * @param urlEncode 是否需要 URL 编码 + * @return 拆分后的参数 Map + */ + public static Map splitParamsByUrl( + final String url, + final boolean urlEncode + ) { + return splitParams(getUrlParams(url), urlEncode); + } + + /** + * 拆分参数 + * @param params 请求参数字符串 + * @return 拆分后的参数 Map + */ + public static Map splitParams(final String params) { + return splitParams(params, false); + } + + /** + * 拆分参数 + * @param params 请求参数字符串 + * @param urlEncode 是否需要 URL 编码 + * @return 拆分后的参数 Map + */ + public static Map splitParams( + final String params, + final boolean urlEncode + ) { + Map mapParams = new LinkedHashMap<>(); + if (StringUtils.isNotEmpty(params)) { + // 拆分数据 + String[] keyValues = params.split("&"); + // 数据长度 + int valLength; + // 进行循环遍历 + for (String val : keyValues) { + // 数据不为 null + if (val != null && (valLength = val.length()) != 0) { + // 获取首位 = 索引 + int indexOf = val.indexOf('='); + // 不存在则不处理 + if (indexOf != -1) { + // 获取 key + String key = val.substring(0, indexOf); + // 获取 value + String value; + // 防止资源浪费 + if (indexOf + 1 == valLength) { + value = ""; + } else { + value = val.substring(indexOf + 1, valLength); + } + // 判断是否编码 + if (urlEncode) { + mapParams.put(key, urlEncode(value)); + } else { + mapParams.put(key, value); + } + } + } + } + } + return mapParams; + } + + // = + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @return 拼接后的参数 + */ + public static String joinParams(final Map mapParams) { + return joinParams(mapParams, false); + } + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @param urlEncode 是否需要 URL 编码 + * @return 拼接后的参数 + */ + public static String joinParams( + final Map mapParams, + final boolean urlEncode + ) { + if (mapParams != null) { + int index = 0; + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : mapParams.entrySet()) { + if (index > 0) builder.append('&'); + builder.append(entry.getKey()); + builder.append('='); + builder.append(urlEncode ? urlEncode(entry.getValue()) : entry.getValue()); + index++; + } + return builder.toString(); + } + return null; + } + + // = + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @return 拼接后的参数 + */ + public static String joinParamsObj(final Map mapParams) { + return joinParamsObj(mapParams, false); + } + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @param urlEncode 是否需要 URL 编码 + * @return 拼接后的参数 + */ + public static String joinParamsObj( + final Map mapParams, + final boolean urlEncode + ) { + if (mapParams != null) { + int index = 0; + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : mapParams.entrySet()) { + if (index > 0) builder.append('&'); + builder.append(entry.getKey()); + builder.append('='); + if (urlEncode) { + String strValue = ConvertUtils.newStringNotArrayDecode( + entry.getValue() + ); + if (strValue != null) { + builder.append(urlEncode(strValue)); + } + } else { + builder.append(entry.getValue()); + } + index++; + } + return builder.toString(); + } + return null; + } + + // ====================================== + // = 拼接成, 模拟 JavaScript 传递对象数组格式 = + // ====================================== + + /** + * 进行转换对象处理 ( 请求发送对象 ) + * @param mapParams Map 请求参数 + * @param objStr 数组名 + * @param key 数组 key + * @param value 数组 [key] 保存值 + * @return {@code true} success, {@code false} fail + */ + public static boolean convertObjToMS( + final Map mapParams, + final String objStr, + final String key, + final String value + ) { + if (mapParams != null) { + String data = null; + try { + data = URLEncoder.encode(value, DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertObjToMS"); + } + mapParams.put(objStr + "[" + key + "]", data); + return true; + } + return false; + } + + /** + * 进行转换对象处理 ( 请求发送对象 ) + * @param mapParams Map 请求参数 + * @param objStr 数组名 + * @param key 数组 key + * @param value 数组 [key] 保存值 + * @return {@code true} success, {@code false} fail + */ + public static boolean convertObjToMO( + final Map mapParams, + final String objStr, + final String key, + final Object value + ) { + if (mapParams != null) { + Object data = null; + try { + data = URLEncoder.encode(value.toString(), DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertObjToMO"); + } + mapParams.put(objStr + "[" + key + "]", data); + return true; + } + return false; + } + + // =============== + // = StringUtils = + // =============== + + /** + * 进行 URL 编码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 编码后的字符串 + */ + public static String urlEncode(final String str) { + return StringUtils.urlEncode(str); + } + + /** + * 进行 URL 编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式 URL 编码后的字符串 + */ + public static String urlEncode( + final String str, + final String enc + ) { + return StringUtils.urlEncode(str, enc); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/HttpURLConnectionUtils.java b/lib/DevApp/src/main/java/dev/utils/common/HttpURLConnectionUtils.java new file mode 100644 index 0000000000..c71ae4b752 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/HttpURLConnectionUtils.java @@ -0,0 +1,271 @@ +package dev.utils.common; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: HttpURLConnection 网络工具类 + * @author Ttt + *
+ *     详细解释 HttpURLConnection 类
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class HttpURLConnectionUtils { + + private HttpURLConnectionUtils() { + } + + // 日志 TAG + private static final String TAG = HttpURLConnectionUtils.class.getSimpleName(); + + // 请求超时时间 + private static final int TIMEOUT_IN_MILLIONS = 5000; + + /** + * detail: 请求回调 + * @author Ttt + */ + public interface Callback { + + /** + * 请求响应回调 + * @param result 请求结果 + * @param response 请求响应时间 + */ + void onResponse( + String result, + long response + ); + + /** + * 请求失败 + * @param error 失败异常 + */ + void onFail(Throwable error); + } + + /** + * 异步的 Get 请求 + * @param urlStr 请求地址 + * @param callback 请求回调接口 + */ + public static void doGetAsync( + final String urlStr, + final Callback callback + ) { + new Thread(() -> { + try { + request("GET", urlStr, null, null, callback); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doGetAsync"); + } + }).start(); + } + + /** + * 异步的 Post 请求 + * @param urlStr 请求地址 + * @param params 请求参数 + * @param callback 请求回调接口 + */ + public static void doPostAsync( + final String urlStr, + final String params, + final Callback callback + ) { + new Thread(() -> { + try { + request("POST", urlStr, null, params, callback); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doPostAsync"); + } + }).start(); + } + + /** + * 发送请求 + * @param method 请求方法 + * @param urlStr 请求地址字符串 + * @param headers 请求头信息 + * @param params 请求参数 + * @param callback 请求回调接口 + */ + public static void request( + final String method, + final String urlStr, + final Map headers, + final String params, + final Callback callback + ) { + // 获取连接对象 + HttpURLConnection connection = null; + InputStream is = null; + ByteArrayOutputStream baos = null; + try { + // 请求路径 + URL url = new URL(urlStr); + // 获取连接对象 + connection = (HttpURLConnection) url.openConnection(); + // 设置请求方法 + connection.setRequestMethod(method); + // 设置请求头信息 + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + // 判断是否需要写入数据 + if (params != null && params.length() != 0) { + // 允许写入 + connection.setDoInput(true); + // 设置是否向 connection 输出, 如果是 post 请求, 参数要放在 http 正文内, 因此需要设为 true + connection.setDoOutput(true); + // post 请求不能使用缓存 + connection.setUseCaches(false); + // 写入数据 + OutputStream os = connection.getOutputStream(); + os.write(params.getBytes()); + os.flush(); + os.close(); + } + // 单位是毫秒 + connection.setConnectTimeout(TIMEOUT_IN_MILLIONS); // 设置连接超时 + connection.setReadTimeout(TIMEOUT_IN_MILLIONS); // 设置读取超时 + // 获取请求状态码 + int responseCode = connection.getResponseCode(); + // 判断请求码是否是 200 + if (responseCode >= 200 && responseCode < 300) { + // 输入流 + is = connection.getInputStream(); + baos = new ByteArrayOutputStream(); + // 设置缓存流大小 + byte[] buffer = new byte[1024]; + int len; + while (((len = is.read(buffer)) != -1)) { + baos.write(buffer, 0, len); + } + // 获取请求结果 + String result = baos.toString(); + // 判断是否回调 + if (callback != null) { + // 请求成功, 触发回调 + callback.onResponse(result, connection.getDate()); + } + } else { + // 响应成功, 非 200 直接返回 null + if (callback != null) { + callback.onFail(new Exception("responseCode not >= 200 or < 300, code: " + responseCode)); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "request"); + if (callback != null) { + callback.onFail(e); + } + } finally { + CloseUtils.closeIOQuietly(baos, is); + + if (connection != null) { + try { // 关闭底层连接 Socket + connection.disconnect(); + } catch (Exception ignore) { + } + } + } + } + + // ================= + // = 获取网络时间处理 = + // ================= + + public static final String BAIDU_URL = "https://www.baidu.com"; + + /** + * detail: 时间回调 + * @author Ttt + */ + public interface TimeCallback { + + /** + * 请求响应回调 + * @param millis 响应时间 ( 毫秒 ) + */ + void onResponse(long millis); + + /** + * 请求失败 + * @param error 失败异常 + */ + void onFail(Throwable error); + } + + /** + * 获取网络时间 ( 默认使用百度链接 ) + * @param callback 请求时间回调接口 + */ + public static void getNetTime(final TimeCallback callback) { + getNetTime(BAIDU_URL, callback); + } + + /** + * 获取网络时间 + * @param urlStr 请求地址 + * @param callback 请求时间回调接口 + */ + public static void getNetTime( + final String urlStr, + final TimeCallback callback + ) { + new Thread(() -> reqNetTime(urlStr, callback)).start(); + } + + /** + * 请求网络时间 ( 内部私有 ) + * @param urlStr 请求地址 + * @param callback 请求时间回调接口 + */ + private static void reqNetTime( + final String urlStr, + final TimeCallback callback + ) { + // 获取连接对象 + HttpURLConnection connection = null; + try { + // 请求路径 + URL url = new URL(urlStr); + // 获取连接对象 + connection = (HttpURLConnection) url.openConnection(); + // 获取时间 + long date = connection.getDate(); + // 获取失败, 则进行修改 + if (date <= 0) { + date = -1L; + } + // 触发回调 + if (callback != null) { + callback.onResponse(date); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNetTime"); + // 触发回调 + if (callback != null) { + callback.onFail(e); + } + } finally { + if (connection != null) { + try { // 关闭底层连接 Socket + connection.disconnect(); + } catch (Exception ignore) { + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/MapUtils.java b/lib/DevApp/src/main/java/dev/utils/common/MapUtils.java new file mode 100644 index 0000000000..6f8d484c34 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/MapUtils.java @@ -0,0 +1,1344 @@ +package dev.utils.common; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Map 工具类 + * @author Ttt + */ +public final class MapUtils { + + private MapUtils() { + } + + // 日志 TAG + private static final String TAG = MapUtils.class.getSimpleName(); + + // ======= + // = Map = + // ======= + + /** + * 判断 Map 是否为 null + * @param map {@link Map} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Map map) { + return (map == null || map.size() == 0); + } + + /** + * 判断 Map 是否不为 null + * @param map {@link Map} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Map map) { + return (map != null && map.size() != 0); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取 Map 长度 + * @param map {@link Map} + * @return 如果 Map 为 null, 则返回默认长度, 如果不为 null, 则返回 map.size() + */ + public static int length(final Map map) { + return length(map, 0); + } + + /** + * 获取 Map 长度 + * @param map {@link Map} + * @param defaultLength 集合为 null 默认长度 + * @return 如果 Map 为 null, 则返回 defaultLength, 如果不为 null, 则返回 map.size() + */ + public static int length( + final Map map, + final int defaultLength + ) { + return map != null ? map.size() : defaultLength; + } + + // = + + /** + * 获取长度 Map 是否等于期望长度 + * @param map {@link Map} + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Map map, + final int length + ) { + return map != null && map.size() == length; + } + + // = + + /** + * 判断 Map 长度是否大于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThan( + final Map map, + final int length + ) { + return map != null && map.size() > length; + } + + /** + * 判断 Map 长度是否大于等于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThanOrEqual( + final Map map, + final int length + ) { + return map != null && map.size() >= length; + } + + // = + + /** + * 判断 Map 长度是否小于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThan( + final Map map, + final int length + ) { + return map != null && map.size() < length; + } + + /** + * 判断 Map 长度是否小于等于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThanOrEqual( + final Map map, + final int length + ) { + return map != null && map.size() <= length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取 Map 数组长度总和 + * @param maps Map[] + * @return Map 数组长度总和 + */ + public static int getCount(final Map... maps) { + if (maps == null) return 0; + int count = 0; + for (Map map : maps) { + count += length(map); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取 value + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 的 value + */ + public static V get( + final Map map, + final K key + ) { + if (map != null) { + try { + return map.get(key); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 获取 value 如果 value 为 null, 则返回 defaultValue + * @param map {@link Map} + * @param key key + * @param defaultValue 默认 value + * @param key + * @param value + * @return 指定 key 的 value 如果 value 为 null, 则返回 defaultValue + */ + public static V get( + final Map map, + final K key, + final V defaultValue + ) { + if (map != null) { + try { + V value = map.get(key); + if (value == null) { + return defaultValue; + } else { + return value; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 通过 value 获取 key + * @param map {@link Map} + * @param value Value + * @param key + * @param value + * @return 等于 value 的 key + */ + public static K getKeyByValue( + final Map map, + final V value + ) { + if (map != null) { + try { + for (Map.Entry entry : map.entrySet()) { + V v = entry.getValue(); + if (equals(v, value)) { + return entry.getKey(); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeyByValue"); + } + } + return null; + } + + /** + * 通过 value 获取 key 集合 ( 返回等于 value 的 key 集合 ) + * @param map {@link Map} + * @param value Value + * @param key + * @param value + * @return 等于 value 的 key 集合 + */ + public static List getKeysByValue( + final Map map, + final V value + ) { + if (map != null) { + try { + List lists = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + V v = entry.getValue(); + if (equals(v, value)) { + lists.add(entry.getKey()); + } + } + return lists; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeysByValue"); + } + } + return null; + } + + // = + + /** + * 通过 Map 获取 key 集合 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 key 集合 + */ + public static List getKeys(final Map map) { + if (map != null) { + try { + return new ArrayList<>(map.keySet()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeys"); + } + } + return null; + } + + /** + * 通过 Map 获取 key 数组 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 key 数组 + */ + public static K[] getKeysToArrays(final Map map) { + if (map != null) { + try { + return CollectionUtils.toArrayT(getKeys(map)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeysToArrays"); + } + } + return null; + } + + /** + * 通过 Map 获取 value 集合 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 value 数组 + */ + public static List getValues(final Map map) { + if (map != null) { + try { + return new ArrayList<>(map.values()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getValues"); + } + } + return null; + } + + /** + * 通过 Map 获取 value 数组 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 value 数组 + */ + public static V[] getValuesToArrays(final Map map) { + if (map != null) { + try { + return CollectionUtils.toArrayT(getValues(map)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getValuesToArrays"); + } + } + return null; + } + + // = + + /** + * 获取第一条数据 + * @param map {@link Map} + * @param key + * @param value + * @return 第一条数据 {@link Map.Entry} + */ + public static Map.Entry getFirst(final LinkedHashMap map) { + if (map != null) { + try { + return map.entrySet().iterator().next(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFirst"); + } + } + return null; + } + + /** + * 获取最后一条数据 + * @param map {@link Map} + * @param key + * @param value + * @return 最后一条数据 {@link Map.Entry} + */ + public static Map.Entry getLast(final LinkedHashMap map) { + return getLast(map, true); + } + + /** + * 获取最后一条数据 + * @param map {@link Map} + * @param isReflection 是否使用反射 + * @param key + * @param value + * @return 最后一条数据 {@link Map.Entry} + */ + public static Map.Entry getLast( + final LinkedHashMap map, + final boolean isReflection + ) { + if (map != null) { + if (isReflection) { + try { + // 反射方式 + Field tail = map.getClass().getDeclaredField("tail"); + tail.setAccessible(true); + return (Map.Entry) tail.get(map); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getLast"); + } + } else { + try { + // 遍历方式 + Iterator> iterator = map.entrySet().iterator(); + Map.Entry tail = null; + while (iterator.hasNext()) { + tail = iterator.next(); + } + return tail; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getLast"); + } + } + } + return null; + } + + // = + + /** + * 根据指定 key 获取 key 所在位置的下一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 下一条数据 {@link Map.Entry} + */ + public static Map.Entry getNext( + final LinkedHashMap map, + final K key + ) { + if (map != null) { + try { + for (Map.Entry entry : map.entrySet()) { + K k = entry.getKey(); + if (equals(k, key)) { + return entry; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNext"); + } + } + return null; + } + + /** + * 根据指定 key 获取 key 所在位置的上一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 上一条数据 {@link Map.Entry} + */ + public static Map.Entry getPrevious( + final LinkedHashMap map, + final K key + ) { + if (map != null) { + try { + Iterator> iterator = map.entrySet().iterator(); + // 临时保存处理 + Map.Entry temp = null; + // 循环处理 + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + K k = entry.getKey(); + // 判断 key 是否相同 + if (equals(k, key)) { + return temp; + } + // 赋值上一条数据 + temp = entry; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPrevious"); + } + } + return null; + } + + // ========== + // = 添加数据 = + // ========== + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final K key, + final V value + ) { + return put(map, key, value, false); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final K key, + final V value, + final boolean notNull + ) { + if (map != null) { + if (notNull && key == null) { + return false; + } + try { + map.put(key, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "put"); + } + } + return false; + } + + /** + * 添加一条数据 ( 不允许 key 为 null ) + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putNotNull( + final Map map, + final K key, + final V value + ) { + return put(map, key, value, true); + } + + // = + + /** + * 添加一条数据 + * @param map {@link Map} + * @param entry entry + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final Map.Entry entry + ) { + return put(map, entry, false); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param entry entry + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final Map.Entry entry, + final boolean notNull + ) { + if (map != null && entry != null) { + if (notNull && entry.getKey() == null) { + return false; + } + try { + map.put(entry.getKey(), entry.getValue()); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "put"); + } + } + return false; + } + + /** + * 添加一条数据 ( 不允许 key 为 null ) + * @param map {@link Map} + * @param entry entry + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putNotNull( + final Map map, + final Map.Entry entry + ) { + return put(map, entry, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final List listKeys, + final List listValues + ) { + return putAll(map, listKeys, listValues, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final List listKeys, + final List listValues, + final boolean notNull + ) { + if (map != null && listKeys != null && listValues != null + && listKeys.size() == listValues.size()) { + try { + // 循环保存 + for (int i = 0, len = listKeys.size(); i < len; i++) { + K key = listKeys.get(i); + if (notNull && key == null) { + continue; // 忽略进行下一个 + } + // 添加数据 + map.put(key, listValues.get(i)); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final List listKeys, + final List listValues + ) { + return putAll(map, listKeys, listValues, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param keys keys + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final K[] keys, + final V[] values + ) { + return putAll(map, keys, values, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param keys keys + * @param values values + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final K[] keys, + final V[] values, + final boolean notNull + ) { + if (map != null && keys != null && values != null && keys.length == values.length) { + try { + // 循环保存 + for (int i = 0, len = keys.length; i < len; i++) { + K key = keys[i]; + if (notNull && key == null) { + continue; // 忽略进行下一个 + } + // 添加数据 + map.put(key, values[i]); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param keys keys + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final K[] keys, + final V[] values + ) { + return putAll(map, keys, values, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param mapData map 数据 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final Map mapData + ) { + return putAll(map, mapData, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param mapData map 数据 + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final Map mapData, + final boolean notNull + ) { + if (map != null && mapData != null) { + if (notNull) { + try { + for (Map.Entry entry : mapData.entrySet()) { + K key = entry.getKey(); + if (key != null) { + map.put(key, entry.getValue()); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } else { + try { + map.putAll(mapData); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param mapData map 数据 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final Map mapData + ) { + return putAll(map, mapData, true); + } + + // ========== + // = 删除数据 = + // ========== + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Map map, + final K key + ) { + if (map != null) { + try { + map.remove(key); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Map map, + final K key, + final V value + ) { + if (map != null) { + try { + // 判断值是否一样, 一样则移除 key + if (equals(value, map.get(key))) { + map.remove(key); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除多条数据 + * @param map {@link Map} + * @param keys keys + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToKeys( + final Map map, + final Collection keys + ) { + if (map != null && keys != null) { + try { + for (K key : keys) { + map.remove(key); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToKeys"); + } + } + return false; + } + + /** + * 移除等于 value 的所有数据 + * @param map {@link Map} + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToValue( + final Map map, + final V value + ) { + if (map != null) { + try { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + V v = entry.getValue(); + if (equals(v, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToValue"); + } + } + return false; + } + + /** + * 移除等于 value 的所有数据 ( Collection ) + * @param map {@link Map} + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToValues( + final Map map, + final Collection values + ) { + if (map != null && values != null) { + try { + for (V value : values) { + removeToValue(map, value); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToValues"); + } + } + return false; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // = + + /** + * 切换保存状态 + *
+     *     1. 如果存在, 则删除
+     *     2. 如果不存在, 则保存
+     * 
+ * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean toggle( + final Map map, + final K key, + final V value + ) { + if (map != null) { + // 判断是否存在 key + boolean existKey = map.containsKey(key); + try { + if (existKey) { // 存在则删除 + map.remove(key); + } else { // 不存在则保存 + map.put(key, value); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toggle"); + } + } + return false; + } + + /** + * 判断指定 key 的 value 是否为 null + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullToValue( + final Map map, + final K key + ) { + if (map != null) { + return map.get(key) == null; + } + return true; + } + + /** + * 判断 Map 是否存储 key + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean containsKey( + final Map map, + final K key + ) { + if (map != null) { + try { + return map.containsKey(key); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "containsKey"); + } + } + return false; + } + + /** + * 判断 Map 是否存储 value + * @param map {@link Map} + * @param value value + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean containsValue( + final Map map, + final V value + ) { + if (map != null) { + try { + return map.containsValue(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "containsValue"); + } + } + return false; + } + + // =============== + // = 特殊 Map 操作 = + // =============== + + /** + * 添加一条数据 + * @param map 待添加 {@link Map} + * @param key key + * @param value value, add to list + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean putToList( + final Map> map, + final K key, + final T value + ) { + return putToList(map, key, value, true); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value, add to list + * @param isNew 当指定 (key) 的 value 为 null, 是否创建 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean putToList( + final Map> map, + final K key, + final T value, + final boolean isNew + ) { + if (map != null) { + if (map.containsKey(key)) { + List lists = map.get(key); + if (lists != null) { + try { + lists.add(value); + map.put(key, lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putToList"); + } + } + } else { + // 判断是否创建 + if (isNew) { + try { + List lists = new ArrayList<>(); + lists.add(value); + map.put(key, lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putToList"); + } + } + } + } + return false; + } + + // = + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToList( + final Map> map, + final K key + ) { + if (map != null) { + try { + map.remove(key); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToList"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param value value, remove to list + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToList( + final Map> map, + final K key, + final T value + ) { + if (map != null) { + if (map.containsKey(key)) { + List lists = map.get(key); + if (lists != null) { + try { + lists.remove(value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToList"); + } + } + } + } + return false; + } + + /** + * 移除多条数据 + * @param map {@link Map} + * @param key key + * @param lists 删除的 list 数据源 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToLists( + final Map> map, + final K key, + final List lists + ) { + if (map != null && lists != null) { + if (map.containsKey(key)) { + List list = map.get(key); + if (list != null) { + try { + list.removeAll(lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToLists"); + } + } + } + } + return false; + } + + // = + + /** + * 移除多条数据 ( 通过 Map 进行移除 ) + * @param map {@link Map} + * @param removeMap {@link Map} 移除对比数据源 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToMap( + final Map> map, + final Map> removeMap + ) { + return removeToMap(map, removeMap, true, false); + } + + /** + * 移除多条数据 ( 通过 Map 进行移除 ) + * @param map {@link Map} + * @param removeMap {@link Map} 移除对比数据源 + * @param removeEmpty 是否移除 null、长度为 0 的数据 + * @param isNullRemoveAll 如果待移除的 List 是 null, 是否移除全部 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToMap( + final Map> map, + final Map> removeMap, + final boolean removeEmpty, + final boolean isNullRemoveAll + ) { + if (map != null && removeMap != null) { + for (Map.Entry> entry : removeMap.entrySet()) { + K key = entry.getKey(); + // 进行移除处理 + if (map.containsKey(key)) { + List value = entry.getValue(); + try { + if (value != null) { + map.get(key).removeAll(value); + } else { + if (isNullRemoveAll) { + map.remove(key); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToMap - removeAll"); + } + // 判断是否移除 null、长度为 0 的数据 + if (removeEmpty) { + List lists = map.get(key); + try { + // 不存在数据了, 则移除 + if (lists == null || lists.size() == 0) { + map.remove(key); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToMap"); + } + } + } + } + return true; + } + return false; + } + + // ========== + // = 拼接处理 = + // ========== + + /** + * 键值对拼接 + * @param map {@link Map} + * @param symbol 拼接符号 + * @param key + * @param value + * @return {@link StringBuilder} + */ + public static StringBuilder mapToString( + final Map map, + final String symbol + ) { + return mapToString(map, symbol, new StringBuilder()); + } + + /** + * 键值对拼接 + * @param map {@link Map} + * @param symbol 拼接符号 + * @param builder Builder + * @param key + * @param value + * @return {@link StringBuilder} + */ + public static StringBuilder mapToString( + final Map map, + final String symbol, + final StringBuilder builder + ) { + if (map != null && builder != null) { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + builder.append(ConvertUtils.toString(entry.getKey())); + builder.append(symbol); + builder.append(ConvertUtils.toString(entry.getValue())); + // 如果还有下一行则追加换行 + if (iterator.hasNext()) { + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + } + } + return builder; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/NumberUtils.java b/lib/DevApp/src/main/java/dev/utils/common/NumberUtils.java new file mode 100644 index 0000000000..1758d11edf --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/NumberUtils.java @@ -0,0 +1,544 @@ +package dev.utils.common; + +import java.math.BigDecimal; + +import dev.utils.common.validator.ValidatorUtils; + +/** + * detail: 数字 ( 计算 ) 工具类 + * @author Ttt + */ +public final class NumberUtils { + + private NumberUtils() { + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @return 自动补 0 字符串 + */ + public static String addZero(final int value) { + return addZero(value, true); + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @param appendZero 是否自动补 0 + * @return 自动补 0 字符串 + */ + public static String addZero( + final int value, + final boolean appendZero + ) { + if (!appendZero) return String.valueOf(value); + int temp = Math.max(0, value); + return temp >= 10 ? String.valueOf(temp) : "0" + temp; + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @return 自动补 0 字符串 + */ + public static String addZero(final long value) { + return addZero(value, true); + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @param appendZero 是否自动补 0 + * @return 自动补 0 字符串 + */ + public static String addZero( + final long value, + final boolean appendZero + ) { + if (!appendZero) return String.valueOf(value); + long temp = Math.max(0, value); + return temp >= 10 ? String.valueOf(temp) : "0" + temp; + } + + /** + * 去掉结尾多余的 . 与 0 + * @param value 待处理数值 + * @return 处理后的数值字符串 + */ + public static String subZeroAndDot(final double value) { + return subZeroAndDot(String.valueOf(value)); + } + + /** + * 去掉结尾多余的 . 与 0 + * @param value 待处理数值 + * @return 处理后的数值字符串 + */ + public static String subZeroAndDot(final String value) { + if (StringUtils.isNotEmpty(value)) { + String str = value; + if (str.contains(".")) { + // 去掉多余的 0 + str = str.replaceAll("0+?$", ""); + // 最后一位是 . 则去掉 + str = str.replaceAll("[.]$", ""); + } + return str; + } + return value; + } + + // ============= + // = Unit Span = + // ============= + + /** + * 计算指定单位倍数 + *
+     *     返回数组前面都为整数倍, 最后一位可能存在小数点
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static double[] calculateUnitD( + final double value, + final double[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + double[] result = new double[len]; + double temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + double multiple = temp / unitSpans[i]; + if (i != len - 1) { + multiple = (int) multiple; + } + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static int[] calculateUnitI( + final int value, + final int[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + int[] result = new int[len]; + int temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + int multiple = temp / unitSpans[i]; + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static long[] calculateUnitL( + final long value, + final long[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + long[] result = new long[len]; + long temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + long multiple = temp / unitSpans[i]; + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     返回数组前面都为整数倍, 最后一位可能存在小数点
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static float[] calculateUnitF( + final float value, + final float[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + float[] result = new float[len]; + float temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + float multiple = temp / unitSpans[i]; + if (i != len - 1) { + multiple = (int) multiple; + } + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + // =========== + // = percent = + // =========== + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static Double percentD( + final double value, + final double max + ) { + if (max <= 0) return 0D; + if (value <= 0) return 0D; + if (value >= max) return 1D; + return value / max; + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static int percentI( + final double value, + final double max + ) { + return percentD(value, max).intValue(); + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static long percentL( + final double value, + final double max + ) { + return percentD(value, max).longValue(); + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static float percentF( + final double value, + final double max + ) { + return percentD(value, max).floatValue(); + } + + // ============================ + // = 计算百分比值 ( 可超出 100% ) = + // ============================ + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static Double percentD2( + final double value, + final double max + ) { + if (max <= 0) return 0D; + if (value <= 0) return 0D; + return value / max; + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static int percentI2( + final double value, + final double max + ) { + return percentD2(value, max).intValue(); + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static long percentL2( + final double value, + final double max + ) { + return percentD2(value, max).longValue(); + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static float percentF2( + final double value, + final double max + ) { + return percentD2(value, max).floatValue(); + } + + // ============ + // = Multiple = + // ============ + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static Double multipleD( + final double value, + final double divisor + ) { + if (value <= 0D || divisor <= 0D) return 0D; + return value / divisor; + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static int multipleI( + final double value, + final double divisor + ) { + return multipleD(value, divisor).intValue(); + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static long multipleL( + final double value, + final double divisor + ) { + return multipleD(value, divisor).longValue(); + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static float multipleF( + final double value, + final double divisor + ) { + return multipleD(value, divisor).floatValue(); + } + + // = + + /** + * 获取整数倍数 ( 自动补 1 ) + *
+     *     能够整除返回整数倍数
+     *     不能够整除返回整数倍数 + 1
+     * 
+ * @param value 被除数 + * @param divisor 除数 + * @return 整数倍数 + */ + public static int multiple( + final double value, + final double divisor + ) { + if (value <= 0 || divisor <= 0) return 0; + if (value <= divisor) return 1; + int result = (int) (value / divisor); + return ((value - divisor * result == 0D) ? result : result + 1); + } + + // ========= + // = clamp = + // ========= + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static double clamp( + final double value, + final double max, + final double min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static int clamp( + final int value, + final int max, + final int min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static long clamp( + final long value, + final long max, + final long min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static float clamp( + final float value, + final float max, + final float min + ) { + return value > max ? max : Math.max(value, min); + } + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final double number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final String number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final BigDecimal number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + // ================== + // = ValidatorUtils = + // ================== + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumber(final String str) { + return ValidatorUtils.isNumber(str); + } + + /** + * 检验数字或包含小数点 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberDecimal(final String str) { + return ValidatorUtils.isNumberDecimal(str); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ObjectUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ObjectUtils.java new file mode 100644 index 0000000000..f2fb029ad2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ObjectUtils.java @@ -0,0 +1,169 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 对象相关工具类 + * @author Ttt + */ +public final class ObjectUtils { + + private ObjectUtils() { + } + + // 日志 TAG + private static final String TAG = ObjectUtils.class.getSimpleName(); + + // ========== + // = Object = + // ========== + + /** + * 判断对象是否为空 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object object) { + if (object == null) return true; + try { + if (object.getClass().isArray() && Array.getLength(object) == 0) { + return true; + } + if (object instanceof CharSequence && object.toString().length() == 0) { + return true; + } + if (object instanceof Collection && ((Collection) object).isEmpty()) { + return true; + } + if (object instanceof Map && ((Map) object).isEmpty()) { + return true; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isEmpty"); + } + return false; + } + + /** + * 判断对象是否非空 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object object) { + return !isEmpty(object); + } + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + // 两个值都不为 null + if (value1 != null && value2 != null) { + try { + if (value1 instanceof String && value2 instanceof String) { + return value1.equals(value2); + } else if (value1 instanceof CharSequence && value2 instanceof CharSequence) { + CharSequence v1 = (CharSequence) value1; + CharSequence v2 = (CharSequence) value2; + // 获取数据长度 + int length = v1.length(); + // 判断数据长度是否一致 + if (length == v2.length()) { + for (int i = 0; i < length; i++) { + if (v1.charAt(i) != v2.charAt(i)) { + return false; + } + } + return true; + } + return false; + } + // 其他都使用 equals 判断 + return value1.equals(value2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "equals"); + } + return false; + } + // 防止两个值都为 null + return (value1 == null && value2 == null); + } + + /** + * 检查对象是否为 null, 为 null 则抛出异常, 不为 null 则返回该对象 + * @param object 对象 + * @param message 报错信息 + * @param 泛型 + * @return 非空对象 + * @throws NullPointerException null 异常 + */ + public static T requireNonNull( + final T object, + final String message + ) + throws NullPointerException { + if (object == null) throw new NullPointerException(message); + return object; + } + + /** + * 获取非空或默认对象 + * @param object 对象 + * @param defaultObject 默认值 + * @param 泛型 + * @return 非空或默认对象 + */ + public static T getOrDefault( + final T object, + final T defaultObject + ) { + return (object != null) ? object : defaultObject; + } + + /** + * 获取对象哈希值 + * @param object 对象 + * @return 哈希值 + */ + public static int hashCode(final Object object) { + return object != null ? object.hashCode() : 0; + } + + /** + * 获取一个对象的独一无二的标记 + * @param object 对象 + * @return 对象唯一标记 + */ + public static String getObjectTag(final Object object) { + if (object == null) return null; + // 对象所在的包名 + 对象的内存地址 + return object.getClass().getName() + Integer.toHexString(object.hashCode()); + } + + /** + * Object 转换所需类型对象 + * @param object Object + * @param 泛型 + * @return Object convert T object + */ + public static T convert(final Object object) { + if (object == null) return null; + try { + return (T) object; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/RandomUtils.java b/lib/DevApp/src/main/java/dev/utils/common/RandomUtils.java new file mode 100644 index 0000000000..e31a4804ea --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/RandomUtils.java @@ -0,0 +1,625 @@ +package dev.utils.common; + +import java.util.Random; + +import dev.utils.JCLogUtils; + +/** + * detail: 随机工具类 + * @author Ttt + */ +public final class RandomUtils { + + private RandomUtils() { + } + + // 日志 TAG + private static final String TAG = RandomUtils.class.getSimpleName(); + + // 0123456789 + private static final char[] NUMBERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 + }; + + // abcdefghijklmnopqrstuvwxyz + private static final char[] LOWER_CASE_LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 + }; + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] CAPITAL_LETTERS = { + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90 + }; + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] NUMBERS_AND_LETTERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + /** + * 获取伪随机 boolean 值 + * @param random Random + * @return 随机 boolean 值 + */ + public static boolean nextBoolean(final Random random) { + return random != null ? random.nextBoolean() : new Random().nextBoolean(); + } + + /** + * 获取伪随机 byte[] + * @param random Random + * @param data 随机数据源 + * @return 随机 byte[] + */ + public static byte[] nextBytes( + final Random random, + final byte[] data + ) { + if (random == null || data == null) return data; + try { + random.nextBytes(data); + } catch (Exception ignored) { + } + return data; + } + + /** + * 获取伪随机 double 值 + * @param random Random + * @return 随机 double 值 + */ + public static double nextDouble(final Random random) { + return random != null ? random.nextDouble() : new Random().nextDouble(); + } + + /** + * 获取伪随机高斯分布值 + * @param random Random + * @return 伪随机高斯分布值 + */ + public static double nextGaussian(final Random random) { + return random != null ? random.nextGaussian() : new Random().nextGaussian(); + } + + /** + * 获取伪随机 float 值 + * @param random Random + * @return 随机 float 值 + */ + public static float nextFloat(final Random random) { + return random != null ? random.nextFloat() : new Random().nextFloat(); + } + + /** + * 获取伪随机 int 值 + * @param random Random + * @return 随机 int 值 + */ + public static int nextInt(final Random random) { + return random != null ? random.nextInt() : new Random().nextInt(); + } + + /** + * 获取伪随机 int 值 ( 该值介于 [0, n) 的区间 ) + * @param random Random + * @param number 最大随机值 + * @return 随机介于 [0, n) 的区间值 + */ + public static int nextInt( + final Random random, + final int number + ) { + if (number <= 0) return 0; + return random != null ? random.nextInt(number) : new Random().nextInt(number); + } + + /** + * 获取伪随机 long 值 + * @param random Random + * @return 随机 long 值 + */ + public static long nextLong(final Random random) { + return random != null ? random.nextLong() : new Random().nextLong(); + } + + // = + + /** + * 获取伪随机 boolean 值 + * @return 随机 boolean 值 + */ + public static boolean nextBoolean() { + return new Random().nextBoolean(); + } + + /** + * 获取伪随机 byte[] + * @param data 随机数据源 + * @return 随机 byte[] + */ + public static byte[] nextBytes(final byte[] data) { + if (data == null) return null; + try { + new Random().nextBytes(data); + } catch (Exception ignored) { + } + return data; + } + + /** + * 获取伪随机 double 值 + * @return 随机 double 值 + */ + public static double nextDouble() { + return new Random().nextDouble(); + } + + /** + * 获取伪随机高斯分布值 + * @return 伪随机高斯分布值 + */ + public static double nextGaussian() { + return new Random().nextGaussian(); + } + + /** + * 获取伪随机 float 值 + * @return 随机 float 值 + */ + public static float nextFloat() { + return new Random().nextFloat(); + } + + /** + * 获取伪随机 int 值 + * @return 随机 int 值 + */ + public static int nextInt() { + return new Random().nextInt(); + } + + /** + * 获取伪随机 int 值 ( 该值介于 [0, n) 的区间 ) + * @param number 最大随机值 + * @return 随机介于 [0, n) 的区间值 + */ + public static int nextInt(final int number) { + if (number <= 0) return 0; + return new Random().nextInt(number); + } + + /** + * 获取伪随机 long 值 + * @return 随机 long 值 + */ + public static long nextLong() { + return new Random().nextLong(); + } + + // = + + /** + * 获取数字自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomNumbers(final int length) { + return getRandom(NUMBERS, length); + } + + /** + * 获取小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomLowerCaseLetters(final int length) { + return getRandom(LOWER_CASE_LETTERS, length); + } + + /** + * 获取大写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomCapitalLetters(final int length) { + return getRandom(CAPITAL_LETTERS, length); + } + + /** + * 获取大小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomLetters(final int length) { + return getRandom(LETTERS, length); + } + + /** + * 获取数字、大小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomNumbersAndLetters(final int length) { + return getRandom(NUMBERS_AND_LETTERS, length); + } + + /** + * 获取自定义数据自定义长度的随机数 + * @param source 随机的数据源 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandom( + final String source, + final int length + ) { + if (source == null) return null; + return getRandom(source.toCharArray(), length); + } + + /** + * 获取 char[] 内的随机数 + * @param chars 随机的数据源 + * @param length 需要最终长度 + * @return 随机字符串 + */ + public static String getRandom( + final char[] chars, + final int length + ) { + if (length > 0 && chars != null && chars.length != 0) { + StringBuilder builder = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) { + builder.append(chars[random.nextInt(chars.length)]); + } + return builder.toString(); + } + return null; + } + + /** + * 获取 String[] 内的随机数 + * @param strings 随机的数据源 + * @param length 需要最终长度 + * @return 随机字符串 + */ + public static String getRandom( + final String[] strings, + final int length + ) { + if (length > 0 && strings != null && strings.length != 0) { + StringBuilder builder = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) { + builder.append(strings[random.nextInt(strings.length)]); + } + return builder.toString(); + } + return null; + } + + /** + * 获取 0 - 最大随机数之间的随机数 + * @param max 最大随机数 + * @return 随机介于 [0, max) 的区间值 + */ + public static int getRandom(final int max) { + return getRandom(0, max); + } + + /** + * 获取两个数之间的随机数 ( 不含最大随机数, 需要 + 1) + * @param min 最小随机数 + * @param max 最大随机数 + * @return 随机介于 [min, max) 的区间值 + */ + public static int getRandom( + final int min, + final int max + ) { + if (min > max) { + return 0; + } else if (min == max) { + return min; + } + return min + new Random().nextInt(max - min); + } + + // = + + // 内置洗牌算法 + // java.util.Collections.shuffle(List list); + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param objects 随机数据源 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle(final Object[] objects) { + if (objects == null) return false; + return shuffle(objects, getRandom(1, objects.length)); + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param objects 随机数据源 + * @param shuffleCount 洗牌次数 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle( + final Object[] objects, + final int shuffleCount + ) { + int length; + if (shuffleCount > 0 && objects != null && (length = objects.length) >= shuffleCount) { + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(0, length - i); + Object temp = objects[length - i]; + objects[length - i] = objects[random]; + objects[random] = temp; + } + return true; + } + return false; + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param ints 随机数据源 + * @return 随机 int[] + */ + public static int[] shuffle(final int[] ints) { + if (ints == null) return null; + return shuffle(ints, getRandom(1, ints.length)); + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param ints 随机数据源 + * @param shuffleCount 洗牌次数 + * @return 随机 int[] + */ + public static int[] shuffle( + final int[] ints, + final int shuffleCount + ) { + int length; + if (shuffleCount > 0 && ints != null && (length = ints.length) >= shuffleCount) { + int[] out = new int[shuffleCount]; + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(0, length - i); + out[i - 1] = ints[random]; + int temp = ints[length - i]; + ints[length - i] = ints[random]; + ints[random] = temp; + } + return out; + } + return null; + } + + // = + + /** + * 洗牌算法 ( 第二种 ) 随机置换指定的数组使用的默认源的随机性 + * @param objects 随机数据源 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle2(final Object[] objects) { + if (objects == null) return false; + int len = objects.length; + if (len > 0) { + for (int i = 0; i < len - 1; i++) { + int idx = (int) (Math.random() * (len - i)); + Object temp = objects[idx]; + objects[idx] = objects[len - i - 1]; + objects[len - i - 1] = temp; + } + return true; + } + return false; + } + + // = + + /** + * 获取指定范围 int 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 int 值 + * @throws IllegalArgumentException 参数错误 + */ + public static int nextIntRange( + final int origin, + final int bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + Random random = new Random(); + int n = bound - origin; + if (n > 0) { + return random.nextInt(n) + origin; + } else { + int r; + do { + r = random.nextInt(); + } while (r < origin || r >= bound); + return r; + } + } + + /** + * 获取指定范围 long 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 long 值 + * @throws IllegalArgumentException 参数错误 + */ + public static long nextLongRange( + final long origin, + final long bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + Random random = new Random(); + long r = random.nextLong(); + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + { + r = (r & m) + origin; + } else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = random.nextLong() >>> 1) // retry + { + } + r += origin; + } else { // range not representable as long + while (r < origin || r >= bound) { + r = random.nextLong(); + } + } + return r; + } + + /** + * 获取指定范围 double 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 double 值 + * @throws IllegalArgumentException 参数错误 + */ + public static double nextDoubleRange( + final double origin, + final double bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + double r = new Random().nextDouble(); + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + { + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /** + * 获取随机 int[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 int[] + */ + public static int[] ints( + final int streamSize, + final int randomNumberOrigin, + final int randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// IntStream intStream = new Random().ints(streamSize, randomNumberOrigin, randomNumberBound); +// return intStream.toArray(); + try { + int[] ints = new int[streamSize]; + for (int i = 0; i < streamSize; i++) { + ints[i] = nextIntRange(randomNumberOrigin, randomNumberBound); + } + return ints; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "ints"); + } + return null; + } + + /** + * 获取随机 long[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 long[] + */ + public static long[] longs( + final int streamSize, + final long randomNumberOrigin, + final long randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// LongStream longStream = new Random().longs(streamSize, randomNumberOrigin, randomNumberBound); +// return longStream.toArray(); + try { + long[] longs = new long[streamSize]; + for (int i = 0; i < streamSize; i++) { + longs[i] = nextLongRange(randomNumberOrigin, randomNumberBound); + } + return longs; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "longs"); + } + return null; + } + + /** + * 获取随机 double[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 double[] + */ + public static double[] doubles( + final int streamSize, + final double randomNumberOrigin, + final double randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// DoubleStream doubleStream = new Random().doubles(streamSize, randomNumberOrigin, randomNumberBound); +// return doubleStream.toArray(); + try { + double[] doubles = new double[streamSize]; + for (int i = 0; i < streamSize; i++) { + doubles[i] = nextDoubleRange(randomNumberOrigin, randomNumberBound); + } + return doubles; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doubles"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/Reflect2Utils.java b/lib/DevApp/src/main/java/dev/utils/common/Reflect2Utils.java new file mode 100644 index 0000000000..6516ebf274 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/Reflect2Utils.java @@ -0,0 +1,699 @@ +package dev.utils.common; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import dev.utils.JCLogUtils; + +/** + * detail: 反射相关工具类 + * @author Ttt + *
+ *     有两个方法: getMethod, getDeclaredMethod
+ *     

+ * getDeclaredMethod() 获取的是类自身声明的所有方法, 包含 public、protected 和 private 方法 + * getMethod() 获取的是类的所有共有方法, 这就包括自身的所有 public 方法, 和从基类继承的、从接口实现的所有 public 方法 + *

+ * getMethod 只能调用 public 声明的方法, 而 getDeclaredMethod 基本可以调用任何类型声明的方法 + * 反射多用 getDeclaredMethod 尽量少用 getMethod + *
+ */ +public final class Reflect2Utils { + + private Reflect2Utils() { + } + + // 日志 TAG + private static final String TAG = Reflect2Utils.class.getSimpleName(); + + // ========== + // = 对象变量 = + // ========== + + /** + * 设置某个对象变量值 ( 可设置静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param value 字段值 + * @return {@code true} success, {@code false} fail + */ + public static boolean setProperty( + final Object object, + final String fieldName, + final Object value + ) { + if (object == null || fieldName == null) return false; + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "setProperty"); + } + return false; + } + + /** + * 获取某个对象的变量 ( 可获取静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getProperty( + final Object object, + final String fieldName + ) { + if (object == null || fieldName == null) return null; + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProperty"); + } + return null; + } + + // =================== + // = 获取某个类的静态变量 = + // =================== + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final Object object, + final String fieldName + ) { + if (object == null) return null; + return getStaticProperty(object.getClass().getName(), fieldName); + } + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param clazz 类 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final Class clazz, + final String fieldName + ) { + if (clazz == null) return null; + return getStaticProperty(clazz.getName(), fieldName); + } + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param className 类名 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final String className, + final String fieldName + ) { + if (className == null || fieldName == null) return null; + try { + Class clazz = Class.forName(className); + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(clazz); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getStaticProperty"); + } + return null; + } + + // ================ + // = 执行某个对象方法 = + // ================ + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName + ) { + return invokeMethod(object, methodName, null, null); + } + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName, + final Object[] args + ) { + return invokeMethod(object, methodName, args, getArgsClass(args)); + } + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (object == null || methodName == null) return null; + try { + Class clazz = object.getClass(); + if (args != null && argsClass != null) { // 参数、参数类型不为 null, 并且数量相等 + if (args.length == argsClass.length && args.length != 0) { + Method method = clazz.getDeclaredMethod(methodName, argsClass); + method.setAccessible(true); + return (T) method.invoke(object, args); + } + } else { + // 无参数、参数类型, 才执行 + if (args == null && argsClass == null) { + Method method = clazz.getDeclaredMethod(methodName); + method.setAccessible(true); + return (T) method.invoke(object); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "invokeMethod"); + } + return null; + } + + // ==================== + // = 执行某个类的静态方法 = + // ==================== + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName, + final Object[] args + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, args, argsClass); + } + + // = + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName, + final Object[] args + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, args, argsClass); + } + + // = + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName + ) { + if (className == null) return null; + return invokeStaticMethod(className, methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName, + final Object[] args + ) { + if (className == null) return null; + return invokeStaticMethod(className, methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (className == null || methodName == null) return null; + try { + Class clazz = Class.forName(className); + if (args != null && argsClass != null) { // 参数、参数类型不为 null, 并且数量相等 + if (args.length == argsClass.length && args.length != 0) { + Method method = clazz.getDeclaredMethod(methodName, argsClass); + method.setAccessible(true); + return (T) method.invoke(clazz, args); + } + } else { + // 无参数、参数类型, 才执行 + if (args == null && argsClass == null) { + Method method = clazz.getDeclaredMethod(methodName); + method.setAccessible(true); + return (T) method.invoke(clazz); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "invokeStaticMethod"); + } + return null; + } + + // ========================= + // = 新建实例 ( 构造函数创建 ) = + // ========================= + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final Object object) { + if (object == null) return null; + return newInstance(object.getClass().getName(), null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Object object, + final Object[] args + ) { + if (object == null) return null; + return newInstance(object.getClass().getName(), args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Object object, + final Object[] args, + final Class[] argsClass + ) { + if (object == null) return null; + return newInstance(object.getClass().getName(), args, argsClass); + } + + // = + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final Class clazz) { + if (clazz == null) return null; + return newInstance(clazz.getName(), null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Class clazz, + final Object[] args + ) { + if (clazz == null) return null; + return newInstance(clazz.getName(), args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Class clazz, + final Object[] args, + final Class[] argsClass + ) { + if (clazz == null) return null; + return newInstance(clazz.getName(), args, argsClass); + } + + // = + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final String className) { + if (className == null) return null; + return newInstance(className, null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final String className, + final Object[] args + ) { + if (className == null) return null; + return newInstance(className, args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final String className, + final Object[] args, + final Class[] argsClass + ) { + if (className == null) return null; + try { + Class newClass = Class.forName(className); + if (args == null) { + return (T) newClass.newInstance(); + } else { + Constructor cons = newClass.getConstructor(argsClass); + return (T) cons.newInstance(args); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + } + return null; + } + + // = + + /** + * 是不是某个类的实例 + * @param object 实例 + * @param clazz 待判断类 + * @return 如果 obj 是此类的实例, 则返回 true + */ + public static boolean isInstance( + final Object object, + final Class clazz + ) { + if (object == null || clazz == null) return false; + try { + return clazz.isInstance(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isInstance"); + } + return false; + } + + /** + * 获取参数类型 + * @param args 参数 + * @return 参数类型数组 + */ + public static Class[] getArgsClass(final Object... args) { + if (args != null) { + try { + Class[] argsClass = new Class[args.length]; + for (int i = 0, len = args.length; i < len; i++) { + argsClass[i] = args[i].getClass(); + } + return argsClass; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getArgsClass"); + } + } + return new Class[0]; + } + + // = + + /** + * 获取某个对象的变量 + *
+     *     例: 获取父类中的变量
+     *     Object object = 对象;
+     *     getObject(getDeclaredFieldParent(object, "父类中变量名"), object);
+     * 
+ * @param object 对象 + * @param field {@link Field} + * @param 泛型 + * @return 该变量对象 + */ + public static T getProperty( + final Object object, + final Field field + ) { + if (object == null || field == null) return null; + try { + field.setAccessible(true); + return (T) field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProperty"); + } + return null; + } + + /** + * 获取父类中的变量对象 + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param 泛型 + * @return 父类中的变量对象 + */ + public static T getPropertyByParent( + final Object object, + final String fieldName + ) { + return getPropertyByParent(object, fieldName, 1); + } + + /** + * 获取父类中的变量对象 + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param fieldNumber 字段出现次数, 如果父类还有父类, 并且有相同变量名, 设置负数 一直会跟到最后的变量 + * @param 泛型 + * @return 父类中的变量对象 + */ + public static T getPropertyByParent( + final Object object, + final String fieldName, + final int fieldNumber + ) { + if (object == null || fieldName == null) return null; + try { + Field field = getDeclaredFieldParent(object, fieldName, fieldNumber); + return getProperty(object, field); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPropertyByParent"); + } + return null; + } + + /** + * 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) + * @param object 对象 + * @param fieldName 属性名 + * @return {@link Field} + */ + public static Field getDeclaredFieldParent( + final Object object, + final String fieldName + ) { + return getDeclaredFieldParent(object, fieldName, 1); + } + + /** + * 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param fieldNumber 字段出现次数, 如果父类还有父类, 并且有相同变量名, 设置负数 一直会跟到最后的变量 + * @return {@link Field} 父类中的属性对象 + */ + public static Field getDeclaredFieldParent( + final Object object, + final String fieldName, + final int fieldNumber + ) { + if (object == null || fieldName == null) return null; + try { + if (fieldNumber == 0) return null; + // 获取当前出现次数 + int number = 0; + // 限制值 + int limitNumber = (fieldNumber >= 0) ? fieldNumber : Integer.MAX_VALUE; + // = + Field field = null; + Class clazz = object.getClass(); + for (; clazz != Object.class; clazz = clazz.getSuperclass()) { + try { + field = clazz.getDeclaredField(fieldName); + number++; + } catch (Exception e) { + // 这里甚么都不要做, 并且这里的异常必须这样写, 不能抛出去 + // 如果这里的异常打印或者往外抛, 则就不会执行 clazz = clazz.getSuperclass(), 最后就不会进入到父类中了 + } + if (number >= limitNumber) { + return field; + } + } + // 负数表示跟到最后 + if (fieldNumber < 0) { + return field; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredFieldParent"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ReflectUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ReflectUtils.java new file mode 100644 index 0000000000..49be738951 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ReflectUtils.java @@ -0,0 +1,781 @@ +package dev.utils.common; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 反射相关工具类 + * @author Ttt + */ +public final class ReflectUtils { + + // 日志 TAG + private static final String TAG = ReflectUtils.class.getSimpleName(); + + private final Class mType; + + private final Object mObject; + + private ReflectUtils(final Class type) { + this(type, type); + } + + private ReflectUtils( + final Class type, + final Object object + ) { + this.mType = type; + this.mObject = object; + } + + // =========== + // = reflect = + // =========== + + /** + * 设置要反射的类 + * @param className 完整类名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public static ReflectUtils reflect(final String className) + throws ReflectException { + return reflect(forName(className)); + } + + /** + * 设置要反射的类 + * @param className 完整类名 + * @param classLoader 类加载器 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public static ReflectUtils reflect( + final String className, + final ClassLoader classLoader + ) + throws ReflectException { + return reflect(forName(className, classLoader)); + } + + /** + * 设置要反射的类 + * @param clazz 类的类型 + * @return {@link ReflectUtils} + */ + public static ReflectUtils reflect(final Class clazz) { + return new ReflectUtils(clazz); + } + + /** + * 设置要反射的类 + * @param object 类对象 + * @return {@link ReflectUtils} + */ + public static ReflectUtils reflect(final Object object) { + return new ReflectUtils(object == null ? Object.class : object.getClass(), object); + } + + // = + + /** + * 获取 Class + * @param className 类名 + * @param 未知类型 + * @return 指定类 + * @throws ReflectException 反射异常 + */ + private static Class forName(final String className) + throws ReflectException { + try { + return Class.forName(className); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "forName"); + throw new ReflectException(e); + } + } + + /** + * 获取 Class + * @param name 类名 + * @param classLoader 类加载器 + * @param 未知类型 + * @return 指定类 + * @throws ReflectException 反射异常 + */ + private static Class forName( + final String name, + final ClassLoader classLoader + ) + throws ReflectException { + try { + return Class.forName(name, true, classLoader); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "forName"); + throw new ReflectException(e); + } + } + + // =============== + // = newInstance = + // =============== + + /** + * 实例化反射对象 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils newInstance() + throws ReflectException { + return newInstance(new Object[0]); + } + + /** + * 实例化反射对象 + * @param args 实例化需要的参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils newInstance(final Object... args) + throws ReflectException { + Class[] types = getArgsType(args); + try { + Constructor constructor = type().getDeclaredConstructor(types); + return newInstance(constructor, args); + } catch (NoSuchMethodException e) { + List> list = new ArrayList<>(); + for (Constructor constructor : type().getDeclaredConstructors()) { + if (match(constructor.getParameterTypes(), types)) { + list.add(constructor); + } + } + if (list.isEmpty()) { + throw new ReflectException(e); + } else { + sortConstructors(list); + return newInstance(list.get(0), args); + } + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 获取参数类型 + * @param args 参数数组 + * @param 未知类型 + * @return 参数类型数组 + */ + private Class[] getArgsType(final Object... args) { + if (args == null) return new Class[0]; + Class[] result = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + Object value = args[i]; + result[i] = value == null ? NULL.class : value.getClass(); + } + return result; + } + + /** + * 进行排序 + * @param list 类构造函数信息集合 + */ + private void sortConstructors(final List> list) { + if (list == null) return; + Collections.sort(list, new Comparator>() { + @Override + public int compare( + Constructor o1, + Constructor o2 + ) { + Class[] types1 = o1.getParameterTypes(); + Class[] types2 = o2.getParameterTypes(); + int len = types1.length; + for (int i = 0; i < len; i++) { + if (!types1[i].equals(types2[i])) { + if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { + return 1; + } else { + return -1; + } + } + } + return 0; + } + }); + } + + /** + * 获取实例对象 + * @param constructor 类构造函数信息 + * @param args 构造参数数组 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + private ReflectUtils newInstance( + final Constructor constructor, + final Object... args + ) + throws ReflectException { + try { + return new ReflectUtils( + constructor.getDeclaringClass(), + accessible(constructor).newInstance(args) + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + throw new ReflectException(e); + } + } + + // ========= + // = field = + // ========= + + /** + * 设置反射的字段 + * @param name 字段名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils field(final String name) + throws ReflectException { + try { + Field field = getField(name); + return new ReflectUtils(field.getType(), field.get(mObject)); + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 设置反射的字段 + * @param name 字段名 + * @param value 字段值 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils field( + final String name, + final Object value + ) + throws ReflectException { + try { + Field field = getField(name); + field.set(mObject, unwrap(value)); + return this; + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 获取 Field 对象 + * @param name 字段名 + * @return {@link Field} + * @throws ReflectException 反射异常 + */ + private Field getField(final String name) + throws ReflectException { + Field field = getAccessibleField(name); + if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { + try { + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } catch (NoSuchFieldException ignore) { + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getField"); + throw new ReflectException(e); + } + } + return field; + } + + /** + * 获取可访问字段, 返回 Field 对象 + * @param name 字段名 + * @return {@link Field} + * @throws ReflectException 反射异常 + */ + private Field getAccessibleField(final String name) + throws ReflectException { + Class type = type(); + try { + return accessible(type.getField(name)); + } catch (NoSuchFieldException e) { + do { + try { + return accessible(type.getDeclaredField(name)); + } catch (NoSuchFieldException ignore) { + } + type = type.getSuperclass(); + } while (type != null); + JCLogUtils.eTag(TAG, e, "getAccessibleField"); + throw new ReflectException(e); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAccessibleField"); + throw new ReflectException(e); + } + } + + /** + * 获取对象 + * @param object 对象 + * @return 需要反射的对象 + */ + private Object unwrap(final Object object) { + if (object instanceof ReflectUtils) { + return ((ReflectUtils) object).get(); + } + return object; + } + + // = + + /** + * 设置枚举值 + * @param clazz 类型 + * @param name 字段名 + * @param value 字段值 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils setEnumVal( + final Class clazz, + final String name, + final String value + ) + throws ReflectException { + try { + return field(name, Enum.valueOf((Class) clazz, value)); + } catch (Exception e) { + throw new ReflectException(e); + } + } + + // ========== + // = method = + // ========== + + /** + * 设置反射的方法 + * @param name 方法名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils method(final String name) + throws ReflectException { + return method(name, new Object[0]); + } + + /** + * 设置反射的方法 + * @param name 方法名 + * @param args 方法需要的参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils method( + final String name, + final Object... args + ) + throws ReflectException { + Class[] types = getArgsType(args); + try { + Method method = exactMethod(name, types); + return method(method, mObject, args); + } catch (Exception e) { + try { + Method method = similarMethod(name, types); + return method(method, mObject, args); + } catch (Exception e1) { + throw new ReflectException(e1); + } + } + } + + /** + * 设置反射的方法处理 + * @param method 方法 + * @param object 对象 + * @param args 参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + private ReflectUtils method( + final Method method, + final Object object, + final Object... args + ) + throws ReflectException { + try { + accessible(method); + if (method.getReturnType() == void.class) { + method.invoke(object, args); + return reflect(object); + } else { + return reflect(method.invoke(object, args)); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "method"); + throw new ReflectException(e); + } + } + + /** + * 获取准确参数的方法 + * @param name 方法 + * @param types 参数类型 + * @return {@link Method} + * @throws ReflectException 反射异常 + */ + private Method exactMethod( + final String name, + final Class[] types + ) + throws ReflectException { + Class type = type(); + try { + return type.getMethod(name, types); + } catch (Exception e) { + do { + try { + return type.getDeclaredMethod(name, types); + } catch (Exception ignore) { + } + type = type.getSuperclass(); + } while (type != null); + JCLogUtils.eTag(TAG, e, "exactMethod"); + throw new ReflectException(e); + } + } + + /** + * 获取相似参数的方法 + * @param name 方法 + * @param types 参数类型 + * @return {@link Method} + * @throws ReflectException 反射异常 + */ + private Method similarMethod( + final String name, + final Class[] types + ) + throws ReflectException { + Class type = type(); + List methods = new ArrayList<>(); + for (Method method : type.getMethods()) { + if (isSimilarSignature(method, name, types)) { + methods.add(method); + } + } + if (!methods.isEmpty()) { + sortMethods(methods); + return methods.get(0); + } + do { + for (Method method : type.getDeclaredMethods()) { + if (isSimilarSignature(method, name, types)) { + methods.add(method); + } + } + if (!methods.isEmpty()) { + sortMethods(methods); + return methods.get(0); + } + type = type.getSuperclass(); + } while (type != null); + throw new ReflectException( + String.format( + "No similar method %s with params %s could be found on type %s", + name, Arrays.toString(types), type() + ) + ); + } + + /** + * 进行方法排序 + * @param methods 方法集合 + */ + private void sortMethods(final List methods) { + if (methods == null) return; + Collections.sort(methods, (o1, o2) -> { + Class[] types1 = o1.getParameterTypes(); + Class[] types2 = o2.getParameterTypes(); + int len = types1.length; + for (int i = 0; i < len; i++) { + if (!types1[i].equals(types2[i])) { + if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { + return 1; + } else { + return -1; + } + } + } + return 0; + }); + } + + /** + * 判断是否相似方法 + * @param possiblyMatchingMethod 可能的匹配方法 + * @param desiredMethodName 期望方法名 + * @param desiredParamTypes 所需参数类型 + * @return {@code true} yes, {@code false} no + */ + private boolean isSimilarSignature( + final Method possiblyMatchingMethod, + final String desiredMethodName, + final Class[] desiredParamTypes + ) { + return possiblyMatchingMethod.getName().equals(desiredMethodName) + && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes); + } + + /** + * 对比处理, 判断是否一样 + * @param declaredTypes 声明类型 + * @param actualTypes 实际类型 + * @return {@code true} yes, {@code false} no + */ + private boolean match( + final Class[] declaredTypes, + final Class[] actualTypes + ) { + if (declaredTypes != null && actualTypes != null + && declaredTypes.length == actualTypes.length) { + for (int i = 0; i < actualTypes.length; i++) { + if (actualTypes[i] == NULL.class + || wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) { + continue; + } + return false; + } + return true; + } else { + return false; + } + } + + /** + * 设置对象可访问处理 + * @param accessible 对象 + * @param 泛型 + * @return 传入的对象 + */ + private T accessible(final T accessible) { + if (accessible == null) return null; + if (accessible instanceof Member) { + Member member = (Member) accessible; + if (Modifier.isPublic(member.getModifiers()) + && Modifier.isPublic(member.getDeclaringClass().getModifiers())) { + return accessible; + } + } + if (!accessible.isAccessible()) accessible.setAccessible(true); + return accessible; + } + + // ======= + // = 代理 = + // ======= + + /** + * 根据类, 代理创建并返回对象 + * @param proxyType 代理类 + * @param

泛型 + * @return 代理的对象 + */ + public

P proxy(final Class

proxyType) { + if (proxyType == null || mObject == null) return null; + final boolean isMap = (mObject instanceof Map); + final InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke( + Object proxy, + Method method, + Object[] args + ) { + String name = method.getName(); + try { + return reflect(mObject).method(name, args).get(); + } catch (Exception e) { + if (isMap) { + Map map = (Map) mObject; + int length = (args == null ? 0 : args.length); + if (length == 0 && name.startsWith("get")) { + return map.get(property(name.substring(3))); + } else if (length == 0 && name.startsWith("is")) { + return map.get(property(name.substring(2))); + } else if (length == 1 && name.startsWith("set")) { + map.put(property(name.substring(3)), args[0]); + return null; + } + } + JCLogUtils.eTag(TAG, e, "proxy"); + } + return null; + } + }; + return (P) Proxy.newProxyInstance( + proxyType.getClassLoader(), + new Class[]{proxyType}, handler + ); + } + + /** + * 获取实体类属性名 get/set + * @param str 属性名 + * @return 属性名字 + */ + private String property(final String str) { + int length = str.length(); + if (length == 0) { + return ""; + } else if (length == 1) { + return str.toLowerCase(); + } else { + return str.substring(0, 1).toLowerCase() + str.substring(1); + } + } + + // = + + /** + * 获取类型 + * @return {@link Class} + */ + public Class type() { + return mType; + } + + /** + * 获取类型 + * @param type {@link Class} + * @param 未知类型 + * @return {@link Class} 类所属类型 + */ + private Class wrapper(final Class type) { + if (type == null) { + return null; + } else if (type.isPrimitive()) { + if (boolean.class == type) { + return Boolean.class; + } else if (int.class == type) { + return Integer.class; + } else if (long.class == type) { + return Long.class; + } else if (short.class == type) { + return Short.class; + } else if (byte.class == type) { + return Byte.class; + } else if (double.class == type) { + return Double.class; + } else if (float.class == type) { + return Float.class; + } else if (char.class == type) { + return Character.class; + } else if (void.class == type) { + return Void.class; + } + } + return type; + } + + /** + * 获取反射想要获取的 + * @param 泛型 + * @return 反射想要获取的 + */ + public T get() { + return (T) mObject; + } + + /** + * 获取 HashCode + * @return hashCode + */ + @Override + public int hashCode() { + return this.mObject != null ? mObject.hashCode() : 0; + } + + /** + * 判断反射的两个对象是否一样 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equals(final Object object) { + if (this.mObject == null && object == null) { + return true; + } else { + if (this.mObject != null && object != null) { + if (object instanceof ReflectUtils) { + return this.mObject.equals(((ReflectUtils) object).get()); + } else { + return this.mObject.equals(object); + } + } + return false; + } + } + + /** + * 获取反射获取的对象 + * @return {@link Object#toString()} + */ + @Override + public String toString() { + return this.mObject != null ? mObject.toString() : null; + } + + // = + + /** + * detail: 内部标记 null + * @author Ttt + */ + private static class NULL { + } + + /** + * detail: 定义 ReflectUtils 工具异常类 + * @author Ttt + */ + public static class ReflectException + extends Exception { + + private static final long serialVersionUID = 858774075258496016L; + + public ReflectException(String message) { + super(message); + } + + public ReflectException( + String message, + Throwable cause + ) { + super(message, cause); + } + + public ReflectException(Throwable cause) { + super(cause); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ScaleUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ScaleUtils.java new file mode 100644 index 0000000000..df1cda07a3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ScaleUtils.java @@ -0,0 +1,689 @@ +package dev.utils.common; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 计算比例工具类 + * @author Ttt + */ +public final class ScaleUtils { + + private ScaleUtils() { + } + + // 日志 TAG + private static final String TAG = ScaleUtils.class.getSimpleName(); + + /** + * 计算比例 ( 商 ) + * @param dividend 被除数 + * @param divisor 除数 + * @return 商 + */ + public static double calcScale( + final double dividend, + final double divisor + ) { + try { + return dividend / divisor; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScale"); + } + return -1D; + } + + /** + * 计算比例 ( 被除数 ( 最大值 ) / 除数 ( 最小值 ) ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return 被除数 ( 最大值 ) / 除数 ( 最小值 ) = 商 + */ + public static double calcScaleToMath( + final double value1, + final double value2 + ) { + try { + return Math.max(value1, value2) / Math.min(value1, value2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToMath"); + } + return -1D; + } + + // ========== + // = double = + // ========== + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcScaleToWidth( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + try { + if (currentWidth == 0D) { + return new double[]{0D, 0D}; + } + // 计算比例 + double scale = targetWidth / currentWidth; + // 计算缩放后的高度 + double scaleHeight = scale * currentHeight; + // 返回对应的数据 + return new double[]{targetWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToWidth"); + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcScaleToHeight( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + try { + if (currentHeight == 0D) { + return new double[]{0D, 0D}; + } + // 计算比例 + double scale = targetHeight / currentHeight; + // 计算缩放后的宽度 + double scaleWidth = scale * currentWidth; + // 返回对应的数据 + return new double[]{scaleWidth, targetHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToHeight"); + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcWidthHeightToScale( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + try { + // 如果宽度的比例, 大于等于高度比例 + if (widthScale >= heightScale) { // 以宽度为基准 + // 设置宽度, 以宽度为基准 + double scaleWidth = width; + // 计算宽度 + double scaleHeight = scaleWidth * (heightScale / widthScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } else { // 以高度为基准 + // 设置高度 + double scaleHeight = height; + // 同步缩放比例 + double scaleWidth = scaleHeight * (widthScale / heightScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcWidthHeightToScale"); + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcWidthToScale( + final double width, + final double widthScale, + final double heightScale + ) { + try { + // 设置宽度 + double scaleWidth = width; + // 计算高度 + double scaleHeight = scaleWidth * (heightScale / widthScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcWidthToScale"); + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcHeightToScale( + final double height, + final double widthScale, + final double heightScale + ) { + try { + // 设置高度 + double scaleHeight = height; + // 计算宽度 + double scaleWidth = scaleHeight * (widthScale / heightScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcHeightToScale"); + } + return null; + } + + // ======= + // = int = + // ======= + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcScaleToWidthI( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcScaleToHeightI( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcWidthHeightToScaleI( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcWidthToScaleI( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcHeightToScaleI( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + // ======== + // = long = + // ======== + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcScaleToWidthL( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcScaleToHeightL( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcWidthHeightToScaleL( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcWidthToScaleL( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcHeightToScaleL( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + // ========= + // = float = + // ========= + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcScaleToWidthF( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcScaleToHeightF( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcWidthHeightToScaleF( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcWidthToScaleF( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcHeightToScaleF( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + // ======== + // = XY 比 = + // ======== + + /** + * 计算 XY 比 + * @param x X 值 + * @param y Y 值 + * @return XY 比实体类 + */ + public static XY calcXY( + final int x, + final int y + ) { + return calcXY(XY_LIST, x, y); + } + + /** + * 计算 XY 比 + * @param xyLists XY 比集合 + * @param x X 值 + * @param y Y 值 + * @return XY 比实体类 + */ + public static XY calcXY( + final List xyLists, + final int x, + final int y + ) { + if (xyLists != null && xyLists.size() != 0) { + List lists = new ArrayList<>(xyLists); + Collections.sort(lists); + double scale = calcScale(x, y); + for (int i = 0, len = lists.size(); i < len; i++) { + XY xy = lists.get(i); + if (scale >= xy.scale) return xy; + } + } + return null; + } + + // ======== + // = 实体类 = + // ======== + + public static final List XY_LIST; + + static { + List xys = new ArrayList<>(); + xys.add(new XY(16, 9)); + xys.add(new XY(17, 10)); + xys.add(new XY(15, 9)); + xys.add(new XY(16, 10)); + xys.add(new XY(3, 2)); + xys.add(new XY(4, 3)); + xys.add(new XY(5, 4)); + xys.add(new XY(1, 1)); + XY_LIST = Collections.unmodifiableList(xys); + } + + /** + * detail: XY 比实体类 + * @author Ttt + */ + public static class XY + implements Comparable { + + public XY( + final int x, + final int y + ) { + this(x, y, 0); + } + + public XY( + final int x, + final int y, + final int type + ) { + this.x = x; + this.y = y; + this.scale = calcScale(x, y); + this.type = type; + } + + public final int x; + public final int y; + public final double scale; + public final int type; + + public String getXYx() { + return getXY("x"); + } + + public String getXY() { + return getXY(":"); + } + + public String getXY(String delimiter) { + return x + delimiter + y; + } + + @Override + public int compareTo(XY xy) { + return Double.compare(xy.scale, this.scale); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/StreamUtils.java b/lib/DevApp/src/main/java/dev/utils/common/StreamUtils.java new file mode 100644 index 0000000000..102d0889de --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/StreamUtils.java @@ -0,0 +1,200 @@ +package dev.utils.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import dev.utils.JCLogUtils; + +/** + * detail: 流操作工具类 + * @author Ttt + */ +public final class StreamUtils { + + private StreamUtils() { + } + + // 日志 TAG + private static final String TAG = StreamUtils.class.getSimpleName(); + + /** + * 输入流转输出流 + * @param inputStream {@link InputStream} + * @return {@link ByteArrayOutputStream} + */ + public static ByteArrayOutputStream inputToOutputStream(final InputStream inputStream) { + if (inputStream == null) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer, 0, 1024)) != -1) { + baos.write(buffer, 0, len); + } + return baos; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputToOutputStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(inputStream); + } + } + + /** + * 输出流转输入流 + * @param outputStream {@link OutputStream} + * @return {@link ByteArrayInputStream} + */ + public static ByteArrayInputStream outputToInputStream(final OutputStream outputStream) { + if (outputStream == null) return null; + try { + return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputToInputStream"); + return null; + } + } + + /** + * 输入流转 byte[] + * @param inputStream {@link InputStream} + * @return byte[] + */ + public static byte[] inputStreamToBytes(final InputStream inputStream) { + if (inputStream == null) return null; + try { + return inputToOutputStream(inputStream).toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputStreamToBytes"); + return null; + } + } + + /** + * byte[] 转输出流 + * @param bytes 数据源 + * @return {@link InputStream} + */ + public static InputStream bytesToInputStream(final byte[] bytes) { + if (bytes == null || bytes.length == 0) return null; + try { + return new ByteArrayInputStream(bytes); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToInputStream"); + return null; + } + } + + /** + * 输出流转 byte[] + * @param outputStream {@link OutputStream} + * @return byte[] + */ + public static byte[] outputStreamToBytes(final OutputStream outputStream) { + if (outputStream == null) return null; + try { + return ((ByteArrayOutputStream) outputStream).toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputStreamToBytes"); + return null; + } + } + + /** + * byte[] 转 输出流 + * @param bytes 数据源 + * @return {@link OutputStream} + */ + public static OutputStream bytesToOutputStream(final byte[] bytes) { + if (bytes == null || bytes.length == 0) return null; + ByteArrayOutputStream baos = null; + try { + baos = new ByteArrayOutputStream(); + baos.write(bytes); + return baos; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToOutputStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(baos); + } + } + + /** + * 输入流转 String + * @param inputStream {@link InputStream} + * @param charsetName 编码格式 + * @return 指定编码字符串 + */ + public static String inputStreamToString( + final InputStream inputStream, + final String charsetName + ) { + if (inputStream == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new String(inputStreamToBytes(inputStream), charsetName); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputStreamToString"); + return null; + } + } + + /** + * String 转换输入流 + * @param str 数据源 + * @param charsetName 编码格式 + * @return {@link InputStream} + */ + public static InputStream stringToInputStream( + final String str, + final String charsetName + ) { + if (str == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new ByteArrayInputStream(str.getBytes(charsetName)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "stringToInputStream"); + return null; + } + } + + /** + * 输出流转 String + * @param outputStream {@link OutputStream} + * @param charsetName 编码格式 + * @return 指定编码字符串 + */ + public static String outputStreamToString( + final OutputStream outputStream, + final String charsetName + ) { + if (outputStream == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new String(outputStreamToBytes(outputStream), charsetName); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputStreamToString"); + return null; + } + } + + /** + * String 转 输出流 + * @param str 数据源 + * @param charsetName 编码格式 + * @return {@link OutputStream} + */ + public static OutputStream stringToOutputStream( + final String str, + final String charsetName + ) { + if (str == null || StringUtils.isEmpty(charsetName)) return null; + try { + return bytesToOutputStream(str.getBytes(charsetName)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "stringToOutputStream"); + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/StringUtils.java b/lib/DevApp/src/main/java/dev/utils/common/StringUtils.java new file mode 100644 index 0000000000..a6c63cb847 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/StringUtils.java @@ -0,0 +1,2191 @@ +package dev.utils.common; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 字符串工具类 + * @author Ttt + */ +public final class StringUtils { + + private StringUtils() { + } + + // 日志 TAG + private static final String TAG = StringUtils.class.getSimpleName(); + + // ========== + // = String = + // ========== + + /** + * 判断字符串是否为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final CharSequence str) { + return (str == null || str.length() == 0); + } + + /** + * 判断多个字符串是否存在为 null 的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final CharSequence... args) { + if (args != null && args.length != 0) { + for (CharSequence value : args) { + if (isEmpty(value)) { + return true; + } + } + return false; + } + return true; + } + + /** + * 判断字符串是否为 null ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmptyClear(final String str) { + return isEmpty(clearSpaceTabLineTrim(str)); + } + + /** + * 判断多个字符串是否存在为 null 的字符串 ( 调用 clearSpaceTabLineTrim ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmptyClear(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isEmptyClear(value)) { + return true; + } + } + return false; + } + return true; + } + + // = + + /** + * 判断字符串是否不为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final CharSequence str) { + return (str != null && str.length() != 0); + } + + /** + * 判断字符串是否不为 null ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmptyClear(final String str) { + return isNotEmpty(clearSpaceTabLineTrim(str)); + } + + // ======== + // = Null = + // ======== + + /** + * 判断字符串是否为 "null" + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNull(final String str) { + return isEmpty(str) || DevFinal.SYMBOL.NULL.equalsIgnoreCase(str); + } + + /** + * 判断多个字符串是否存在为 "null" 的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNull(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isNull(value)) { + return true; + } + } + return false; + } + return true; + } + + /** + * 判断字符串是否为 "null" ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullClear(final String str) { + return isNull(clearSpaceTabLineTrim(str)); + } + + /** + * 判断多个字符串是否存在为 "null" 的字符串 ( 调用 clearSpaceTabLineTrim ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullClear(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isNullClear(value)) { + return true; + } + } + return false; + } + return true; + } + + // = + + /** + * 判断字符串是否不为 "null" + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotNull(final String str) { + return !isNull(str); + } + + /** + * 判断字符串是否不为 "null" ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotNullClear(final String str) { + return isNotNull(clearSpaceTabLineTrim(str)); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取字符串长度 + * @param str 待校验的字符串 + * @return 字符串长度, 如果字符串为 null, 则返回 0 + */ + public static int length(final String str) { + return str == null ? 0 : str.length(); + } + + /** + * 获取字符串长度 + * @param str 待校验的字符串 + * @param defaultLength 字符串为 null 时, 返回的长度 + * @return 字符串长度, 如果字符串为 null, 则返回 defaultLength + */ + public static int length( + final String str, + final int defaultLength + ) { + return str != null ? str.length() : defaultLength; + } + + // = + + /** + * 获取字符串长度 是否等于期望长度 + * @param str 待校验的字符串 + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final String str, + final int length + ) { + return str != null && str.length() == length; + } + + // ========== + // = 对比判断 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + /** + * 判断两个值是否一样 ( 非 null 判断 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsNotNull( + final T value1, + final T value2 + ) { + return value1 != null && ObjectUtils.equals(value1, value2); + } + + /** + * 判断两个值是否一样 ( 忽略大小写 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsIgnoreCase( + final String value1, + final String value2 + ) { + if (value1 != null) { + return value1.equalsIgnoreCase(value2); + } else { + return value2 == null; + } + } + + /** + * 判断两个值是否一样 ( 忽略大小写 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsIgnoreCaseNotNull( + final String value1, + final String value2 + ) { + return value1 != null && equalsIgnoreCase(value1, value2); + } + + // = + + /** + * 判断多个字符串是否相等, 只有全相等才返回 true ( 对比大小写 ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEquals(final String... args) { + return isEquals(false, args); + } + + /** + * 判断多个字符串是否相等, 只有全相等才返回 true + * @param isIgnore 是否忽略大小写 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEquals( + final boolean isIgnore, + final String... args + ) { + if (args != null) { + String last = null; + // 获取数据长度 + int len = args.length; + // 如果最多只有一个数据判断, 则直接跳过 + if (len <= 1) { + return false; + } + for (String value : args) { + // 如果等于 null, 则跳过 + if (value == null) { + return false; + } + if (last != null) { + if (isIgnore) { + if (!value.equalsIgnoreCase(last)) { + return false; + } + } else { + if (!value.equals(last)) { + return false; + } + } + } + last = value; + } + return true; + } + return false; + } + + /** + * 判断多个字符串, 只要有一个符合条件则通过 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isOrEquals( + final String str, + final String... args + ) { + return isOrEquals(false, str, args); + } + + /** + * 判断多个字符串, 只要有一个符合条件则通过 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isOrEquals( + final boolean isIgnore, + final String str, + final String... args + ) { + if (str != null && args != null && args.length != 0) { + for (String value : args) { + if (isIgnore) { + if (str.equalsIgnoreCase(value)) { + return true; + } + } else { + if (str.equals(value)) { + return true; + } + } + } + } + return false; + } + + /** + * 判断一堆值中, 是否存在符合该条件的 ( 包含 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContains( + final String str, + final String... args + ) { + return isContains(false, str, args); + } + + /** + * 判断一堆值中, 是否存在符合该条件的 ( 包含 ) + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContains( + final boolean isIgnore, + final String str, + final String... args + ) { + if (str != null && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + // 获取内容长度 + int strLength = tempString.length(); + // 遍历判断 + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value) && strLength != 0) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否包含 + if (valIgnore.contains(tempString)) { + return true; + } + } else { + // 判断是否包含 + if (value.contains(tempString)) { + return true; + } + } + } else { + if (tempString.equals(value)) { + return true; + } + } + } + } + return false; + } + + /** + * 判断内容, 是否属于特定字符串开头 ( 对比大小写 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isStartsWith( + final String str, + final String... args + ) { + return isStartsWith(false, str, args); + } + + /** + * 判断内容, 是否属于特定字符串开头 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isStartsWith( + final boolean isIgnore, + final String str, + final String... args + ) { + if (!isEmpty(str) && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value)) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否属于 val 开头 + if (tempString.startsWith(valIgnore)) { + return true; + } + } else { + // 判断是否属于 val 开头 + if (tempString.startsWith(value)) { + return true; + } + } + } + } + } + return false; + } + + /** + * 判断内容, 是否属于特定字符串结尾 ( 对比大小写 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEndsWith( + final String str, + final String... args + ) { + return isEndsWith(false, str, args); + } + + /** + * 判断内容, 是否属于特定字符串结尾 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEndsWith( + final boolean isIgnore, + final String str, + final String... args + ) { + if (!isEmpty(str) && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value)) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否属于 val 结尾 + if (tempString.endsWith(valIgnore)) { + return true; + } + } else { + // 判断是否属于 val 结尾 + if (tempString.endsWith(value)) { + return true; + } + } + } + } + } + return false; + } + + /** + * 统计字符串匹配个数 + * @param str 待匹配字符串 + * @param keyword 匹配 key + * @return 字符串 key 匹配个数 + */ + public static int countMatches( + final String str, + final String keyword + ) { + if (isEmpty(str) || isEmpty(keyword)) return 0; + try { + int count = 0; + Matcher matcher = Pattern.compile(keyword).matcher(str); + // find() 对字符串进行匹配, 匹配到的字符串可以在任何位置 + while (matcher.find()) { + count++; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "countMatches"); + } + return -1; + } + + /** + * 统计字符串匹配个数 + * @param str 待匹配字符串 + * @param keyword 匹配 key + * @return 字符串 key 匹配个数 + */ + public static int countMatches2( + final String str, + final String keyword + ) { + if (isEmpty(str) || isEmpty(keyword)) return 0; + try { + // 获取匹配 key 长度 + int keyLength = keyword.length(); + // = + int count = 0, index = 0; + while ((index = str.indexOf(keyword, index)) != -1) { + index = index + keyLength; + count++; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "countMatches2"); + } + return -1; + } + + // ========== + // = 其他处理 = + // ========== + + /** + * 判断字符串是否为 null 或全为空白字符 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSpace(final CharSequence str) { + if (str == null) return true; + for (int i = 0, len = str.length(); i < len; ++i) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + /** + * 字符串 转 byte[] + * @param str 待处理字符串 + * @return byte[] + */ + public static byte[] getBytes(final String str) { + return (str != null) ? str.getBytes() : null; + } + + // = + + /** + * 清空字符串全部空格 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpace(final String str) { + return replaceAll(str, DevFinal.SYMBOL.SPACE, ""); + } + + /** + * 清空字符串全部 Tab + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearTab(final String str) { + return replaceAll(str, DevFinal.SYMBOL.TAB, ""); + } + + /** + * 清空字符串全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLine(final String str) { + return replaceAll(str, DevFinal.SYMBOL.NEW_LINE, ""); + } + + /** + * 清空字符串全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLine2(final String str) { + return replaceAll(str, DevFinal.SYMBOL.NL, ""); + } + + // = + + /** + * 清空字符串前后全部空格 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.SPACE); + } + + /** + * 清空字符串前后全部 Tab + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearTabTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.TAB); + } + + /** + * 清空字符串前后全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLineTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.NEW_LINE); + } + + /** + * 清空字符串前后全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLineTrim2(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.NL); + } + + /** + * 清空字符串全部空格、Tab、换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTabLine(final String str) { + if (isEmpty(str)) return str; + String value = clearSpace(str); + value = clearTab(value); + value = clearLine(value); + value = clearLine2(value); + return value; + } + + /** + * 清空字符串前后全部空格、Tab、换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTabLineTrim(final String str) { + if (isEmpty(str)) return str; + String value = str; + while (true) { + boolean space = (value.startsWith(DevFinal.SYMBOL.SPACE) || value.endsWith(DevFinal.SYMBOL.SPACE)); + if (space) value = clearSpaceTrim(value); + + boolean tab = (value.startsWith(DevFinal.SYMBOL.TAB) || value.endsWith(DevFinal.SYMBOL.TAB)); + if (tab) value = clearTabTrim(value); + + boolean line = (value.startsWith(DevFinal.SYMBOL.NEW_LINE) || value.endsWith(DevFinal.SYMBOL.NEW_LINE)); + if (line) value = clearLineTrim(value); + + boolean line2 = (value.startsWith(DevFinal.SYMBOL.NL) || value.endsWith(DevFinal.SYMBOL.NL)); + if (line2) value = clearLineTrim2(value); + + // 都不存在则返回值 + if (!space && !tab && !line && !line2) return value; + } + } + + // = + + /** + * 追加空格 + * @param number 空格数量 + * @return 指定数量的空格字符串 + */ + public static String appendSpace(final int number) { + return forString(number, DevFinal.SYMBOL.SPACE); + } + + /** + * 追加 Tab + * @param number tab 键数量 + * @return 指定数量的 Tab 字符串 + */ + public static String appendTab(final int number) { + return forString(number, DevFinal.SYMBOL.TAB); + } + + /** + * 追加换行 + * @param number 换行数量 + * @return 指定数量的换行字符串 + */ + public static String appendLine(final int number) { + return forString(number, DevFinal.SYMBOL.NEW_LINE); + } + + /** + * 追加换行 + * @param number 换行数量 + * @return 指定数量的换行字符串 + */ + public static String appendLine2(final int number) { + return forString(number, DevFinal.SYMBOL.NL); + } + + /** + * 循环指定数量字符串 + * @param number 空格数量 + * @param str 待追加字符串 + * @return 指定数量字符串 + */ + public static String forString( + final int number, + final String str + ) { + StringBuilder builder = new StringBuilder(); + if (number > 0) { + for (int i = 0; i < number; i++) { + builder.append(str); + } + } + return builder.toString(); + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param values 待拼接对象 + * @return 拼接后的值 + */ + public static String joinArgs( + final Object delimiter, + final Object... values + ) { + if (values != null) { + int length = values.length; + if (length != 0) { + StringBuilder builder = new StringBuilder(); + builder.append(values[0]); + for (int i = 1; i < length; i++) { + builder.append(delimiter); + builder.append(values[i]); + } + return builder.toString(); + } + } + return null; + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param values 待拼接对象 + * @return 拼接后的值 + */ + public static String join( + final Object delimiter, + final Object[] values + ) { + if (values != null) { + int length = values.length; + if (length != 0) { + StringBuilder builder = new StringBuilder(); + builder.append(values[0]); + for (int i = 1; i < length; i++) { + builder.append(delimiter); + builder.append(values[i]); + } + return builder.toString(); + } + } + return null; + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param iterable 待拼接对象 + * @return 拼接后的值 + */ + public static String join( + final Object delimiter, + final Iterable iterable + ) { + if (iterable != null) { + final Iterator it = iterable.iterator(); + if (!it.hasNext()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + builder.append(it.next()); + while (it.hasNext()) { + builder.append(delimiter); + builder.append(it.next()); + } + return builder.toString(); + } + return null; + } + + /** + * 冒号分割处理 + * @param str 待处理字符串 + * @return 冒号分割后的字符串 + */ + public static String colonSplit(final String str) { + if (!isEmpty(str)) { + return str.replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0"); + } + return str; + } + + // = + + /** + * 获取字符串 ( 判 null ) + * @param str 待校验的字符串 + * @return 校验后的字符串 + */ + public static String getString(final String str) { + return getString(str, DevFinal.SYMBOL.NULL); + } + + /** + * 获取字符串 ( 判 null ) + * @param str 待校验的字符串 + * @param defaultStr 默认字符串 + * @return 校验后的字符串 + */ + public static String getString( + final String str, + final String defaultStr + ) { + return str != null ? str : defaultStr; + } + + /** + * 获取字符串 ( 判 null ) + * @param object 待校验的对象 + * @return 校验后的字符串 + */ + public static String getString(final Object object) { + return getString(object, DevFinal.SYMBOL.NULL); + } + + /** + * 获取字符串 ( 判 null ) + * @param object 待校验的对象 + * @param defaultStr 默认字符串 + * @return 校验后的字符串 + */ + public static String getString( + final Object object, + final String defaultStr + ) { + return object != null ? object.toString() : defaultStr; + } + + /** + * 检查字符串 + * @param str 待校验字符串 + * @return 如果待校验字符串为 null, 则返回默认字符串, 如果不为 null, 则返回该字符串 + */ + public static String checkValue(final String str) { + return checkValue("", str); + } + + /** + * 检查字符串 + * @param defaultStr 默认字符串 + * @param str 待校验字符串 + * @return 如果待校验字符串为 null, 则返回 defaultStr, 如果不为 null, 则返回该字符串 + */ + public static String checkValue( + final String defaultStr, + final String str + ) { + return isEmpty(str) ? defaultStr : str; + } + + /** + * 检查字符串 ( 单独检查两个值 ) + * @param defaultStr 默认字符串 + * @param value1 第一个待校验字符串 + * @param value2 第二个待校验字符串 + * @return 两个待校验字符串中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValue( + final String defaultStr, + final String value1, + final String value2 + ) { + if (isEmpty(value1)) { + if (isEmpty(value2)) { + return defaultStr; + } else { + return value2; + } + } else { + return value1; + } + } + + /** + * 检查字符串 ( 多个值 ) + * @param defaultStr 默认字符串 + * @param args 待校验字符串数组 + * @return 字符串数组中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValues( + final String defaultStr, + final String... args + ) { + if (args != null && args.length != 0) { + for (String value : args) { + if (!isEmpty(value)) { + return value; + } + } + } + return defaultStr; + } + + /** + * 检查字符串 ( 多个值, 删除前后空格对比判断 ) + * @param defaultStr 默认字符串 + * @param args 待校验字符串数组 + * @return 字符串数组中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValuesSpace( + final String defaultStr, + final String... args + ) { + if (args != null && args.length != 0) { + for (String value : args) { + // 删除前后空格处理后, 进行返回 + String result = clearSpaceTrim(value); + if (!isEmpty(result)) { + return result; + } + } + } + return defaultStr; + } + + // =============== + // = 数据格式化处理 = + // =============== + + /** + * 字符串格式化 + * @param format 待格式化字符串 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String format( + final String format, + final Object... args + ) { + if (format == null) return null; + try { + if (args != null && args.length != 0) { + return String.format(format, args); + } else { + return format; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "format"); + } + return null; + } + + /** + * 根据可变参数数量自动格式化 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String argsFormat(final Object... args) { + return FormatUtils.argsOf("%s", " %s").format(args); + } + + // = + + /** + * 字符串连接, 将参数列表拼接为一个字符串 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concat(final Object... args) { + return concatSpiltWith("", args); + } + + /** + * 字符串连接, 将参数列表拼接为一个字符串 + * @param split 追加间隔 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concatSpiltWith( + final String split, + final Object... args + ) { + if (args == null) return null; + StringBuilder builder = new StringBuilder(); + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (Object value : args) { + builder.append(value).append(split); + } + } + return builder.toString(); + } + + /** + * 字符串连接, 将参数列表拼接为一个字符串 ( 最后一个不追加间隔 ) + * @param split 追加间隔 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concatSpiltWithIgnoreLast( + final String split, + final Object... args + ) { + if (args == null) return null; + StringBuilder builder = new StringBuilder(); + int len = args.length; + if (len > 0) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (int i = 0; i < len - 1; i++) { + builder.append(args[i]).append(split); + } + builder.append(args[len - 1]); + } + } + return builder.toString(); + } + + /** + * StringBuilder 拼接处理 + * @param builder 拼接 Builder + * @param split 追加间隔 + * @param args 拼接数据源 + * @return {@link StringBuilder} + */ + public static StringBuilder appends( + final StringBuilder builder, + final String split, + final Object... args + ) { + if (builder != null && args != null) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (Object value : args) { + builder.append(value).append(split); + } + } + } + return builder; + } + + /** + * StringBuilder 拼接处理 ( 最后一个不追加间隔 ) + * @param builder 拼接 Builder + * @param split 追加间隔 + * @param args 拼接数据源 + * @return {@link StringBuilder} + */ + public static StringBuilder appendsIgnoreLast( + final StringBuilder builder, + final String split, + final Object... args + ) { + if (builder != null && args != null) { + int len = args.length; + if (len > 0) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (int i = 0; i < len - 1; i++) { + builder.append(args[i]).append(split); + } + builder.append(args[len - 1]); + } + } + } + return builder; + } + + // ========== + // = 转换处理 = + // ========== + + /** + * 字符串进行 GBK 编码 + * @param str 待处理字符串 + * @return GBK 编码后的字符串 + */ + public static String gbkEncode(final String str) { + return strEncode(str, DevFinal.ENCODE.GBK); + } + + /** + * 字符串进行 GBK2312 编码 + * @param str 待处理字符串 + * @return GBK2312 编码后的字符串 + */ + public static String gbk2312Encode(final String str) { + return strEncode(str, DevFinal.ENCODE.GBK_2312); + } + + /** + * 字符串进行 UTF-8 编码 + * @param str 待处理字符串 + * @return UTF-8 编码后的字符串 + */ + public static String utf8Encode(final String str) { + return strEncode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行字符串编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式编码后的字符串 + */ + public static String strEncode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return new String(str.getBytes(), enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "strEncode"); + } + return str; + } + + // = + + /** + * 进行 URL 编码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 编码后的字符串 + */ + public static String urlEncode(final String str) { + return urlEncode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行 URL 编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式 URL 编码后的字符串 + */ + public static String urlEncode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return URLEncoder.encode(str, enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "urlEncode"); + } + return null; + } + + // = + + /** + * 进行 URL 解码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 解码后的字符串 + */ + public static String urlDecode(final String str) { + return urlDecode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行 URL 解码 + * @param str 待处理字符串 + * @param enc 解码格式 + * @return 指定编码格式 URL 解码后的字符串 + */ + public static String urlDecode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return URLDecoder.decode(str, enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "urlDecode"); + } + return null; + } + + // = + + /** + * 进行 URL 解码, 默认 UTF-8 ( 循环到非 URL 编码为止 ) + * @param str 待处理字符串 + * @param threshold 解码次数阈值, 超过该次数还未完成则直接返回 + * @return UTF-8 编码格式 URL 解码后的字符串 + */ + public static String urlDecodeWhile( + final String str, + final int threshold + ) { + return urlDecodeWhile(str, DevFinal.ENCODE.UTF_8, threshold); + } + + /** + * 进行 URL 解码 ( 循环到非 URL 编码为止 ) + * @param str 待处理字符串 + * @param enc 解码格式 + * @param threshold 解码次数阈值, 超过该次数还未完成则直接返回 + * @return 指定编码格式 URL 解码后的字符串 + */ + public static String urlDecodeWhile( + final String str, + final String enc, + final int threshold + ) { + if (str == null || enc == null) return null; + int count = Math.max(threshold, 1); + int number = 0; + String result = str; + String decodeValue = StringUtils.urlDecode(str, enc); + while (true) { + // 如果相同则直接返回 + if (result.equals(decodeValue)) { + return decodeValue; + } + if (decodeValue == null) return result; + result = decodeValue; + decodeValue = StringUtils.urlDecode(result, enc); + // 判断循环次数 + number++; + if (number > count) { + if (decodeValue != null) { + return decodeValue; + } else { + return result; + } + } + } + } + + // = + + /** + * 将字符串转移为 ASCII 码 + * @param str 待处理字符串 + * @return 字符串转 ASCII 码后的字符串 + */ + public static String ascii(final String str) { + if (isEmpty(str)) return str; + try { + StringBuilder builder = new StringBuilder(); + byte[] bytes = str.getBytes(); + for (byte value : bytes) { + builder.append(Integer.toHexString(value & 0xff)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "ascii"); + } + return null; + } + + /** + * 将字符串转移为 Unicode 码 + * @param str 待处理字符串 + * @return 字符串转 Unicode 码后的字符串 + */ + public static String unicode(final String str) { + if (isEmpty(str)) return str; + try { + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + for (char value : chars) { + builder.append("\\u").append(Integer.toHexString(value)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unicode"); + } + return null; + } + + /** + * 将字符数组转移为 Unicode 码 + * @param chars char[] + * @return char[] 转 Unicode 码后的字符串 + */ + public static String unicodeString(final char[] chars) { + if (chars == null) return null; + try { + StringBuilder builder = new StringBuilder(); + for (char value : chars) { + builder.append("\\u").append(Integer.toHexString(value)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unicodeString"); + } + return null; + } + + /** + * 转化为半角字符 + * @param str 待处理字符串 + * @return 转换半角字符串 + */ + public static String dbc(final String str) { + if (isEmpty(str)) return str; + char[] chars = str.toCharArray(); + for (int i = 0, len = chars.length; i < len; i++) { + if (chars[i] == 12288) { + chars[i] = ' '; + } else if (65281 <= chars[i] && chars[i] <= 65374) { + chars[i] = (char) (chars[i] - 65248); + } else { + chars[i] = chars[i]; + } + } + return new String(chars); + } + + /** + * 转化为全角字符 如: a = a, A = A + * @param str 待处理字符串 + * @return 转换全角字符串 + */ + public static String sbc(final String str) { + if (isEmpty(str)) return str; + char[] chars = str.toCharArray(); + for (int i = 0, len = chars.length; i < len; i++) { + if (chars[i] == ' ') { + chars[i] = (char) 12288; + } else if (33 <= chars[i] && chars[i] <= 126) { + chars[i] = (char) (chars[i] + 65248); + } else { + chars[i] = chars[i]; + } + } + return new String(chars); + } + + // = + + /** + * 检测字符串是否全是中文 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkChineseToString(final String str) { + if (isEmpty(str)) return false; + boolean result = true; + char[] chars = str.toCharArray(); + for (char value : chars) { + if (!isChinese(value)) { + result = false; + break; + } + } + return result; + } + + /** + * 判断输入汉字 + * @param ch 待校验字符 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChinese(final char ch) { + Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch); + return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS + || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS + || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A + || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION + || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION + || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS; + } + + // =============== + // = 字符串处理方法 = + // =============== + + /** + * 首字母大写 + * @param str 待处理字符串 + * @return 首字母大写字符串 + */ + public static String upperFirstLetter(final String str) { + if (isEmpty(str) || !Character.isLowerCase(str.charAt(0))) return str; + try { + return (char) (str.charAt(0) - 32) + str.substring(1); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "upperFirstLetter"); + return str; + } + } + + /** + * 首字母小写 + * @param str 待处理字符串 + * @return 首字母小写字符串 + */ + public static String lowerFirstLetter(final String str) { + if (isEmpty(str) || !Character.isUpperCase(str.charAt(0))) return str; + try { + return (char) (str.charAt(0) + 32) + str.substring(1); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "lowerFirstLetter"); + return str; + } + } + + /** + * 反转字符串 + * @param str 待处理字符串 + * @return 反转字符串 + */ + public static String reverse(final String str) { + int len = length(str); + if (len <= 1) return str; + int mid = len >> 1; + char[] chars = str.toCharArray(); + char ch; + for (int i = 0; i < mid; ++i) { + ch = chars[i]; + chars[i] = chars[len - i - 1]; + chars[len - i - 1] = ch; + } + return new String(chars); + } + + /** + * 下划线命名转为驼峰命名 + * @param str 下划线命名格式字符串 + * @return 驼峰命名格式字符串 + */ + public static String underScoreCaseToCamelCase(final String str) { + if (isEmpty(str)) return str; + if (!str.contains("_")) return str; + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + boolean hitUnderScore = false; + builder.append(chars[0]); + for (int i = 1, len = chars.length; i < len; i++) { + char c = chars[i]; + if (c == '_') { + hitUnderScore = true; + } else { + if (hitUnderScore) { + builder.append(Character.toUpperCase(c)); + hitUnderScore = false; + } else { + builder.append(c); + } + } + } + return builder.toString(); + } + + /** + * 驼峰命名法转为下划线命名 + * @param str 驼峰命名格式字符串 + * @return 下划线命名格式字符串 + */ + public static String camelCaseToUnderScoreCase(final String str) { + if (isEmpty(str)) return str; + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + for (char value : chars) { + if (Character.isUpperCase(value)) { + builder.append("_").append(Character.toLowerCase(value)); + } else { + builder.append(value); + } + } + return builder.toString(); + } + + /** + * 字符串数据库字符转义 + * @param str 待处理字符串 + * @return 转义处理后的字符串 + */ + public static String sqliteEscape(final String str) { + if (isEmpty(str)) return str; + String keyWord = str; + // 替换关键字 + keyWord = keyWord.replace("/", "//"); + keyWord = keyWord.replace("'", "''"); + keyWord = keyWord.replace("[", "/["); + keyWord = keyWord.replace("]", "/]"); + keyWord = keyWord.replace("%", "/%"); + keyWord = keyWord.replace("&", "/&"); + keyWord = keyWord.replace("_", "/_"); + keyWord = keyWord.replace("(", "/("); + keyWord = keyWord.replace(")", "/)"); + return keyWord; + } + + // ============ + // = 字符串处理 = + // ============ + + /** + * 转换手机号 + * @param phone 待处理字符串 + * @return 处理后的字符串 + */ + public static String convertHideMobile(final String phone) { + return convertHideMobile(phone, "*"); + } + + /** + * 转换手机号 + * @param phone 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String convertHideMobile( + final String phone, + final String symbol + ) { + return convertSymbolHide(3, phone, symbol); + } + + /** + * 转换符号处理 + * @param start 开始位置 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String convertSymbolHide( + final int start, + final String str, + final String symbol + ) { + if (!isEmpty(str)) { + if (start <= 0) { + return str; + } + // 获取数据长度 + int length = str.length(); + // 如果数据小于 start 位则直接返回 + if (length <= start) { + return str; + } else { // 大于 start 位 + StringBuilder builder = new StringBuilder(); + builder.append(str.substring(0, start)); + int len = length - start; + // 进行平分 + len /= 2; + // 进行遍历保存 + for (int i = 0; i < len; i++) { + builder.append(symbol); + } + builder.append(str.substring(start + len, length)); + return builder.toString(); + } + } + return ""; + } + + // = + + /** + * 裁剪超出的内容, 并且追加符号 ( 如 ... ) + * @param maxLength 允许最大的长度 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subEllipsize( + final int maxLength, + final String str, + final String symbol + ) { + if (maxLength >= 1) { + // 获取内容长度 + int strLength = length(str); + // 防止为不存在数据 + if (strLength != 0) { + if (maxLength >= strLength) { + return str; + } + return str.substring(0, maxLength) + checkValue(symbol); + } + } + return ""; + } + + /** + * 裁剪符号处理 + * @param start 开始位置 + * @param symbolNumber 转换数量 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subSymbolHide( + final int start, + final int symbolNumber, + final String str, + final String symbol + ) { + if (!isEmpty(str)) { + if (start <= 0 || symbolNumber <= 0) { + return str; + } + // 获取数据长度 + int length = str.length(); + // 如果数据小于 start 位则直接返回 + if (length <= start) { + return str; + } else { // 大于 start 位 + StringBuilder builder = new StringBuilder(); + builder.append(str.substring(0, start)); + int len = length - start - symbolNumber; + // 如果超出总长度, 则进行控制 + if (len <= 0) { // 表示后面的全部转换 + len = length - start; + } else { // 需要裁剪的数量 + len = symbolNumber; + } + // 进行遍历保存 + for (int i = 0; i < len; i++) { + builder.append(symbol); + } + builder.append(str.substring(start + len, length)); + return builder.toString(); + } + } + return ""; + } + + /** + * 裁剪内容 ( 设置符号处理 ) + * @param str 待处理字符串 + * @param frontRetainLength 前面保留的长度 + * @param rearRetainLength 后面保留的长度 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subSetSymbol( + final String str, + final int frontRetainLength, + final int rearRetainLength, + final String symbol + ) { + if (str != null) { + try { + // 截取前面需保留的内容 + String startStr = str.substring(0, frontRetainLength); + // 截取后面需保留的内容 + String endStr = str.substring(str.length() - rearRetainLength); + // 特殊符号长度 + int symbolLength = str.length() - (frontRetainLength + rearRetainLength); + if (symbolLength >= 1) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < symbolLength; i++) { + builder.append(symbol); + } + return startStr + builder.toString() + endStr; + } + return startStr + endStr; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subSetSymbol"); + } + } + return null; + } + + // =============== + // = 替换、截取操作 = + // =============== + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param endIndex 结束裁剪的位置 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int endIndex + ) { + return substring(str, 0, endIndex, true); + } + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param endIndex 结束裁剪的位置 + * @param isReturn 开始位置超过限制是否返回内容 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int endIndex, + final boolean isReturn + ) { + return substring(str, 0, endIndex, isReturn); + } + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param beginIndex 开始裁剪的位置 + * @param endIndex 结束裁剪的位置 + * @param isReturn 开始位置超过限制是否返回内容 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int beginIndex, + final int endIndex, + final boolean isReturn + ) { + if (!isEmpty(str) && beginIndex >= 0 && endIndex >= 0 && endIndex >= beginIndex) { + // 获取数据长度 + int len = length(str); + // 防止超过限制 + if (beginIndex > len) { + return isReturn ? str : ""; + } + // 防止超过限制 + return str.substring(beginIndex, Math.min(endIndex, len)); + } + return isReturn ? str : ""; + } + + // = + + /** + * 替换特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 ____a_a_a_a____ + * @param str 待处理字符串 + * @param suffix 替换符号字符串 + * @return 处理后的字符串 + */ + public static String replaceSEWith( + final String str, + final String suffix + ) { + return replaceSEWith(str, suffix, ""); + } + + /** + * 替换特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _, c 等于 c____a_a_a_a____c + * @param str 待处理字符串 + * @param suffix 替换匹配内容 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceSEWith( + final String str, + final String suffix, + final String replace + ) { + try { + if (isEmpty(str) || isEmpty(suffix) || replace == null || suffix.equals(replace)) { + return str; + } + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 判断是否在最头部 + if (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + // 追加内容 + builder.insert(0, replace); + } + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 判断是否在最尾部 + if (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 追加内容 + builder.insert(lastIndexOf, replace); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceSEWith"); + } + return str; + } + + // = + + /** + * 替换开头字符串 + * @param str 待处理字符串 + * @param prefix 开头匹配字符串 + * @return 处理后的字符串 + */ + public static String replaceStartsWith( + final String str, + final String prefix + ) { + return replaceStartsWith(str, prefix, ""); + } + + /** + * 替换开头字符串 + * @param str 待处理字符串 + * @param prefix 开头匹配字符串 + * @param startAppend 开头追加的内容 + * @return 处理后的字符串 + */ + public static String replaceStartsWith( + final String str, + final String prefix, + final String startAppend + ) { + if (!isEmpty(str) && !isEmpty(prefix)) { + try { + if (str.startsWith(prefix)) { + return checkValue(startAppend) + str.substring(prefix.length()); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceStartsWith"); + } + } + return str; + } + + /** + * 替换结尾字符串 + * @param str 待处理字符串 + * @param suffix 结尾匹配字符串 + * @return 处理后的字符串 + */ + public static String replaceEndsWith( + final String str, + final String suffix + ) { + return replaceEndsWith(str, suffix, ""); + } + + /** + * 替换结尾字符串 + * @param str 待处理字符串 + * @param suffix 结尾匹配字符串 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceEndsWith( + final String str, + final String suffix, + final String replace + ) { + if (!isEmpty(str) && !isEmpty(suffix)) { + try { + if (str.endsWith(suffix)) { + return str.substring(0, str.length() - suffix.length()) + replace; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceEndsWith"); + } + } + return str; + } + + // = + + /** + * 清空特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 a_a_a_a + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearSEWiths( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 进行循环判断 ( 属于最前面的, 才进行处理 ) + while (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + } + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 进行循环判断 ( 属于最后面的, 才进行处理 ) + while (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 重置数据 + lastIndexOf = builder.lastIndexOf(suffix); + bufLength = builder.length(); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearSEWiths"); + } + return str; + } + + /** + * 清空特定字符串开头的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 a_a_a_a_____ + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearStartsWith( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 进行循环判断 ( 属于最前面的, 才进行处理 ) + while (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearStartsWith"); + } + return str; + } + + /** + * 清空特定字符串结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 _____a_a_a_a + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearEndsWith( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 进行循环判断 ( 属于最后面的, 才进行处理 ) + while (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 重置数据 + lastIndexOf = builder.lastIndexOf(suffix); + bufLength = builder.length(); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearEndsWith"); + } + return str; + } + + // = + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceAll( + final String str, + final String suffix, + final String replace + ) { + // 如果替换的内容或者判断的字符串为 null, 则直接跳过 + if (!isEmpty(str) && !isEmpty(suffix) && replace != null && !suffix.equals(replace)) { + try { + return str.replaceAll(suffix, replace); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceAll"); + } + } + return str; + } + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @param replace 替换的内容 + * @return 处理后的字符串, 替换失败则返回 null + */ + public static String replaceAllToNull( + final String str, + final String suffix, + final String replace + ) { + // 如果替换的内容或者判断的字符串为 null, 则直接跳过 + if (!isEmpty(str) && !isEmpty(suffix) && replace != null && !suffix.equals(replace)) { + try { + return str.replaceAll(suffix, replace); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceAllToNull"); + } + } + return null; + } + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffixArrays 匹配判断字符串数组 + * @param replaceArrays 准备替换的字符串数组 + * @return 处理后的字符串 + */ + public static String replaceAlls( + final String str, + final String[] suffixArrays, + final String[] replaceArrays + ) { + // 防止数据为 null + if (str != null && suffixArrays != null && replaceArrays != null) { + String tempString = str; + // 替换的特殊字符串长度 + int spCount = suffixArrays.length; + // 替换的内容长度 + int reCount = replaceArrays.length; + // 相同才进行处理 + if (spCount == reCount) { + // 遍历进行判断 + for (int i = 0; i < spCount; i++) { + // 进行替换字符串 + tempString = replaceAll(tempString, suffixArrays[i], replaceArrays[i]); + } + return tempString; + } + } + return null; + } + + /** + * 拆分字符串 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @return 拆分后的数组 + */ + public static String[] split( + final String str, + final String regex + ) { + if (!StringUtils.isEmpty(str) && !StringUtils.isEmpty(regex)) { + return str.split(regex); + } + return null; + } + + /** + * 拆分字符串获取指定索引 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @param index 索引 + * @return 拆分后的数组 + */ + public static String split( + final String str, + final String regex, + final int index + ) { + return split(str, regex, index, null); + } + + /** + * 拆分字符串获取指定索引 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @param index 索引 + * @param defaultStr 默认字符串 + * @return 拆分后的数组 + */ + public static String split( + final String str, + final String regex, + final int index, + final String defaultStr + ) { + if (index >= 0) { + String[] arrays = split(str, regex); + if (arrays != null && arrays.length > index) { + return arrays[index]; + } + } + return defaultStr; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ThrowableUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ThrowableUtils.java new file mode 100644 index 0000000000..2a7c1adeb6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ThrowableUtils.java @@ -0,0 +1,100 @@ +package dev.utils.common; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import dev.utils.JCLogUtils; + +/** + * detail: 异常处理工具类 + * @author Ttt + */ +public final class ThrowableUtils { + + private ThrowableUtils() { + } + + // 日志 TAG + private static final String TAG = ThrowableUtils.class.getSimpleName(); + + /** + * 获取异常信息 + * @param throwable 异常 + * @return 异常信息字符串 + */ + public static String getThrowable(final Throwable throwable) { + return getThrowable(throwable, "error(null)"); + } + + /** + * 获取异常信息 + * @param throwable 异常 + * @param errorInfo 获取失败返回字符串 + * @return 异常信息字符串 + */ + public static String getThrowable( + final Throwable throwable, + final String errorInfo + ) { + if (throwable != null) { + Throwable cause = throwable.getCause(); + if (cause != null) { + return cause.toString(); + } + return throwable.toString(); + } + return errorInfo; + } + + // ============ + // = 异常栈信息 = + // ============ + + /** + * 获取异常栈信息 + * @param throwable 异常 + * @return 异常栈信息字符串 + */ + public static String getThrowableStackTrace(final Throwable throwable) { + return getThrowableStackTrace(throwable, "error(null)"); + } + + /** + * 获取异常栈信息 + * @param throwable 异常 + * @param errorInfo 获取失败返回字符串 + * @return 异常栈信息字符串 + */ + public static String getThrowableStackTrace( + final Throwable throwable, + final String errorInfo + ) { + if (throwable != null) { + PrintWriter printWriter = null; + try { + Writer writer = new StringWriter(); + printWriter = new PrintWriter(writer); + throwable.printStackTrace(printWriter); +// // 获取错误栈信息 +// StackTraceElement[] stElement = throwable.getStackTrace(); +// // 标题, 提示属于什么异常 +// printWriter.append(throwable.toString()); +// printWriter.append(DevFinal.SYMBOL.NEW_LINE); +// // 遍历错误栈信息, 并且进行换行缩进 +// for (StackTraceElement element : stElement) { +// printWriter.append("\tat "); +// printWriter.append(element.toString()); +// printWriter.append(DevFinal.SYMBOL.NEW_LINE); +// } + return writer.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getThrowableStackTrace"); + return e.toString(); + } finally { + CloseUtils.closeIOQuietly(printWriter); + } + } + return errorInfo; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/TypeUtils.java b/lib/DevApp/src/main/java/dev/utils/common/TypeUtils.java new file mode 100644 index 0000000000..2d81fed375 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/TypeUtils.java @@ -0,0 +1,185 @@ +package dev.utils.common; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import dev.utils.JCLogUtils; + +/** + * detail: 类型工具类 + * @author Ttt + *

+ *     Java 中与泛型相关的接口之 ParameterizedType
+ *     @see 
+ *     Java 知识总结之 Type
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class TypeUtils { + + private TypeUtils() { + } + + // 日志 TAG + private static final String TAG = TypeUtils.class.getSimpleName(); + + /** + * 获取 Array Type + * @param type Bean.class + * @return Bean[] Type + */ + public static Type getArrayType(final Type type) { + if (type == null) return null; + try { + return new GenericArrayType() { + @Override + public Type getGenericComponentType() { + return type; + } + }; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getArrayType"); + } + return null; + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + public static Type getListType(final Type type) { + if (type == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{type}, null, List.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getListType"); + } + return null; + } + + /** + * 获取 Set Type + * @param type Bean.class + * @return Set Type + */ + public static Type getSetType(final Type type) { + if (type == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{type}, null, Set.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getSetType"); + } + return null; + } + + /** + * 获取 Map Type + * @param keyType Key.class + * @param valueType Value.class + * @return Map Type + */ + public static Type getMapType( + final Type keyType, + final Type valueType + ) { + if (keyType == null || valueType == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{keyType, valueType}, null, Map.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMapType"); + } + return null; + } + + /** + * 获取 Type + * @param rawType raw type + * @param typeArguments type arguments + * @return Type + */ + public static Type getType( + final Type rawType, + final Type... typeArguments + ) { + try { + return new ParameterizedTypeImpl( + typeArguments, null, rawType + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getType"); + } + return null; + } + + // ======== + // = Type = + // ======== + + /** + * detail: ParameterizedType 实现类 + * @author Ttt + */ + public static class ParameterizedTypeImpl + implements ParameterizedType { + + private final Type[] actualTypeArguments; + private final Type ownerType; + private final Type rawType; + + public ParameterizedTypeImpl( + Type[] actualTypeArguments, + Type ownerType, + Type rawType + ) { + this.actualTypeArguments = actualTypeArguments; + this.ownerType = ownerType; + this.rawType = rawType; + } + + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + public Type getOwnerType() { + return ownerType; + } + + public Type getRawType() { + return rawType; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + + ParameterizedTypeImpl that = (ParameterizedTypeImpl) object; + if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; + if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) { + return false; + } + return rawType != null ? rawType.equals(that.rawType) : that.rawType == null; + } + + @Override + public int hashCode() { + int result = actualTypeArguments != null ? Arrays.hashCode(actualTypeArguments) : 0; + result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); + result = 31 * result + (rawType != null ? rawType.hashCode() : 0); + return result; + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/ZipUtils.java b/lib/DevApp/src/main/java/dev/utils/common/ZipUtils.java new file mode 100644 index 0000000000..b26f8a4e55 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/ZipUtils.java @@ -0,0 +1,456 @@ +package dev.utils.common; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import dev.utils.JCLogUtils; + +/** + * detail: 压缩相关工具类 + * @author Ttt + */ +public final class ZipUtils { + + private ZipUtils() { + } + + // 日志 TAG + private static final String TAG = ZipUtils.class.getSimpleName(); + + // 缓存大小 + private static final int BUFFER_LEN = 8192; + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件路径集合 + * @param zipFilePath 压缩文件路径 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final String zipFilePath + ) + throws Exception { + return zipFiles(resFiles, zipFilePath, null); + } + + /** + * 批量压缩文件 + * @param resFilePaths 待压缩文件路径集合 + * @param zipFilePath 压缩文件路径 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFilePaths, + final String zipFilePath, + final String comment + ) + throws Exception { + if (resFilePaths == null || zipFilePath == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFilePath)); + for (String resFile : resFilePaths) { + if (!zipFile(FileUtils.getFileByPath(resFile), "", zos, comment)) { + return false; + } + } + return true; + } finally { + if (zos != null) { + zos.finish(); + CloseUtils.closeIOQuietly(zos); + } + } + } + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件集合 + * @param zipFile 压缩文件 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final File zipFile + ) + throws Exception { + return zipFiles(resFiles, zipFile, null); + } + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件集合 + * @param zipFile 压缩文件 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final File zipFile, + final String comment + ) + throws Exception { + if (resFiles == null || zipFile == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFile)); + for (File resFile : resFiles) { + if (!zipFile(resFile, "", zos, comment)) return false; + } + return true; + } finally { + if (zos != null) { + zos.finish(); + CloseUtils.closeIOQuietly(zos); + } + } + } + + /** + * 压缩文件 + * @param resFilePath 待压缩文件路径 + * @param zipFilePath 压缩文件路径 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final String resFilePath, + final String zipFilePath + ) + throws Exception { + return zipFile( + FileUtils.getFileByPath(resFilePath), + FileUtils.getFileByPath(zipFilePath), + null + ); + } + + /** + * 压缩文件 + * @param resFilePath 待压缩文件路径 + * @param zipFilePath 压缩文件路径 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final String resFilePath, + final String zipFilePath, + final String comment + ) + throws Exception { + return zipFile( + FileUtils.getFileByPath(resFilePath), + FileUtils.getFileByPath(zipFilePath), + comment + ); + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param zipFile 压缩文件 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final File resFile, + final File zipFile + ) + throws Exception { + return zipFile(resFile, zipFile, null); + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param zipFile 压缩文件 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final File resFile, + final File zipFile, + final String comment + ) + throws Exception { + if (resFile == null || zipFile == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFile)); + return zipFile(resFile, "", zos, comment); + } finally { + CloseUtils.closeIOQuietly(zos); + } + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param rootPath 相对于压缩文件的路径 + * @param zos 压缩文件输出流 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + private static boolean zipFile( + final File resFile, + final String rootPath, + final ZipOutputStream zos, + final String comment + ) + throws Exception { + // 处理后的文件路径 + String filePath = rootPath + (StringUtils.isEmpty(rootPath) ? "" : File.separator) + resFile.getName(); + if (resFile.isDirectory()) { + File[] fileList = resFile.listFiles(); + // 如果是空文件夹那么创建它 + if (fileList == null || fileList.length == 0) { + ZipEntry entry = new ZipEntry(filePath + '/'); + entry.setComment(comment); + zos.putNextEntry(entry); + zos.closeEntry(); + } else { + for (File file : fileList) { + // 如果递归返回 false 则返回 false + if (!zipFile(file, filePath, zos, comment)) return false; + } + } + } else { + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(resFile)); + ZipEntry entry = new ZipEntry(filePath); + entry.setComment(comment); + zos.putNextEntry(entry); + byte[] buffer = new byte[BUFFER_LEN]; + int len; + while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + } finally { + CloseUtils.closeIOQuietly(is); + } + } + return true; + } + + /** + * 解压文件 + * @param zipFilePath 待解压文件路径 + * @param destDirPath 目标目录路径 + * @return 文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFile( + final String zipFilePath, + final String destDirPath + ) + throws Exception { + return unzipFileByKeyword(zipFilePath, destDirPath, null); + } + + /** + * 解压文件 + * @param zipFile 待解压文件 + * @param destDir 目标目录 + * @return 文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFile( + final File zipFile, + final File destDir + ) + throws Exception { + return unzipFileByKeyword(zipFile, destDir, null); + } + + /** + * 解压带有关键字的文件 + * @param zipFilePath 待解压文件路径 + * @param destDirPath 目标目录路径 + * @param keyword 关键字 + * @return 带有关键字的文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFileByKeyword( + final String zipFilePath, + final String destDirPath, + final String keyword + ) + throws Exception { + return unzipFileByKeyword( + FileUtils.getFileByPath(zipFilePath), + FileUtils.getFileByPath(destDirPath), + keyword + ); + } + + /** + * 解压带有关键字的文件 + * @param zipFile 待解压文件 + * @param destDir 目标目录 + * @param keyword 关键字 + * @return 带有关键字的文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFileByKeyword( + final File zipFile, + final File destDir, + final String keyword + ) + throws Exception { + if (zipFile == null || destDir == null) return null; + List files = new ArrayList<>(); + ZipFile zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + if (StringUtils.isEmpty(keyword)) { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + continue; + } + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } else { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + continue; + } + if (entryName.contains(keyword)) { + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } + } + return files; + } + + /** + * 解压文件 + * @param destDir 目标目录 + * @param files 解压文件链表 + * @param zf 压缩文件条目 + * @param entry 压缩文件信息 + * @param entryName 文件名 + * @return {@code true} success, {@code false} fail + * @throws Exception 异常时抛出 + */ + private static boolean unzipChildFile( + final File destDir, + final List files, + final ZipFile zf, + final ZipEntry entry, + final String entryName + ) + throws Exception { + File file = new File(destDir, entryName); + files.add(file); + if (entry.isDirectory()) { + return FileUtils.createOrExistsDir(file); + } else { + if (!FileUtils.createOrExistsFile(file)) return false; + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(zf.getInputStream(entry)); + os = new BufferedOutputStream(new FileOutputStream(file)); + byte[] buffer = new byte[BUFFER_LEN]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } finally { + CloseUtils.closeIOQuietly(is, os); + } + } + return true; + } + + /** + * 获取压缩文件中的文件路径链表 + * @param zipFilePath 压缩文件路径 + * @return 压缩文件中的文件路径链表 + * @throws Exception 异常时抛出 + */ + public static List getFilesPath(final String zipFilePath) + throws Exception { + return getFilesPath(FileUtils.getFileByPath(zipFilePath)); + } + + /** + * 获取压缩文件中的文件路径链表 + * @param zipFile 压缩文件 + * @return 压缩文件中的文件路径链表 + * @throws Exception 异常时抛出 + */ + public static List getFilesPath(final File zipFile) + throws Exception { + if (zipFile == null) return null; + List paths = new ArrayList<>(); + Enumeration entries = new ZipFile(zipFile).entries(); + while (entries.hasMoreElements()) { + String entryName = ((ZipEntry) entries.nextElement()).getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + paths.add(entryName); + } else { + paths.add(entryName); + } + } + return paths; + } + + /** + * 获取压缩文件中的注释链表 + * @param zipFilePath 压缩文件路径 + * @return 压缩文件中的注释链表 + * @throws Exception 异常时抛出 + */ + public static List getComments(final String zipFilePath) + throws Exception { + return getComments(FileUtils.getFileByPath(zipFilePath)); + } + + /** + * 获取压缩文件中的注释链表 + * @param zipFile 压缩文件 + * @return 压缩文件中的注释链表 + * @throws Exception 异常时抛出 + */ + public static List getComments(final File zipFile) + throws Exception { + if (zipFile == null) return null; + List comments = new ArrayList<>(); + Enumeration entries = new ZipFile(zipFile).entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + comments.add(entry.getComment()); + } + return comments; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Bindingable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Bindingable.java new file mode 100644 index 0000000000..de078e66f8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Bindingable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Binding 接口 + * @author Ttt + *
+ *     所有通用快捷 Binding 接口定义存储类
+ *     全部接口只定义一个方法 bind() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Bindingable { + + private Bindingable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Binding { + + T bind(); + } + + // ======= + // = 有参 = + // ======= + + public interface BindingByParam { + + T bind(Param param); + } + + public interface BindingByParam2 { + + T bind( + Param param, + Param2 param2 + ); + } + + public interface BindingByParam3 { + + T bind( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface BindingByParamArgs { + + T bind(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Byable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Byable.java new file mode 100644 index 0000000000..14067b2bd0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Byable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 By 接口 + * @author Ttt + *
+ *     所有通用快捷 By 接口定义存储类
+ *     全部接口只定义一个方法 by() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Byable { + + private Byable() { + } + + // ======= + // = 无参 = + // ======= + + public interface By { + + T by(); + } + + // ======= + // = 有参 = + // ======= + + public interface ByByParam { + + T by(Param param); + } + + public interface ByByParam2 { + + T by( + Param param, + Param2 param2 + ); + } + + public interface ByByParam3 { + + T by( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ByByParamArgs { + + T by(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Calculateable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Calculateable.java new file mode 100644 index 0000000000..a59e063b46 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Calculateable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Calculate 接口 + * @author Ttt + *
+ *     所有通用快捷 Calculate 接口定义存储类
+ *     全部接口只定义一个方法 calculate() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Calculateable { + + private Calculateable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Calculate { + + T calculate(); + } + + // ======= + // = 有参 = + // ======= + + public interface CalculateByParam { + + T calculate(Param param); + } + + public interface CalculateByParam2 { + + T calculate( + Param param, + Param2 param2 + ); + } + + public interface CalculateByParam3 { + + T calculate( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CalculateByParamArgs { + + T calculate(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Callable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Callable.java new file mode 100644 index 0000000000..08ef3f7397 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Callable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Call 接口 + * @author Ttt + *
+ *     所有通用快捷 Call 接口定义存储类
+ *     全部接口只定义一个方法 callback() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Callable { + + private Callable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Call { + + T callback(); + } + + // ======= + // = 有参 = + // ======= + + public interface CallByParam { + + T callback(Param param); + } + + public interface CallByParam2 { + + T callback( + Param param, + Param2 param2 + ); + } + + public interface CallByParam3 { + + T callback( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CallByParamArgs { + + T callback(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Cloneable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Cloneable.java new file mode 100644 index 0000000000..4f4fbf62b2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Cloneable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Clone 接口 + * @author Ttt + *
+ *     所有通用快捷 Clone 接口定义存储类
+ *     全部接口只定义一个方法 cloneMethod() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Cloneable { + + private Cloneable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Clone { + + T cloneMethod(); + } + + // ======= + // = 有参 = + // ======= + + public interface CloneByParam { + + T cloneMethod(Param param); + } + + public interface CloneByParam2 { + + T cloneMethod( + Param param, + Param2 param2 + ); + } + + public interface CloneByParam3 { + + T cloneMethod( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CloneByParamArgs { + + T cloneMethod(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Closeable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Closeable.java new file mode 100644 index 0000000000..4916aa5b10 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Closeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Close 接口 + * @author Ttt + *
+ *     所有通用快捷 Close 接口定义存储类
+ *     全部接口只定义一个方法 close() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Closeable { + + private Closeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Close { + + T close(); + } + + // ======= + // = 有参 = + // ======= + + public interface CloseByParam { + + T close(Param param); + } + + public interface CloseByParam2 { + + T close( + Param param, + Param2 param2 + ); + } + + public interface CloseByParam3 { + + T close( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CloseByParamArgs { + + T close(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Commandable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Commandable.java new file mode 100644 index 0000000000..c7cccda05f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Commandable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Command 接口 + * @author Ttt + *
+ *     所有通用快捷 Command 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Commandable { + + private Commandable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Command { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface CommandByParam { + + T execute(Param param); + } + + public interface CommandByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface CommandByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CommandByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Consumerable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Consumerable.java new file mode 100644 index 0000000000..412f9fde4a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Consumerable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Consumer 接口 + * @author Ttt + *
+ *     所有通用快捷 Consumer 接口定义存储类
+ *     全部接口只定义一个方法 accept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Consumerable { + + private Consumerable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Consumer { + + T accept(); + } + + // ======= + // = 有参 = + // ======= + + public interface ConsumerByParam { + + T accept(Param param); + } + + public interface ConsumerByParam2 { + + T accept( + Param param, + Param2 param2 + ); + } + + public interface ConsumerByParam3 { + + T accept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ConsumerByParamArgs { + + T accept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Convertable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Convertable.java new file mode 100644 index 0000000000..9df957d175 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Convertable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Convert 接口 + * @author Ttt + *
+ *     所有通用快捷 Convert 接口定义存储类
+ *     全部接口只定义一个方法 convert() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Convertable { + + private Convertable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Convert { + + T convert(); + } + + // ======= + // = 有参 = + // ======= + + public interface ConvertByParam { + + T convert(Param param); + } + + public interface ConvertByParam2 { + + T convert( + Param param, + Param2 param2 + ); + } + + public interface ConvertByParam3 { + + T convert( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ConvertByParamArgs { + + T convert(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Copyable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Copyable.java new file mode 100644 index 0000000000..6919e5aaed --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Copyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Copy 接口 + * @author Ttt + *
+ *     所有通用快捷 Copy 接口定义存储类
+ *     全部接口只定义一个方法 copy() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Copyable { + + private Copyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Copy { + + T copy(); + } + + // ======= + // = 有参 = + // ======= + + public interface CopyByParam { + + T copy(Param param); + } + + public interface CopyByParam2 { + + T copy( + Param param, + Param2 param2 + ); + } + + public interface CopyByParam3 { + + T copy( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CopyByParamArgs { + + T copy(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Correctable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Correctable.java new file mode 100644 index 0000000000..6e97f3a4bb --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Correctable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Correct 接口 + * @author Ttt + *
+ *     所有通用快捷 Correct 接口定义存储类
+ *     全部接口只定义一个方法 correct() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Correctable { + + private Correctable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Correct { + + T correct(); + } + + // ======= + // = 有参 = + // ======= + + public interface CorrectByParam { + + T correct(Param param); + } + + public interface CorrectByParam2 { + + T correct( + Param param, + Param2 param2 + ); + } + + public interface CorrectByParam3 { + + T correct( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CorrectByParamArgs { + + T correct(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Decodeable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Decodeable.java new file mode 100644 index 0000000000..2d8a4729aa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Decodeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Decode 接口 + * @author Ttt + *
+ *     所有通用快捷 Decode 接口定义存储类
+ *     全部接口只定义一个方法 decode() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Decodeable { + + private Decodeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Decode { + + T decode(); + } + + // ======= + // = 有参 = + // ======= + + public interface DecodeByParam { + + T decode(Param param); + } + + public interface DecodeByParam2 { + + T decode( + Param param, + Param2 param2 + ); + } + + public interface DecodeByParam3 { + + T decode( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface DecodeByParamArgs { + + T decode(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Decryptable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Decryptable.java new file mode 100644 index 0000000000..7b631c7742 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Decryptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Decrypt 接口 + * @author Ttt + *
+ *     所有通用快捷 Decrypt 接口定义存储类
+ *     全部接口只定义一个方法 decrypt() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Decryptable { + + private Decryptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Decrypt { + + T decrypt(); + } + + // ======= + // = 有参 = + // ======= + + public interface DecryptByParam { + + T decrypt(Param param); + } + + public interface DecryptByParam2 { + + T decrypt( + Param param, + Param2 param2 + ); + } + + public interface DecryptByParam3 { + + T decrypt( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface DecryptByParamArgs { + + T decrypt(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Editorable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Editorable.java new file mode 100644 index 0000000000..abc4224900 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Editorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Editor 接口 + * @author Ttt + *
+ *     所有通用快捷 Editor 接口定义存储类
+ *     全部接口只定义一个方法 edit() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Editorable { + + private Editorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Editor { + + T edit(); + } + + // ======= + // = 有参 = + // ======= + + public interface EditorByParam { + + T edit(Param param); + } + + public interface EditorByParam2 { + + T edit( + Param param, + Param2 param2 + ); + } + + public interface EditorByParam3 { + + T edit( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EditorByParamArgs { + + T edit(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Encodeable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Encodeable.java new file mode 100644 index 0000000000..848c1d20bb --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Encodeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Encode 接口 + * @author Ttt + *
+ *     所有通用快捷 Encode 接口定义存储类
+ *     全部接口只定义一个方法 encode() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Encodeable { + + private Encodeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Encode { + + T encode(); + } + + // ======= + // = 有参 = + // ======= + + public interface EncodeByParam { + + T encode(Param param); + } + + public interface EncodeByParam2 { + + T encode( + Param param, + Param2 param2 + ); + } + + public interface EncodeByParam3 { + + T encode( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EncodeByParamArgs { + + T encode(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Encryptable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Encryptable.java new file mode 100644 index 0000000000..4ac57e06aa --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Encryptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Encrypt 接口 + * @author Ttt + *
+ *     所有通用快捷 Encrypt 接口定义存储类
+ *     全部接口只定义一个方法 encrypt() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Encryptable { + + private Encryptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Encrypt { + + T encrypt(); + } + + // ======= + // = 有参 = + // ======= + + public interface EncryptByParam { + + T encrypt(Param param); + } + + public interface EncryptByParam2 { + + T encrypt( + Param param, + Param2 param2 + ); + } + + public interface EncryptByParam3 { + + T encrypt( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EncryptByParamArgs { + + T encrypt(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Errorable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Errorable.java new file mode 100644 index 0000000000..9f97696404 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Errorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Error 接口 + * @author Ttt + *
+ *     所有通用快捷 Error 接口定义存储类
+ *     全部接口只定义一个方法 tryCatch() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Errorable { + + private Errorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Error { + + T tryCatch(); + } + + // ======= + // = 有参 = + // ======= + + public interface ErrorByParam { + + T tryCatch(Param param); + } + + public interface ErrorByParam2 { + + T tryCatch( + Param param, + Param2 param2 + ); + } + + public interface ErrorByParam3 { + + T tryCatch( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ErrorByParamArgs { + + T tryCatch(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Filterable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Filterable.java new file mode 100644 index 0000000000..6a9eff782a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Filterable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Filter 接口 + * @author Ttt + *
+ *     所有通用快捷 Filter 接口定义存储类
+ *     全部接口只定义一个方法 accept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Filterable { + + private Filterable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Filter { + + T accept(); + } + + // ======= + // = 有参 = + // ======= + + public interface FilterByParam { + + T accept(Param param); + } + + public interface FilterByParam2 { + + T accept( + Param param, + Param2 param2 + ); + } + + public interface FilterByParam3 { + + T accept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FilterByParamArgs { + + T accept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Findable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Findable.java new file mode 100644 index 0000000000..721e424511 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Findable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Find 接口 + * @author Ttt + *
+ *     所有通用快捷 Find 接口定义存储类
+ *     全部接口只定义一个方法 find() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Findable { + + private Findable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Find { + + T find(); + } + + // ======= + // = 有参 = + // ======= + + public interface FindByParam { + + T find(Param param); + } + + public interface FindByParam2 { + + T find( + Param param, + Param2 param2 + ); + } + + public interface FindByParam3 { + + T find( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FindByParamArgs { + + T find(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Flowable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Flowable.java new file mode 100644 index 0000000000..6bb46d4bb6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Flowable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Flow 接口 + * @author Ttt + *
+ *     所有通用快捷 Flow 接口定义存储类
+ *     全部接口只定义一个方法 flow() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Flowable { + + private Flowable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Flow { + + T flow(); + } + + // ======= + // = 有参 = + // ======= + + public interface FlowByParam { + + T flow(Param param); + } + + public interface FlowByParam2 { + + T flow( + Param param, + Param2 param2 + ); + } + + public interface FlowByParam3 { + + T flow( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FlowByParamArgs { + + T flow(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Functionable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Functionable.java new file mode 100644 index 0000000000..487b6f229f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Functionable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Function 接口 + * @author Ttt + *
+ *     所有通用快捷 Function 接口定义存储类
+ *     全部接口只定义一个方法 apply() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Functionable { + + private Functionable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Function { + + T apply(); + } + + // ======= + // = 有参 = + // ======= + + public interface FunctionByParam { + + T apply(Param param); + } + + public interface FunctionByParam2 { + + T apply( + Param param, + Param2 param2 + ); + } + + public interface FunctionByParam3 { + + T apply( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FunctionByParamArgs { + + T apply(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Getable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Getable.java new file mode 100644 index 0000000000..79d7d7fda1 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Getable.java @@ -0,0 +1,63 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Get 接口 + * @author Ttt + *
+ *     所有通用快捷 Get 接口定义存储类
+ *     全部接口只定义一个方法 get() 且返回值一致
+ *     唯一差异就是参数不同
+ *     

+ * 其他类全部都是 copy {@link Getable} 代码完全一致, 只有方法名、接口名不同 + * 不通用 {@link Getable} 接口是为了区分功能, 对常用的功能定义对应类 + * 如 Readable、Writeable 读写较为常用, 所以专门定义对应接口类 + *

+ * 正常如 {@link Writeable} write() 方法需要返回写入结果, 可自行传入 泛型为 Boolean + * 也能自行决定需要返回什么类型值 + *
+ */ +public final class Getable { + + private Getable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Get { + + T get(); + } + + // ======= + // = 有参 = + // ======= + + public interface GetByParam { + + T get(Param param); + } + + public interface GetByParam2 { + + T get( + Param param, + Param2 param2 + ); + } + + public interface GetByParam3 { + + T get( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface GetByParamArgs { + + T get(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/IOable.java b/lib/DevApp/src/main/java/dev/utils/common/able/IOable.java new file mode 100644 index 0000000000..86c44003be --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/IOable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 IO 接口 + * @author Ttt + *
+ *     所有通用快捷 IO 接口定义存储类
+ *     全部接口只定义一个方法 io() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class IOable { + + private IOable() { + } + + // ======= + // = 无参 = + // ======= + + public interface IO { + + T io(); + } + + // ======= + // = 有参 = + // ======= + + public interface IOByParam { + + T io(Param param); + } + + public interface IOByParam2 { + + T io( + Param param, + Param2 param2 + ); + } + + public interface IOByParam3 { + + T io( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface IOByParamArgs { + + T io(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Inputable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Inputable.java new file mode 100644 index 0000000000..c58f6e563a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Inputable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Input 接口 + * @author Ttt + *
+ *     所有通用快捷 Input 接口定义存储类
+ *     全部接口只定义一个方法 input() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Inputable { + + private Inputable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Input { + + T input(); + } + + // ======= + // = 有参 = + // ======= + + public interface InputByParam { + + T input(Param param); + } + + public interface InputByParam2 { + + T input( + Param param, + Param2 param2 + ); + } + + public interface InputByParam3 { + + T input( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface InputByParamArgs { + + T input(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Interceptable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Interceptable.java new file mode 100644 index 0000000000..f9dce34c76 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Interceptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Intercept 接口 + * @author Ttt + *
+ *     所有通用快捷 Intercept 接口定义存储类
+ *     全部接口只定义一个方法 intercept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Interceptable { + + private Interceptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Intercept { + + T intercept(); + } + + // ======= + // = 有参 = + // ======= + + public interface InterceptByParam { + + T intercept(Param param); + } + + public interface InterceptByParam2 { + + T intercept( + Param param, + Param2 param2 + ); + } + + public interface InterceptByParam3 { + + T intercept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface InterceptByParamArgs { + + T intercept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Methodable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Methodable.java new file mode 100644 index 0000000000..d2ddc8bf54 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Methodable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Method 接口 + * @author Ttt + *
+ *     所有通用快捷 Method 接口定义存储类
+ *     全部接口只定义一个方法 invoke() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Methodable { + + private Methodable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Method { + + T invoke(); + } + + // ======= + // = 有参 = + // ======= + + public interface MethodByParam { + + T invoke(Param param); + } + + public interface MethodByParam2 { + + T invoke( + Param param, + Param2 param2 + ); + } + + public interface MethodByParam3 { + + T invoke( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface MethodByParamArgs { + + T invoke(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Modifyable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Modifyable.java new file mode 100644 index 0000000000..d8cb9f84bd --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Modifyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Modify 接口 + * @author Ttt + *
+ *     所有通用快捷 Modify 接口定义存储类
+ *     全部接口只定义一个方法 modify() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Modifyable { + + private Modifyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Modify { + + T modify(); + } + + // ======= + // = 有参 = + // ======= + + public interface ModifyByParam { + + T modify(Param param); + } + + public interface ModifyByParam2 { + + T modify( + Param param, + Param2 param2 + ); + } + + public interface ModifyByParam3 { + + T modify( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ModifyByParamArgs { + + T modify(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Notifyable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Notifyable.java new file mode 100644 index 0000000000..dc85aa9009 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Notifyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Notify 接口 + * @author Ttt + *
+ *     所有通用快捷 Notify 接口定义存储类
+ *     全部接口只定义一个方法 notifyMethod() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Notifyable { + + private Notifyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Notify { + + T notifyMethod(); + } + + // ======= + // = 有参 = + // ======= + + public interface NotifyByParam { + + T notifyMethod(Param param); + } + + public interface NotifyByParam2 { + + T notifyMethod( + Param param, + Param2 param2 + ); + } + + public interface NotifyByParam3 { + + T notifyMethod( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface NotifyByParamArgs { + + T notifyMethod(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Ofable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Ofable.java new file mode 100644 index 0000000000..7a689fb8a7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Ofable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Of 接口 + * @author Ttt + *
+ *     所有通用快捷 Of 接口定义存储类
+ *     全部接口只定义一个方法 of() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Ofable { + + private Ofable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Of { + + T of(); + } + + // ======= + // = 有参 = + // ======= + + public interface OfByParam { + + T of(Param param); + } + + public interface OfByParam2 { + + T of( + Param param, + Param2 param2 + ); + } + + public interface OfByParam3 { + + T of( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OfByParamArgs { + + T of(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Operatorable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Operatorable.java new file mode 100644 index 0000000000..464cbcf837 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Operatorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Operator 接口 + * @author Ttt + *
+ *     所有通用快捷 Operator 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Operatorable { + + private Operatorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Operator { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface OperatorByParam { + + T execute(Param param); + } + + public interface OperatorByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface OperatorByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OperatorByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Outputable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Outputable.java new file mode 100644 index 0000000000..1ab98cccea --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Outputable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Output 接口 + * @author Ttt + *
+ *     所有通用快捷 Output 接口定义存储类
+ *     全部接口只定义一个方法 output() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Outputable { + + private Outputable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Output { + + T output(); + } + + // ======= + // = 有参 = + // ======= + + public interface OutputByParam { + + T output(Param param); + } + + public interface OutputByParam2 { + + T output( + Param param, + Param2 param2 + ); + } + + public interface OutputByParam3 { + + T output( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OutputByParamArgs { + + T output(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Parseable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Parseable.java new file mode 100644 index 0000000000..90f780a9f5 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Parseable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Parse 接口 + * @author Ttt + *
+ *     所有通用快捷 Parse 接口定义存储类
+ *     全部接口只定义一个方法 parse() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Parseable { + + private Parseable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Parse { + + T parse(); + } + + // ======= + // = 有参 = + // ======= + + public interface ParseByParam { + + T parse(Param param); + } + + public interface ParseByParam2 { + + T parse( + Param param, + Param2 param2 + ); + } + + public interface ParseByParam3 { + + T parse( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ParseByParamArgs { + + T parse(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Pasteable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Pasteable.java new file mode 100644 index 0000000000..9c68593550 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Pasteable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Paste 接口 + * @author Ttt + *
+ *     所有通用快捷 Paste 接口定义存储类
+ *     全部接口只定义一个方法 paste() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Pasteable { + + private Pasteable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Paste { + + T paste(); + } + + // ======= + // = 有参 = + // ======= + + public interface PasteByParam { + + T paste(Param param); + } + + public interface PasteByParam2 { + + T paste( + Param param, + Param2 param2 + ); + } + + public interface PasteByParam3 { + + T paste( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface PasteByParamArgs { + + T paste(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Queryable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Queryable.java new file mode 100644 index 0000000000..567b109ded --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Queryable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Query 接口 + * @author Ttt + *
+ *     所有通用快捷 Query 接口定义存储类
+ *     全部接口只定义一个方法 query() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Queryable { + + private Queryable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Query { + + T query(); + } + + // ======= + // = 有参 = + // ======= + + public interface QueryByParam { + + T query(Param param); + } + + public interface QueryByParam2 { + + T query( + Param param, + Param2 param2 + ); + } + + public interface QueryByParam3 { + + T query( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface QueryByParamArgs { + + T query(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Readable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Readable.java new file mode 100644 index 0000000000..d90c26d3e7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Readable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Read 接口 + * @author Ttt + *
+ *     所有通用快捷 Read 接口定义存储类
+ *     全部接口只定义一个方法 read() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Readable { + + private Readable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Read { + + T read(); + } + + // ======= + // = 有参 = + // ======= + + public interface ReadByParam { + + T read(Param param); + } + + public interface ReadByParam2 { + + T read( + Param param, + Param2 param2 + ); + } + + public interface ReadByParam3 { + + T read( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ReadByParamArgs { + + T read(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Refreshable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Refreshable.java new file mode 100644 index 0000000000..95d2ad68ec --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Refreshable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Refresh 接口 + * @author Ttt + *
+ *     所有通用快捷 Refresh 接口定义存储类
+ *     全部接口只定义一个方法 refresh() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Refreshable { + + private Refreshable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Refresh { + + T refresh(); + } + + // ======= + // = 有参 = + // ======= + + public interface RefreshByParam { + + T refresh(Param param); + } + + public interface RefreshByParam2 { + + T refresh( + Param param, + Param2 param2 + ); + } + + public interface RefreshByParam3 { + + T refresh( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface RefreshByParamArgs { + + T refresh(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Replaceable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Replaceable.java new file mode 100644 index 0000000000..be736d50be --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Replaceable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Replace 接口 + * @author Ttt + *
+ *     所有通用快捷 Replace 接口定义存储类
+ *     全部接口只定义一个方法 replace() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Replaceable { + + private Replaceable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Replace { + + T replace(); + } + + // ======= + // = 有参 = + // ======= + + public interface ReplaceByParam { + + T replace(Param param); + } + + public interface ReplaceByParam2 { + + T replace( + Param param, + Param2 param2 + ); + } + + public interface ReplaceByParam3 { + + T replace( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ReplaceByParamArgs { + + T replace(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Routerable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Routerable.java new file mode 100644 index 0000000000..5f800b4148 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Routerable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Router 接口 + * @author Ttt + *
+ *     所有通用快捷 Router 接口定义存储类
+ *     全部接口只定义一个方法 router() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Routerable { + + private Routerable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Router { + + T router(); + } + + // ======= + // = 有参 = + // ======= + + public interface RouterByParam { + + T router(Param param); + } + + public interface RouterByParam2 { + + T router( + Param param, + Param2 param2 + ); + } + + public interface RouterByParam3 { + + T router( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface RouterByParamArgs { + + T router(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Searchable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Searchable.java new file mode 100644 index 0000000000..4bc32376a3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Searchable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Search 接口 + * @author Ttt + *
+ *     所有通用快捷 Search 接口定义存储类
+ *     全部接口只定义一个方法 search() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Searchable { + + private Searchable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Search { + + T search(); + } + + // ======= + // = 有参 = + // ======= + + public interface SearchByParam { + + T search(Param param); + } + + public interface SearchByParam2 { + + T search( + Param param, + Param2 param2 + ); + } + + public interface SearchByParam3 { + + T search( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SearchByParamArgs { + + T search(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Sendable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Sendable.java new file mode 100644 index 0000000000..79e47eb5ea --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Sendable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Send 接口 + * @author Ttt + *
+ *     所有通用快捷 Send 接口定义存储类
+ *     全部接口只定义一个方法 post() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Sendable { + + private Sendable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Send { + + T post(); + } + + // ======= + // = 有参 = + // ======= + + public interface SendByParam { + + T post(Param param); + } + + public interface SendByParam2 { + + T post( + Param param, + Param2 param2 + ); + } + + public interface SendByParam3 { + + T post( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SendByParamArgs { + + T post(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Sortable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Sortable.java new file mode 100644 index 0000000000..a19114b586 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Sortable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Sort 接口 + * @author Ttt + *
+ *     所有通用快捷 Sort 接口定义存储类
+ *     全部接口只定义一个方法 sort() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Sortable { + + private Sortable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Sort { + + T sort(); + } + + // ======= + // = 有参 = + // ======= + + public interface SortByParam { + + T sort(Param param); + } + + public interface SortByParam2 { + + T sort( + Param param, + Param2 param2 + ); + } + + public interface SortByParam3 { + + T sort( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SortByParamArgs { + + T sort(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Splitable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Splitable.java new file mode 100644 index 0000000000..a31f8c66c7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Splitable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Split 接口 + * @author Ttt + *
+ *     所有通用快捷 Split 接口定义存储类
+ *     全部接口只定义一个方法 split() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Splitable { + + private Splitable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Split { + + T split(); + } + + // ======= + // = 有参 = + // ======= + + public interface SplitByParam { + + T split(Param param); + } + + public interface SplitByParam2 { + + T split( + Param param, + Param2 param2 + ); + } + + public interface SplitByParam3 { + + T split( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SplitByParamArgs { + + T split(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Supplierable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Supplierable.java new file mode 100644 index 0000000000..8c0ffd7860 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Supplierable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Supplier 接口 + * @author Ttt + *
+ *     所有通用快捷 Supplier 接口定义存储类
+ *     全部接口只定义一个方法 getAs() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Supplierable { + + private Supplierable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Supplier { + + T getAs(); + } + + // ======= + // = 有参 = + // ======= + + public interface SupplierByParam { + + T getAs(Param param); + } + + public interface SupplierByParam2 { + + T getAs( + Param param, + Param2 param2 + ); + } + + public interface SupplierByParam3 { + + T getAs( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SupplierByParamArgs { + + T getAs(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Taskable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Taskable.java new file mode 100644 index 0000000000..672393f339 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Taskable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Task 接口 + * @author Ttt + *
+ *     所有通用快捷 Task 接口定义存储类
+ *     全部接口只定义一个方法 result() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Taskable { + + private Taskable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Task { + + T result(); + } + + // ======= + // = 有参 = + // ======= + + public interface TaskByParam { + + T result(Param param); + } + + public interface TaskByParam2 { + + T result( + Param param, + Param2 param2 + ); + } + + public interface TaskByParam3 { + + T result( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface TaskByParamArgs { + + T result(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Threadable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Threadable.java new file mode 100644 index 0000000000..ad53d1dafc --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Threadable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Thread 接口 + * @author Ttt + *
+ *     所有通用快捷 Thread 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Threadable { + + private Threadable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Thread { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface ThreadByParam { + + T execute(Param param); + } + + public interface ThreadByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface ThreadByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ThreadByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Toable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Toable.java new file mode 100644 index 0000000000..f077d9023b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Toable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 To 接口 + * @author Ttt + *
+ *     所有通用快捷 To 接口定义存储类
+ *     全部接口只定义一个方法 to() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Toable { + + private Toable() { + } + + // ======= + // = 无参 = + // ======= + + public interface To { + + T to(); + } + + // ======= + // = 有参 = + // ======= + + public interface ToByParam { + + T to(Param param); + } + + public interface ToByParam2 { + + T to( + Param param, + Param2 param2 + ); + } + + public interface ToByParam3 { + + T to( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ToByParamArgs { + + T to(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/UnBinderable.java b/lib/DevApp/src/main/java/dev/utils/common/able/UnBinderable.java new file mode 100644 index 0000000000..b57c0aa186 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/UnBinderable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 UnBinder 接口 + * @author Ttt + *
+ *     所有通用快捷 UnBinder 接口定义存储类
+ *     全部接口只定义一个方法 unbind() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class UnBinderable { + + private UnBinderable() { + } + + // ======= + // = 无参 = + // ======= + + public interface UnBinder { + + T unbind(); + } + + // ======= + // = 有参 = + // ======= + + public interface UnBinderByParam { + + T unbind(Param param); + } + + public interface UnBinderByParam2 { + + T unbind( + Param param, + Param2 param2 + ); + } + + public interface UnBinderByParam3 { + + T unbind( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface UnBinderByParamArgs { + + T unbind(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/able/Writeable.java b/lib/DevApp/src/main/java/dev/utils/common/able/Writeable.java new file mode 100644 index 0000000000..82a311ad03 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/able/Writeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Write 接口 + * @author Ttt + *
+ *     所有通用快捷 Write 接口定义存储类
+ *     全部接口只定义一个方法 write() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Writeable { + + private Writeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Write { + + T write(); + } + + // ======= + // = 有参 = + // ======= + + public interface WriteByParam { + + T write(Param param); + } + + public interface WriteByParam2 { + + T write( + Param param, + Param2 param2 + ); + } + + public interface WriteByParam3 { + + T write( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface WriteByParamArgs { + + T write(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/Averager.java b/lib/DevApp/src/main/java/dev/utils/common/assist/Averager.java new file mode 100644 index 0000000000..3988977b50 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/Averager.java @@ -0,0 +1,64 @@ +package dev.utils.common.assist; + +import java.util.ArrayList; +import java.util.List; + +/** + * detail: 均值计算 ( 用以统计平均数 ) 辅助类 + * @author Ttt + */ +public class Averager { + + private final List mNumLists = new ArrayList<>(); + + /** + * 添加一个数字 + * @param number Number + * @return {@link Averager} + */ + public synchronized Averager add(final Number number) { + mNumLists.add(number); + return this; + } + + /** + * 清除全部 + * @return {@link Averager} + */ + public Averager clear() { + mNumLists.clear(); + return this; + } + + /** + * 获取参与均值计算的数字个数 + * @return 参与均值计算的数字个数 + */ + public Number size() { + return mNumLists.size(); + } + + /** + * 获取平均数 + * @return 全部数字平均数 + */ + public Number getAverage() { + if (mNumLists.size() == 0) { + return 0; + } else { + float sum = 0F; + for (int i = 0, len = mNumLists.size(); i < len; i++) { + sum = sum + mNumLists.get(i).floatValue(); + } + return sum / mNumLists.size(); + } + } + + /** + * 输出参与均值计算的数字 + * @return 参与均值计算的数字 + */ + public String print() { + return "printList(" + size() + "): " + mNumLists; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/FlagsValue.java b/lib/DevApp/src/main/java/dev/utils/common/assist/FlagsValue.java new file mode 100644 index 0000000000..9f1d600813 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/FlagsValue.java @@ -0,0 +1,78 @@ +package dev.utils.common.assist; + +/** + * detail: 标记值计算存储 ( 位运算符 ) + * @author Ttt + */ +public final class FlagsValue { + + public FlagsValue() { + } + + public FlagsValue(final int flags) { + this.mFlags = flags; + } + + // ============= + // = 对外公开方法 = + // ============= + + // flags value + private int mFlags; + + /** + * 获取 flags value + * @return flags value + */ + public int getFlags() { + return mFlags; + } + + /** + * 设置 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue setFlags(final int flags) { + this.mFlags = flags; + return this; + } + + /** + * 添加 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue addFlags(final int flags) { + this.mFlags |= flags; + return this; + } + + /** + * 移除 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue clearFlags(final int flags) { + this.mFlags &= ~flags; + return this; + } + + /** + * 是否存在 flags value + * @param flags flags value + * @return {@code true} yes, {@code false} no + */ + public boolean hasFlags(final int flags) { + return (mFlags & flags) == flags; + } + + /** + * 是否不存在 flags value + * @param flags flags value + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFlags(final int flags) { + return (mFlags & flags) != flags; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/TimeAverager.java b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeAverager.java new file mode 100644 index 0000000000..01b76b5438 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeAverager.java @@ -0,0 +1,66 @@ +package dev.utils.common.assist; + +/** + * detail: 时间均值计算辅助类 + * @author Ttt + */ +public class TimeAverager { + + // 计时器 + private final TimeCounter mTimeCounter = new TimeCounter(); + // 均值器 + private final Averager mAverager = new Averager(); + + /** + * 开始计时 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long start() { + return mTimeCounter.start(); + } + + /** + * 结束计时 ( 毫秒 ) + * @return 结束时间 ( 毫秒 ) + */ + public long end() { + long time = mTimeCounter.duration(); + mAverager.add(time); + return time; + } + + /** + * 结束计时, 并重新启动新的计时 + * @return 距离上次计时的时间差 ( 毫秒 ) + */ + public long endAndRestart() { + long time = mTimeCounter.durationRestart(); + mAverager.add(time); + return time; + } + + /** + * 求全部计时均值 + * @return 全部计时时间均值 + */ + public Number average() { + return mAverager.getAverage(); + } + + /** + * 输出全部时间值 + * @return 计时信息 + */ + public String print() { + return mAverager.print(); + } + + /** + * 清除计时数据 + * @return {@link TimeAverager} + */ + public TimeAverager clear() { + mAverager.clear(); + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/TimeCounter.java b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeCounter.java new file mode 100644 index 0000000000..60e287d77c --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeCounter.java @@ -0,0 +1,59 @@ +package dev.utils.common.assist; + +/** + * detail: 时间计时辅助类 + * @author Ttt + */ +public class TimeCounter { + + // 开始时间 + private long mStart; + + public TimeCounter() { + this(true); + } + + /** + * 构造函数 + * @param isStart 是否开始计时 + */ + public TimeCounter(final boolean isStart) { + if (isStart) start(); + } + + /** + * 开始计时 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long start() { + mStart = System.currentTimeMillis(); + return mStart; + } + + /** + * 获取持续的时间并重新启动 ( 毫秒 ) + * @return 距离上次开始时间的时间差 ( 毫秒 ) + */ + public long durationRestart() { + long now = System.currentTimeMillis(); + long diff = now - mStart; + mStart = now; + return diff; + } + + /** + * 获取持续的时间 ( 毫秒 ) + * @return 距离开始时间的时间差 ( 毫秒 ) + */ + public long duration() { + return System.currentTimeMillis() - mStart; + } + + /** + * 获取开始时间 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStart; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/TimeKeeper.java b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeKeeper.java new file mode 100644 index 0000000000..1419779390 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/TimeKeeper.java @@ -0,0 +1,71 @@ +package dev.utils.common.assist; + +/** + * detail: 堵塞时间辅助类 + * @author Ttt + */ +public class TimeKeeper { + + /** + * 设置等待一段时间后, 通知方法 ( 异步 ) + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param callback 结束回调通知 + */ + public void waitForEndAsync( + final long keepTimeMillis, + final OnEndCallback callback + ) { + if (keepTimeMillis > 0L) { + new Thread(() -> waitForEnd(keepTimeMillis, callback)).start(); + } + } + + /** + * 设置等待一段时间后, 通知方法 ( 同步 ) + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param callback 结束回调通知 + */ + public void waitForEnd( + final long keepTimeMillis, + final OnEndCallback callback + ) { + if (keepTimeMillis > 0L) { + // 开始堵塞时间 + long startTime = System.currentTimeMillis(); + try { + // 进行堵塞 + Thread.sleep(keepTimeMillis); + // 触发回调 + if (callback != null) { + callback.onEnd(keepTimeMillis, startTime, System.currentTimeMillis(), false); + } + } catch (Exception e) { + // 触发回调 + if (callback != null) { + callback.onEnd(keepTimeMillis, startTime, System.currentTimeMillis(), true); + } + } + } + } + + /** + * detail: 结束通知回调 + * @author Ttt + */ + public interface OnEndCallback { + + /** + * 结束触发通知方法 + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param startTimeMillis 开始堵塞时间 ( 毫秒 ) + * @param endTimeMillis 结束时间 ( 毫秒 ) + * @param isError 是否发生异常 + */ + void onEnd( + long keepTimeMillis, + long startTimeMillis, + long endTimeMillis, + boolean isError + ); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java b/lib/DevApp/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java new file mode 100644 index 0000000000..0e6dc29944 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java @@ -0,0 +1,153 @@ +package dev.utils.common.assist; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * detail: 弱引用辅助类 + * @author Ttt + */ +public final class WeakReferenceAssist { + + // 日志 TAG + private final String TAG = WeakReferenceAssist.class.getSimpleName(); + + // 弱引用存储 + private final Map> mWeakMaps = new HashMap<>(); + + // ============= + // = 对外公开方法 = + // ============= + + // ========== + // = Single = + // ========== + + /** + * 获取单个弱引用对象 + * @return 单个弱引用对象 + */ + public WeakReference getSingleWeak() { + return getWeak(TAG); + } + + /** + * 获取单个弱引用对象值 + * @return 单个弱引用对象值 + */ + public T getSingleWeakValue() { + return getWeakValue(TAG); + } + + /** + * 获取单个弱引用对象值 + * @param defaultValue 默认值 + * @return 单个弱引用对象值 + */ + public T getSingleWeakValue(final T defaultValue) { + return getWeakValue(TAG, defaultValue); + } + + // = + + /** + * 保存单个弱引用对象值 + * @param value 待存储值 + * @return {@code true} success, {@code false} fail + */ + public boolean setSingleWeakValue(final T value) { + return setWeakValue(TAG, value); + } + + /** + * 移除单个弱引用持有对象 + * @return {@code true} success, {@code false} fail + */ + public boolean removeSingleWeak() { + return removeWeak(TAG); + } + + // =========== + // = Map Key = + // =========== + + /** + * 获取弱引用对象 + * @param key Key + * @return 弱引用对象 + */ + public WeakReference getWeak(final String key) { + return mWeakMaps.get(key); + } + + /** + * 获取弱引用对象值 + * @param key Key + * @return 弱引用对象值 + */ + public T getWeakValue(final String key) { + return getWeakValue(key, null); + } + + /** + * 获取弱引用对象值 + * @param key Key + * @param defaultValue 默认值 + * @return 弱引用对象值 + */ + public T getWeakValue( + final String key, + final T defaultValue + ) { + WeakReference weak = mWeakMaps.get(key); + if (weak == null) return defaultValue; + T value = weak.get(); + if (value != null) return value; + return defaultValue; + } + + // = + + /** + * 保存弱引用对象值 + * @param key Key + * @param value 待存储值 + * @return {@code true} success, {@code false} fail + */ + public boolean setWeakValue( + final String key, + final T value + ) { + if (key == null || value == null) return false; + mWeakMaps.put(key, new WeakReference<>(value)); + return true; + } + + /** + * 移除指定弱引用持有对象 + * @param key Key + * @return {@code true} success, {@code false} fail + */ + public boolean removeWeak(final String key) { + if (key == null) return false; + WeakReference weak = mWeakMaps.remove(key); + if (weak == null) return false; + weak.clear(); + return true; + } + + /** + * 清空全部弱引用持有对象 + */ + public void clear() { + List> lists = new ArrayList<>(mWeakMaps.values()); + mWeakMaps.clear(); + for (WeakReference weak : lists) { + if (weak != null) weak.clear(); + } + lists.clear(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java b/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java new file mode 100644 index 0000000000..187b2e2894 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java @@ -0,0 +1,306 @@ +package dev.utils.common.assist.record; + +import java.io.File; + +import dev.utils.DevFinal; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.ThrowableUtils; + +/** + * detail: 文件记录分析工具类 + * @author Ttt + */ +public final class FileRecordUtils { + + private FileRecordUtils() { + } + + // 成功常量字符串 + private static final String RECORD_SUCCESS = "record successful"; + // 是否处理记录 + private static boolean sHandler = true; + // 日志记录插入信息 + private static RecordInsert sRecordInsert = null; + // 文件记录回调 + private static Callback sCallback = null; + + // ========== + // = 接口回调 = + // ========== + + /** + * detail: 文件记录回调 + * @author Ttt + */ + public interface Callback { + + /** + * 记录结果回调 + * @param result 保存结果 + * @param config 日志记录配置信息 + * @param filePath 存储路径 + * @param fileName 文件名 ( 含后缀 ) + * @param logContent 日志信息 + * @param logs 原始日志内容数组 + */ + void callback( + boolean result, + RecordConfig config, + String filePath, + String fileName, + String logContent, + Object... logs + ); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 拼接插入日志 + * @param recordInsert 日志记录插入信息 + * @param logContent 日志内容 + * @return 最终日志信息 + */ + private static String concatInsertLog( + final RecordInsert recordInsert, + final String logContent + ) { + if (recordInsert == null) { + return logContent; + } + StringBuilder builder = new StringBuilder(); + // 追加头部信息 + if (StringUtils.isNotEmpty(recordInsert.getLogHeader())) { + builder.append(recordInsert.getLogHeader()); + } + // 加入日志内容 + builder.append(logContent); + // 追加尾部信息 + if (StringUtils.isNotEmpty(recordInsert.getLogTail())) { + builder.append(recordInsert.getLogTail()); + } + return builder.toString(); + } + + /** + * 拼接日志 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 拼接后的日志内容 + */ + private static String concatLog( + final RecordConfig config, + final Object... logs + ) { + if (logs == null || logs.length == 0) return null; + // 是否插入头数据 ( time =>、logs[] ) + boolean headerData = (config == null || config.isInsertHeaderData()); + + StringBuilder builder = new StringBuilder(); + if (headerData) { + builder.append(DevFinal.SYMBOL.NEW_LINE_X2) + // 获取当前时间 + .append(DateUtils.getDateNow()) + // 追加边距、换行 + .append(" =>"); + } + // 循环追加内容 + for (int i = 0, len = logs.length; i < len; i++) { + // 追加存储内容 + builder.append(DevFinal.SYMBOL.NEW_LINE_X2); + if (headerData) { + builder.append("logs[").append(i).append("]: ") + .append(DevFinal.SYMBOL.NEW_LINE); + } + Object object = logs[i]; + if (object instanceof Throwable) { + String errorLog = ThrowableUtils.getThrowableStackTrace((Throwable) object); + builder.append(errorLog); + } else { + builder.append(ConvertUtils.toString(object)); + } + } + return builder.toString(); + } + + /** + * 最终记录方法 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 记录结果提示 + */ + private static String finalRecord( + final RecordConfig config, + final Object... logs + ) { + // 判断全局是否处理 + if (!sHandler) return "global do not handle"; + // 判断配置是否为 null + if (config == null) return "config is null"; + // 判断配置是否处理 + if (!config.isHandler()) return "config do not handle"; + // 判断是否存在日志内容 + if (logs == null || logs.length == 0) return "no data record"; + + // 文件路径 + String filePath = config.getFinalPath(); + // 文件名 + String fileName = config.getFileName(); + // 文件路径、文件名为 null 则不处理 + if (StringUtils.isEmpty(filePath, fileName)) return "filePath is null"; + + // 日志记录插入信息 + RecordInsert recordInsert = config.getRecordInsert(sRecordInsert); + // 日志内容 + String logContent = concatLog(config, logs); + // 拼接最终内容 + String finalLogContent = concatInsertLog(recordInsert, logContent); + + // 获取存储文件 + File file = FileUtils.getFile(filePath, fileName); + // 文件不存在则进行追加文件信息 + if (!FileUtils.isFileExists(file)) { + if (recordInsert != null) { + String fileInfo = recordInsert.getFileInfo(); + if (fileInfo != null) { + // 文件信息 ( 一个文件只会添加一次文件信息, 且在最顶部 ) + FileUtils.saveFile(file, StringUtils.getBytes(fileInfo)); + } + } + } + // 追加日志内容 + boolean result = FileUtils.appendFile(file, StringUtils.getBytes(finalLogContent)); + + if (sCallback != null) { + sCallback.callback(result, config, filePath, fileName, finalLogContent, logs); + } + return result ? RECORD_SUCCESS : "record failed"; + } + + // ============= + // = 对外公开方法 = + // ============= + + // =========== + // = get/set = + // =========== + + /** + * 校验记录方法返回字符串是否成功 + * @param value 待校验值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSuccessful(final String value) { + return RECORD_SUCCESS.equals(value); + } + + /** + * 是否处理记录 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHandler() { + return sHandler; + } + + /** + * 设置是否处理记录 + * @param handler 是否处理记录 + */ + public static void setHandler(final boolean handler) { + FileRecordUtils.sHandler = handler; + } + + /** + * 获取日志记录插入信息 + * @return 日志记录插入信息 + */ + public static RecordInsert getRecordInsert() { + return sRecordInsert; + } + + /** + * 设置日志记录插入信息 + * @param recordInsert 日志记录插入信息 + */ + public static void setRecordInsert(final RecordInsert recordInsert) { + FileRecordUtils.sRecordInsert = recordInsert; + } + + /** + * 设置文件记录回调 + * @param callback 文件记录回调 + */ + public static void setCallback(final Callback callback) { + FileRecordUtils.sCallback = callback; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取日志内容 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordConfig config, + final Object... logs + ) { + if (config != null) { + return getLogContent(config, config.getRecordInsert(sRecordInsert), logs); + } + return getLogContent(null, sRecordInsert, logs); + } + + /** + * 获取日志内容 + * @param recordInsert 日志记录插入信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordInsert recordInsert, + final Object... logs + ) { + return getLogContent(null, recordInsert, logs); + } + + /** + * 获取日志内容 + * @param config 日志记录配置信息 + * @param recordInsert 日志记录插入信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordConfig config, + final RecordInsert recordInsert, + final Object... logs + ) { + String logContent = concatLog(config, logs); + if (StringUtils.isEmpty(logContent)) return null; + return concatInsertLog(recordInsert, logContent); + } + + // = + + /** + * 记录方法 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 记录结果提示 + */ + public static String record( + final RecordConfig config, + final Object... logs + ) { + return finalRecord(config, logs); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordConfig.java b/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordConfig.java new file mode 100644 index 0000000000..d3812a6aac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordConfig.java @@ -0,0 +1,311 @@ +package dev.utils.common.assist.record; + +import java.io.File; +import java.util.Date; + +import dev.utils.DevFinal; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 日志记录配置信息 + * @author Ttt + */ +public final class RecordConfig { + + // 存储路径 + private final String mStoragePath; + // 文件名 ( 固定 ) + private final String mFileName = "log_record.txt"; + // 文件夹名 ( 模块名 ) + private final String mFolderName; + // 文件记录间隔时间 如: HH + private final TIME mFileIntervalTime; + // 是否处理记录 + private boolean mHandler; + // 是否插入头数据 ( time =>、logs[] ) + private boolean mInsertHeaderData; + // 日志记录插入信息 + private RecordInsert mRecordInsert; + + // ======= + // = 枚举 = + // ======= + + /** + * detail: 文件记录间隔时间枚举 + * @author Ttt + */ + public enum TIME { + + // DEFAULT ( 默认天, 在根目录下 ) + DEFAULT, + + // 小时 + HH, + + // 分钟 + MM, + + // 秒 + SS + } + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @param insertHeaderData 是否插入头数据 + */ + private RecordConfig( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler, + final boolean insertHeaderData + ) { + this.mStoragePath = storagePath; + this.mFolderName = folderName; + this.mFileIntervalTime = fileIntervalTime; + this.mHandler = handler; + this.mInsertHeaderData = insertHeaderData; + } + + // = + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName + ) { + return get(storagePath, folderName, TIME.DEFAULT, true, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime + ) { + return get(storagePath, folderName, fileIntervalTime, true, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler + ) { + return get(storagePath, folderName, fileIntervalTime, handler, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @param insertHeaderData 是否插入头数据 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler, + final boolean insertHeaderData + ) { + if (StringUtils.isEmpty(storagePath, folderName)) return null; + return new RecordConfig( + storagePath, folderName, + (fileIntervalTime != null ? fileIntervalTime : TIME.DEFAULT), + handler, insertHeaderData + ); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取存储路径 + * @return 存储路径 + */ + public String getStoragePath() { + return mStoragePath; + } + + /** + * 获取文件名 ( 固定 ) + * @return 文件名 ( 固定 ) + */ + public String getFileName() { + return mFileName; + } + + /** + * 获取文件夹名 ( 模块名 ) + * @return 文件夹名 ( 模块名 ) + */ + public String getFolderName() { + return mFolderName; + } + + /** + * 获取文件记录间隔时间 + * @return 文件记录间隔时间 + */ + public TIME getFileIntervalTime() { + return mFileIntervalTime; + } + + /** + * 是否处理记录 + * @return {@code true} yes, {@code false} no + */ + public boolean isHandler() { + return mHandler; + } + + /** + * 设置是否处理记录 + * @param handler 是否处理记录 + * @return {@link RecordConfig} + */ + public RecordConfig setHandler(final boolean handler) { + this.mHandler = handler; + return this; + } + + /** + * 是否插入头数据 + * @return {@code true} yes, {@code false} no + */ + public boolean isInsertHeaderData() { + return mInsertHeaderData; + } + + /** + * 设置是否插入头数据 + * @param insertHeaderData 是否插入头数据 + * @return {@link RecordConfig} + */ + public RecordConfig setInsertHeaderData(final boolean insertHeaderData) { + this.mInsertHeaderData = insertHeaderData; + return this; + } + + /** + * 获取日志记录插入信息 + * @return 日志记录插入信息 + */ + public RecordInsert getRecordInsert() { + return mRecordInsert; + } + + /** + * 获取日志记录插入信息 + * @param defaultValue 默认值 + * @return 日志记录插入信息 + */ + public RecordInsert getRecordInsert(final RecordInsert defaultValue) { + if (mRecordInsert != null) return mRecordInsert; + return defaultValue; + } + + /** + * 设置日志记录插入信息 + * @param recordInsert 日志记录插入信息 + * @return {@link RecordConfig} + */ + public RecordConfig setRecordInsert(final RecordInsert recordInsert) { + this.mRecordInsert = recordInsert; + return this; + } + + // = + + /** + * 获取文件地址 + * @return 文件地址 + */ + public String getFinalPath() { + File file = FileUtils.getFile(mStoragePath, getIntervalTimeFolder()); + // 创建文件夹 + FileUtils.createFolder(file); + return FileUtils.getAbsolutePath(file); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取时间间隔所属的文件夹 + * @return 时间间隔所属的文件夹 + */ + private String getIntervalTimeFolder() { + // 文件夹 + String folder = String.format( + "FileRecord/%s/%s/", + DateUtils.getDateNow(DevFinal.TIME.yyyyMMdd_UNDERSCORE), + mFolderName + ); + // 进行判断 + switch (mFileIntervalTime) { + case DEFAULT: + return folder; + case HH: + case MM: + case SS: + Date date = new Date(); + // 小时格式 ( 24 ) + String hh_Format = DateUtils.timeAddZero(DateUtils.get24Hour(date)); + // 判断属于小时格式 + if (mFileIntervalTime == TIME.HH) { + // folder/HH_number/ + return folder + "HH_" + hh_Format + File.separator; + } else { + // 分钟格式 + String mm_Format = DateUtils.timeAddZero(DateUtils.getMinute(date)); + // 判断是否属于分钟 + if (mFileIntervalTime == TIME.MM) { + // folder/HH_number/MM_number/ + return folder + "HH_" + hh_Format + "/MM_" + mm_Format + File.separator; + } else { // 属于秒 + // 秒格式 + String ss_Format = DateUtils.timeAddZero(DateUtils.getSecond(date)); + // folder/HH_number/MM_number/SS_number/ + return folder + "HH_" + hh_Format + "/MM_" + mm_Format + "/SS_" + ss_Format + File.separator; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordInsert.java b/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordInsert.java new file mode 100644 index 0000000000..696584c439 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordInsert.java @@ -0,0 +1,66 @@ +package dev.utils.common.assist.record; + +/** + * detail: 日志记录插入信息 + * @author Ttt + */ +public class RecordInsert { + + // 文件信息 ( 一个文件只会添加一次文件信息, 且在最顶部 ) + protected String mFileInfo; + + // 每条日志头部信息 + protected String mLogHeader; + + // 每条日志尾部信息 + protected String mLogTail; + + // ========== + // = 构造函数 = + // ========== + + public RecordInsert(final String fileInfo) { + this.mFileInfo = fileInfo; + } + + public RecordInsert( + final String fileInfo, + final String logHeader, + final String logTail + ) { + this.mFileInfo = fileInfo; + this.mLogHeader = logHeader; + this.mLogTail = logTail; + } + + // =========== + // = get/set = + // =========== + + public String getFileInfo() { + return mFileInfo; + } + + public RecordInsert setFileInfo(final String fileInfo) { + this.mFileInfo = fileInfo; + return this; + } + + public String getLogHeader() { + return mLogHeader; + } + + public RecordInsert setLogHeader(final String logHeader) { + this.mLogHeader = logHeader; + return this; + } + + public String getLogTail() { + return mLogTail; + } + + public RecordInsert setLogTail(final String logTail) { + this.mLogTail = logTail; + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java b/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java new file mode 100644 index 0000000000..1c705ebc4b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java @@ -0,0 +1,414 @@ +package dev.utils.common.assist.search; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +import dev.utils.JCLogUtils; + +/** + * detail: 文件广度优先搜索算法 ( 多线程 + 队列, 搜索某个目录下的全部文件 ) + * @author Ttt + */ +public final class FileBreadthFirstSearchUtils { + + // 日志 TAG + private static final String TAG = FileBreadthFirstSearchUtils.class.getSimpleName(); + + /** + * 构造函数 + */ + public FileBreadthFirstSearchUtils() { + } + + /** + * 构造函数 + * @param searchHandler 搜索处理接口 + */ + public FileBreadthFirstSearchUtils(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + } + + /** + * detail: 文件信息 Item + * @author Ttt + */ + public static final class FileItem { + + public FileItem(final File file) { + this.file = file; + } + + // 文件 + public File file; + + // 子文件夹目录 + public Map mapChilds; + + /** + * 保存子文件信息 + * @param file 文件 + * @return 文件信息 {@link FileItem} + */ + private synchronized FileItem put(final File file) { + if (mapChilds == null) { + mapChilds = new HashMap<>(); + } + if (file != null) { + FileItem fileItem = new FileItem(file); + mapChilds.put(file.getAbsolutePath(), fileItem); + return fileItem; + } + return null; + } + } + + /** + * detail: 文件队列 + * @author Ttt + */ + private static class FileQueue { + + FileQueue( + File file, + FileItem fileItem + ) { + this.file = file; + this.fileItem = fileItem; + } + + // 当前准备处理文件夹 + private final File file; + + // 上一级目录对象 + private final FileItem fileItem; + } + + /** + * detail: 搜索处理接口 + * @author Ttt + */ + public interface SearchHandler { + + /** + * 判断是否处理该文件 + * @param file 文件 + * @return {@code true} 处理该文件, {@code false} 跳过该文件不处理 + */ + boolean isHandlerFile(File file); + + /** + * 是否添加到集合 + * @param file 文件 + * @return {@code true} 添加, {@code false} 不添加 + */ + boolean isAddToList(File file); + + /** + * 搜索结束监听 + * @param rootFileItem 根文件信息 {@link FileItem} + * @param startTime 开始扫描时间 + * @param endTime 扫描结束时间 + */ + void onEndListener( + FileItem rootFileItem, + long startTime, + long endTime + ); + } + + // 搜索处理接口 + private SearchHandler mSearchHandler; + + // 内部实现接口 + private final SearchHandler mInnerHandler = new SearchHandler() { + @Override + public boolean isHandlerFile(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isHandlerFile(file); + } + return true; + } + + @Override + public boolean isAddToList(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isAddToList(file); + } + return true; + } + + @Override + public void onEndListener( + FileItem rootFileItem, + long startTime, + long endTime + ) { + // 表示非搜索中 + mRunning = false; + // 触发回调 + if (mSearchHandler != null) { + mSearchHandler.onEndListener(rootFileItem, startTime, endTime); + } + } + }; + + /** + * 设置搜索处理接口 + * @param searchHandler 搜索处理接口 + * @return {@link FileBreadthFirstSearchUtils} + */ + public FileBreadthFirstSearchUtils setSearchHandler(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + return this; + } + + /** + * 获取任务队列同时进行数量 + * @return 队列数量 + */ + public int getQueueSameTimeNumber() { + return mQueueSameTimeNumber; + } + + /** + * 任务队列同时进行数量 + * @param queueSameTimeNumber 同一时间线程队列数量 + * @return {@link FileBreadthFirstSearchUtils} + */ + public synchronized FileBreadthFirstSearchUtils setQueueSameTimeNumber(final int queueSameTimeNumber) { + if (mRunning) { + return this; + } + this.mQueueSameTimeNumber = queueSameTimeNumber; + return this; + } + + /** + * 是否搜索中 + * @return {@code true} 搜索 / 运行中, {@code false} 非搜索 / 运行中 + */ + public boolean isRunning() { + return mRunning; + } + + /** + * 停止搜索 + */ + public void stop() { + mStop = true; + } + + /** + * 是否停止搜索 + * @return {@code true} 已停止搜索, {@code false} 搜索中 + */ + public boolean isStop() { + return mStop; + } + + /** + * 获取开始搜索时间 ( 毫秒 ) + * @return 开始搜索时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStartTime; + } + + /** + * 获取结束搜索时间 ( 毫秒 ) + * @return 结束搜索时间 ( 毫秒 ) + */ + public long getEndTime() { + return mEndTime; + } + + /** + * 获取延迟校验时间 ( 毫秒 ) + * @return 延迟线程校验时间 ( 毫秒 ) + */ + public long getDelayTime() { + return mDelayTime; + } + + /** + * 设置延迟校验时间 ( 毫秒 ) + * @param delayTimeMillis 延迟校验时间 ( 毫秒 ) + * @return {@link FileBreadthFirstSearchUtils} + */ + public FileBreadthFirstSearchUtils setDelayTime(final long delayTimeMillis) { + this.mDelayTime = delayTimeMillis; + return this; + } + + // = + + // 根目录对象 + private FileItem mRootFileItem; + // 判断是否运行中 + private boolean mRunning = false; + // 是否停止搜索 + private boolean mStop = false; + // 开始搜索时间 + private long mStartTime = 0L; + // 结束搜索时间 + private long mEndTime = 0L; + // 延迟时间 + private long mDelayTime = 50L; + // 任务队列同时进行数量 + private int mQueueSameTimeNumber = 5; + // 线程池 + private final ExecutorService mExecutor = Executors.newCachedThreadPool(); + // 任务队列 + private final LinkedBlockingQueue mTaskQueue = new LinkedBlockingQueue<>(); + + /** + * 搜索目录 + * @param path 根目录路径 + */ + public synchronized void query(final String path) { + if (mRunning) { + return; + } else if (path == null || path.trim().length() == 0) { + // 触发结束回调 + mInnerHandler.onEndListener(null, -1, -1); + return; + } + // 表示运行中 + mRunning = true; + mStop = false; + // 设置开始搜索时间 + mStartTime = System.currentTimeMillis(); + try { + // 获取根目录 File + File file = new File(path); + // 初始化根目录 + mRootFileItem = new FileItem(file); + // 判断是否文件 + if (file.isFile()) { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + return; + } + // 获取文件夹全部子文件 + String[] fileArrays = file.list(); + // 获取文件总数 + if (fileArrays != null && fileArrays.length != 0) { + new Thread(() -> { + // 查询文件 + queryFile(mRootFileItem.file, mRootFileItem); + // 循环队列 + whileQueue(); + }).start(); + } else { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "query"); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } + } + + /** + * 搜索文件 + * @param file 文件 + * @param fileItem 所在文件夹信息对象 ( 上一级目录 ) + */ + private void queryFile( + final File file, + final FileItem fileItem + ) { + try { + if (mStop) { + return; + } + if (file != null && file.exists()) { + // 判断是否处理 + if (mInnerHandler.isHandlerFile(file)) { + // 如果属于文件夹 + if (file.isDirectory()) { + // 获取文件夹全部子文件 + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File queryFile : files) { + // 属于文件夹 + if (queryFile.isDirectory()) { + if (mStop) { + return; + } + FileItem subFileItem = fileItem.put(queryFile); + // 添加任务 + mTaskQueue.offer(new FileQueue(queryFile, subFileItem)); + } else { // 属于文件 + if (!mStop && mInnerHandler.isAddToList(queryFile)) { + // 属于文件则直接保存 + fileItem.put(queryFile); + } + } + } + } else { // 属于文件 + if (!mStop && mInnerHandler.isAddToList(file)) { + // 属于文件则直接保存 + fileItem.put(file); + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "queryFile"); + } + } + + // ============ + // = 线程池处理 = + // ============ + + /** + * 循环队列 + */ + private void whileQueue() { + // 判断是否为 null + boolean isEmpty = mTaskQueue.isEmpty(); + // 循环则不处理 + while (!isEmpty) { + if (mStop) break; + // 获取线程活动数量 + int threadCount = ((ThreadPoolExecutor) mExecutor).getActiveCount(); + // 判断是否超过 + if (threadCount > mQueueSameTimeNumber) { + continue; + } + // 获取文件对象 + final FileQueue fileQueue = mTaskQueue.poll(); + // 判断是否为 null + if (fileQueue != null) { + mExecutor.execute(() -> queryFile(fileQueue.file, fileQueue.fileItem)); + } + + // 判断是否存在队列数据 + isEmpty = (mTaskQueue.isEmpty() && threadCount == 0); + if (isEmpty) { // 如果不存在, 防止搜索过快, 延迟再次判断 + if (mStop) break; + try { + Thread.sleep(mDelayTime); + } catch (Exception ignored) { + } + isEmpty = mTaskQueue.isEmpty(); + } + } + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java b/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java new file mode 100644 index 0000000000..5db82f5f4d --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java @@ -0,0 +1,295 @@ +package dev.utils.common.assist.search; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 文件深度优先搜索算法 ( 递归搜索某个目录下的全部文件 ) + * @author Ttt + */ +public final class FileDepthFirstSearchUtils { + + // 日志 TAG + private static final String TAG = FileDepthFirstSearchUtils.class.getSimpleName(); + + /** + * 构造函数 + */ + public FileDepthFirstSearchUtils() { + } + + /** + * 构造函数 + * @param searchHandler 搜索处理接口 + */ + public FileDepthFirstSearchUtils(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + } + + /** + * detail: 文件信息 Item + * @author Ttt + */ + public static final class FileItem { + + public FileItem(final File file) { + this.file = file; + } + + // 文件 + public File file; + + // 子文件夹目录 + public List listChilds = null; + } + + /** + * detail: 搜索处理接口 + * @author Ttt + */ + public interface SearchHandler { + + /** + * 判断是否处理该文件 + * @param file 文件 + * @return {@code true} 处理该文件, {@code false} 跳过该文件不处理 + */ + boolean isHandlerFile(File file); + + /** + * 是否添加到集合 + * @param file 文件 + * @return {@code true} 添加, {@code false} 不添加 + */ + boolean isAddToList(File file); + + /** + * 搜索结束监听 + * @param lists 根目录的子文件目录集合 + * @param startTime 开始扫描时间 + * @param endTime 扫描结束时间 + */ + void onEndListener( + List lists, + long startTime, + long endTime + ); + } + + // 搜索处理接口 + private SearchHandler mSearchHandler; + + // 内部实现接口 + private final SearchHandler mInnerHandler = new SearchHandler() { + @Override + public boolean isHandlerFile(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isHandlerFile(file); + } + return true; + } + + @Override + public boolean isAddToList(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isAddToList(file); + } + return true; + } + + @Override + public void onEndListener( + List lists, + long startTime, + long endTime + ) { + // 表示非搜索中 + mRunning = false; + // 触发回调 + if (mSearchHandler != null) { + mSearchHandler.onEndListener(lists, startTime, endTime); + } + } + }; + + /** + * 设置搜索处理接口 + * @param searchHandler 搜索处理接口 + * @return {@link FileDepthFirstSearchUtils} + */ + public FileDepthFirstSearchUtils setSearchHandler(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + return this; + } + + /** + * 是否搜索中 + * @return {@code true} 搜索 / 运行中, {@code false} 非搜索 / 运行中 + */ + public boolean isRunning() { + return mRunning; + } + + /** + * 停止搜索 + */ + public void stop() { + mStop = true; + } + + /** + * 是否停止搜索 + * @return {@code true} 已停止搜索, {@code false} 搜索中 + */ + public boolean isStop() { + return mStop; + } + + /** + * 获取开始搜索时间 ( 毫秒 ) + * @return 开始搜索时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStartTime; + } + + /** + * 获取结束搜索时间 ( 毫秒 ) + * @return 结束搜索时间 ( 毫秒 ) + */ + public long getEndTime() { + return mEndTime; + } + + // = + + // 判断是否运行中 + private boolean mRunning = false; + // 是否停止搜索 + private boolean mStop = false; + // 开始搜索时间 + private long mStartTime = 0L; + // 结束搜索时间 + private long mEndTime = 0L; + + /** + * 搜索目录 + * @param path 根目录路径 + * @param isRelation 是否关联到 Child List + */ + public synchronized void query( + final String path, + final boolean isRelation + ) { + if (mRunning) { + return; + } else if (path == null || path.trim().length() == 0) { + // 触发结束回调 + mInnerHandler.onEndListener(null, -1, -1); + return; + } + // 表示运行中 + mRunning = true; + mStop = false; + // 设置开始搜索时间 + mStartTime = System.currentTimeMillis(); + try { + // 获取根目录 File + final File file = new File(path); + // 判断是否文件 + if (file.isFile()) { + List lists = new ArrayList<>(); + lists.add(new FileItem(file)); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(lists, mStartTime, mEndTime); + return; + } + // 获取文件夹全部子文件 + String[] fileArrays = file.list(); + // 获取文件总数 + if (fileArrays != null && fileArrays.length != 0) { + new Thread(() -> { + List lists = new ArrayList<>(); + // 查询文件 + queryFile(file, lists, isRelation); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(lists, mStartTime, mEndTime); + }).start(); + } else { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(null, mStartTime, mEndTime); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "query"); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(null, mStartTime, mEndTime); + } + } + + /** + * 搜索文件 + * @param file 文件 + * @param lists 保存数据源 + * @param isRelation 是否关联到 Child List + */ + private void queryFile( + final File file, + final List lists, + final boolean isRelation + ) { + try { + if (mStop) { + return; + } + if (file != null && file.exists()) { + // 判断是否处理 + if (mInnerHandler.isHandlerFile(file)) { + // 如果属于文件夹 + if (file.isDirectory()) { + // 获取文件夹全部子文件 + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File queryFile : files) { + if (isRelation) { + if (queryFile.isDirectory()) { + List childs = new ArrayList<>(); + // 查找文件 + queryFile(queryFile, childs, isRelation); + // 保存数据 + FileItem fileItem = new FileItem(queryFile); + fileItem.listChilds = childs; + lists.add(fileItem); + } else { + // 属于文件 + if (mInnerHandler.isAddToList(queryFile)) { + // 属于文件则直接保存 + lists.add(new FileItem(queryFile)); + } + } + } else { + // 查找文件 + queryFile(queryFile, lists, isRelation); + } + } + } else { // 属于文件 + if (mInnerHandler.isAddToList(file)) { + // 属于文件则直接保存 + lists.add(new FileItem(file)); + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "queryFile"); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java b/lib/DevApp/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java new file mode 100644 index 0000000000..2ebad80411 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java @@ -0,0 +1,123 @@ +package dev.utils.common.assist.url; + +import java.util.LinkedHashMap; +import java.util.Map; + +import dev.utils.common.HttpParamsUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Dev 库 Java 通用 Url 解析器 + * @author Ttt + *
+ *     不依赖 android api
+ * 
+ */ +public class DevJavaUrlParser + implements UrlExtras.Parser { + + // 完整 Url + private String mUrl; + // Url 前缀 ( 去除参数部分 ) + private String mUrlPrefix; + // Url 参数部分字符串 + private String mUrlParams; + // Url Params Map + private Map mUrlParamsMap; + // Url Params Map ( 参数值进行 UrlDecode ) + private Map mUrlParamsDecodeMap; + // 是否解析、转换 Param Map + private boolean mConvertMap = true; + + // ==================== + // = UrlExtras.Parser = + // ==================== + + @Override + public UrlExtras.Parser reset(final String url) { + return new DevJavaUrlParser().setUrl(url); + } + + @Override + public UrlExtras.Parser setUrl(final String url) { + initialize(url); + return this; + } + + @Override + public String getUrl() { + return this.mUrl; + } + + @Override + public String getUrlByPrefix() { + return this.mUrlPrefix; + } + + @Override + public String getUrlByParams() { + return this.mUrlParams; + } + + @Override + public Map getUrlParams() { + return this.mUrlParamsMap; + } + + @Override + public Map getUrlParamsDecode() { + return this.mUrlParamsDecodeMap; + } + + @Override + public boolean isConvertMap() { + return this.mConvertMap; + } + + @Override + public UrlExtras.Parser setConvertMap(final boolean convertMap) { + this.mConvertMap = convertMap; + return this; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化方法 + *
+     *     会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义
+     * 
+ * @param url 待处理完整 Url + */ + private void initialize(final String url) { + this.mUrl = StringUtils.clearSpaceTabLine(url); + this.mUrlPrefix = null; + this.mUrlParams = null; + this.mUrlParamsMap = null; + this.mUrlParamsDecodeMap = null; + + if (StringUtils.isNotEmpty(mUrl)) { + String[] array = HttpParamsUtils.getUrlParamsArray(mUrl); + this.mUrlPrefix = array[0]; + this.mUrlParams = array[1]; + + if (mConvertMap && StringUtils.isNotEmpty(mUrlParams)) { + this.mUrlParamsMap = HttpParamsUtils.splitParams( + mUrlParams, false + ); + this.mUrlParamsDecodeMap = new LinkedHashMap<>(); + for (Map.Entry entry : mUrlParamsMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + String decode = StringUtils.urlDecodeWhile(value, 10); + this.mUrlParamsDecodeMap.put( + key, StringUtils.checkValue(value, decode) + ); + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/assist/url/UrlExtras.java b/lib/DevApp/src/main/java/dev/utils/common/assist/url/UrlExtras.java new file mode 100644 index 0000000000..3c715bb53b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/assist/url/UrlExtras.java @@ -0,0 +1,168 @@ +package dev.utils.common.assist.url; + +import java.util.Map; + +/** + * detail: Url 携带信息解析 + * @author Ttt + */ +public class UrlExtras { + + // 完整 Url + private String mUrl; + // Url 解析器 + private Parser mParser; + + // ========== + // = 构造函数 = + // ========== + + public UrlExtras(final String url) { + this(url, new DevJavaUrlParser()); + } + + public UrlExtras( + final String url, + final Parser parser + ) { + this.mUrl = url; + setParser(parser); + } + + // ======== + // = 解析器 = + // ======== + + /** + * detail: Url 解析器 + * @author Ttt + */ + public interface Parser { + + /** + * 重置并返回一个新的解析器 + * @param url 完整 Url + * @return Parser + */ + Parser reset(String url); + + /** + * 设置完整 Url + * @param url Url + * @return Parser + */ + Parser setUrl(String url); + + /** + * 获取完整 Url + * @return Url + */ + String getUrl(); + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @return Url 前缀 + */ + String getUrlByPrefix(); + + /** + * 获取 Url 参数部分字符串 + * @return Url 参数部分字符串 + */ + String getUrlByParams(); + + /** + * 获取 Url Params Map + * @return Url Params Map + */ + Map getUrlParams(); + + /** + * 获取 Url Params Map ( 参数值进行 UrlDecode ) + * @return Url Params Map + */ + Map getUrlParamsDecode(); + + // = + + /** + * 是否解析、转换 Param Map + * @return {@code true} yes, {@code false} no + */ + boolean isConvertMap(); + + /** + * 设置是否解析、转换 Param Map + * @param convertMap {@code true} yes, {@code false} no + * @return Parser + */ + Parser setConvertMap(boolean convertMap); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取完整 Url + * @return Url + */ + public String getUrl() { + return mUrl; + } + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @return Url 前缀 + */ + public String getUrlByPrefix() { + return (mParser != null) ? mParser.getUrlByPrefix() : null; + } + + /** + * 获取 Url 参数部分字符串 + * @return Url 参数部分字符串 + */ + public String getUrlByParams() { + return (mParser != null) ? mParser.getUrlByParams() : null; + } + + /** + * 获取 Url Params Map + * @return Url Params Map + */ + public Map getUrlParams() { + return (mParser != null) ? mParser.getUrlParams() : null; + } + + /** + * 获取 Url Params Map ( 参数值进行 UrlDecode ) + * @return Url Params Map + */ + public Map getUrlParamsDecode() { + return (mParser != null) ? mParser.getUrlParamsDecode() : null; + } + + // = + + /** + * 获取 Url 解析器 + * @return Parser + */ + public Parser getParser() { + return mParser; + } + + /** + * 设置 Url 解析器 + * @param parser Parser + * @return UrlExtras + */ + public UrlExtras setParser(final Parser parser) { + if (parser != null) { + parser.setUrl(mUrl); + } + this.mParser = parser; + return this; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64.java new file mode 100644 index 0000000000..2ad842f93b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64.java @@ -0,0 +1,781 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.utils.common.cipher; + +import java.io.UnsupportedEncodingException; + +/** + * detail: Base64 工具类 + * @author Android + */ +public final class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to Base64OutputStream to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process( + byte[] input, + int offset, + int len, + boolean finish + ); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + String str, + int flags + ) { + return decode(str.getBytes(), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + byte[] input, + int flags + ) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + byte[] input, + int offset, + int len, + int flags + ) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder + extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int[] DECODE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int[] DECODE_WEBSAFE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Non-data values in the DECODE arrays. + */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder( + int flags, + byte[] output + ) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3 / 4 + 10; + } + + /** + * Decode another block of input data. + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process( + byte[] input, + int offset, + int len, + boolean finish + ) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p + 4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p + 1] & 0xff] << 12) | + (alphabet[input[p + 2] & 0xff] << 6) | + (alphabet[input[p + 3] & 0xff]))) >= 0) { + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op + 1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString( + byte[] input, + int flags + ) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString( + byte[] input, + int offset, + int len, + int flags + ) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode( + byte[] input, + int flags + ) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode( + byte[] input, + int offset, + int len, + int flags + ) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: + break; + case 1: + output_len += 2; + break; + case 2: + output_len += 3; + break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder + extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE_WEBSAFE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder( + int flags, + byte[] output + ) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8 / 5 + 10; + } + + public boolean process( + byte[] input, + int offset, + int len, + boolean finish + ) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + + case 1: + if (p + 2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + ; + break; + + case 2: + if (p + 1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p + 3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p + 1] & 0xff) << 8) | + (input[p + 2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op + 1] = alphabet[(v >> 12) & 0x3f]; + output[op + 2] = alphabet[(v >> 6) & 0x3f]; + output[op + 3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p - tailLen == len - 1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p - tailLen == len - 2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len - 1) { + tail[tailLen++] = input[p]; + } else if (p == len - 2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p + 1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { + } // don't instantiate +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64Cipher.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64Cipher.java new file mode 100644 index 0000000000..5021a0966f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/Base64Cipher.java @@ -0,0 +1,79 @@ +package dev.utils.common.cipher; + +/** + * detail: Base64 编解码 ( 并进行 ) 加解密 + * @author Ttt + */ +public class Base64Cipher + implements Cipher { + + // 中间加密层 + private Cipher mCipher; + // Base64 编解码 flags + private int mFlags = Base64.DEFAULT; + + /** + * 构造函数 + * @param flags Base64 编解码 flags + */ + public Base64Cipher(final int flags) { + this.mFlags = flags; + } + + /** + * 构造函数 + * @param cipher 加解密中间层 + */ + public Base64Cipher(final Cipher cipher) { + this.mCipher = cipher; + } + + /** + * 构造函数 + * @param cipher 加解密中间层 + * @param flags Base64 编解码 flags + */ + public Base64Cipher( + final Cipher cipher, + final int flags + ) { + this.mCipher = cipher; + this.mFlags = flags; + } + + // = + + /** + * 解码 + * @param data 待解码数据 + * @return 解码后的 byte[] + */ + @Override + public byte[] decrypt(byte[] data) { + if (data == null) return null; + // 先解码 + data = Base64.decode(data, mFlags); + // 再解密 + if (mCipher != null) { + data = mCipher.decrypt(data); + } + return data; + } + + /** + * 编码 + * @param data 待编码数据 + * @return 编码后的 byte[] + */ + @Override + public byte[] encrypt(byte[] data) { + if (data == null) return null; + // 先加密 + if (mCipher != null) { + data = mCipher.encrypt(data); + } + if (data == null) return null; + // 再编码 + return Base64.encode(data, mFlags); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/Cipher.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/Cipher.java new file mode 100644 index 0000000000..c54f4bc69d --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/Cipher.java @@ -0,0 +1,10 @@ +package dev.utils.common.cipher; + +/** + * detail: 通用加解密中间层 + * @author Ttt + */ +public interface Cipher + extends Decrypt, + Encrypt { +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/CipherUtils.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/CipherUtils.java new file mode 100644 index 0000000000..056d412067 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/CipherUtils.java @@ -0,0 +1,65 @@ +package dev.utils.common.cipher; + +import dev.utils.common.ConvertUtils; + +/** + * detail: 加密工具类 + * @author Ttt + */ +public final class CipherUtils { + + private CipherUtils() { + } + + /** + * 加密方法 + * @param object 待加密对象 + * @return 加密后的十六进制字符串 + */ + public static String encrypt(final Object object) { + return encrypt(object, null); + } + + /** + * 加密方法 + * @param object 待加密对象 + * @param cipher 加解密中间层 + * @return 加密后的十六进制字符串 + */ + public static String encrypt( + final Object object, + final Cipher cipher + ) { + if (object == null) return null; + byte[] bytes = ConvertUtils.objectToBytes(object); + if (cipher != null) bytes = cipher.encrypt(bytes); + return ConvertUtils.toHexString(bytes); + } + + // = + + /** + * 解密方法 + * @param hex 十六进制字符串 + * @return 解密后的对象 + */ + public static Object decrypt(final String hex) { + return decrypt(hex, null); + } + + /** + * 解密方法 + * @param hex 十六进制字符串 + * @param cipher 加解密中间层 + * @return 解密后的对象 + */ + public static Object decrypt( + final String hex, + final Cipher cipher + ) { + if (hex == null) return null; + byte[] bytes = ConvertUtils.decodeHex(hex.toCharArray()); + if (cipher != null) bytes = cipher.decrypt(bytes); + return ConvertUtils.bytesToObject(bytes); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/Decrypt.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/Decrypt.java new file mode 100644 index 0000000000..ab696494a6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/Decrypt.java @@ -0,0 +1,15 @@ +package dev.utils.common.cipher; + +/** + * detail: 解密 ( 解码 ) 接口 + * @author Ttt + */ +public interface Decrypt { + + /** + * 解密 ( 解码 ) 方法 + * @param data 待解码数据 + * @return 解码后的 byte[] + */ + byte[] decrypt(byte[] data); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/cipher/Encrypt.java b/lib/DevApp/src/main/java/dev/utils/common/cipher/Encrypt.java new file mode 100644 index 0000000000..d802d33e72 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/cipher/Encrypt.java @@ -0,0 +1,15 @@ +package dev.utils.common.cipher; + +/** + * detail: 加密 ( 编码 ) 接口 + * @author Ttt + */ +public interface Encrypt { + + /** + * 加密 ( 编码 ) 方法 + * @param data 待编码数据 + * @return 编码后的 byte[] + */ + byte[] encrypt(byte[] data); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/ComparatorUtils.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/ComparatorUtils.java new file mode 100644 index 0000000000..a4347090c9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/ComparatorUtils.java @@ -0,0 +1,479 @@ +package dev.utils.common.comparator; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import dev.utils.common.CollectionUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.comparator.sort.DateSort; +import dev.utils.common.comparator.sort.DateSortAsc; +import dev.utils.common.comparator.sort.DateSortDesc; +import dev.utils.common.comparator.sort.DoubleSort; +import dev.utils.common.comparator.sort.DoubleSortAsc; +import dev.utils.common.comparator.sort.DoubleSortDesc; +import dev.utils.common.comparator.sort.FileLastModifiedSortAsc; +import dev.utils.common.comparator.sort.FileLastModifiedSortDesc; +import dev.utils.common.comparator.sort.FileLengthSortAsc; +import dev.utils.common.comparator.sort.FileLengthSortDesc; +import dev.utils.common.comparator.sort.FileNameSortAsc; +import dev.utils.common.comparator.sort.FileNameSortDesc; +import dev.utils.common.comparator.sort.FileSortAsc; +import dev.utils.common.comparator.sort.FileSortDesc; +import dev.utils.common.comparator.sort.FloatSort; +import dev.utils.common.comparator.sort.FloatSortAsc; +import dev.utils.common.comparator.sort.FloatSortDesc; +import dev.utils.common.comparator.sort.IntSort; +import dev.utils.common.comparator.sort.IntSortAsc; +import dev.utils.common.comparator.sort.IntSortDesc; +import dev.utils.common.comparator.sort.LongSort; +import dev.utils.common.comparator.sort.LongSortAsc; +import dev.utils.common.comparator.sort.LongSortDesc; +import dev.utils.common.comparator.sort.StringSort; +import dev.utils.common.comparator.sort.StringSortAsc; +import dev.utils.common.comparator.sort.StringSortDesc; +import dev.utils.common.comparator.sort.StringSortWindowsSimple; +import dev.utils.common.comparator.sort.StringSortWindowsSimple2; +import dev.utils.common.comparator.sort.WindowsExplorerFileSimpleComparator; +import dev.utils.common.comparator.sort.WindowsExplorerFileSimpleComparator2; +import dev.utils.common.comparator.sort.WindowsExplorerStringSimpleComparator; +import dev.utils.common.comparator.sort.WindowsExplorerStringSimpleComparator2; + +/** + * detail: 排序比较器工具类 + * @author Ttt + *

+ *     使用以下方法要求 List 中不能存在 null 数据
+ *     {@link #sort(List, Comparator)}
+ *     {@link #sortAsc(List)}
+ *     {@link #sortDesc(List)}
+ *     视情况可用以下方法清空 null 数据
+ *     {@link CollectionUtils#clearNull(Collection)}
+ *     

+ * File 排序可直接使用以下方法获取 List + * {@link FileUtils#listOrEmpty(File)} + * {@link FileUtils#listFilesOrEmpty(File)} + *
+ */ +public final class ComparatorUtils { + + private ComparatorUtils() { + } + + /** + * List 反转处理 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean reverse(final List list) { + if (list != null) { + Collections.reverse(list); + return true; + } + return false; + } + + /** + * List 排序处理 + * @param list 集合 + * @param comparator 排序比较器 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sort( + final List list, + final Comparator comparator + ) { + if (list != null) { + Collections.sort(list, comparator); + return true; + } + return false; + } + + /** + * List 升序处理 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static > boolean sortAsc(final List list) { + if (list != null) { + Collections.sort(list); + return true; + } + return false; + } + + /** + * List 降序处理 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDesc(final List list) { + return sort(list, Collections.reverseOrder()); + } + + // ======== + // = File = + // ======== + + /** + * 文件修改时间升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLastModifiedAsc(final List list) { + return sort(list, new FileLastModifiedSortAsc()); + } + + /** + * 文件修改时间降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLastModifiedDesc(final List list) { + return sort(list, new FileLastModifiedSortDesc()); + } + + /** + * 文件大小升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLengthAsc(final List list) { + return sort(list, new FileLengthSortAsc()); + } + + /** + * 文件大小降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLengthDesc(final List list) { + return sort(list, new FileLengthSortDesc()); + } + + /** + * 文件名升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileNameAsc(final List list) { + return sort(list, new FileNameSortAsc()); + } + + /** + * 文件名降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileNameDesc(final List list) { + return sort(list, new FileNameSortDesc()); + } + + /** + * 文件升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileAsc(final List list) { + return sort(list, new FileSortAsc()); + } + + /** + * 文件降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileDesc(final List list) { + return sort(list, new FileSortDesc()); + } + + // ======== + // = Date = + // ======== + + /** + * Date 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDateAsc(final List list) { + return sort(list, new DateSortAsc<>()); + } + + /** + * Date 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDateDesc(final List list) { + return sort(list, new DateSortDesc<>()); + } + + // ========= + // = Double = + // ========= + + /** + * Double 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDoubleAsc(final List list) { + return sort(list, new DoubleSortAsc<>()); + } + + /** + * Double 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDoubleDesc(final List list) { + return sort(list, new DoubleSortDesc<>()); + } + + // ========= + // = Float = + // ========= + + /** + * Float 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFloatAsc(final List list) { + return sort(list, new FloatSortAsc<>()); + } + + /** + * Float 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFloatDesc(final List list) { + return sort(list, new FloatSortDesc<>()); + } + + // ======= + // = Int = + // ======= + + /** + * Int 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortIntAsc(final List list) { + return sort(list, new IntSortAsc<>()); + } + + /** + * Int 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortIntDesc(final List list) { + return sort(list, new IntSortDesc<>()); + } + + // ======== + // = Long = + // ======== + + /** + * Long 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortLongAsc(final List list) { + return sort(list, new LongSortAsc<>()); + } + + /** + * Long 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortLongDesc(final List list) { + return sort(list, new LongSortDesc<>()); + } + + // ========= + // = String = + // ========= + + /** + * String 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringAsc(final List list) { + return sort(list, new StringSortAsc<>()); + } + + /** + * String 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringDesc(final List list) { + return sort(list, new StringSortDesc<>()); + } + + // = + + /** + * String Windows 排序比较器简单实现升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimpleAsc(final List list) { + return sort(list, new StringSortWindowsSimple<>()); + } + + /** + * String Windows 排序比较器简单实现降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimpleDesc(final List list) { + boolean result = sortStringWindowsSimpleAsc(list); + if (result) reverse(list); + return result; + } + + /** + * String Windows 排序比较器简单实现升序排序 ( 实现方式二 ) + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimple2Asc(final List list) { + return sort(list, new StringSortWindowsSimple2<>()); + } + + /** + * String Windows 排序比较器简单实现降序排序 ( 实现方式二 ) + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimple2Desc(final List list) { + boolean result = sortStringWindowsSimple2Asc(list); + if (result) reverse(list); + return result; + } + + // ==================== + // = Windows Explorer = + // ==================== + + /** + * Windows 目录资源文件升序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparatorAsc(final List list) { + return sort(list, new WindowsExplorerFileSimpleComparator()); + } + + /** + * Windows 目录资源文件降序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparatorDesc(final List list) { + boolean result = sortWindowsExplorerFileSimpleComparatorAsc(list); + if (result) reverse(list); + return result; + } + + /** + * Windows 目录资源文件升序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparator2Asc(final List list) { + return sort(list, new WindowsExplorerFileSimpleComparator2()); + } + + /** + * Windows 目录资源文件降序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparator2Desc(final List list) { + boolean result = sortWindowsExplorerFileSimpleComparator2Asc(list); + if (result) reverse(list); + return result; + } + + // = + + /** + * Windows 目录资源文件名升序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparatorAsc(final List list) { + return sort(list, new WindowsExplorerStringSimpleComparator()); + } + + /** + * Windows 目录资源文件名降序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparatorDesc(final List list) { + boolean result = sortWindowsExplorerStringSimpleComparatorAsc(list); + if (result) reverse(list); + return result; + } + + /** + * Windows 目录资源文件名升序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparator2Asc(final List list) { + return sort(list, new WindowsExplorerStringSimpleComparator2()); + } + + /** + * Windows 目录资源文件名降序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparator2Desc(final List list) { + boolean result = sortWindowsExplorerStringSimpleComparator2Asc(list); + if (result) reverse(list); + return result; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSort.java new file mode 100644 index 0000000000..59c01a2658 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSort.java @@ -0,0 +1,12 @@ +package dev.utils.common.comparator.sort; + +import java.util.Date; + +/** + * detail: Date 排序值 + * @author Ttt + */ +public interface DateSort { + + Date getDateSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java new file mode 100644 index 0000000000..78a5c09fac --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java @@ -0,0 +1,24 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.Date; + +/** + * detail: Date 升序排序 + * @author Ttt + */ +public class DateSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + Date date1 = (t != null) ? t.getDateSortValue() : null; + Date date2 = (t1 != null) ? t1.getDateSortValue() : null; + long value1 = (date1 != null) ? date1.getTime() : 0L; + long value2 = (date2 != null) ? date2.getTime() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java new file mode 100644 index 0000000000..7249586f47 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java @@ -0,0 +1,24 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.Date; + +/** + * detail: Date 降序排序 + * @author Ttt + */ +public class DateSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + Date date1 = (t != null) ? t.getDateSortValue() : null; + Date date2 = (t1 != null) ? t1.getDateSortValue() : null; + long value1 = (date1 != null) ? date1.getTime() : 0L; + long value2 = (date2 != null) ? date2.getTime() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java new file mode 100644 index 0000000000..642ae94f0d --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Double 排序值 + * @author Ttt + */ +public interface DoubleSort { + + double getDoubleSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java new file mode 100644 index 0000000000..c5a3355eba --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Double 升序排序 + * @author Ttt + */ +public class DoubleSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + double value1 = (t != null) ? t.getDoubleSortValue() : 0D; + double value2 = (t1 != null) ? t1.getDoubleSortValue() : 0D; + return Double.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java new file mode 100644 index 0000000000..ec2d2a3e9a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Double 降序排序 + * @author Ttt + */ +public class DoubleSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + double value1 = (t != null) ? t.getDoubleSortValue() : 0D; + double value2 = (t1 != null) ? t1.getDoubleSortValue() : 0D; + return Double.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java new file mode 100644 index 0000000000..d149b80729 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java @@ -0,0 +1,22 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件修改时间升序排序 + * @author Ttt + */ +public class FileLastModifiedSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.lastModified() : 0L; + long value2 = (f1 != null) ? f1.lastModified() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java new file mode 100644 index 0000000000..be86ac298e --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java @@ -0,0 +1,22 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件修改时间降序排序 + * @author Ttt + */ +public class FileLastModifiedSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.lastModified() : 0L; + long value2 = (f1 != null) ? f1.lastModified() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java new file mode 100644 index 0000000000..ac69d9fed9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件大小升序排序 + * @author Ttt + *
+ *     不考虑文件夹内部文件大小, 文件夹大小根据 API length() 进行比较
+ * 
+ */ +public class FileLengthSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.length() : 0L; + long value2 = (f1 != null) ? f1.length() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java new file mode 100644 index 0000000000..a6bd181246 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件大小降序排序 + * @author Ttt + *
+ *     不考虑文件夹内部文件大小, 文件夹大小根据 API length() 进行比较
+ * 
+ */ +public class FileLengthSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.length() : 0L; + long value2 = (f1 != null) ? f1.length() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java new file mode 100644 index 0000000000..c1ca6a6052 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java @@ -0,0 +1,29 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件名升序排序 + * @author Ttt + */ +public class FileNameSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + if (f.isDirectory() && f1.isFile()) { + return -1; + } + if (f.isFile() && f1.isDirectory()) { + return 1; + } + return f.getName().compareTo(f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java new file mode 100644 index 0000000000..7dd63a5338 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java @@ -0,0 +1,29 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件名降序排序 + * @author Ttt + */ +public class FileNameSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return 0; + } + if (f.isDirectory() && f1.isFile()) { + return 1; + } + if (f.isFile() && f1.isDirectory()) { + return -1; + } + return f1.getName().compareTo(f.getName()); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java new file mode 100644 index 0000000000..d4e8fd6ca0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件升序排序 + * @author Ttt + */ +public class FileSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return f.compareTo(f1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java new file mode 100644 index 0000000000..ebe96504b3 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件降序排序 + * @author Ttt + */ +public class FileSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return 0; + } + return f1.compareTo(f); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSort.java new file mode 100644 index 0000000000..ee56a1d948 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Float 排序值 + * @author Ttt + */ +public interface FloatSort { + + float getFloatSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java new file mode 100644 index 0000000000..44006fd6e0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Float 升序排序 + * @author Ttt + */ +public class FloatSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + float value1 = (t != null) ? t.getFloatSortValue() : 0F; + float value2 = (t1 != null) ? t1.getFloatSortValue() : 0F; + return Float.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java new file mode 100644 index 0000000000..ebd933634c --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Float 降序排序 + * @author Ttt + */ +public class FloatSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + float value1 = (t != null) ? t.getFloatSortValue() : 0F; + float value2 = (t1 != null) ? t1.getFloatSortValue() : 0F; + return Float.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSort.java new file mode 100644 index 0000000000..14bd095622 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Int 排序值 + * @author Ttt + */ +public interface IntSort { + + int getIntSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java new file mode 100644 index 0000000000..c6a8dd1fe7 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Int 升序排序 + * @author Ttt + */ +public class IntSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + int value1 = (t != null) ? t.getIntSortValue() : 0; + int value2 = (t1 != null) ? t1.getIntSortValue() : 0; + return Integer.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java new file mode 100644 index 0000000000..ae4488636d --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Int 降序排序 + * @author Ttt + */ +public class IntSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + int value1 = (t != null) ? t.getIntSortValue() : 0; + int value2 = (t1 != null) ? t1.getIntSortValue() : 0; + return Integer.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSort.java new file mode 100644 index 0000000000..9a5e00ade0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Long 排序值 + * @author Ttt + */ +public interface LongSort { + + long getLongSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java new file mode 100644 index 0000000000..a4f6982480 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Long 升序排序 + * @author Ttt + */ +public class LongSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + long value1 = (t != null) ? t.getLongSortValue() : 0L; + long value2 = (t1 != null) ? t1.getLongSortValue() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java new file mode 100644 index 0000000000..30c204f2e8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Long 降序排序 + * @author Ttt + */ +public class LongSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + long value1 = (t != null) ? t.getLongSortValue() : 0L; + long value2 = (t1 != null) ? t1.getLongSortValue() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSort.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSort.java new file mode 100644 index 0000000000..d05e7eef46 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: String 排序值 + * @author Ttt + */ +public interface StringSort { + + String getStringSortValue(); +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java new file mode 100644 index 0000000000..4150baaf91 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String 升序排序 + * @author Ttt + */ +public class StringSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return value1.compareTo(value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java new file mode 100644 index 0000000000..6ee185a4d0 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String 降序排序 + * @author Ttt + */ +public class StringSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return value2.compareTo(value1); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java new file mode 100644 index 0000000000..003812e62d --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String Windows 排序比较器简单实现 + * @author Ttt + */ +public class StringSortWindowsSimple + implements Comparator { + + private final WindowsExplorerStringSimpleComparator COMPARATOR = new WindowsExplorerStringSimpleComparator(); + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return COMPARATOR.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java new file mode 100644 index 0000000000..bc87069fc9 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String Windows 排序比较器简单实现 + * @author Ttt + */ +public class StringSortWindowsSimple2 + implements Comparator { + + private final WindowsExplorerStringSimpleComparator2 COMPARATOR = new WindowsExplorerStringSimpleComparator2(); + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return COMPARATOR.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java new file mode 100644 index 0000000000..94067eeaf2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: Windows 目录资源文件排序比较器 + * @author Ttt + */ +public class WindowsExplorerFileSimpleComparator + implements Comparator { + + private final WindowsExplorerStringSimpleComparator COMPARATOR = new WindowsExplorerStringSimpleComparator(); + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return COMPARATOR.compare(f.getName(), f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java new file mode 100644 index 0000000000..644cc9833f --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: Windows 目录资源文件排序比较器 + * @author Ttt + */ +public class WindowsExplorerFileSimpleComparator2 + implements Comparator { + + private final WindowsExplorerStringSimpleComparator2 COMPARATOR = new WindowsExplorerStringSimpleComparator2(); + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return COMPARATOR.compare(f.getName(), f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java new file mode 100644 index 0000000000..0a30585b91 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java @@ -0,0 +1,93 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Windows 目录资源文件名排序比较器 + * @author Ttt + *
+ *     非完全符合 Windows 目录页排序结果 ( 一定程度上相似 )
+ *     用于目录页对比排序, 而非全部子目录完整路径对比
+ *     

+ * 代码来源 + * @see + *
+ */ +public class WindowsExplorerStringSimpleComparator + implements Comparator { + + @Override + public int compare( + String o1, + String o2 + ) { + if (o1 == null || o2 == null) { + return -1; + } + return innerCompare(o1, o2); + } + + // ========== + // = 具体实现 = + // ========== + + private final Pattern splitPattern = Pattern.compile("^(.*?)(\\d*)(?:\\.([^.]*))?$"); + + private int innerCompare( + String str1, + String str2 + ) { + SplitFileName data1 = getSplitFileName(str1); + SplitFileName data2 = getSplitFileName(str2); + + // Compare the name part case insensitive. + int result = data1.name.compareToIgnoreCase(data2.name); + // If name is equal, then compare by number + if (result == 0) { + result = data1.number.compareTo(data2.number); + } + // If numbers are equal then compare by length text of number. This + // is valid because it differs only by heading zeros. Longer comes first. + if (result == 0) { + result = -Integer.compare(data1.numberText.length(), data2.numberText.length()); + } + // If all above is equal, compare by ext. + if (result == 0) { + result = data1.ext.compareTo(data2.ext); + } + return result; + } + + private SplitFileName getSplitFileName(String fileName) { + Matcher matcher = splitPattern.matcher(fileName); + if (matcher.matches()) { + return new SplitFileName(matcher.group(1), matcher.group(2), matcher.group(3)); + } else { + return new SplitFileName(fileName, "", ""); + } + } + + private static class SplitFileName { + + String name; + Long number; + String numberText; + String ext; + + public SplitFileName( + String name, + String numberText, + String ext + ) { + this.name = StringUtils.checkValue(name); + this.number = ConvertUtils.toLong(numberText, -1L); + this.numberText = StringUtils.checkValue(numberText); + this.ext = StringUtils.checkValue(ext); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java new file mode 100644 index 0000000000..fac24f0601 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java @@ -0,0 +1,94 @@ +package dev.utils.common.comparator.sort; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * detail: Windows 目录资源文件名排序比较器 + * @author Ttt + *
+ *     非完全符合 Windows 目录页排序结果 ( 一定程度上相似 )
+ *     用于目录页对比排序, 而非全部子目录完整路径对比
+ *     

+ * 代码来源 + * @see
+ *
+ */ +public class WindowsExplorerStringSimpleComparator2 + implements Comparator { + + @Override + public int compare( + String o1, + String o2 + ) { + if (o1 == null || o2 == null) { + return -1; + } + return innerCompare(o1, o2); + } + + // ========== + // = 具体实现 = + // ========== + + private final Pattern splitPattern = Pattern.compile("\\d+|\\.|\\s"); + + private int innerCompare( + String str1, + String str2 + ) { + Iterator i1 = splitStringPreserveDelimiter(str1).iterator(); + Iterator i2 = splitStringPreserveDelimiter(str2).iterator(); + while (true) { + // Til here all is equal. + if (!i1.hasNext() && !i2.hasNext()) { + return 0; + } + // first has no more parts -> comes first + if (!i1.hasNext() && i2.hasNext()) { + return -1; + } + // first has more parts than i2 -> comes after + if (i1.hasNext() && !i2.hasNext()) { + return 1; + } + + String data1 = i1.next(); + String data2 = i2.next(); + int result; + try { + // If both datas are numbers, then compare numbers + result = Long.compare(Long.parseLong(data1), Long.parseLong(data2)); + // If numbers are equal than longer comes first + if (result == 0) { + result = -Integer.compare(data1.length(), data2.length()); + } + } catch (NumberFormatException ex) { + // compare text case insensitive + result = data1.compareToIgnoreCase(data2); + } + + if (result != 0) { + return result; + } + } + } + + private List splitStringPreserveDelimiter(String str) { + Matcher matcher = splitPattern.matcher(str); + List list = new ArrayList<>(); + int pos = 0; + while (matcher.find()) { + list.add(str.substring(pos, matcher.start())); + list.add(matcher.group()); + pos = matcher.end(); + } + list.add(str.substring(pos)); + return list; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/AESUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/AESUtils.java new file mode 100644 index 0000000000..6c2b904cb2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/AESUtils.java @@ -0,0 +1,85 @@ +package dev.utils.common.encrypt; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: AES 对称加密工具类 + * @author Ttt + *
+ *     Advanced Encryption Standard 高级数据加密标准 ( 对称加密算法 )
+ *     AES 算法可以有效抵制针对 DES 的攻击算法
+ * 
+ */ +public final class AESUtils { + + private AESUtils() { + } + + // 日志 TAG + private static final String TAG = AESUtils.class.getSimpleName(); + + /** + * 生成密钥 + * @return 密钥 byte[] + */ + public static byte[] initKey() { + try { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); // 192 256 + SecretKey secretKey = keyGen.generateKey(); + return secretKey.getEncoded(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "initKey"); + } + return null; + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/CRCUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/CRCUtils.java new file mode 100644 index 0000000000..7b73d0e306 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/CRCUtils.java @@ -0,0 +1,84 @@ +package dev.utils.common.encrypt; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.zip.CRC32; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: CRC 工具类 + * @author Ttt + *
+ *     Cyclic Redundancy Check 循环冗余校验
+ *     CRC 是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数
+ * 
+ */ +public final class CRCUtils { + + private CRCUtils() { + } + + // 日志 TAG + private static final String TAG = CRCUtils.class.getSimpleName(); + + /** + * 获取 CRC32 值 + * @param data 字符串数据 + * @return CRC32 long 值 + */ + public static long getCRC32(final String data) { + if (data == null) return -1L; + try { + CRC32 crc32 = new CRC32(); + crc32.update(data.getBytes()); + return crc32.getValue(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getCRC32"); + } + return -1L; + } + + /** + * 获取 CRC32 值 + * @param data 字符串数据 + * @return CRC32 字符串 + */ + public static String getCRC32ToHexString(final String data) { + if (data == null) return null; + try { + CRC32 crc32 = new CRC32(); + crc32.update(data.getBytes()); + return Long.toHexString(crc32.getValue()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getCRC32ToHexString"); + } + return null; + } + + /** + * 获取文件 CRC32 值 + * @param filePath 文件路径 + * @return 文件 CRC32 值 + */ + public static String getFileCRC32(final String filePath) { + if (filePath == null) return null; + InputStream is = null; + try { + is = new FileInputStream(filePath); + byte[] buffer = new byte[1024]; + CRC32 crc32 = new CRC32(); + int numRead; + while ((numRead = is.read(buffer)) > 0) { + crc32.update(buffer, 0, numRead); + } + return Long.toHexString(crc32.getValue()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileCRC32"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/DESUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/DESUtils.java new file mode 100644 index 0000000000..d024c8451c --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/DESUtils.java @@ -0,0 +1,88 @@ +package dev.utils.common.encrypt; + +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: DES 对称加密工具类 + * @author Ttt + *
+ *     Data Encryption Standard 数据加密标准 ( 对称加密算法 )
+ * 
+ */ +public final class DESUtils { + + private DESUtils() { + } + + // 日志 TAG + private static final String TAG = DESUtils.class.getSimpleName(); + + /** + * 获取可逆算法 DES 的密钥 + * @param key 前八个字节将被用来生成密钥 + * @return 可逆算法 DES 的密钥 + */ + public static Key getDESKey(final byte[] key) { + if (key == null) return null; + try { + DESKeySpec desKey = new DESKeySpec(key); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + return keyFactory.generateSecret(desKey); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDESKey"); + } + return null; + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DES"); + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DES"); + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/EncryptUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/EncryptUtils.java new file mode 100644 index 0000000000..124f62d551 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/EncryptUtils.java @@ -0,0 +1,1188 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.security.DigestInputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; +import dev.utils.common.ArrayUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.cipher.Base64; + +/** + * detail: 加解密通用工具类 + * @author Blankj + * @author Ttt + */ +public final class EncryptUtils { + + private EncryptUtils() { + } + + // 日志 TAG + private static final String TAG = EncryptUtils.class.getSimpleName(); + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的数据 + */ + public static byte[] encryptMD2(final byte[] data) { + return hashTemplate(data, "MD2"); + } + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的十六进制字符串 + */ + public static String encryptMD2ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptMD2ToHexString(data.getBytes()); + } + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的十六进制字符串 + */ + public static String encryptMD2ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptMD2(data)); + } + + // = + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的数据 + */ + public static byte[] encryptMD5(final byte[] data) { + return hashTemplate(data, "MD5"); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptMD5ToHexString(data.getBytes()); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @param salt salt + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString( + final String data, + final String salt + ) { + if (data == null && salt == null) return null; + if (salt == null) return ConvertUtils.toHexString(encryptMD5(data.getBytes())); + if (data == null) return ConvertUtils.toHexString(encryptMD5(salt.getBytes())); + return ConvertUtils.toHexString(encryptMD5((data + salt).getBytes())); + } + + // = + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptMD5(data)); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @param salt salt + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString( + final byte[] data, + final byte[] salt + ) { + if (data == null && salt == null) return null; + if (salt == null) return ConvertUtils.toHexString(encryptMD5(data)); + if (data == null) return ConvertUtils.toHexString(encryptMD5(salt)); + // 拼接数据 + byte[] bytes = ArrayUtils.arrayCopy(data, salt); + return ConvertUtils.toHexString(encryptMD5(bytes)); + } + + // = + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] encryptMD5File(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return encryptMD5File(file); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String encryptMD5FileToHexString(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return encryptMD5FileToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String encryptMD5FileToHexString(final File file) { + return ConvertUtils.toHexString(encryptMD5File(file)); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] encryptMD5File(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, digest); + byte[] buffer = new byte[256 * 1024]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + digest = dis.getMessageDigest(); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encryptMD5File"); + return null; + } finally { + CloseUtils.closeIOQuietly(dis); + } + } + + // = + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据 + */ + public static byte[] encryptSHA1(final byte[] data) { + return hashTemplate(data, "SHA-1"); + } + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据转十六进制字符串 + */ + public static String encryptSHA1ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA1ToHexString(data.getBytes()); + } + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据转十六进制字符串 + */ + public static String encryptSHA1ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA1(data)); + } + + // = + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据 + */ + public static byte[] encryptSHA224(final byte[] data) { + return hashTemplate(data, "SHA224"); + } + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据转十六进制字符串 + */ + public static String encryptSHA224ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA224ToHexString(data.getBytes()); + } + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据转十六进制字符串 + */ + public static String encryptSHA224ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA224(data)); + } + + // = + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据 + */ + public static byte[] encryptSHA256(final byte[] data) { + return hashTemplate(data, "SHA-256"); + } + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据转十六进制字符串 + */ + public static String encryptSHA256ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA256ToHexString(data.getBytes()); + } + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据转十六进制 + */ + public static String encryptSHA256ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA256(data)); + } + + // = + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据 + */ + public static byte[] encryptSHA384(final byte[] data) { + return hashTemplate(data, "SHA-384"); + } + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据转十六进制 + */ + public static String encryptSHA384ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA384ToHexString(data.getBytes()); + } + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据转十六进制 + */ + public static String encryptSHA384ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA384(data)); + } + + // = + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据 + */ + public static byte[] encryptSHA512(final byte[] data) { + return hashTemplate(data, "SHA-512"); + } + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据转十六进制 + */ + public static String encryptSHA512ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA512ToHexString(data.getBytes()); + } + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据转十六进制 + */ + public static String encryptSHA512ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA512(data)); + } + + /** + * Hash 加密模版方法 + * @param data 待加密数据 + * @param algorithm 算法 + * @return 指定加密算法加密后的数据 + */ + public static byte[] hashTemplate( + final byte[] data, + final String algorithm + ) { + if (data == null || data.length == 0) return null; + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + digest.update(data); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hashTemplate"); + return null; + } + } + + // = + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据 + */ + public static byte[] encryptHmacMD5( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacMD5"); + } + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据转十六进制 + */ + public static String encryptHmacMD5ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacMD5ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据转十六进制 + */ + public static String encryptHmacMD5ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacMD5(data, key)); + } + + // = + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据 + */ + public static byte[] encryptHmacSHA1( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA1"); + } + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据转十六进制 + */ + public static String encryptHmacSHA1ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA1ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据转十六进制 + */ + public static String encryptHmacSHA1ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA1(data, key)); + } + + // = + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据 + */ + public static byte[] encryptHmacSHA224( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA224"); + } + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据转十六进制 + */ + public static String encryptHmacSHA224ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA224ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据转十六进制 + */ + public static String encryptHmacSHA224ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA224(data, key)); + } + + // = + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据 + */ + public static byte[] encryptHmacSHA256( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA256"); + } + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据转十六进制 + */ + public static String encryptHmacSHA256ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA256ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据转十六进制 + */ + public static String encryptHmacSHA256ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA256(data, key)); + } + + // = + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据 + */ + public static byte[] encryptHmacSHA384( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA384"); + } + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据转十六进制 + */ + public static String encryptHmacSHA384ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA384ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据转十六进制 + */ + public static String encryptHmacSHA384ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA384(data, key)); + } + + // = + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据 + */ + public static byte[] encryptHmacSHA512( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA512"); + } + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据转十六进制 + */ + public static String encryptHmacSHA512ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA512ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据转十六进制 + */ + public static String encryptHmacSHA512ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA512(data, key)); + } + + /** + * Hmac 加密模版方法 + * @param data 待加密数据 + * @param key 密钥 + * @param algorithm 算法 + * @return 指定加密算法和密钥, 加密后的数据 + */ + public static byte[] hmacTemplate( + final byte[] data, + final byte[] key, + final String algorithm + ) { + if (data == null || data.length == 0 || key == null || key.length == 0) return null; + try { + SecretKeySpec secretKey = new SecretKeySpec(key, algorithm); + Mac mac = Mac.getInstance(algorithm); + mac.init(secretKey); + return mac.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hmacTemplate"); + return null; + } + } + + // = + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据 + */ + public static byte[] encryptDES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DES", transformation, iv, true); + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据转 Base64 + */ + public static byte[] encryptDESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encryptDES(data, key, transformation, iv)); + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据转十六进制 + */ + public static String encryptDESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encryptDES(data, key, transformation, iv)); + } + + // = + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 解密后的数据 + */ + public static byte[] decryptDES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DES", transformation, iv, false); + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 DES 解密后的数据 + */ + public static byte[] decryptDESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptDES(base64Decode(data), key, transformation, iv); + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 DES 解密后的数据 + */ + public static byte[] decryptDESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptDES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + // = + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据 + */ + public static byte[] encrypt3DES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DESede", transformation, iv, true); + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据转 Base64 + */ + public static byte[] encrypt3DESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encrypt3DES(data, key, transformation, iv)); + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据转十六进制 + */ + public static String encrypt3DESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encrypt3DES(data, key, transformation, iv)); + } + + // = + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 解密后的数据 + */ + public static byte[] decrypt3DES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DESede", transformation, iv, false); + } + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 3DES 解密后的数据 + */ + public static byte[] decrypt3DESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decrypt3DES(base64Decode(data), key, transformation, iv); + } + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 3DES 解密后的数据 + */ + public static byte[] decrypt3DESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decrypt3DES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + // = + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据 + */ + public static byte[] encryptAES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "AES", transformation, iv, true); + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据转 Base64 + */ + public static byte[] encryptAESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encryptAES(data, key, transformation, iv)); + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据转十六进制 + */ + public static String encryptAESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encryptAES(data, key, transformation, iv)); + } + + // = + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 解密后的数据 + */ + public static byte[] decryptAES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "AES", transformation, iv, false); + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 AES 解密后的数据 + */ + public static byte[] decryptAESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptAES(base64Decode(data), key, transformation, iv); + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 AES 解密后的数据 + */ + public static byte[] decryptAESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptAES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + /** + * 对称加密模版方法 + * @param data 待加解密数据 + * @param key 密钥 + * @param algorithm 算法 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @param isEncrypt 是否加密处理 + * @return 指定加密算法, 加解密后的数据 + */ + public static byte[] symmetricTemplate( + final byte[] data, + final byte[] key, + final String algorithm, + final String transformation, + final byte[] iv, + final boolean isEncrypt + ) { + if (data == null || data.length == 0 || key == null || key.length == 0) return null; + try { + SecretKey secretKey; + if ("DES".equals(algorithm)) { + DESKeySpec desKey = new DESKeySpec(key); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); + secretKey = keyFactory.generateSecret(desKey); + } else { + secretKey = new SecretKeySpec(key, algorithm); + } + Cipher cipher = Cipher.getInstance(transformation); + if (iv == null || iv.length == 0) { + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey); + } else { + AlgorithmParameterSpec params = new IvParameterSpec(iv); + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, params); + } + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "symmetricTemplate"); + return null; + } + } + + // = + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据 + */ + public static byte[] encryptRSA( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return rsaTemplate(data, key, isPublicKey, transformation, true); + } + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据转 Base64 + */ + public static byte[] encryptRSAToBase64( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return base64Encode(encryptRSA(data, key, isPublicKey, transformation)); + } + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据转十六进制 + */ + public static String encryptRSAToHexString( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return ConvertUtils.toHexString(encryptRSA(data, key, isPublicKey, transformation)); + } + + // = + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 解密后的数据 + */ + public static byte[] decryptRSA( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return rsaTemplate(data, key, isPublicKey, transformation, false); + } + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return Base64 解码后, 在进行 RSA 解密后的数据 + */ + public static byte[] decryptRSAToBase64( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return decryptRSA(base64Decode(data), key, isPublicKey, transformation); + } + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return 十六进制转换后, 在进行 RSA 解密后的数据 + */ + public static byte[] decryptRSAToHexString( + final String data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return decryptRSA(ConvertUtils.decodeHex(data), key, isPublicKey, transformation); + } + + /** + * RSA 加解密模版方法 + * @param data 待加解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @param isEncrypt 是否加密处理 + * @return 指定加密算法, 加解密后的数据 + */ + public static byte[] rsaTemplate( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation, + final boolean isEncrypt + ) { + if (data == null || key == null) return null; + try { + int dataLength = data.length; + int keyLength = key.length; + if (dataLength == 0 || keyLength == 0) return null; + + Key rsaKey; + if (isPublicKey) { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec); + } else { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); + rsaKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); + } + if (rsaKey == null) return null; + Cipher cipher = Cipher.getInstance(transformation); + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey); + int maxLen = isEncrypt ? 117 : 128; + int count = dataLength / maxLen; + if (count > 0) { + byte[] ret = new byte[0]; + byte[] buffer = new byte[maxLen]; + int index = 0; + for (int i = 0; i < count; i++) { + System.arraycopy(data, index, buffer, 0, maxLen); + ret = ArrayUtils.arrayCopy(ret, cipher.doFinal(buffer)); + index += maxLen; + } + if (index != dataLength) { + int restLen = dataLength - index; + buffer = new byte[restLen]; + System.arraycopy(data, index, buffer, 0, restLen); + ret = ArrayUtils.arrayCopy(ret, cipher.doFinal(buffer)); + } + return ret; + } else { + return cipher.doFinal(data); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "rsaTemplate"); + } + return null; + } + + // ========== + // = 私有方法 = + // ========== + + /** + * Base64 编码 + * @param input 待编码数据 + * @return Base64 编码后的 byte[] + */ + private static byte[] base64Encode(final byte[] input) { + if (input == null) return null; + return Base64.encode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待解码数据 + * @return Base64 解码后的 byte[] + */ + private static byte[] base64Decode(final byte[] input) { + if (input == null) return null; + return Base64.decode(input, Base64.NO_WRAP); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/EscapeUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/EscapeUtils.java new file mode 100644 index 0000000000..27b40240b1 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/EscapeUtils.java @@ -0,0 +1,148 @@ +package dev.utils.common.encrypt; + +/** + * detail: 字符串 ( 编解码 ) 工具类 + * @author Ttt + */ +public final class EscapeUtils { + + private EscapeUtils() { + } + + /** + * 编码 + * @param data 待编码数据 + * @return 编码后的字符串 + */ + public static String escape(final String data) { + if (data == null) return null; + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = data.length(); i < len; i++) { + int ch = data.charAt(i); + if ('A' <= ch && ch <= 'Z') { + builder.append((char) ch); + } else if ('a' <= ch && ch <= 'z') { + builder.append((char) ch); + } else if ('0' <= ch && ch <= '9') { + builder.append((char) ch); + } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' + || ch == '~' || ch == '*' || ch == '\'' || ch == '(' + || ch == ')') { + builder.append((char) ch); + } else if (ch <= 0x007F) { + builder.append('%'); + builder.append(HEX[ch]); + } else { + builder.append('%'); + builder.append('u'); + builder.append(HEX[(ch >>> 8)]); + builder.append(HEX[(0x00FF & ch)]); + } + } + return builder.toString(); + } + + /** + * 解码 + *
+     *     本方法不论参数 data 是否经过 escape() 编码, 均能获取正确的 ( 解码 ) 结果
+     * 
+ * @param data 待解码数据 + * @return 解码后的字符串 + */ + public static String unescape(final String data) { + if (data == null) return null; + StringBuilder builder = new StringBuilder(); + int i = 0; + int len = data.length(); + while (i < len) { + int ch = data.charAt(i); + if ('A' <= ch && ch <= 'Z') { + builder.append((char) ch); + } else if ('a' <= ch && ch <= 'z') { + builder.append((char) ch); + } else if ('0' <= ch && ch <= '9') { + builder.append((char) ch); + } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' + || ch == '~' || ch == '*' || ch == '\'' || ch == '(' + || ch == ')') { + builder.append((char) ch); + } else if (ch == '%') { + int cint = 0; + if ('u' != data.charAt(i + 1)) { + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 1)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 2)]; + i += 2; + } else { + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 2)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 3)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 4)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 5)]; + i += 5; + } + builder.append((char) cint); + } else { + builder.append((char) ch); + } + i++; + } + return builder.toString(); + } + + // = + + // 十六进制 0-255 + private static final String[] HEX = { + "00", "01", "02", "03", "04", "05", + "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", + "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26", + "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", + "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", + "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", + "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", + "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", + "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", + "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", + "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", + "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", + "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5", + "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0", + "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", + "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6", + "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1", + "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", + "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", + "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" + }; + + private static final byte[] BYTE_VALUES = { + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F + }; +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/MD5Utils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/MD5Utils.java new file mode 100644 index 0000000000..97e7b5fa73 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/MD5Utils.java @@ -0,0 +1,154 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: MD5 加密工具类 + * @author Ttt + *
+ *     Message Digest 消息摘要算法
+ * 
+ */ +public final class MD5Utils { + + private MD5Utils() { + } + + // 日志 TAG + private static final String TAG = MD5Utils.class.getSimpleName(); + + /** + * 加密内容 ( 32 位小写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5(final String data) { + if (data == null) return null; + try { + return md5(data.getBytes()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5"); + } + return null; + } + + /** + * 加密内容 ( 32 位小写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5(final byte[] data) { + if (data == null) return null; + try { + // 获取 MD5 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance("MD5"); + // 使用指定的字节更新摘要 + digest.update(data); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5"); + } + return null; + } + + /** + * 加密内容 ( 32 位大写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5Upper(final String data) { + if (data == null) return null; + try { + return md5Upper(data.getBytes()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5Upper"); + } + return null; + } + + /** + * 加密内容 ( 32 位大写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5Upper(final byte[] data) { + if (data == null) return null; + try { + // 获取 MD5 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance("MD5"); + // 使用指定的字节更新摘要 + digest.update(data); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), false); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5Upper"); + } + return null; + } + + // = + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] getFileMD5(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileMD5(file); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileMD5ToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final File file) { + return ConvertUtils.toHexString(getFileMD5(file)); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] getFileMD5(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, digest); + byte[] buffer = new byte[256 * 1024]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + digest = dis.getMessageDigest(); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileMD5"); + return null; + } finally { + CloseUtils.closeIOQuietly(dis); + } + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/SHAUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/SHAUtils.java new file mode 100644 index 0000000000..abb7d8f086 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/SHAUtils.java @@ -0,0 +1,165 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.MessageDigest; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: SHA 加密工具类 + * @author Ttt + */ +public final class SHAUtils { + + private SHAUtils() { + } + + // 日志 TAG + private static final String TAG = SHAUtils.class.getSimpleName(); + + /** + * 加密内容 SHA1 + * @param data 待加密数据 + * @return SHA1 加密后的字符串 + */ + public static String sha1(final String data) { + return shaHex(data, "SHA-1"); + } + + /** + * 加密内容 SHA224 + * @param data 待加密数据 + * @return SHA224 加密后的字符串 + */ + public static String sha224(final String data) { + return shaHex(data, "SHA-224"); + } + + /** + * 加密内容 SHA256 + * @param data 待加密数据 + * @return SHA256 加密后的字符串 + */ + public static String sha256(final String data) { + return shaHex(data, "SHA-256"); + } + + /** + * 加密内容 SHA384 + * @param data 待加密数据 + * @return SHA384 加密后的字符串 + */ + public static String sha384(final String data) { + return shaHex(data, "SHA-384"); + } + + /** + * 加密内容 SHA512 + * @param data 待加密数据 + * @return SHA512 加密后的字符串 + */ + public static String sha512(final String data) { + return shaHex(data, "SHA-512"); + } + + // = + + /** + * 获取文件 SHA1 值 + * @param filePath 文件路径 + * @return 文件 SHA1 字符串信息 + */ + public static String getFileSHA1(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileSHA(file, "SHA-1"); + } + + /** + * 获取文件 SHA1 值 + * @param file 文件 + * @return 文件 SHA1 字符串信息 + */ + public static String getFileSHA1(final File file) { + return getFileSHA(file, "SHA-1"); + } + + /** + * 获取文件 SHA256 值 + * @param filePath 文件路径 + * @return 文件 SHA256 字符串信息 + */ + public static String getFileSHA256(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileSHA(file, "SHA-256"); + } + + /** + * 获取文件 SHA256 值 + * @param file 文件 + * @return 文件 SHA256 字符串信息 + */ + public static String getFileSHA256(final File file) { + return getFileSHA(file, "SHA-256"); + } + + // = + + /** + * 加密内容 SHA 模板 + * @param data 待加密数据 + * @param algorithm 算法 + * @return SHA 算法加密后的字符串 + */ + public static String shaHex( + final String data, + final String algorithm + ) { + if (data == null || algorithm == null) return null; + try { + byte[] bytes = data.getBytes(); + // 获取 SHA-1 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance(algorithm); + // 使用指定的字节更新摘要 + digest.update(bytes); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "shaHex"); + } + return null; + } + + /** + * 获取文件 SHA 值 + * @param file 文件 + * @param algorithm 算法 + * @return 文件指定 SHA 字符串信息 + */ + public static String getFileSHA( + final File file, + final String algorithm + ) { + if (file == null || algorithm == null) return null; + InputStream is = null; + try { + is = new FileInputStream(file); + byte[] buffer = new byte[1024]; + MessageDigest digest = MessageDigest.getInstance(algorithm); + int numRead; + while ((numRead = is.read(buffer)) > 0) { + digest.update(buffer, 0, numRead); + } + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileSHA"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java new file mode 100644 index 0000000000..77ae64dc2e --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java @@ -0,0 +1,84 @@ +package dev.utils.common.encrypt; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: 3DES 对称加密工具类 + * @author Ttt + *
+ *     Triple DES、DESede 进行了三重 DES 加密的算法 ( 对称加密算法 )
+ * 
+ */ +public final class TripleDESUtils { + + private TripleDESUtils() { + } + + // 日志 TAG + private static final String TAG = TripleDESUtils.class.getSimpleName(); + + /** + * 生成密钥 + * @return 密钥 byte[] + */ + public static byte[] initKey() { + try { + KeyGenerator keyGen = KeyGenerator.getInstance("DESede"); + keyGen.init(168); // 112 168 + SecretKey secretKey = keyGen.generateKey(); + return secretKey.getEncoded(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "initKey"); + } + return null; + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DESede"); + Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * 3DES 解密 + * @param data 待加密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DESede"); + Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/encrypt/XorUtils.java b/lib/DevApp/src/main/java/dev/utils/common/encrypt/XorUtils.java new file mode 100644 index 0000000000..5940b03e32 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/encrypt/XorUtils.java @@ -0,0 +1,95 @@ +package dev.utils.common.encrypt; + +/** + * detail: 异或工具类 + * @author Ttt + *
+ *     位运算可以实现很多高级、高效的运算
+ *     可用于 IM 二进制数据包加密
+ *     1. 能够实现加密
+ *     2. 采用异或加密算法不会改变二进制数据的长度这对二进制数据包封包起到不小的好处
+ *     也可用于记事本等场景
+ *     

+ * 参考链接 + * @see
+ *
+ */ +public final class XorUtils { + + private XorUtils() { + } + + /** + * 加解密 ( 固定 Key 方式 ) 这种方式 加解密 方法共用 + *
+     *     加密: byte[] bytes = encryptAsFix("123".getBytes());
+     *     解密: String str = new String(encryptAsFix(bytes));
+     * 
+ * @param data 待加解密数据 + * @return 加解密后的数据 byte[] + */ + public static byte[] encryptAsFix(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = 0; i < len; i++) { + data[i] ^= key; + } + return data; + } + + // = + + /** + * 加密 ( 非固定 Key 方式 ) + * @param data 待加密数据 + * @return 加密后的数据 byte[] + */ + public static byte[] encrypt(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = 0; i < len; i++) { + data[i] = (byte) (data[i] ^ key); + key = data[i]; + } + return data; + } + + /** + * 解密 ( 非固定 Key 方式 ) + * @param data 待解密数据 + * @return 解密后的数据 byte[] + */ + public static byte[] decrypt(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = len - 1; i > 0; i--) { + data[i] = (byte) (data[i] ^ data[i - 1]); + } + data[0] = (byte) (data[0] ^ key); + return data; + } + + // = + + /** + * 数据异或校验位计算 + * @param data 待计算数据 + * @return 校验位值 + */ + public static byte xorChecksum(final byte[] data) { + if (data == null) return 0; + int len = data.length; + if (len == 0) return 0; + byte value = data[0]; + for (int i = 1; i < len; i++) { + value ^= data[i]; + } + return value; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/file/FilePartAssist.java b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartAssist.java new file mode 100644 index 0000000000..ed7037e2bf --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartAssist.java @@ -0,0 +1,155 @@ +package dev.utils.common.file; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.common.CollectionUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 文件分片辅助类 + * @author Ttt + */ +public final class FilePartAssist { + + // 文件路径 + private final File file; + // 文件分片信息集合 + private final List filePartItems = new ArrayList<>(); + + /** + * 构造函数 + * @param file 文件 + * @param filePartItems 文件分片信息集合 + */ + public FilePartAssist( + final File file, + final List filePartItems + ) { + this.file = file; + if (filePartItems != null) { + this.filePartItems.addAll(filePartItems); + } + } + + /** + * 构造函数 + * @param filePath 文件路径 + * @param partCount 分片总数 + */ + public FilePartAssist( + final String filePath, + final int partCount + ) { + this(FileUtils.getFile(filePath), partCount); + } + + /** + * 构造函数 + * @param file 文件 + * @param partCount 分片总数 + */ + public FilePartAssist( + final File file, + final int partCount + ) { + this.file = file; + if (file != null && file.exists() && partCount > 0) { + // 原始文件总字节 + long fileByteLength = file.length(); + // 分片总字节 + long partByteLength = fileByteLength / partCount; + // 余数 ( 全部加到最后一个分片 ) + long remainder = fileByteLength - partByteLength * partCount; + // 当前分片字节累加总数 + long total = 0; + if (partCount > 1) { // 如果分片大于 1 个, 则处理前面的数量 + for (int i = 0, len = partCount - 1; i < len; i++) { + FilePartItem item = new FilePartItem( + i, partCount, partByteLength, fileByteLength, + total, total + partByteLength + ); + total += partByteLength; + filePartItems.add(item); + } + } + // 最后一个分片片段 + FilePartItem item = new FilePartItem( + partCount - 1, partCount, partByteLength, fileByteLength, + total, total + partByteLength + remainder + ); + filePartItems.add(item); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取文件 + * @return {@link File} + */ + public File getFile() { + return file; + } + + /** + * 获取文件名 + * @return 文件名 + */ + public String getFileName() { + return FileUtils.getFileName(file); + } + + /** + * 获取文件分片信息集合 + * @return {@link List} + */ + public List getFilePartItems() { + return filePartItems; + } + + /** + * 获取指定索引文件分片信息 + * @param partIndex 分片索引 + * @return {@link FilePartItem} + */ + public FilePartItem getFilePartItem(final int partIndex) { + return CollectionUtils.get(filePartItems, partIndex); + } + + /** + * 获取分片总数 + * @return 分片总数 + */ + public int getPartCount() { + return filePartItems.size(); + } + + /** + * 是否存在分片 + * @return {@code true} yes, {@code false} no + */ + public boolean existsPart() { + return getPartCount() != 0; + } + + /** + * 是否只有一个分片 + * @return {@code true} yes, {@code false} no + */ + public boolean isOnlyOne() { + return getPartCount() == 1; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public String getPartName(final int partIndex) { + return FilePartUtils.getPartName(getFileName(), partIndex); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/file/FilePartItem.java b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartItem.java new file mode 100644 index 0000000000..2a6d533330 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartItem.java @@ -0,0 +1,82 @@ +package dev.utils.common.file; + +/** + * detail: 文件分片信息 Item + * @author Ttt + */ +public class FilePartItem { + + // 分片索引 + public final int partIndex; + // 分片总数 + public final int partCount; + // 分片总字节 + public final long partByteLength; + // 原始文件总字节 + public final long fileByteLength; + // 分片字节开始索引 + public final long start; + // 分片字节结束索引 + public final long end; + + public FilePartItem( + int partIndex, + int partCount, + long partByteLength, + long fileByteLength, + long start, + long end + ) { + this.partIndex = partIndex; + this.partCount = partCount; + this.partByteLength = partByteLength; + this.fileByteLength = fileByteLength; + this.start = start; + this.end = end; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断是否 First Item + * @return {@code true} yes, {@code false} no + */ + public boolean isFirstItem() { + return partIndex == 0; + } + + /** + * 判断是否 Last Item + * @return {@code true} yes, {@code false} no + */ + public boolean isLastItem() { + return partIndex + 1 == partCount; + } + + /** + * 是否存在分片 + * @return {@code true} yes, {@code false} no + */ + public boolean existsPart() { + return partCount != 0; + } + + /** + * 是否只有一个分片 + * @return {@code true} yes, {@code false} no + */ + public boolean isOnlyOne() { + return partCount == 1; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param fileName 原始文件名 + * @return 分片文件名 + */ + public String getPartName(final String fileName) { + return FilePartUtils.getPartName(fileName, partIndex); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/file/FilePartUtils.java b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartUtils.java new file mode 100644 index 0000000000..bb3aea266b --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/file/FilePartUtils.java @@ -0,0 +1,765 @@ +package dev.utils.common.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 文件分片工具类 + * @author Ttt + *
+ *     当下载大文件时, 如果网络不稳定或者程序异常退出, 会导致下载失败, 甚至重试多次仍无法完成下载
+ *     可以使用断点续传下载:
+ *     断点续传下载将需要下载的文件分成若干个分片分别下载, 所有分片都下载完成后, 将所有分片合并成完整的文件
+ *     也可以用于断点续传上传 ( 分片续传 )
+ *     

+ * RandomAccessFile 简介与使用 + * @see
+ *

+ * 可用 {@link FileUtils#getFileMD5(File)} 进行校验分片合并后与源文件 MD5 值是否一致 + *
+ */ +public final class FilePartUtils { + + private FilePartUtils() { + } + + // 日志 TAG + private static final String TAG = FilePartUtils.class.getSimpleName(); + // 分片文件后缀 + public static final String PART_SUFFIX = "_part_"; + // 分片数量 + public static final int PART_COUNT = 10; + // 分片片段允许最小值 byte ( 默认 1mb ) + public static final long MIN_LENGTH = 1048576L; + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param item {@link FilePartItem} + * @param fileName 原始文件名 + * @return 分片文件名 + */ + public static String getPartName( + final FilePartItem item, + final String fileName + ) { + return item != null ? item.getPartName(fileName) : null; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public static String getPartName( + final FilePartAssist assist, + final int partIndex + ) { + return assist != null ? assist.getPartName(partIndex) : null; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param fileName 原始文件名 + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public static String getPartName( + final String fileName, + final int partIndex + ) { + return String.format("%s%s%s", fileName, PART_SUFFIX, partIndex); + } + + // = + + /** + * 获取文件分片辅助类 + * @param filePath 文件路径 + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist(final String filePath) { + return getFilePartAssist(FileUtils.getFile(filePath), PART_COUNT, MIN_LENGTH); + } + + /** + * 获取文件分片辅助类 + * @param filePath 文件路径 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist( + final String filePath, + final int partCount, + final long minLength + ) { + return getFilePartAssist(FileUtils.getFile(filePath), partCount, minLength); + } + + /** + * 获取文件分片辅助类 + * @param file 文件 + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist(final File file) { + return getFilePartAssist(file, PART_COUNT, MIN_LENGTH); + } + + /** + * 获取文件分片辅助类 + * @param file 文件 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist( + final File file, + final int partCount, + final long minLength + ) { + boolean filePart = isFilePart(file, partCount, minLength); + return new FilePartAssist(file, filePart ? partCount : 1); + } + + // = + + /** + * 是否符合文件分片条件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart(final String filePath) { + return isFilePart(FileUtils.getFile(filePath), PART_COUNT, MIN_LENGTH); + } + + /** + * 是否符合文件分片条件 + * @param filePath 文件路径 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart( + final String filePath, + final int partCount, + final long minLength + ) { + return isFilePart(FileUtils.getFile(filePath), partCount, minLength); + } + + /** + * 是否符合文件分片条件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart(final File file) { + return isFilePart(file, PART_COUNT, MIN_LENGTH); + } + + /** + * 是否符合文件分片条件 + * @param file 文件 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart( + final File file, + final int partCount, + final long minLength + ) { + // 原始文件总字节 + long fileByteLength = FileUtils.getFileLength(file); + // 分片总字节 + long partByteLength = fileByteLength / partCount; + return partByteLength >= minLength; + } + + // ========== + // = 文件拆分 = + // ========== + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final long start, + final long end + ) { + return fileSplit(FileUtils.getFile(filePath), start, end); + } + + /** + * 文件拆分 + *
+     *     慎用, 防止内存溢出
+     * 
+ * @param file 文件 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final long start, + final long end + ) { + if (file != null && file.exists() && start >= 0 && end > start) { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(file, "r"); + if (end > raf.length()) return null; + raf.seek(start); + byte[] bytes = new byte[(int) (end - start)]; + raf.read(bytes); + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplit"); + } finally { + CloseUtils.closeIOQuietly(raf); + } + } + return null; + } + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final FilePartItem item + ) { + return fileSplit(FileUtils.getFile(filePath), item); + } + + /** + * 文件拆分 + * @param file 文件 + * @param item {@link FilePartItem} + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final FilePartItem item + ) { + if (file == null || item == null) return null; + return fileSplit(file, item.start, item.end); + } + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final FilePartAssist assist, + final int partIndex + ) { + return fileSplit(FileUtils.getFile(filePath), assist, partIndex); + } + + /** + * 文件拆分 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final FilePartAssist assist, + final int partIndex + ) { + if (file == null || assist == null) return null; + return fileSplit(file, assist.getFilePartItem(partIndex)); + } + + // = + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @param destFolderPath 存储目标文件夹地址 + * @param partName 分片文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final long start, + final long end, + final String destFolderPath, + final String partName + ) { + return fileSplitSave(FileUtils.getFile(filePath), start, end, destFolderPath, partName); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @param destFolderPath 存储目标文件夹地址 + * @param partName 分片文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final long start, + final long end, + final String destFolderPath, + final String partName + ) { + if (file != null && file.exists() && start >= 0 && end > start) { + FileInputStream fis = null; + FileChannel inputChannel = null; + FileOutputStream fos = null; + FileChannel outputChannel = null; + try { + fis = new FileInputStream(file); + if (end > file.length()) return false; + inputChannel = fis.getChannel(); + fos = new FileOutputStream(new File(destFolderPath, partName)); + outputChannel = fos.getChannel(); + inputChannel.transferTo(start, (int) (end - start), outputChannel); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitSave"); + } finally { + CloseUtils.closeIOQuietly(outputChannel, fos, inputChannel, fis); + } + } + return false; + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final FilePartItem item, + final String destFolderPath + ) { + return fileSplitSave(FileUtils.getFile(filePath), item, destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param item {@link FilePartItem} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final FilePartItem item, + final String destFolderPath + ) { + if (file == null || item == null) return false; + return fileSplitSave(file, item.start, item.end, destFolderPath, + item.getPartName(FileUtils.getFileName(file)) + ); + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + return fileSplitSave(FileUtils.getFile(filePath), assist, destFolderPath, partIndex); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + if (file == null || assist == null) return false; + return fileSplitSave(file, assist.getFilePartItem(partIndex), destFolderPath); + } + + // = + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final String filePath, + final String destFolderPath + ) { + return fileSplitSaves(FileUtils.getFile(filePath), destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final File file, + final String destFolderPath + ) { + return fileSplitSaves(file, getFilePartAssist(file), destFolderPath); + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final String filePath, + final FilePartAssist assist, + final String destFolderPath + ) { + return fileSplitSaves(FileUtils.getFile(filePath), assist, destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final File file, + final FilePartAssist assist, + final String destFolderPath + ) { + if (file == null || assist == null) return false; + if (!assist.existsPart()) return false; + String fileName = FileUtils.getFileName(file); + if (fileName == null) return false; + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + FilePartItem item = assist.getFilePartItem(i); + if (item != null) { + boolean result = fileSplitSave(file, item.start, item.end, destFolderPath, + item.getPartName(fileName) + ); + if (!result) return false; + } else { + return false; + } + } + return true; + } + + // ========== + // = 分片删除 = + // ========== + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final String filePath, + final FilePartItem item, + final String destFolderPath + ) { + return fileSplitDelete(FileUtils.getFile(filePath), item, destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param item {@link FilePartItem} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final File file, + final FilePartItem item, + final String destFolderPath + ) { + if (file == null || item == null) return false; + return FileUtils.deleteFile( + FileUtils.getFile(destFolderPath, item.getPartName(FileUtils.getFileName(file))) + ); + } + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + return fileSplitDelete(FileUtils.getFile(filePath), assist, destFolderPath, partIndex); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + if (file == null || assist == null) return false; + return fileSplitDelete(file, assist.getFilePartItem(partIndex), destFolderPath); + } + + // = + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final String filePath, + final String destFolderPath + ) { + return fileSplitDeletes(FileUtils.getFile(filePath), destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final File file, + final String destFolderPath + ) { + return fileSplitDeletes(file, getFilePartAssist(file), destFolderPath); + } + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final String filePath, + final FilePartAssist assist, + final String destFolderPath + ) { + return fileSplitDeletes(FileUtils.getFile(filePath), assist, destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final File file, + final FilePartAssist assist, + final String destFolderPath + ) { + if (file == null || assist == null) return false; + if (!assist.existsPart()) return false; + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + fileSplitDelete(file, assist.getFilePartItem(i), destFolderPath); + } + return true; + } + + // ========== + // = 分片合并 = + // ========== + + /** + * 分片合并 + * @param filePath 文件路径 + * @param paths 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergePaths( + final String filePath, + final List paths + ) { + return fileSplitMergePaths(FileUtils.getFile(filePath), paths); + } + + /** + * 分片合并 + * @param file 文件 + * @param paths 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergePaths( + final File file, + final List paths + ) { + if (file == null || paths == null) return false; + return fileSplitMergeFiles(file, FileUtils.convertFiles(paths)); + } + + /** + * 分片合并 + * @param filePath 文件路径 + * @param files 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergeFiles( + final String filePath, + final List files + ) { + return fileSplitMergeFiles(FileUtils.getFile(filePath), files); + } + + /** + * 分片合并 + * @param file 文件 + * @param files 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergeFiles( + final File file, + final List files + ) { + if (file == null || files == null) return false; + if (files.isEmpty()) return false; + FileUtils.deleteFile(file); + RandomAccessFile raf = null; + RandomAccessFile reader = null; + try { + raf = new RandomAccessFile(file, "rw"); + for (int i = 0, len = files.size(); i < len; i++) { + reader = new RandomAccessFile(files.get(i), "r"); + byte[] buffer = new byte[1024]; + int readLen; // 读取的字节数 + while ((readLen = reader.read(buffer)) != -1) { + raf.write(buffer, 0, readLen); + } + CloseUtils.closeIOQuietly(reader); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitMergeFiles"); + FileUtils.deleteFile(file); + } finally { + CloseUtils.closeIOQuietly(reader, raf); + } + return false; + } + + /** + * 分片合并 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 分片所在文件夹地址 + * @param fileName 原文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMerge( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final String fileName + ) { + return fileSplitMerge(FileUtils.getFile(filePath), assist, destFolderPath, fileName); + } + + /** + * 分片合并 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 分片所在文件夹地址 + * @param fileName 原文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMerge( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final String fileName + ) { + if (file == null || assist == null || destFolderPath == null || fileName == null) { + return false; + } + if (!assist.existsPart()) return false; + List files = new ArrayList<>(); + try { + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + FilePartItem item = assist.getFilePartItems().get(i); + File partFile = new File(destFolderPath, item.getPartName(fileName)); + files.add(partFile); + } + return fileSplitMergeFiles(file, files); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitMerge"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/format/ArgsFormatter.java b/lib/DevApp/src/main/java/dev/utils/common/format/ArgsFormatter.java new file mode 100644 index 0000000000..188ab16f53 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/format/ArgsFormatter.java @@ -0,0 +1,204 @@ +package dev.utils.common.format; + +import dev.utils.JCLogUtils; + +/** + * detail: 可变数组格式化 + * @author Ttt + */ +public class ArgsFormatter { + + // 日志 TAG + private final String TAG = ArgsFormatter.class.getSimpleName(); + + // 开始占位说明符 + private final String startSpecifier; + // 中间占位说明符 + private final String middleSpecifier; + // 结尾占位说明符 + private final String endSpecifier; + // 是否抛出异常 + private final boolean throwError; + // 格式化异常默认值 + private final String defaultValue; + + /** + * 构造函数 + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + */ + public ArgsFormatter( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + this.startSpecifier = startSpecifier; + this.middleSpecifier = middleSpecifier; + this.endSpecifier = endSpecifier; + this.throwError = throwError; + this.defaultValue = defaultValue; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, middleSpecifier, + false, null + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, endSpecifier, + false, null + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, endSpecifier, + throwError, defaultValue + ); + } + + // ======= + // = get = + // ======= + + /** + * 获取开始占位说明符 + * @return 开始占位说明符 + */ + public String getStartSpecifier() { + return startSpecifier; + } + + /** + * 获取中间占位说明符 + * @return 中间占位说明符 + */ + public String getMiddleSpecifier() { + return middleSpecifier; + } + + /** + * 获取结尾占位说明符 + * @return 结尾占位说明符 + */ + public String getEndSpecifier() { + return endSpecifier; + } + + /** + * 是否抛出异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isThrowError() { + return throwError; + } + + /** + * 获取格式化异常默认值 + * @return 格式化异常默认值 + */ + public String getDefaultValue() { + return defaultValue; + } + + // ============ + // = 格式化方法 = + // ============ + + /** + * 根据可变参数数量自动格式化 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public String format(final Object... args) { + return formatByArray(args); + } + + /** + * 根据可变参数数量自动格式化 + * @param objects 格式化参数 + * @return 格式化后的字符串 + */ + public String formatByArray(final Object[] objects) { + if (objects != null && objects.length != 0) { + String format = createFormatString(objects); + try { + return String.format(format, objects); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatByArray"); + if (throwError) throw e; + } + } + return defaultValue; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 创建格式化占位说明符字符串 + * @param objects 格式化参数 + * @return 格式化占位说明符字符串 + */ + private String createFormatString(final Object[] objects) { + StringBuilder builder = new StringBuilder(); + builder.append(startSpecifier); + int length = objects.length; + for (int i = 1; i < length; i++) { + if (i == length - 1) { + builder.append(endSpecifier); + } else { + builder.append(middleSpecifier); + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/format/UnitSpanFormatter.java b/lib/DevApp/src/main/java/dev/utils/common/format/UnitSpanFormatter.java new file mode 100644 index 0000000000..631aabf275 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/format/UnitSpanFormatter.java @@ -0,0 +1,435 @@ +package dev.utils.common.format; + +import dev.utils.common.BigDecimalUtils; +import dev.utils.common.NumberUtils; + +/** + * detail: 单位数组范围格式化 + * @author Ttt + */ +public class UnitSpanFormatter { + + // 单位格式化精度 + private final int precision; + // 是否自动补 0 ( 只有 int、long 有效 ) + private final boolean appendZero; + // 格式化异常默认值 + private final String defaultValue; + + /** + * 构造函数 + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + */ + public UnitSpanFormatter( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + this.precision = precision; + this.appendZero = appendZero; + this.defaultValue = defaultValue; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision + ) { + return new UnitSpanFormatter(precision, false, null); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final String defaultValue + ) { + return new UnitSpanFormatter(precision, false, defaultValue); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final boolean appendZero + ) { + return new UnitSpanFormatter(precision, appendZero, null); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + return new UnitSpanFormatter(precision, appendZero, defaultValue); + } + + // ======= + // = get = + // ======= + + /** + * 获取单位格式化精度 + * @return 单位格式化精度 + */ + public int getPrecision() { + return precision; + } + + /** + * 是否自动补 0 + * @return {@code true} yes, {@code false} no + */ + public boolean isAppendZero() { + return appendZero; + } + + /** + * 获取格式化异常默认值 + * @return 格式化异常默认值 + */ + public String getDefaultValue() { + return defaultValue; + } + + // ============ + // = 格式化方法 = + // ============ + + // ========== + // = double = + // ========== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final double[] values, + final String[] units + ) { + return format(values, units, null); + } + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String format( + final double[] values, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + BigDecimalUtils.Operation op = null; + if (operation != null) op = operation.clone(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + if (op != null) { + String value = op.setBigDecimal(values[i]).round() + .toPlainString(); + builder.append(value); + } else { + builder.append(values[i]); + } + builder.append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ========= + // = float = + // ========= + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final float[] values, + final String[] units + ) { + return format(values, units, null); + } + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String format( + final float[] values, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + BigDecimalUtils.Operation op = null; + if (operation != null) op = operation.clone(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + if (op != null) { + String value = op.setBigDecimal(values[i]).round() + .toPlainString(); + builder.append(value); + } else { + builder.append(values[i]); + } + builder.append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ======== + // = long = + // ======== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final long[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + long value = values[i]; + builder.append(NumberUtils.addZero(value, appendZero)) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ======= + // = int = + // ======= + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final int[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + long value = values[i]; + builder.append(NumberUtils.addZero(value, appendZero)) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // =========== + // = Generic = + // =========== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final Object[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + builder.append(values[i]) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ============= + // = Unit Span = + // ============= + + // ========== + // = double = + // ========== + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final double value, + final double[] unitSpans, + final String[] units + ) { + return formatBySpan(value, unitSpans, units, null); + } + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final double value, + final double[] unitSpans, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (unitSpans != null && units != null) { + double[] values = NumberUtils.calculateUnitD(value, unitSpans); + return format(values, units, operation); + } + return defaultValue; + } + + // ========= + // = float = + // ========= + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final float value, + final float[] unitSpans, + final String[] units + ) { + return formatBySpan(value, unitSpans, units, null); + } + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final float value, + final float[] unitSpans, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (unitSpans != null && units != null) { + float[] values = NumberUtils.calculateUnitF(value, unitSpans); + return format(values, units, operation); + } + return defaultValue; + } + + // ======== + // = long = + // ======== + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final long value, + final long[] unitSpans, + final String[] units + ) { + if (unitSpans != null && units != null) { + long[] values = NumberUtils.calculateUnitL(value, unitSpans); + return format(values, units); + } + return defaultValue; + } + + // ======= + // = int = + // ======= + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final int value, + final int[] unitSpans, + final String[] units + ) { + if (unitSpans != null && units != null) { + int[] values = NumberUtils.calculateUnitI(value, unitSpans); + return format(values, units); + } + return defaultValue; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/random/AliasMethod.java b/lib/DevApp/src/main/java/dev/utils/common/random/AliasMethod.java new file mode 100644 index 0000000000..0e7eb2b332 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/random/AliasMethod.java @@ -0,0 +1,179 @@ +package dev.utils.common.random; + +/** + * File: AliasMethod.java + * Author: Keith Schwarz (htiek@cs.stanford.edu) + * An implementation of the alias method implemented using Vose's algorithm. + * The alias method allows for efficient sampling of random values from a + * discrete probability distribution (i.e. rolling a loaded die) in O(1) time + * each after O(n) preprocessing time. + * For a complete writeup on the alias method, including the intuition and + * important proofs, please see the article "Darts, Dice, and Coins: Smpling + * from a Discrete Distribution" at + * http://www.keithschwarz.com/darts-dice-coins/ + */ + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Random; + +/** + * detail: 随机概率采样算法 + * @author Keith Schwarz + *
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class AliasMethod { + + /* The random number generator used to sample from the distribution. */ + private final Random random; + + /* The probability and alias tables. */ + private final int[] alias; + private final double[] probability; + + /** + * Constructs a new AliasMethod to sample from a discrete distribution and + * hand back outcomes based on the probability distribution. + *

+ * Given as input a list of probabilities corresponding to outcomes 0, 1, + * ..., n - 1, this constructor creates the probability and alias tables + * needed to efficiently sample from this distribution. + * @param probabilities The list of probabilities. + */ + public AliasMethod(List probabilities) { + this(probabilities, new Random()); + } + + /** + * Constructs a new AliasMethod to sample from a discrete distribution and + * hand back outcomes based on the probability distribution. + *

+ * Given as input a list of probabilities corresponding to outcomes 0, 1, + * ..., n - 1, along with the random number generator that should be used + * as the underlying generator, this constructor creates the probability + * and alias tables needed to efficiently sample from this distribution. + * @param probabilities The list of probabilities. + * @param random The random number generator + */ + public AliasMethod( + List probabilities, + Random random + ) { + /* Begin by doing basic structural checks on the inputs. */ + if (probabilities == null || random == null) { + throw new NullPointerException(); + } + if (probabilities.size() == 0) { + throw new IllegalArgumentException("Probability vector must be nonempty."); + } + + /* Allocate space for the probability and alias tables. */ + probability = new double[probabilities.size()]; + alias = new int[probabilities.size()]; + + /* Store the underlying generator. */ + this.random = random; + + /* Compute the average probability and cache it for later use. */ + final double average = 1.0 / probabilities.size(); + + /* Make a copy of the probabilities list, since we will be making + * changes to it. + */ + probabilities = new ArrayList(probabilities); + + /* Create two stacks to act as worklists as we populate the tables. */ + Deque small = new ArrayDeque<>(); + Deque large = new ArrayDeque<>(); + + /* Populate the stacks with the input probabilities. */ + for (int i = 0; i < probabilities.size(); ++i) { + /* If the probability is below the average probability, then we add + * it to the small list; otherwise we add it to the large list. + */ + if (probabilities.get(i) >= average) { + large.add(i); + } else { + small.add(i); + } + } + + /* As a note: in the mathematical specification of the algorithm, we + * will always exhaust the small list before the big list. However, + * due to floating point inaccuracies, this is not necessarily true. + * Consequently, this inner loop (which tries to pair small and large + * elements) will have to check that both lists aren't empty. + */ + while (!small.isEmpty() && !large.isEmpty()) { + /* Get the index of the small and the large probabilities. */ + int less = small.removeLast(); + int more = large.removeLast(); + + /* These probabilities have not yet been scaled up to be such that + * 1/n is given weight 1.0. We do this here instead. + */ + probability[less] = probabilities.get(less) * probabilities.size(); + alias[less] = more; + + /* Decrease the probability of the larger one by the appropriate + * amount. + */ + probabilities.set( + more, + (probabilities.get(more) + probabilities.get(less)) - average + ); + + /* If the new probability is less than the average, add it into the + * small list; otherwise add it to the large list. + */ + if (probabilities.get(more) >= 1.0 / probabilities.size()) { + large.add(more); + } else { + small.add(more); + } + } + + /* At this point, everything is in one list, which means that the + * remaining probabilities should all be 1/n. Based on this, set them + * appropriately. Due to numerical issues, we can't be sure which + * stack will hold the entries, so we empty both. + */ + while (!small.isEmpty()) { + probability[small.removeLast()] = 1.0; + } + while (!large.isEmpty()) { + probability[large.removeLast()] = 1.0; + } + } + + /** + * 获取随机索引 ( 对应几率索引 ) + * Samples a value from the underlying distribution. + * @return A random value sampled from the underlying distribution. + */ + public int next() { + /* Generate a fair die roll to determine which column to inspect. */ + int column = random.nextInt(probability.length); + + /* Generate a biased coin toss to determine which option to pick. */ + boolean coinToss = random.nextDouble() < probability[column]; + + /* Based on the outcome, return either the column or its alias. */ + return coinToss ? column : alias[column]; + } + +// public static void main(String[] args) { +// // 使用方法 +// List lists = new ArrayList<>(); +// lists.add(0.15); // 0.15 几率 +// lists.add(0.85); // 0.85 几率 +// AliasMethod aliasMethod = new AliasMethod(lists); +// int result = aliasMethod.next(); +// // result 0 = 0.15, 1 = 0.85 +// } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java b/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java new file mode 100644 index 0000000000..31bb29bb6e --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java @@ -0,0 +1,103 @@ +package dev.utils.common.thread; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * detail: 线程池管理工具类 + * @author Ttt + */ +public final class DevThreadManager { + + private DevThreadManager() { + } + + // 默认通用线程池 ( 通过 CPU 自动处理 ) + private static final DevThreadPool sDevThreadPool = new DevThreadPool(DevThreadPool.DevThreadPoolType.CALC_CPU); + // 线程池数据 + private static final Map sThreadMaps = new LinkedHashMap<>(); + // 配置数据 + private static final Map sConfigMaps = new HashMap<>(); + + /** + * 获取 DevThreadManager 实例 + * @param threadNumber 线程数量 + * @return {@link DevThreadPool} + */ + public static synchronized DevThreadPool getInstance(final int threadNumber) { + // 初始化 key + String key = "n_" + threadNumber; + // 如果不为 null, 则直接返回 + DevThreadPool devThreadPool = sThreadMaps.get(key); + if (devThreadPool != null) { + return devThreadPool; + } + devThreadPool = new DevThreadPool(threadNumber); + sThreadMaps.put(key, devThreadPool); + return devThreadPool; + } + + /** + * 获取 DevThreadManager 实例 + * @param key 线程配置 key {@link DevThreadPool.DevThreadPoolType} or int-Integer + * @return {@link DevThreadPool} + */ + public static synchronized DevThreadPool getInstance(final String key) { + // 如果不为 null, 则直接返回 + DevThreadPool devThreadPool = sThreadMaps.get(key); + if (devThreadPool != null) { + return devThreadPool; + } + Object object = sConfigMaps.get(key); + if (object != null) { + try { + // 判断是否属于线程池类型 + if (object instanceof DevThreadPool.DevThreadPoolType) { + devThreadPool = new DevThreadPool((DevThreadPool.DevThreadPoolType) object); + } else if (object instanceof Integer) { + devThreadPool = new DevThreadPool((Integer) object); + } else { // 其他类型, 统一转换 Integer + devThreadPool = new DevThreadPool(Integer.parseInt((String) object)); + } + sThreadMaps.put(key, devThreadPool); + return devThreadPool; + } catch (Exception e) { + return sDevThreadPool; + } + } + return sDevThreadPool; + } + + // = + + /** + * 初始化配置信息 + * @param mapConfigs 线程配置信息 Map + */ + public static void initConfig(final Map mapConfigs) { + if (mapConfigs != null) { + sConfigMaps.putAll(mapConfigs); + } + } + + /** + * 添加配置信息 + * @param key 线程配置 key + * @param value 线程配置 value + */ + public static void putConfig( + final String key, + final Object value + ) { + sConfigMaps.put(key, value); + } + + /** + * 移除配置信息 + * @param key 线程配置 key + */ + public static void removeConfig(final String key) { + sConfigMaps.remove(key); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java b/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java new file mode 100644 index 0000000000..eaa6bad6a8 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java @@ -0,0 +1,489 @@ +package dev.utils.common.thread; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * detail: 线程池 ( 构建类 ) + * @author Ttt + *

+ *     @see 
+ *     

+ * 创建线程池 ( 参数 ) + * 1. 线程池里面管理多少个线程 + * 2. 如果排队满了, 额外的开的线程数 + * 3. 如果线程池没有要执行的任务存活多久 + * 4. 时间的单位 + * 5. 如果 线程池里管理的线程都已经用了, 剩下的任务临时存到 LinkedBlockingQueue 对象中排队 + * public ThreadPoolExecutor(int corePoolSize, + * int maximumPoolSize, + * long keepAliveTime, + * TimeUnit unit, + * BlockingQueue workQueue) { + * this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + * Executors.defaultThreadFactory(), defaultHandler); + * } + *
+ */ +public final class DevThreadPool { + + // 线程池对象 + private final ExecutorService mThreadPool; + // 定时任务线程池 + private ScheduledExecutorService mScheduleExec; + + /** + * 构造函数 + * @param threadNumber 线程数量 + */ + public DevThreadPool(int threadNumber) { + // 如果小于等于 0, 则默认使用 1 + if (threadNumber <= 0) { + threadNumber = 1; + } + this.mThreadPool = Executors.newFixedThreadPool(threadNumber); + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(threadNumber); + } + + /** + * 构造函数 + * @param threadPool {@link ExecutorService} + */ + public DevThreadPool(final ExecutorService threadPool) { + this.mThreadPool = threadPool; + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(getThreads()); + } + + /** + * 构造函数 + * @param devThreadPoolType 线程初始化类型 {@link DevThreadPoolType} + */ + public DevThreadPool(final DevThreadPoolType devThreadPoolType) { + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(getThreads()); + // = + if (devThreadPoolType != null) { + switch (devThreadPoolType) { + case SINGLE: + mThreadPool = Executors.newSingleThreadExecutor(); + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(1); + break; +// case AUTO_CPU: +// mThreadPool = Executors.newWorkStealingPool(); +// break; + case CALC_CPU: + mThreadPool = Executors.newFixedThreadPool(getThreads()); + break; + case CACHE: + mThreadPool = Executors.newCachedThreadPool(); + break; + default: + mThreadPool = Executors.newFixedThreadPool(getThreads()); + break; + } + } else { + mThreadPool = Executors.newFixedThreadPool(getThreads()); + } + } + + /** + * detail: 线程池初始化枚举类型 + * @author Ttt + *
+     *     @see 
+     *     @see 
+     * 
+ */ + public enum DevThreadPoolType { + + // 如果当前线程意外终止, 会创建一个新线程继续执行任务, 这和我们直接创建线程不同, 也和 newFixedThreadPool(1) 不同 + SINGLE, // newSingleThreadExecutor 获取的是一个单个的线程, 这个线程会保证你的任务执行完成 + + AUTO_CPU, // 根据 CPU 来创建 ( 自定义创建 ) + + CALC_CPU, // 手动计算 CPU 来创建 + + CACHE, // 可缓存线程池 + +// 1 newCachedThreadPool: 创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程 +// 2 newFixedThreadPool: 创建一个固定数目的、可重用的线程池 +// 3 newScheduledThreadPool: 创建一个定长线程池, 支持定时及周期性任务执行 +// 4 newSingleThreadExecutor: 创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序 (FIFO、LIFO) 优先级执行 +// 5 newSingleThreadScheduledExcutor: 创建一个单例线程池, 定期或延时执行任务 +// 6 newWorkStealingPool: 创建持有足够线程的线程池来支持给定的并行级别, 并通过使用多个队列, 减少竞争 +// 它需要穿一个并行级别的参数, 如果不传, 则被设定为默认的 CPU 数量 +// 7 ForkJoinPool: 支持大任务分解成小任务的线程池, 这是 Java8 新增线程池, 通常配合 ForkJoinTask 接口的子类 RecursiveAction 或 RecursiveTask 使用 + } + + // = + + /** + * 获取线程数 + * @return {@link DevThreadPool#getCalcThreads()} + */ + public static int getThreads() { + return getCalcThreads(); + } + + /** + * 获取线程数 + * @return 自动计算 CPU 核心数 + */ + public static int getCalcThreads() { + // 获取 CPU 核心数 + int cpuNumber = Runtime.getRuntime().availableProcessors(); + // 如果小于等于 5, 则返回 5 + if (cpuNumber <= 5) { + return 5; + } else { // 大于 5 的情况 + // 防止线程数量过大, 当大于 10 的时候, 返回 10 + // 不大于 10 的时候, 默认返回 支持的数量 * 2 + 1 + return Math.min(cpuNumber * 2 + 1, 10); + } + } + + // = + + /** + * 加入到线程池任务队列 + * @param runnable 线程 + */ + public void execute(final Runnable runnable) { + if (mThreadPool != null && runnable != null) { + mThreadPool.execute(runnable); + } + } + + /** + * 加入到线程池任务队列 + * @param runnables 线程集合 + */ + public void execute(final List runnables) { + if (mThreadPool != null && runnables != null) { + for (Runnable command : runnables) { + if (command != null) { + mThreadPool.execute(command); + } + } + } + } + + /** + * 通过反射, 调用某个类的方法 + * @param method 方法 + * @param object 对象 + */ + public void execute( + final Method method, + final Object object + ) { + if (mThreadPool != null && method != null && object != null) { + mThreadPool.execute(() -> { + try { + method.invoke(object); + } catch (Exception ignore) { + } + }); + } + } + + // = + + /** + * shutdown 会等待所有提交的任务执行完成, 不管是正在执行还是保存在任务队列中的已提交任务 + * 待以前提交的任务执行完毕后关闭线程池 + * 启动一次顺序关闭, 执行以前提交的任务, 但不接受新任务 + * 如果已经关闭, 则调用没有作用 + */ + public void shutdown() { + if (mThreadPool != null) { + mThreadPool.shutdown(); + } + } + + /** + * shutdownNow 会尝试中断正在执行的任务 ( 其主要是中断一些指定方法如 sleep 方法 ) , 并且停止执行等待队列中提交的任务 + * 试图停止所有正在执行的活动任务, 暂停处理正在等待的任务, 并返回等待执行的任务列表 + * 无法保证能够停止正在处理的活动执行任务, 但是会尽力尝试 + * @return {@link List} + */ + public List shutdownNow() { + if (mThreadPool != null) { + return mThreadPool.shutdownNow(); + } + return null; + } + + /** + * 判断线程池是否已关闭 ( isShutDown 当调用 shutdown() 方法后返回为 true ) + * @return {@code true} yes, {@code false} no + */ + public boolean isShutdown() { + if (mThreadPool != null) { + return mThreadPool.isShutdown(); + } + return false; + } + + /** + * 若关闭后所有任务都已完成, 则返回 true + * 注意除非首先调用 shutdown 或 shutdownNow, 否则 isTerminated 永不为 true + * isTerminated 当调用 shutdown() 方法后, 并且所有提交的任务完成后返回为 true + * @return {@code true} yes, {@code false} no + */ + public boolean isTerminated() { + if (mThreadPool != null) { + return mThreadPool.isTerminated(); + } + return false; + } + + /** + * 请求关闭、发生超时或者当前线程中断 + * 无论哪一个首先发生之后, 都将导致阻塞, 直到所有任务完成执行 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @return {@code true} 请求成功, {@code false} 请求超时 + * @throws InterruptedException 终端异常 + */ + public boolean awaitTermination( + final long timeout, + final TimeUnit unit + ) + throws InterruptedException { + if (mThreadPool != null && unit != null) { + return mThreadPool.awaitTermination(timeout, unit); + } + return false; + } + + /** + * 提交一个 Callable 任务用于执行 + * 如果想立即阻塞任务的等待, 则可以使用 {@code result = threadPool.submit(aCallable).get();} 形式的构造 + * @param task 任务 + * @param 泛型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回该任务的结果 + */ + public Future submit(final Callable task) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task); + } + return null; + } + + /** + * 提交一个 Runnable 任务用于执行 + * @param task 任务 + * @param result 返回的结果 + * @param 泛型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回该任务的结果 + */ + public Future submit( + final Runnable task, + final T result + ) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task, result); + } + return null; + } + + /** + * 提交一个 Runnable 任务用于执行 + * @param task 任务 + * @param 未知类型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回 null 结果 + */ + public Future submit(final Runnable task) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task); + } + return null; + } + + /** + * 执行给定的任务 + * 当所有任务完成时, 返回保持任务状态和结果的 Future 列表 + * 返回列表的所有元素的 {@link Future#isDone} 为 {@code true} + * 注意, 可以正常地或通过抛出异常来终止已完成任务 + * 如果正在进行此操作时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param 泛型 + * @return 表示任务的 Future 列表, 列表顺序与给定任务列表的迭代器所生成的顺序相同, 每个任务都已完成 + * @throws InterruptedException 如果等待时发生中断, 在这种情况下取消尚未完成的任务 + */ + public List> invokeAll(final Collection> tasks) + throws InterruptedException { + if (mThreadPool != null && tasks != null) { + return mThreadPool.invokeAll(tasks); + } + return null; + } + + /** + * 执行给定的任务 + * 当所有任务完成或超时期满时 ( 无论哪个首先发生 ), 返回保持任务状态和结果的 Future 列表 + * 返回列表的所有元素的 {@link Future#isDone} 为 {@code true} + * 一旦返回后, 即取消尚未完成的任务 + * 注意, 可以正常地或通过抛出异常来终止已完成任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @param 泛型 + * @return 表示任务的 Future 列表, 列表顺序与给定任务列表的迭代器所生成的顺序相同 + * 如果操作未超时, 则已完成所有任务, 如果确实超时了, 则某些任务尚未完成 + * @throws InterruptedException 如果等待时发生中断, 在这种情况下取消尚未完成的任务 + */ + public List> invokeAll( + final Collection> tasks, + final long timeout, + final TimeUnit unit + ) + throws InterruptedException { + if (mThreadPool != null && tasks != null && unit != null) { + return mThreadPool.invokeAll(tasks, timeout, unit); + } + return null; + } + + /** + * 执行给定的任务 + * 如果某个任务已成功完成 ( 也就是未抛出异常 ), 则返回其结果 + * 一旦正常或异常返回后, 则取消尚未完成的任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param 泛型 + * @return 某个任务返回的结果 + * @throws InterruptedException 如果等待时发生中断 + * @throws ExecutionException 如果没有任务成功完成 + */ + public T invokeAny(final Collection> tasks) + throws InterruptedException, ExecutionException { + if (mThreadPool != null && tasks != null) { + return mThreadPool.invokeAny(tasks); + } + return null; + } + + /** + * 执行给定的任务 + * 如果在给定的超时期满前某个任务已成功完成 ( 也就是未抛出异常 ), 则返回其结果 + * 一旦正常或异常返回后, 则取消尚未完成的任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @param 泛型 + * @return 某个任务返回的结果 + * @throws InterruptedException 如果等待时发生中断 + * @throws ExecutionException 如果没有任务成功完成 + * @throws TimeoutException 如果在所有任务成功完成之前给定的超时期满 + */ + public T invokeAny( + final Collection> tasks, + final long timeout, + final TimeUnit unit + ) + throws InterruptedException, ExecutionException, TimeoutException { + if (mThreadPool != null && tasks != null && unit != null) { + return mThreadPool.invokeAny(tasks, timeout, unit); + } + return null; + } + + // = + + /** + * 延迟执行 Runnable 命令 + * @param command 命令 + * @param delay 延迟时间 + * @param unit 单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在完成后将返回 {@code null} + */ + public ScheduledFuture schedule( + final Runnable command, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.schedule(command, delay, unit); + } + return null; + } + + /** + * 延迟执行 Callable 命令 + * @param callable 命令 + * @param delay 延迟时间 + * @param unit 时间单位 + * @param 泛型 + * @return 可用于提取结果或取消的 ScheduledFuture + */ + public ScheduledFuture schedule( + final Callable callable, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && callable != null && unit != null) { + return mScheduleExec.schedule(callable, delay, unit); + } + return null; + } + + /** + * 延迟并循环执行命令 + * @param command 命令 + * @param initialDelay 首次执行的延迟时间 + * @param period 连续执行之间的周期 + * @param unit 时间单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在取消后将抛出异常 + */ + public ScheduledFuture scheduleWithFixedRate( + final Runnable command, + final long initialDelay, + final long period, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.scheduleAtFixedRate(command, initialDelay, period, unit); + } + return null; + } + + /** + * 延迟并以固定休息时间循环执行命令 + * @param command 命令 + * @param initialDelay 首次执行的延迟时间 + * @param delay 每一次执行终止和下一次执行开始之间的延迟 + * @param unit 时间单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在取消后将抛出异常 + */ + public ScheduledFuture scheduleWithFixedDelay( + final Runnable command, + final long initialDelay, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/validator/BankCheckUtils.java b/lib/DevApp/src/main/java/dev/utils/common/validator/BankCheckUtils.java new file mode 100644 index 0000000000..02683179e6 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/validator/BankCheckUtils.java @@ -0,0 +1,745 @@ +package dev.utils.common.validator; + +import dev.utils.JCLogUtils; + +/** + * detail: 银行卡管理工具类 + * @author AbrahamCaiJin + * @author Ttt + *
+ *     当你输入信用卡号码的时候, 有没有担心输错了而造成损失呢, 其实可以不必这么担心
+ *     因为并不是一个随便的信用卡号码都是合法的, 它必须通过 Luhn 算法来验证通过
+ *     

+ * 该校验的过程: + * 1、从卡号最后一位数字开始, 逆向将奇数位 (1、3、5 等等 ) 相加 + * 2、从卡号最后一位数字开始, 逆向将偶数位数字, 先乘以 2 ( 如果乘积为两位数, 则将其减去 9), 再求和 + * 3、将奇数位总和加上偶数位总和, 结果应该可以被 10 整除 + *

+ * 例如, 卡号是: 5432123456788881 + * 则奇数、偶数位 ( 用红色标出 ) 分布: 5432123456788881 + * 奇数位和 = 35 + * 偶数位乘以 2 ( 有些要减去 9) 的结果: 1 6 2 6 1 5 7 7, 求和 = 35 + * 最后 35 + 35 = 70 可以被 10 整除, 认定校验通过 + *
+ */ +public final class BankCheckUtils { + + private BankCheckUtils() { + } + + // 日志 TAG + private static final String TAG = BankCheckUtils.class.getSimpleName(); + + /** + * 校验银行卡卡号是否合法 + * @param cardId 待校验银行卡号 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkBankCard(final String cardId) { + if (cardId == null || cardId.trim().length() == 0) return false; + try { + char bit = getBankCardCheckCode(cardId.substring(0, cardId.length() - 1)); + if (bit == 'N') return false; + return (cardId.charAt(cardId.length() - 1) == bit); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "checkBankCard"); + return false; + } + } + + /** + * 从不含校验位的银行卡卡号采用 Luhn 校验算法获取校验位 + * @param nonCheckCodeCardId 待校验银行卡号 + * @return 银行卡校验位 + */ + public static char getBankCardCheckCode(final String nonCheckCodeCardId) { + try { + if (nonCheckCodeCardId == null + || nonCheckCodeCardId.trim().length() == 0 + || !nonCheckCodeCardId.matches("\\d+")) { + // 如果传的不是数据返回 N + return 'N'; + } + char[] chs = nonCheckCodeCardId.trim().toCharArray(); + int luhnSum = 0; + int len = chs.length; + for (int i = len - 1, j = 0; i >= 0; i--, j++) { + int k = chs[i] - '0'; + if (j % 2 == 0) { + k *= 2; + k = k / 10 + k % 10; + } + luhnSum += k; + } + return (luhnSum % 10 == 0) ? '0' : (char) ((10 - luhnSum % 10) + '0'); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBankCardCheckCode"); + return 'N'; + } + } + + /** + * 通过银行卡的 前六位确定 判断银行开户行及卡种 + * @param cardBin 待校验银行卡号 + * @return 银行开户行及卡种 + */ + public static String getNameOfBank(final String cardBin) { + if (cardBin == null || cardBin.trim().length() < 6) return ""; + try { + // 通过银行卡的前六位确定 + String cardBin6 = cardBin.trim().substring(0, 6); + int index = -1; + for (int i = 0, len = BANK_BIN.length; i < len; i++) { + if (cardBin6.equals(BANK_BIN[i])) { + index = i; + } + } + if (index == -1) { + return ""; + } + return BANK_NAME[index]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNameOfBank"); + return ""; + } + } + + // = + + /* + * 银行卡是由「发卡行标识代码 + 自定义 + 校验码」等部分组成的 BIN 号 银联标准卡与以往发行的银行卡最直接的区别就是其卡号前六位数字的不同 + * 银行卡卡号的前六位是用来表示发卡银行或机构的, 称为「发卡行识别码」(Bank Identification Number, 缩写为「BIN」) + * 银联标准卡是由国内各家商业银行 ( 含邮储、信用社 ) 共同发行、符合银联业务规范和技术标准、卡正面右下角带有「银联」标识 + * ( 目前新发行的银联标准卡一定带有国际化的银联新标识, 新发的非银联标准卡使用旧的联网通用银联标识 )、卡号前六位为 622126 至 622925 之一的银行卡 + * 是中国银行卡产业共有的民族品牌 + */ + + // BIN 号 + private static final String[] BANK_BIN = { + "621098", "622150", "622151", + "622181", "622188", "955100", "621095", "620062", "621285", + "621798", "621799", "621797", "620529", "622199", "621096", + "621622", "623219", "621674", "623218", "621599", "370246", + "370248", "370249", "427010", "427018", "427019", "427020", + "427029", "427030", "427039", "370247", "438125", "438126", + "451804", "451810", "451811", "458071", "489734", "489735", + "489736", "510529", "427062", "524091", "427064", "530970", + "530990", "558360", "620200", "620302", "620402", "620403", + "620404", "524047", "620406", "620407", "525498", "620409", + "620410", "620411", "620412", "620502", "620503", "620405", + "620408", "620512", "620602", "620604", "620607", "620611", + "620612", "620704", "620706", "620707", "620708", "620709", + "620710", "620609", "620712", "620713", "620714", "620802", + "620711", "620904", "620905", "621001", "620902", "621103", + "621105", "621106", "621107", "621102", "621203", "621204", + "621205", "621206", "621207", "621208", "621209", "621210", + "621302", "621303", "621202", "621305", "621306", "621307", + "621309", "621311", "621313", "621211", "621315", "621304", + "621402", "621404", "621405", "621406", "621407", "621408", + "621409", "621410", "621502", "621317", "621511", "621602", + "621603", "621604", "621605", "621608", "621609", "621610", + "621611", "621612", "621613", "621614", "621615", "621616", + "621617", "621607", "621606", "621804", "621807", "621813", + "621814", "621817", "621901", "621904", "621905", "621906", + "621907", "621908", "621909", "621910", "621911", "621912", + "621913", "621915", "622002", "621903", "622004", "622005", + "622006", "622007", "622008", "622010", "622011", "622012", + "621914", "622015", "622016", "622003", "622018", "622019", + "622020", "622102", "622103", "622104", "622105", "622013", + "622111", "622114", "622200", "622017", "622202", "622203", + "622208", "622210", "622211", "622212", "622213", "622214", + "622110", "622220", "622223", "622225", "622229", "622230", + "622231", "622232", "622233", "622234", "622235", "622237", + "622215", "622239", "622240", "622245", "622224", "622303", + "622304", "622305", "622306", "622307", "622308", "622309", + "622238", "622314", "622315", "622317", "622302", "622402", + "622403", "622404", "622313", "622504", "622505", "622509", + "622513", "622517", "622502", "622604", "622605", "622606", + "622510", "622703", "622715", "622806", "622902", "622903", + "622706", "623002", "623006", "623008", "623011", "623012", + "622904", "623015", "623100", "623202", "623301", "623400", + "623500", "623602", "623803", "623901", "623014", "624100", + "624200", "624301", "624402", "62451804", "62451810", "62451811", + "62458071", "623700", "628288", "624000", "628286", "622206", + "621225", "526836", "513685", "543098", "458441", "620058", + "621281", "622246", "900000", "544210", "548943", "370267", + "621558", "621559", "621722", "621723", "620086", "621226", + "402791", "427028", "427038", "548259", "356879", "356880", + "356881", "356882", "528856", "621618", "620516", "621227", + "621721", "900010", "625330", "625331", "625332", "623062", + "622236", "621670", "524374", "550213", "374738", "374739", + "621288", "625708", "625709", "622597", "622599", "360883", + "360884", "625865", "625866", "625899", "621376", "620054", + "620142", "621428", "625939", "621434", "625987", "621761", + "621749", "620184", "621300", "621378", "625114", "622159", + "621720", "625021", "625022", "621379", "620114", "620146", + "621724", "625918", "621371", "620143", "620149", "621414", + "625914", "621375", "620187", "621433", "625986", "621370", + "625925", "622926", "622927", "622928", "622929", "622930", + "622931", "620124", "620183", "620561", "625116", "622227", + "621372", "621464", "625942", "622158", "625917", "621765", + "620094", "620186", "621719", "621719", "621750", "621377", + "620148", "620185", "621374", "621731", "621781", "552599", + "623206", "621671", "620059", "403361", "404117", "404118", + "404119", "404120", "404121", "463758", "514027", "519412", + "519413", "520082", "520083", "558730", "621282", "621336", + "621619", "622821", "622822", "622823", "622824", "622825", + "622826", "622827", "622828", "622836", "622837", "622840", + "622841", "622843", "622844", "622845", "622846", "622847", + "622848", "622849", "623018", "625996", "625997", "625998", + "628268", "625826", "625827", "548478", "544243", "622820", + "622830", "622838", "625336", "628269", "620501", "621660", + "621661", "621662", "621663", "621665", "621667", "621668", + "621669", "621666", "625908", "625910", "625909", "356833", + "356835", "409665", "409666", "409668", "409669", "409670", + "409671", "409672", "456351", "512315", "512316", "512411", + "512412", "514957", "409667", "518378", "518379", "518474", + "518475", "518476", "438088", "524865", "525745", "525746", + "547766", "552742", "553131", "558868", "514958", "622752", + "622753", "622755", "524864", "622757", "622758", "622759", + "622760", "622761", "622762", "622763", "601382", "622756", + "628388", "621256", "621212", "620514", "622754", "622764", + "518377", "622765", "622788", "621283", "620061", "621725", + "620040", "558869", "621330", "621331", "621332", "621333", + "621297", "377677", "621568", "621569", "625905", "625906", + "625907", "628313", "625333", "628312", "623208", "621620", + "621756", "621757", "621758", "621759", "621785", "621786", + "621787", "621788", "621789", "621790", "621672", "625337", + "625338", "625568", "621648", "621248", "621249", "622750", + "622751", "622771", "622772", "622770", "625145", "620531", + "620210", "620211", "622479", "622480", "622273", "622274", + "621231", "621638", "621334", "625140", "621395", "622725", + "622728", "621284", "421349", "434061", "434062", "436728", + "436742", "453242", "491031", "524094", "526410", "544033", + "552245", "589970", "620060", "621080", "621081", "621466", + "621467", "621488", "621499", "621598", "621621", "621700", + "622280", "622700", "622707", "622966", "622988", "625955", + "625956", "553242", "621082", "621673", "623211", "356896", + "356899", "356895", "436718", "436738", "436745", "436748", + "489592", "531693", "532450", "532458", "544887", "552801", + "557080", "558895", "559051", "622166", "622168", "622708", + "625964", "625965", "625966", "628266", "628366", "625362", + "625363", "628316", "628317", "620021", "620521", "405512", + "601428", "405512", "434910", "458123", "458124", "520169", + "522964", "552853", "601428", "622250", "622251", "521899", + "622254", "622255", "622256", "622257", "622258", "622259", + "622253", "622261", "622284", "622656", "628216", "622252", + "66405512", "622260", "66601428", "955590", "955591", "955592", + "955593", "628218", "622262", "621069", "620013", "625028", + "625029", "621436", "621002", "621335", "433670", "433680", + "442729", "442730", "620082", "622690", "622691", "622692", + "622696", "622698", "622998", "622999", "433671", "968807", + "968808", "968809", "621771", "621767", "621768", "621770", + "621772", "621773", "620527", "356837", "356838", "486497", + "622660", "622662", "622663", "622664", "622665", "622666", + "622667", "622669", "622670", "622671", "622672", "622668", + "622661", "622674", "622673", "620518", "621489", "621492", + "620535", "623156", "621490", "621491", "620085", "623155", + "623157", "623158", "623159", "999999", "621222", "623020", + "623021", "623022", "623023", "622630", "622631", "622632", + "622633", "622615", "622616", "622618", "622622", "622617", + "622619", "415599", "421393", "421865", "427570", "427571", + "472067", "472068", "622620", "621691", "545392", "545393", + "545431", "545447", "356859", "356857", "407405", "421869", + "421870", "421871", "512466", "356856", "528948", "552288", + "622600", "622601", "622602", "517636", "622621", "628258", + "556610", "622603", "464580", "464581", "523952", "545217", + "553161", "356858", "622623", "625911", "377152", "377153", + "377158", "377155", "625912", "625913", "356885", "356886", + "356887", "356888", "356890", "402658", "410062", "439188", + "439227", "468203", "479228", "479229", "512425", "521302", + "524011", "356889", "545620", "545621", "545947", "545948", + "552534", "552587", "622575", "622576", "622577", "622579", + "622580", "545619", "622581", "622582", "622588", "622598", + "622609", "690755", "690755", "545623", "621286", "620520", + "621483", "621485", "621486", "628290", "622578", "370285", + "370286", "370287", "370289", "439225", "518710", "518718", + "628362", "439226", "628262", "625802", "625803", "621299", + "966666", "622909", "622908", "438588", "438589", "461982", + "486493", "486494", "486861", "523036", "451289", "527414", + "528057", "622901", "622902", "622922", "628212", "451290", + "524070", "625084", "625085", "625086", "625087", "548738", + "549633", "552398", "625082", "625083", "625960", "625961", + "625962", "625963", "356851", "356852", "404738", "404739", + "456418", "498451", "515672", "356850", "517650", "525998", + "622177", "622277", "622516", "622517", "622518", "622520", + "622521", "622522", "622523", "628222", "628221", "984301", + "984303", "622176", "622276", "622228", "621352", "621351", + "621390", "621792", "625957", "625958", "621791", "620530", + "625993", "622519", "621793", "621795", "621796", "622500", + "623078", "622384", "940034", "940015", "622886", "622391", + "940072", "622359", "940066", "622857", "940065", "621019", + "622309", "621268", "622884", "621453", "622684", "621016", + "621015", "622950", "622951", "621072", "623183", "623185", + "621005", "622172", "622985", "622987", "622267", "622278", + "622279", "622468", "622892", "940021", "621050", "620522", + "356827", "356828", "356830", "402673", "402674", "438600", + "486466", "519498", "520131", "524031", "548838", "622148", + "622149", "622268", "356829", "622300", "628230", "622269", + "625099", "625953", "625350", "625351", "625352", "519961", + "625839", "421317", "602969", "621030", "621420", "621468", + "623111", "422160", "422161", "622865", "940012", "623131", + "622178", "622179", "628358", "622394", "940025", "621279", + "622281", "622316", "940022", "621418", "512431", "520194", + "621626", "623058", "602907", "622986", "622989", "622298", + "622338", "940032", "623205", "621977", "990027", "622325", + "623029", "623105", "621244", "623081", "623108", "566666", + "622455", "940039", "622466", "628285", "622420", "940041", + "623118", "603708", "622993", "623070", "623069", "623172", + "623173", "622383", "622385", "628299", "603506", "603367", + "622878", "623061", "623209", "628242", "622595", "622303", + "622305", "621259", "622596", "622333", "940050", "621439", + "623010", "621751", "628278", "625502", "625503", "625135", + "622476", "621754", "622143", "940001", "623026", "623086", + "628291", "621532", "621482", "622135", "622152", "622153", + "622154", "622996", "622997", "940027", "623099", "623007", + "940055", "622397", "622398", "940054", "622331", "622426", + "625995", "621452", "628205", "628214", "625529", "622428", + "621529", "622429", "621417", "623089", "623200", "940057", + "622311", "623119", "622877", "622879", "621775", "623203", + "603601", "622137", "622327", "622340", "622366", "622134", + "940018", "623016", "623096", "940049", "622425", "622425", + "621577", "622485", "623098", "628329", "621538", "940006", + "621269", "622275", "621216", "622465", "940031", "621252", + "622146", "940061", "621419", "623170", "622440", "940047", + "940017", "622418", "623077", "622413", "940002", "623188", + "622310", "940068", "622321", "625001", "622427", "940069", + "623039", "628273", "622370", "683970", "940074", "621437", + "628319", "990871", "622308", "621415", "623166", "622132", + "621340", "621341", "622140", "623073", "622147", "621633", + "622301", "623171", "621422", "622335", "622336", "622165", + "622315", "628295", "625950", "621760", "622337", "622411", + "623102", "622342", "623048", "622367", "622392", "623085", + "622395", "622441", "622448", "621413", "622856", "621037", + "621097", "621588", "623032", "622644", "623518", "622870", + "622866", "623072", "622897", "628279", "622864", "621403", + "622561", "622562", "622563", "622167", "622777", "621497", + "622868", "622899", "628255", "625988", "622566", "622567", + "622625", "622626", "625946", "628200", "621076", "504923", + "622173", "622422", "622447", "622131", "940076", "621579", + "622876", "622873", "622962", "622936", "623060", "622937", + "623101", "621460", "622939", "622960", "623523", "621591", + "622961", "628210", "622283", "625902", "621010", "622980", + "623135", "621726", "621088", "620517", "622740", "625036", + "621014", "621004", "622972", "623196", "621028", "623083", + "628250", "623121", "621070", "628253", "622979", "621035", + "621038", "621086", "621498", "621296", "621448", "622945", + "621755", "622940", "623120", "628355", "621089", "623161", + "628339", "621074", "621515", "623030", "621345", "621090", + "623178", "621091", "623168", "621057", "623199", "621075", + "623037", "628303", "621233", "621235", "621223", "621780", + "621221", "623138", "628389", "621239", "623068", "621271", + "628315", "621272", "621738", "621273", "623079", "621263", + "621325", "623084", "621327", "621753", "628331", "623160", + "621366", "621388", "621348", "621359", "621360", "621217", + "622959", "621270", "622396", "622511", "623076", "621391", + "621339", "621469", "621625", "623688", "623113", "621601", + "621655", "621636", "623182", "623087", "621696", "622955", + "622478", "940013", "621495", "621688", "623162", "622462", + "628272", "625101", "622323", "623071", "603694", "622128", + "622129", "623035", "623186", "621522", "622271", "940037", + "940038", "985262", "622322", "628381", "622481", "622341", + "940058", "623115", "621258", "621465", "621528", "622328", + "940062", "625288", "623038", "625888", "622332", "940063", + "623123", "622138", "621066", "621560", "621068", "620088", + "621067", "622531", "622329", "623103", "622339", "620500", + "621024", "622289", "622389", "628300", "625516", "621516", + "622859", "622869", "623075", "622895", "623125", "622947", + "621561", "623095", "621073", "623109", "621361", "623033", + "623207", "622891", "621363", "623189", "623510", "622995", + "621053", "621230", "621229", "622218", "628267", "621392", + "621481", "621310", "621396", "623251", "628351" + }; + + // 发卡行·卡种名称 + private static final String[] BANK_NAME = { + "邮储银行·绿卡通", "邮储银行·绿卡银联标准卡", + "邮储银行·绿卡银联标准卡", "邮储银行·绿卡专用卡", "邮储银行·绿卡银联标准卡", "邮储银行·绿卡 (银联卡)", + "邮储银行·绿卡 VIP 卡", "邮储银行·银联标准卡", "邮储银行·中职学生资助卡", "邮政储蓄银行·IC 绿卡通 VIP 卡", + "邮政储蓄银行·IC 绿卡通", "邮政储蓄银行·IC 联名卡", "邮政储蓄银行·IC 预付费卡", "邮储银行·绿卡银联标准卡", + "邮储银行·绿卡通", "邮政储蓄银行·武警军人保障卡", "邮政储蓄银行·中国旅游卡 (金卡)", + "邮政储蓄银行·普通高中学生资助卡", "邮政储蓄银行·中国旅游卡 (普卡)", "邮政储蓄银行·福农卡", + "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", + "工商银行·牡丹 VISA 卡 (单位卡)", "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 卡 (单位卡)", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 信用卡", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹运通卡普通卡", "工商银行·牡丹 VISA 信用卡", + "工商银行·牡丹 VISA 白金卡", "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹贷记卡 (银联卡)", + "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹欧元卡", "工商银行·牡丹欧元卡", + "工商银行·牡丹欧元卡", "工商银行·牡丹万事达国际借记卡", "工商银行·牡丹 VISA 信用卡", "工商银行·海航信用卡", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹万事达信用卡", "工商银行·牡丹万事达信用卡", + "工商银行·牡丹万事达信用卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹万事达白金卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·海航信用卡个人普卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·灵通卡", "工商银行·牡丹灵通卡", "工商银行·E 时代卡", "工商银行·E 时代卡", + "工商银行·理财金卡", "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", + "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", "工商银行·牡丹灵通卡", "工商银行·准贷记卡 (商普)", + "工商银行·牡丹卡 (商务卡)", "工商银行·准贷记卡 (商金)", "工商银行·牡丹卡 (商务卡)", "工商银行·贷记卡 (个普)", + "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", + "工商银行·贷记卡 (个金)", "工商银行·牡丹交通卡", "工商银行·准贷记卡 (个金)", "工商银行·牡丹交通卡", + "工商银行·贷记卡 (商普)", "工商银行·贷记卡 (商金)", "工商银行·牡丹卡 (商务卡)", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹交通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹贷记卡", + "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹灵通卡", + "工商银行·中央预算单位公务卡", "工商银行·牡丹灵通卡", "工商银行·财政预算单位公务卡", "工商银行·牡丹卡白金卡", + "工商银行·牡丹卡普卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", + "工商银行·国航知音牡丹信用卡", "工商银行·银联标准卡", "工商银行·中职学生资助卡", "工商银行·专用信用消费卡", + "工商银行·牡丹社会保障卡", "中国工商银行·牡丹东航联名卡", "中国工商银行·牡丹东航联名卡", + "中国工商银行·牡丹运通白金卡", "中国工商银行·福农灵通卡", "中国工商银行·福农灵通卡", "工商银行·灵通卡", + "工商银行·灵通卡", "中国工商银行·中国旅行卡", "工商银行·牡丹卡普卡", "工商银行·国际借记卡", + "工商银行·国际借记卡", "工商银行·国际借记卡", "工商银行·国际借记卡", "中国工商银行·牡丹 JCB 信用卡", + "中国工商银行·牡丹 JCB 信用卡", "中国工商银行·牡丹 JCB 信用卡", "中国工商银行·牡丹 JCB 信用卡", + "中国工商银行·牡丹多币种卡", "中国工商银行·武警军人保障卡", "工商银行·预付芯片卡", "工商银行·理财金账户金卡", + "工商银行·灵通卡", "工商银行·牡丹宁波市民卡", "中国工商银行·中国旅游卡", "中国工商银行·中国旅游卡", + "中国工商银行·中国旅游卡", "中国工商银行·借记卡", "中国工商银行·借贷合一卡", "中国工商银行·普通高中学生资助卡", + "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹百夫长信用卡", + "中国工商银行·牡丹百夫长信用卡", "工商银行·工银财富卡", "中国工商银行·中小商户采购卡", + "中国工商银行·中小商户采购卡", "中国工商银行·环球旅行金卡", "中国工商银行·环球旅行白金卡", + "中国工商银行·牡丹工银大来卡", "中国工商银行·牡丹工银大莱卡", "中国工商银行·IC 金卡", "中国工商银行·IC 白金卡", + "中国工商银行·工行 IC 卡 (红卡)", "中国工商银行布鲁塞尔分行·借记卡", "中国工商银行布鲁塞尔分行·预付卡", + "中国工商银行布鲁塞尔分行·预付卡", "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", + "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", "中国工商银行加拿大分行·借记卡", + "中国工商银行加拿大分行·借记卡", "中国工商银行加拿大分行·预付卡", "中国工商银行巴黎分行·借记卡", + "中国工商银行巴黎分行·借记卡", "中国工商银行巴黎分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", + "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", + "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·预付卡", "中国工商银行法兰克福分行·预付卡", + "中国工商银行印尼分行·借记卡", "中国工商银行印尼分行·信用卡", "中国工商银行米兰分行·借记卡", + "中国工商银行米兰分行·预付卡", "中国工商银行米兰分行·预付卡", "中国工商银行阿拉木图子行·借记卡", + "中国工商银行阿拉木图子行·贷记卡", "中国工商银行阿拉木图子行·借记卡", "中国工商银行阿拉木图子行·预付卡", + "中国工商银行万象分行·借记卡", "中国工商银行万象分行·贷记卡", "中国工商银行卢森堡分行·借记卡", + "中国工商银行卢森堡分行·贷记卡", "中国工商银行澳门分行·E 时代卡", "中国工商银行澳门分行·E 时代卡", + "中国工商银行澳门分行·E 时代卡", "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·理财金账户", + "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·预付卡", "中国工商银行澳门分行·预付卡", + "中国工商银行澳门分行·工银闪付预付卡", "中国工商银行澳门分行·工银银联公司卡", "中国工商银行澳门分行·Diamond", + "中国工商银行阿姆斯特丹·借记卡", "中国工商银行卡拉奇分行·借记卡", "中国工商银行卡拉奇分行·贷记卡", + "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·借记卡", + "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·借记卡", + "中国工商银行新加坡分行·借记卡", "中国工商银行马德里分行·借记卡", "中国工商银行马德里分行·借记卡", + "中国工商银行马德里分行·预付卡", "中国工商银行马德里分行·预付卡", "中国工商银行伦敦子行·借记卡", + "中国工商银行伦敦子行·工银伦敦借记卡", "中国工商银行伦敦子行·借记卡", "农业银行·金穗贷记卡", "农业银行·中国旅游卡", + "农业银行·普通高中学生资助卡", "农业银行·银联标准卡", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·VISA 白金卡", + "农业银行·万事达白金卡", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡", "农业银行·中职学生资助卡", + "农业银行·专用惠农卡", "农业银行·武警军人保障卡", "农业银行·金穗校园卡 (银联卡)", "农业银行·金穗星座卡 (银联卡)", + "农业银行·金穗社保卡 (银联卡)", "农业银行·金穗旅游卡 (银联卡)", "农业银行·金穗青年卡 (银联卡)", + "农业银行·复合介质金穗通宝卡", "农业银行·金穗海通卡", "农业银行·退役金卡", "农业银行·金穗贷记卡", + "农业银行·金穗贷记卡", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗惠农卡", "农业银行·金穗通宝银卡", + "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡", + "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝钻石卡", "农业银行·掌尚钱包", + "农业银行·银联 IC 卡金卡", "农业银行·银联预算单位公务卡金卡", "农业银行·银联 IC 卡白金卡", "农业银行·金穗公务卡", + "中国农业银行贷记卡·IC 普卡", "中国农业银行贷记卡·IC 金卡", "中国农业银行贷记卡·澳元卡", + "中国农业银行贷记卡·欧元卡", "中国农业银行贷记卡·金穗通商卡", "中国农业银行贷记卡·金穗通商卡", + "中国农业银行贷记卡·银联白金卡", "中国农业银行贷记卡·中国旅游卡", "中国农业银行贷记卡·银联 IC 公务卡", + "宁波市农业银行·市民卡 B 卡", "中国银行·联名卡", "中国银行·个人普卡", "中国银行·个人金卡", "中国银行·员工普卡", + "中国银行·员工金卡", "中国银行·理财普卡", "中国银行·理财金卡", "中国银行·理财银卡", "中国银行·理财白金卡", + "中国银行·中行金融 IC 卡白金卡", "中国银行·中行金融 IC 卡普卡", "中国银行·中行金融 IC 卡金卡", + "中国银行·中银 JCB 卡金卡", "中国银行·中银 JCB 卡普卡", "中国银行·员工普卡", "中国银行·个人普卡", + "中国银行·中银威士信用卡员", "中国银行·中银威士信用卡员", "中国银行·个人白金卡", "中国银行·中银威士信用卡", + "中国银行·长城公务卡", "中国银行·长城电子借记卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", + "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银威士信用卡员", + "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", + "中国银行·长城万事达信用卡", "中国银行·中银奥运信用卡", "中国银行·长城信用卡", "中国银行·长城信用卡", + "中国银行·长城信用卡", "中国银行·长城万事达信用卡", "中国银行·长城公务卡", "中国银行·长城公务卡", + "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", + "中国银行·长城人民币信用卡", "中国银行·长城信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", + "中国银行·长城信用卡", "中国银行·银联单币贷记卡", "中国银行·长城信用卡", "中国银行·长城信用卡", + "中国银行·长城信用卡", "中国银行·长城电子借记卡", "中国银行·长城人民币信用卡", "中国银行·银联标准公务卡", + "中国银行·一卡双账户普卡", "中国银行·财互通卡", "中国银行·电子现金卡", "中国银行·长城人民币信用卡", + "中国银行·长城单位信用卡普卡", "中国银行·中银女性主题信用卡", "中国银行·长城单位信用卡金卡", "中国银行·白金卡", + "中国银行·中职学生资助卡", "中国银行·银联标准卡", "中国银行·金融 IC 卡", "中国银行·长城社会保障卡", + "中国银行·世界卡", "中国银行·社保联名卡", "中国银行·社保联名卡", "中国银行·医保联名卡", "中国银行·医保联名卡", + "中国银行·公司借记卡", "中国银行·银联美运顶级卡", "中国银行·长城福农借记卡金卡", "中国银行·长城福农借记卡普卡", + "中国银行·中行金融 IC 卡普卡", "中国银行·中行金融 IC 卡金卡", "中国银行·中行金融 IC 卡白金卡", + "中国银行·长城银联公务 IC 卡白金卡", "中国银行·中银旅游信用卡", "中国银行·长城银联公务 IC 卡金卡", + "中国银行·中国旅游卡", "中国银行·武警军人保障卡", "中国银行·社保联名借记 IC 卡", "中国银行·社保联名借记 IC 卡", + "中国银行·医保联名借记 IC 卡", "中国银行·医保联名借记 IC 卡", "中国银行·借记 IC 个人普卡", + "中国银行·借记 IC 个人金卡", "中国银行·借记 IC 个人普卡", "中国银行·借记 IC 白金卡", "中国银行·借记 IC 钻石卡", + "中国银行·借记 IC 联名卡", "中国银行·普通高中学生资助卡", "中国银行·长城环球通港澳台旅游金卡", + "中国银行·长城环球通港澳台旅游白金卡", "中国银行·中银福农信用卡", "中国银行金边分行·借记卡", + "中国银行雅加达分行·借记卡", "中国银行首尔分行·借记卡", "中国银行澳门分行·人民币信用卡", + "中国银行澳门分行·人民币信用卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", + "中国银行澳门分行·中银银联双币商务卡", "中国银行澳门分行·预付卡", "中国银行澳门分行·澳门中国银行银联预付卡", + "中国银行澳门分行·澳门中国银行银联预付卡", "中国银行澳门分行·熊猫卡", "中国银行澳门分行·财富卡", + "中国银行澳门分行·银联港币卡", "中国银行澳门分行·银联澳门币卡", "中国银行马尼拉分行·双币种借记卡", + "中国银行胡志明分行·借记卡", "中国银行曼谷分行·借记卡", "中国银行曼谷分行·长城信用卡环球通", + "中国银行曼谷分行·借记卡", "建设银行·龙卡准贷记卡", "建设银行·龙卡准贷记卡金卡", "建设银行·中职学生资助卡", + "建设银行·乐当家银卡 VISA", "建设银行·乐当家金卡 VISA", "建设银行·乐当家白金卡", + "建设银行·龙卡普通卡 VISA", "建设银行·龙卡储蓄卡", "建设银行·VISA 准贷记卡 (银联卡)", + "建设银行·VISA 准贷记金卡", "建设银行·乐当家", "建设银行·乐当家", "建设银行·准贷记金卡", + "建设银行·乐当家白金卡", "建设银行·金融复合 IC 卡", "建设银行·银联标准卡", "建设银行·银联理财钻石卡", + "建设银行·金融 IC 卡", "建设银行·理财白金卡", "建设银行·社保 IC 卡", "建设银行·财富卡私人银行卡", + "建设银行·理财金卡", "建设银行·福农卡", "建设银行·武警军人保障卡", "建设银行·龙卡通", "建设银行·银联储蓄卡", + "建设银行·龙卡储蓄卡 (银联卡)", "建设银行·准贷记卡", "建设银行·理财白金卡", "建设银行·理财金卡", + "建设银行·准贷记卡普卡", "建设银行·准贷记卡金卡", "建设银行·龙卡信用卡", "建设银行·建行陆港通龙卡", + "中国建设银行·普通高中学生资助卡", "中国建设银行·中国旅游卡", "中国建设银行·龙卡 JCB 金卡", + "中国建设银行·龙卡 JCB 白金卡", "中国建设银行·龙卡 JCB 普卡", "中国建设银行·龙卡贷记卡公司卡", + "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡国际普通卡 VISA", "中国建设银行·龙卡国际金卡 VISA", + "中国建设银行·VISA 白金信用卡", "中国建设银行·龙卡国际白金卡", "中国建设银行·龙卡国际普通卡 MASTER", + "中国建设银行·龙卡国际金卡 MASTER", "中国建设银行·龙卡万事达金卡", "中国建设银行·龙卡贷记卡", + "中国建设银行·龙卡万事达白金卡", "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡万事达信用卡", + "中国建设银行·龙卡人民币信用卡", "中国建设银行·龙卡人民币信用金卡", "中国建设银行·龙卡人民币白金卡", + "中国建设银行·龙卡 IC 信用卡普卡", "中国建设银行·龙卡 IC 信用卡金卡", "中国建设银行·龙卡 IC 信用卡白金卡", + "中国建设银行·龙卡银联公务卡普卡", "中国建设银行·龙卡银联公务卡金卡", "中国建设银行·中国旅游卡", + "中国建设银行·中国旅游卡", "中国建设银行·龙卡 IC 公务卡", "中国建设银行·龙卡 IC 公务卡", "交通银行·交行预付卡", + "交通银行·世博预付 IC 卡", "交通银行·太平洋互连卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋互连卡 (银联卡)", + "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", + "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋人民币贷记卡", + "交通银行·太平洋人民币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", + "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋借记卡", + "交通银行·太平洋人民币贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋 MORE 卡", "交通银行·白金卡", + "交通银行·交通银行公务卡普卡", "交通银行·太平洋人民币贷记卡", "交通银行·太平洋互连卡", "交通银行·太平洋借记卡", + "交通银行·太平洋万事顺卡", "交通银行·太平洋贷记卡 (银联卡)", "交通银行·太平洋贷记卡 (银联卡)", + "交通银行·太平洋贷记卡 (银联卡)", "交通银行·太平洋贷记卡 (银联卡)", "交通银行·交通银行公务卡金卡", + "交通银行·交银 IC 卡", "交通银行香港分行·交通银行港币借记卡", "交通银行香港分行·港币礼物卡", + "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币卡", + "交通银行香港分行·银联人民币卡", "交通银行澳门分行·银联借记卡", "中信银行·中信借记卡", "中信银行·中信借记卡", + "中信银行·中信国际借记卡", "中信银行·中信国际借记卡", "中信银行·中国旅行卡", "中信银行·中信借记卡 (银联卡)", + "中信银行·中信借记卡 (银联卡)", "中信银行·中信贵宾卡 (银联卡)", "中信银行·中信理财宝金卡", + "中信银行·中信理财宝白金卡", "中信银行·中信钻石卡", "中信银行·中信钻石卡", "中信银行·中信借记卡", + "中信银行·中信理财宝 (银联卡)", "中信银行·中信理财宝 (银联卡)", "中信银行·中信理财宝 (银联卡)", + "中信银行·借记卡", "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", + "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", "中信银行·主账户复合电子现金卡", "光大银行·阳光商旅信用卡", + "光大银行·阳光商旅信用卡", "光大银行·阳光商旅信用卡", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·借记卡普卡", "光大银行·社会保障 IC 卡", "光大银行·IC 借记卡普卡", + "光大银行·手机支付卡", "光大银行·联名 IC 卡普卡", "光大银行·借记 IC 卡白金卡", "光大银行·借记 IC 卡金卡", + "光大银行·阳光旅行卡", "光大银行·借记 IC 卡钻石卡", "光大银行·联名 IC 卡金卡", "光大银行·联名 IC 卡白金卡", + "光大银行·联名 IC 卡钻石卡", "华夏银行·华夏卡 (银联卡)", "华夏银行·华夏白金卡", "华夏银行·华夏普卡", + "华夏银行·华夏金卡", "华夏银行·华夏白金卡", "华夏银行·华夏钻石卡", "华夏银行·华夏卡 (银联卡)", + "华夏银行·华夏至尊金卡 (银联卡)", "华夏银行·华夏丽人卡 (银联卡)", "华夏银行·华夏万通卡", + "民生银行·民生借记卡 (银联卡)", "民生银行·民生银联借记卡-金卡", "民生银行·钻石卡", + "民生银行·民生借记卡 (银联卡)", "民生银行·民生借记卡 (银联卡)", "民生银行·民生借记卡 (银联卡)", + "民生银行·民生借记卡", "民生银行·民生国际卡", "民生银行·民生国际卡 (银卡)", "民生银行·民生国际卡 (欧元卡)", + "民生银行·民生国际卡 (澳元卡)", "民生银行·民生国际卡", "民生银行·民生国际卡", "民生银行·薪资理财卡", + "民生银行·借记卡普卡", "民生银行·民生 MasterCard", "民生银行·民生 MasterCard", + "民生银行·民生 MasterCard", "民生银行·民生 MasterCard", "民生银行·民生 JCB 信用卡", + "民生银行·民生 JCB 金卡", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生 JCB 普卡", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生信用卡 (银联卡)", "民生银行·民生信用卡 (银联卡)", "民生银行·民生银联白金信用卡", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生银联个人白金卡", "民生银行·公务卡金卡", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生银联商务信用卡", "民生银行·民 VISA 无限卡", + "民生银行·民生 VISA 商务白金卡", "民生银行·民生万事达钛金卡", "民生银行·民生万事达世界卡", + "民生银行·民生万事达白金公务卡", "民生银行·民生 JCB 白金卡", "民生银行·银联标准金卡", "民生银行·银联芯片普卡", + "民生银行·民生运通双币信用卡普卡", "民生银行·民生运通双币信用卡金卡", "民生银行·民生运通双币信用卡钻石卡", + "民生银行·民生运通双币标准信用卡白金卡", "民生银行·银联芯片金卡", "民生银行·银联芯片白金卡", + "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·两地一卡通", "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", + "招商银行·VISA 商务信用卡", "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·招行国际卡 (银联卡)", "招商银行·世纪金花联名信用卡", + "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", "招商银行·万事达信用卡", "招商银行·万事达信用卡", + "招商银行·万事达信用卡", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·一卡通 (银联卡)", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·一卡通 (银联卡)", "招商银行·公司卡 (银联卡)", "招商银行·金卡", "招商银行·招行一卡通", + "招商银行·招行一卡通", "招商银行·万事达信用卡", "招商银行·金葵花卡", "招商银行·电子现金卡", + "招商银行·银联 IC 普卡", "招商银行·银联 IC 金卡", "招商银行·银联金葵花 IC 卡", "招商银行·IC 公务卡", + "招商银行·招商银行信用卡", "招商银行信用卡中心·美国运通绿卡", "招商银行信用卡中心·美国运通金卡", + "招商银行信用卡中心·美国运通商务绿卡", "招商银行信用卡中心·美国运通商务金卡", "招商银行信用卡中心·VISA 信用卡", + "招商银行信用卡中心·MASTER 信用卡", "招商银行信用卡中心·MASTER 信用金卡", + "招商银行信用卡中心·银联标准公务卡 (金卡)", "招商银行信用卡中心·VISA 信用卡", + "招商银行信用卡中心·银联标准财政公务卡", "招商银行信用卡中心·芯片 IC 信用卡", "招商银行信用卡中心·芯片 IC 信用卡", + "招商银行香港分行·香港一卡通", "兴业银行·兴业卡 (银联卡)", "兴业银行·兴业卡 (银联标准卡)", + "兴业银行·兴业自然人生理财卡", "兴业银行·兴业智能卡 (银联卡)", "兴业银行·兴业智能卡", + "兴业银行·visa 标准双币个人普卡", "兴业银行·VISA 商务普卡", "兴业银行·VISA 商务金卡", + "兴业银行·VISA 运动白金信用卡", "兴业银行·万事达信用卡 (银联卡)", "兴业银行·VISA 信用卡 (银联卡)", + "兴业银行·加菲猫信用卡", "兴业银行·个人白金卡", "兴业银行·银联信用卡 (银联卡)", "兴业银行·银联信用卡 (银联卡)", + "兴业银行·银联白金信用卡", "兴业银行·银联标准公务卡", "兴业银行·VISA 信用卡 (银联卡)", + "兴业银行·万事达信用卡 (银联卡)", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记金卡", + "兴业银行·银联标准贷记金卡", "兴业银行·银联标准贷记金卡", "兴业银行·兴业信用卡", "兴业银行·兴业信用卡", + "兴业银行·兴业信用卡", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记普卡", "兴业银行·兴业芯片普卡", + "兴业银行·兴业芯片金卡", "兴业银行·兴业芯片白金卡", "兴业银行·兴业芯片钻石卡", "浦东发展银行·浦发 JCB 金卡", + "浦东发展银行·浦发 JCB 白金卡", "浦东发展银行·信用卡 VISA 普通", "浦东发展银行·信用卡 VISA 金卡", + "浦东发展银行·浦发银行 VISA 年青卡", "浦东发展银行·VISA 白金信用卡", "浦东发展银行·浦发万事达白金卡", + "浦东发展银行·浦发 JCB 普卡", "浦东发展银行·浦发万事达金卡", "浦东发展银行·浦发万事达普卡", + "浦东发展银行·浦发单币卡", "浦东发展银行·浦发银联单币麦兜普卡", "浦东发展银行·东方轻松理财卡", + "浦东发展银行·东方 - 轻松理财卡普卡", "浦东发展银行·东方轻松理财卡", "浦东发展银行·东方轻松理财智业金卡", + "浦东发展银行·东方卡 (银联卡)", "浦东发展银行·东方卡 (银联卡)", "浦东发展银行·东方卡 (银联卡)", + "浦东发展银行·公务卡金卡", "浦东发展银行·公务卡普卡", "浦东发展银行·东方卡", "浦东发展银行·东方卡", + "浦东发展银行·浦发单币卡", "浦东发展银行·浦发联名信用卡", "浦东发展银行·浦发银联白金卡", + "浦东发展银行·轻松理财普卡", "浦东发展银行·移动联名卡", "浦东发展银行·轻松理财消贷易卡", + "浦东发展银行·轻松理财普卡 (复合卡)", "浦东发展银行·贷记卡", "浦东发展银行·贷记卡", + "浦东发展银行·东方借记卡 (复合卡)", "浦东发展银行·电子现金卡 (IC 卡)", "浦东发展银行·移动浦发联名卡", + "浦东发展银行·东方 - 标准准贷记卡", "浦东发展银行·轻松理财金卡 (复合卡)", "浦东发展银行·轻松理财白金卡 (复合卡)", + "浦东发展银行·轻松理财钻石卡 (复合卡)", "浦东发展银行·东方卡", "恒丰银行·九州 IC 卡", + "恒丰银行·九州借记卡 (银联卡)", "恒丰银行·九州借记卡 (银联卡)", "天津市商业银行·银联卡 (银联卡)", + "烟台商业银行·金通卡", "潍坊银行·鸢都卡 (银联卡)", "潍坊银行·鸳都卡 (银联卡)", "临沂商业银行·沂蒙卡 (银联卡)", + "临沂商业银行·沂蒙卡 (银联卡)", "日照市商业银行·黄海卡", "日照市商业银行·黄海卡 (银联卡)", "浙商银行·商卡", + "浙商银行·商卡", "渤海银行·浩瀚金卡", "渤海银行·渤海银行借记卡", "渤海银行·金融 IC 卡", + "渤海银行·渤海银行公司借记卡", "星展银行·星展银行借记卡", "星展银行·星展银行借记卡", "恒生银行·恒生通财卡", + "恒生银行·恒生优越通财卡", "新韩银行·新韩卡", "上海银行·慧通钻石卡", "上海银行·慧通金卡", + "上海银行·私人银行卡", "上海银行·综合保险卡", "上海银行·申卡社保副卡 (有折)", "上海银行·申卡社保副卡 (无折)", + "上海银行·白金 IC 借记卡", "上海银行·慧通白金卡 (配折)", "上海银行·慧通白金卡 (不配折)", + "上海银行·申卡 (银联卡)", "上海银行·申卡借记卡", "上海银行·银联申卡 (银联卡)", "上海银行·单位借记卡", + "上海银行·首发纪念版 IC 卡", "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·J 分期付款信用卡", + "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·上海申卡 IC", "上海银行·申卡贷记卡", + "上海银行·申卡贷记卡普通卡", "上海银行·申卡贷记卡金卡", "上海银行·万事达白金卡", "上海银行·万事达星运卡", + "上海银行·申卡贷记卡金卡", "上海银行·申卡贷记卡普通卡", "上海银行·安融卡", "上海银行·分期付款信用卡", + "上海银行·信用卡", "上海银行·个人公务卡", "上海银行·安融卡", "上海银行·上海银行银联白金卡", + "上海银行·贷记 IC 卡", "上海银行·中国旅游卡 (IC 普卡)", "上海银行·中国旅游卡 (IC 金卡)", + "上海银行·中国旅游卡 (IC 白金卡)", "上海银行·万事达钻石卡", "上海银行·淘宝 IC 普卡", "北京银行·京卡借记卡", + "北京银行·京卡 (银联卡)", "北京银行·京卡借记卡", "北京银行·京卡", "北京银行·京卡", "北京银行·借记 IC 卡", + "北京银行·京卡贵宾金卡", "北京银行·京卡贵宾白金卡", "吉林银行·君子兰一卡通 (银联卡)", + "吉林银行·君子兰卡 (银联卡)", "吉林银行·长白山金融 IC 卡", "吉林银行·信用卡", "吉林银行·信用卡", + "吉林银行·公务卡", "镇江市商业银行·金山灵通卡 (银联卡)", "镇江市商业银行·金山灵通卡 (银联卡)", + "宁波银行·银联标准卡", "宁波银行·汇通借记卡", "宁波银行·汇通卡 (银联卡)", "宁波银行·明州卡", + "宁波银行·汇通借记卡", "宁波银行·汇通国际卡银联双币卡", "宁波银行·汇通国际卡银联双币卡", "平安银行·新磁条借记卡", + "平安银行·平安银行 IC 借记卡", "平安银行·万事顺卡", "平安银行·平安银行借记卡", "平安银行·平安银行借记卡", + "平安银行·万事顺借记卡", "焦作市商业银行·月季借记卡 (银联卡)", "焦作市商业银行·月季城市通 (银联卡)", + "焦作市商业银行·中国旅游卡", "温州银行·金鹿卡", "汉口银行·九通卡 (银联卡)", "汉口银行·九通卡", + "汉口银行·借记卡", "汉口银行·借记卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰 IC 卡", "盛京银行·玫瑰 IC 卡", + "盛京银行·玫瑰卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰卡 (银联卡)", "盛京银行·玫瑰卡 (银联卡)", + "盛京银行·盛京银行公务卡", "洛阳银行·都市一卡通 (银联卡)", "洛阳银行·都市一卡通 (银联卡)", "洛阳银行·--", + "大连银行·北方明珠卡", "大连银行·人民币借记卡", "大连银行·金融 IC 借记卡", "大连银行·大连市社会保障卡", + "大连银行·借记 IC 卡", "大连银行·借记 IC 卡", "大连银行·大连市商业银行贷记卡", "大连银行·大连市商业银行贷记卡", + "大连银行·银联标准公务卡", "苏州市商业银行·姑苏卡", "杭州商业银行·西湖卡", "杭州商业银行·西湖卡", + "杭州商业银行·借记 IC 卡", "杭州商业银行·", "南京银行·梅花信用卡公务卡", "南京银行·梅花信用卡商务卡", + "南京银行·梅花贷记卡 (银联卡)", "南京银行·梅花借记卡 (银联卡)", "南京银行·白金卡", "南京银行·商务卡", + "东莞市商业银行·万顺通卡 (银联卡)", "东莞市商业银行·万顺通卡 (银联卡)", "东莞市商业银行·万顺通借记卡", + "东莞市商业银行·社会保障卡", "乌鲁木齐市商业银行·雪莲借记 IC 卡", "乌鲁木齐市商业银行·乌鲁木齐市公务卡", + "乌鲁木齐市商业银行·福农卡贷记卡", "乌鲁木齐市商业银行·福农卡准贷记卡", "乌鲁木齐市商业银行·雪莲准贷记卡", + "乌鲁木齐市商业银行·雪莲贷记卡 (银联卡)", "乌鲁木齐市商业银行·雪莲借记 IC 卡", + "乌鲁木齐市商业银行·雪莲借记卡 (银联卡)", "乌鲁木齐市商业银行·雪莲卡 (银联卡)", "绍兴银行·兰花 IC 借记卡", + "绍兴银行·社保 IC 借记卡", "绍兴银行·兰花公务卡", "成都商业银行·芙蓉锦程福农卡", "成都商业银行·芙蓉锦程天府通卡", + "成都商业银行·锦程卡 (银联卡)", "成都商业银行·锦程卡金卡", "成都商业银行·锦程卡定活一卡通金卡", + "成都商业银行·锦程卡定活一卡通", "成都商业银行·锦程力诚联名卡", "成都商业银行·锦程力诚联名卡", + "成都商业银行·锦程卡 (银联卡)", "抚顺银行·借记 IC 卡", "临商银行·借记卡", "宜昌市商业银行·三峡卡 (银联卡)", + "宜昌市商业银行·信用卡 (银联卡)", "葫芦岛市商业银行·一通卡", "葫芦岛市商业银行·一卡通 (银联卡)", + "天津市商业银行·津卡", "天津市商业银行·津卡贷记卡 (银联卡)", "天津市商业银行·贷记 IC 卡", "天津市商业银行·--", + "天津银行·商务卡", "宁夏银行·宁夏银行公务卡", "宁夏银行·宁夏银行福农贷记卡", "宁夏银行·如意卡 (银联卡)", + "宁夏银行·宁夏银行福农借记卡", "宁夏银行·如意借记卡", "宁夏银行·如意 IC 卡", "宁夏银行·宁夏银行如意借记卡", + "宁夏银行·中国旅游卡", "齐商银行·金达卡 (银联卡)", "齐商银行·金达借记卡 (银联卡)", "齐商银行·金达 IC 卡", + "徽商银行·黄山卡", "徽商银行·黄山卡", "徽商银行·借记卡", "徽商银行·徽商银行中国旅游卡 (安徽)", + "徽商银行合肥分行·黄山卡", "徽商银行芜湖分行·黄山卡 (银联卡)", "徽商银行马鞍山分行·黄山卡 (银联卡)", + "徽商银行淮北分行·黄山卡 (银联卡)", "徽商银行安庆分行·黄山卡 (银联卡)", "重庆银行·长江卡 (银联卡)", + "重庆银行·长江卡 (银联卡)", "重庆银行·长江卡", "重庆银行·借记 IC 卡", "哈尔滨银行·丁香一卡通 (银联卡)", + "哈尔滨银行·丁香借记卡 (银联卡)", "哈尔滨银行·丁香卡", "哈尔滨银行·福农借记卡", + "无锡市商业银行·太湖金保卡 (银联卡)", "丹东银行·借记 IC 卡", "丹东银行·丹东银行公务卡", "兰州银行·敦煌卡", + "南昌银行·金瑞卡 (银联卡)", "南昌银行·南昌银行借记卡", "南昌银行·金瑞卡", "晋商银行·晋龙一卡通", + "晋商银行·晋龙一卡通", "晋商银行·晋龙卡 (银联卡)", "青岛银行·金桥通卡", "青岛银行·金桥卡 (银联卡)", + "青岛银行·金桥卡 (银联卡)", "青岛银行·金桥卡", "青岛银行·借记 IC 卡", "吉林银行·雾凇卡 (银联卡)", + "吉林银行·雾凇卡 (银联卡)", "南通商业银行·金桥卡 (银联卡)", "南通商业银行·金桥卡 (银联卡)", + "日照银行·黄海卡、财富卡借记卡", "鞍山银行·千山卡 (银联卡)", "鞍山银行·千山卡 (银联卡)", "鞍山银行·千山卡", + "青海银行·三江银行卡 (银联卡)", "青海银行·三江卡", "台州银行·大唐贷记卡", "台州银行·大唐准贷记卡", + "台州银行·大唐卡 (银联卡)", "台州银行·大唐卡", "台州银行·借记卡", "台州银行·公务卡", + "泉州银行·海峡银联卡 (银联卡)", "泉州银行·海峡储蓄卡", "泉州银行·海峡银联卡 (银联卡)", "泉州银行·海峡卡", + "泉州银行·公务卡", "昆明商业银行·春城卡 (银联卡)", "昆明商业银行·春城卡 (银联卡)", + "昆明商业银行·富滇 IC 卡 (复合卡)", "阜新银行·借记 IC 卡", "嘉兴银行·南湖借记卡 (银联卡)", "廊坊银行·白金卡", + "廊坊银行·金卡", "廊坊银行·银星卡 (银联卡)", "廊坊银行·龙凤呈祥卡", "内蒙古银行·百灵卡 (银联卡)", + "内蒙古银行·成吉思汗卡", "湖州市商业银行·百合卡", "湖州市商业银行·", "沧州银行·狮城卡", + "南宁市商业银行·桂花卡 (银联卡)", "包商银行·雄鹰卡 (银联卡)", "包商银行·包头市商业银行借记卡", + "包商银行·雄鹰贷记卡", "包商银行·包商银行内蒙古自治区公务卡", "包商银行·贷记卡", "包商银行·借记卡", + "连云港市商业银行·金猴神通借记卡", "威海商业银行·通达卡 (银联卡)", "威海市商业银行·通达借记 IC 卡", + "攀枝花市商业银行·攀枝花卡 (银联卡)", "攀枝花市商业银行·攀枝花卡", "绵阳市商业银行·科技城卡 (银联卡)", + "泸州市商业银行·酒城卡 (银联卡)", "泸州市商业银行·酒城 IC 卡", "大同市商业银行·云冈卡 (银联卡)", + "三门峡银行·天鹅卡 (银联卡)", "广东南粤银行·南珠卡 (银联卡)", "张家口市商业银行·好运 IC 借记卡", + "桂林市商业银行·漓江卡 (银联卡)", "龙江银行·福农借记卡", "龙江银行·联名借记卡", "龙江银行·福农借记卡", + "龙江银行·龙江 IC 卡", "龙江银行·社会保障卡", "龙江银行·--", "江苏长江商业银行·长江卡", + "徐州市商业银行·彭城借记卡 (银联卡)", "南充市商业银行·借记 IC 卡", "南充市商业银行·熊猫团团卡", + "莱商银行·银联标准卡", "莱芜银行·金凤卡", "莱商银行·借记 IC 卡", "德阳银行·锦程卡定活一卡通", + "德阳银行·锦程卡定活一卡通金卡", "德阳银行·锦程卡定活一卡通", "唐山市商业银行·唐山市城通卡", + "曲靖市商业银行·珠江源卡", "曲靖市商业银行·珠江源 IC 卡", "温州银行·金鹿信用卡", "温州银行·金鹿信用卡", + "温州银行·金鹿公务卡", "温州银行·贷记 IC 卡", "汉口银行·汉口银行贷记卡", "汉口银行·汉口银行贷记卡", + "汉口银行·九通香港旅游贷记普卡", "汉口银行·九通香港旅游贷记金卡", "汉口银行·贷记卡", "汉口银行·九通公务卡", + "江苏银行·聚宝借记卡", "江苏银行·月季卡", "江苏银行·紫金卡", "江苏银行·绿扬卡 (银联卡)", + "江苏银行·月季卡 (银联卡)", "江苏银行·九州借记卡 (银联卡)", "江苏银行·月季卡 (银联卡)", + "江苏银行·聚宝惠民福农卡", "江苏银行·江苏银行聚宝 IC 借记卡", "江苏银行·聚宝 IC 借记卡 VIP 卡", + "长治市商业银行·长治商行银联晋龙卡", "承德市商业银行·热河卡", "承德银行·借记 IC 卡", "德州银行·长河借记卡", + "德州银行·--", "遵义市商业银行·社保卡", "遵义市商业银行·尊卡", "邯郸市商业银行·邯银卡", + "邯郸市商业银行·邯郸银行贵宾 IC 借记卡", "安顺市商业银行·黄果树福农卡", "安顺市商业银行·黄果树借记卡", + "江苏银行·紫金信用卡 (公务卡)", "江苏银行·紫金信用卡", "江苏银行·天翼联名信用卡", "平凉市商业银行·广成卡", + "玉溪市商业银行·红塔卡", "玉溪市商业银行·红塔卡", "浙江民泰商业银行·金融 IC 卡", "浙江民泰商业银行·民泰借记卡", + "浙江民泰商业银行·金融 IC 卡 C 卡", "浙江民泰商业银行·银联标准普卡金卡", "浙江民泰商业银行·商惠通", + "上饶市商业银行·三清山卡", "东营银行·胜利卡", "泰安市商业银行·岱宗卡", "泰安市商业银行·市民一卡通", + "浙江稠州商业银行·义卡", "浙江稠州商业银行·义卡借记 IC 卡", "浙江稠州商业银行·公务卡", "自贡市商业银行·借记 IC 卡", + "自贡市商业银行·锦程卡", "鄂尔多斯银行·天骄公务卡", "鹤壁银行·鹤卡", "许昌银行·连城卡", "铁岭银行·龙凤卡", + "乐山市商业银行·大福卡", "乐山市商业银行·--", "长安银行·长长卡", "长安银行·借记 IC 卡", + "重庆三峡银行·财富人生卡", "重庆三峡银行·借记卡", "石嘴山银行·麒麟借记卡", "石嘴山银行·麒麟借记卡", + "石嘴山银行·麒麟公务卡", "盘锦市商业银行·鹤卡", "盘锦市商业银行·盘锦市商业银行鹤卡", "平顶山银行·平顶山银行公务卡", + "朝阳银行·鑫鑫通卡", "朝阳银行·朝阳银行福农卡", "朝阳银行·红山卡", "宁波东海银行·绿叶卡", + "遂宁市商业银行·锦程卡", "遂宁是商业银行·金荷卡", "保定银行·直隶卡", "保定银行·直隶卡", + "凉山州商业银行·锦程卡", "凉山州商业银行·金凉山卡", "漯河银行·福卡", "漯河银行·福源卡", "漯河银行·福源公务卡", + "达州市商业银行·锦程卡", "新乡市商业银行·新卡", "晋中银行·九州方圆借记卡", "晋中银行·九州方圆卡", + "驻马店银行·驿站卡", "驻马店银行·驿站卡", "驻马店银行·公务卡", "衡水银行·金鼎卡", "衡水银行·借记 IC 卡", + "周口银行·如愿卡", "周口银行·公务卡", "阳泉市商业银行·金鼎卡", "阳泉市商业银行·金鼎卡", + "宜宾市商业银行·锦程卡", "宜宾市商业银行·借记 IC 卡", "库尔勒市商业银行·孔雀胡杨卡", "雅安市商业银行·锦城卡", + "雅安市商业银行·--", "安阳银行·安鼎卡", "信阳银行·信阳卡", "信阳银行·公务卡", "信阳银行·信阳卡", + "华融湘江银行·华融卡", "华融湘江银行·华融卡", "营口沿海银行·祥云借记卡", "景德镇商业银行·瓷都卡", + "哈密市商业银行·瓜香借记卡", "湖北银行·金牛卡", "湖北银行·汉江卡", "湖北银行·借记卡", "湖北银行·三峡卡", + "湖北银行·至尊卡", "湖北银行·金融 IC 卡", "西藏银行·借记 IC 卡", "新疆汇和银行·汇和卡", "广东华兴银行·借记卡", + "广东华兴银行·华兴银联公司卡", "广东华兴银行·华兴联名 IC 卡", "广东华兴银行·华兴金融 IC 借记卡", "濮阳银行·龙翔卡", + "宁波通商银行·借记卡", "甘肃银行·神舟兴陇借记卡", "甘肃银行·甘肃银行神州兴陇 IC 卡", "枣庄银行·借记 IC 卡", + "本溪市商业银行·借记卡", "盛京银行·医保卡", "上海农商银行·如意卡 (银联卡)", "上海农商银行·如意卡 (银联卡)", + "上海农商银行·鑫通卡", "上海农商银行·国际如意卡", "上海农商银行·借记 IC 卡", + "常熟市农村商业银行·粒金贷记卡 (银联卡)", "常熟市农村商业银行·公务卡", "常熟市农村商业银行·粒金准贷卡", + "常熟农村商业银行·粒金借记卡 (银联卡)", "常熟农村商业银行·粒金 IC 卡", "常熟农村商业银行·粒金卡", + "深圳农村商业银行·信通卡 (银联卡)", "深圳农村商业银行·信通商务卡 (银联卡)", "深圳农村商业银行·信通卡", + "深圳农村商业银行·信通商务卡", "广州农村商业银行·福农太阳卡", "广东南海农村商业银行·盛通卡", + "广东南海农村商业银行·盛通卡 (银联卡)", "佛山顺德农村商业银行·恒通卡 (银联卡)", "佛山顺德农村商业银行·恒通卡", + "佛山顺德农村商业银行·恒通卡 (银联卡)", "江阴农村商业银行·暨阳公务卡", "江阴市农村商业银行·合作贷记卡 (银联卡)", + "江阴农村商业银行·合作借记卡", "江阴农村商业银行·合作卡 (银联卡)", "江阴农村商业银行·暨阳卡", + "重庆农村商业银行·江渝借记卡 VIP 卡", "重庆农村商业银行·江渝 IC 借记卡", "重庆农村商业银行·江渝乡情福农卡", + "东莞农村商业银行·信通卡 (银联卡)", "东莞农村商业银行·信通卡 (银联卡)", "东莞农村商业银行·信通信用卡", + "东莞农村商业银行·信通借记卡", "东莞农村商业银行·贷记 IC 卡", "张家港农村商业银行·一卡通 (银联卡)", + "张家港农村商业银行·一卡通 (银联卡)", "张家港农村商业银行·", "北京农村商业银行·信通卡", "北京农村商业银行·惠通卡", + "北京农村商业银行·凤凰福农卡", "北京农村商业银行·惠通卡", "北京农村商业银行·中国旅行卡", "北京农村商业银行·凤凰卡", + "天津农村商业银行·吉祥商联 IC 卡", "天津农村商业银行·信通借记卡 (银联卡)", "天津农村商业银行·借记 IC 卡", + "鄞州农村合作银行·蜜蜂借记卡 (银联卡)", "宁波鄞州农村合作银行·蜜蜂电子钱包 (IC)", + "宁波鄞州农村合作银行·蜜蜂 IC 借记卡", "宁波鄞州农村合作银行·蜜蜂贷记 IC 卡", "宁波鄞州农村合作银行·蜜蜂贷记卡", + "宁波鄞州农村合作银行·公务卡", "成都农村商业银行·福农卡", "成都农村商业银行·福农卡", + "珠海农村商业银行·信通卡 (银联卡)", "太仓农村商业银行·郑和卡 (银联卡)", "太仓农村商业银行·郑和 IC 借记卡", + "无锡农村商业银行·金阿福", "无锡农村商业银行·借记 IC 卡", "黄河农村商业银行·黄河卡", + "黄河农村商业银行·黄河富农卡福农卡", "黄河农村商业银行·借记 IC 卡", "天津滨海农村商业银行·四海通卡", + "天津滨海农村商业银行·四海通 e 芯卡", "武汉农村商业银行·汉卡", "武汉农村商业银行·汉卡", + "武汉农村商业银行·中国旅游卡", "江南农村商业银行·阳湖卡 (银联卡)", "江南农村商业银行·天天红火卡", + "江南农村商业银行·借记 IC 卡", "海口联合农村商业银行·海口联合农村商业银行合卡", "湖北嘉鱼吴江村镇银行·垂虹卡", + "福建建瓯石狮村镇银行·玉竹卡", "浙江平湖工银村镇银行·金平卡", "重庆璧山工银村镇银行·翡翠卡", + "重庆农村商业银行·银联标准贷记卡", "重庆农村商业银行·公务卡", "南阳村镇银行·玉都卡", + "晋中市榆次融信村镇银行·魏榆卡", "三水珠江村镇银行·珠江太阳卡", "东营莱商村镇银行·绿洲卡", "建设银行·单位结算卡", + "玉溪市商业银行·红塔卡" + }; +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/validator/IDCardUtils.java b/lib/DevApp/src/main/java/dev/utils/common/validator/IDCardUtils.java new file mode 100644 index 0000000000..6138f78085 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/validator/IDCardUtils.java @@ -0,0 +1,650 @@ +package dev.utils.common.validator; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 居民身份证工具类 + * @author AbrahamCaiJin + * @author Ttt + */ +public final class IDCardUtils { + + private IDCardUtils() { + } + + // 日志 TAG + private static final String TAG = IDCardUtils.class.getSimpleName(); + + // 加权因子 + private static final int[] POWER = { + 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 + }; + // 身份证最少位数 + public static final int CHINA_ID_MIN_LENGTH = 15; + // 身份证最大位数 + public static final int CHINA_ID_MAX_LENGTH = 18; + // 省份编码 + private static final Map sCityCodeMaps = new HashMap<>(); + // 台湾身份首字母对应数字 + private static final Map sTWFirstCodeMaps = new HashMap<>(); + // 香港身份首字母对应数字 + private static final Map sHKFirstCodeMaps = new HashMap<>(); + + static { + sCityCodeMaps.put("11", "北京"); + sCityCodeMaps.put("12", "天津"); + sCityCodeMaps.put("13", "河北"); + sCityCodeMaps.put("14", "山西"); + sCityCodeMaps.put("15", "内蒙古"); + sCityCodeMaps.put("21", "辽宁"); + sCityCodeMaps.put("22", "吉林"); + sCityCodeMaps.put("23", "黑龙江"); + sCityCodeMaps.put("31", "上海"); + sCityCodeMaps.put("32", "江苏"); + sCityCodeMaps.put("33", "浙江"); + sCityCodeMaps.put("34", "安徽"); + sCityCodeMaps.put("35", "福建"); + sCityCodeMaps.put("36", "江西"); + sCityCodeMaps.put("37", "山东"); + sCityCodeMaps.put("41", "河南"); + sCityCodeMaps.put("42", "湖北"); + sCityCodeMaps.put("43", "湖南"); + sCityCodeMaps.put("44", "广东"); + sCityCodeMaps.put("45", "广西"); + sCityCodeMaps.put("46", "海南"); + sCityCodeMaps.put("50", "重庆"); + sCityCodeMaps.put("51", "四川"); + sCityCodeMaps.put("52", "贵州"); + sCityCodeMaps.put("53", "云南"); + sCityCodeMaps.put("54", "西藏"); + sCityCodeMaps.put("61", "陕西"); + sCityCodeMaps.put("62", "甘肃"); + sCityCodeMaps.put("63", "青海"); + sCityCodeMaps.put("64", "宁夏"); + sCityCodeMaps.put("65", "新疆"); + sCityCodeMaps.put("71", "台湾"); + sCityCodeMaps.put("81", "香港"); + sCityCodeMaps.put("82", "澳门"); + sCityCodeMaps.put("83", "台湾"); + sCityCodeMaps.put("91", "国外"); + sTWFirstCodeMaps.put("A", 10); + sTWFirstCodeMaps.put("B", 11); + sTWFirstCodeMaps.put("C", 12); + sTWFirstCodeMaps.put("D", 13); + sTWFirstCodeMaps.put("E", 14); + sTWFirstCodeMaps.put("F", 15); + sTWFirstCodeMaps.put("G", 16); + sTWFirstCodeMaps.put("H", 17); + sTWFirstCodeMaps.put("J", 18); + sTWFirstCodeMaps.put("K", 19); + sTWFirstCodeMaps.put("L", 20); + sTWFirstCodeMaps.put("M", 21); + sTWFirstCodeMaps.put("N", 22); + sTWFirstCodeMaps.put("P", 23); + sTWFirstCodeMaps.put("Q", 24); + sTWFirstCodeMaps.put("R", 25); + sTWFirstCodeMaps.put("S", 26); + sTWFirstCodeMaps.put("T", 27); + sTWFirstCodeMaps.put("U", 28); + sTWFirstCodeMaps.put("V", 29); + sTWFirstCodeMaps.put("X", 30); + sTWFirstCodeMaps.put("Y", 31); + sTWFirstCodeMaps.put("W", 32); + sTWFirstCodeMaps.put("Z", 33); + sTWFirstCodeMaps.put("I", 34); + sTWFirstCodeMaps.put("O", 35); + sHKFirstCodeMaps.put("A", 1); + sHKFirstCodeMaps.put("B", 2); + sHKFirstCodeMaps.put("C", 3); + sHKFirstCodeMaps.put("R", 18); + sHKFirstCodeMaps.put("U", 21); + sHKFirstCodeMaps.put("Z", 26); + sHKFirstCodeMaps.put("X", 24); + sHKFirstCodeMaps.put("W", 23); + sHKFirstCodeMaps.put("O", 15); + sHKFirstCodeMaps.put("N", 14); + } + + /** + * 身份证校验规则, 验证 15 位身份编码是否合法 + * @param idCard 待验证身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateIdCard15(final String idCard) { + // 属于数字, 并且长度为 15 位数 + if (isNumber(idCard) && idCard.length() == CHINA_ID_MIN_LENGTH) { + // 获取省份编码 + String provinceCode = idCard.substring(0, 2); + if (sCityCodeMaps.get(provinceCode) == null) return false; + // 获取出生日期 + String birthCode = idCard.substring(6, 12); + Date birthDate = null; + try { + birthDate = DateUtils.getSafeDateFormat(DevFinal.TIME.yy).parse(birthCode.substring(0, 2)); + } catch (ParseException e) { + JCLogUtils.eTag(TAG, e, "validateIdCard15"); + } + Calendar calendar = Calendar.getInstance(); + if (birthDate != null) calendar.setTime(birthDate); + // 判断是否有效日期 + return validateDateSmallerThenNow( + calendar.get(Calendar.YEAR), + Integer.parseInt(birthCode.substring(2, 4)), + Integer.parseInt(birthCode.substring(4, 6)) + ); + } + return false; + } + + /** + * 身份证校验规则, 验证 18 位身份编码是否合法 + * @param idCard 待验证身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateIdCard18(final String idCard) { + if (idCard != null && idCard.length() == CHINA_ID_MAX_LENGTH) { + // 前 17 位 + String code17 = idCard.substring(0, 17); + // 第 18 位 + String code18 = idCard.substring(17, CHINA_ID_MAX_LENGTH); + // 判断前 17 位是否数字 + if (isNumber(code17)) { + try { + int[] cardArrays = convertCharToInt(code17.toCharArray()); + int sum17 = getPowerSum(cardArrays); + // 获取校验位 + String str = getCheckCode18(sum17); + // 判断最后一位是否一样 + if (str.length() > 0 && str.equalsIgnoreCase(code18)) { + return true; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateIdCard18"); + } + } + } + return false; + } + + /** + * 将 15 位身份证号码转换为 18 位 + * @param idCard 15 位身份编码 + * @return 18 位身份编码 + */ + public static String convert15CardTo18(final String idCard) { + // 属于数字, 并且长度为 15 位数 + if (isNumber(idCard) && idCard.length() == CHINA_ID_MIN_LENGTH) { + String idCard18; + Date birthDate = null; + // 获取出生日期 + String birthday = idCard.substring(6, 12); + try { + birthDate = DateUtils.getSafeDateFormat(DevFinal.TIME.yyMMdd_HYPHEN).parse(birthday); + } catch (ParseException e) { + JCLogUtils.eTag(TAG, e, "convert15CardTo18"); + } + Calendar calendar = Calendar.getInstance(); + if (birthDate != null) calendar.setTime(birthDate); + try { + // 获取出生年 ( 完全表现形式, 如: 2010) + String year = String.valueOf(calendar.get(Calendar.YEAR)); + // 保存省市区信息 + 年 + 月日 + 后续信息 ( 顺序位、性别等 ) + idCard18 = idCard.substring(0, 6) + year + idCard.substring(8); + // 转换字符数组 + int[] cardArrays = convertCharToInt(idCard18.toCharArray()); + int sum17 = getPowerSum(cardArrays); + // 获取校验位 + String str = getCheckCode18(sum17); + // 判断长度, 拼接校验位 + return (str.length() > 0) ? (idCard18 + str) : null; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert15CardTo18"); + } + } + return null; + } + + /** + * 验证台湾身份证号码 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateTWCard(final String idCard) { + // 台湾身份证 10 位 + if (idCard == null || idCard.length() != 10) return false; + try { + // 第一位英文 不同县市 + String start = idCard.substring(0, 1); + String mid = idCard.substring(1, 9); + String end = idCard.substring(9, 10); + int intStart = sTWFirstCodeMaps.get(start); + int sum = intStart / 10 + (intStart % 10) * 9; + char[] chars = mid.toCharArray(); + int iflag = 8; + for (char value : chars) { + sum = sum + Integer.parseInt(String.valueOf(value)) * iflag; + iflag--; + } + return (sum % 10 == 0 ? 0 : 10 - sum % 10) == Integer.parseInt(end); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateTWCard"); + } + return false; + } + + /** + * 验证香港身份证号码 ( 部份特殊身份证无法检查 ) + * 身份证前 2 位为英文字符, 如果只出现一个英文字符则表示第一位是空格, 对应数字 58 前 2 位英文字符 A-Z 分别对应数字 10-35 + * 最后一位校验码为 0-9 的数字加上字符 "A", "A" 代表 10 + * 将身份证号码全部转换为数字, 分别对应乘 9-1 相加的总和, 整除 11 则证件号码有效 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateHKCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return false; + try { + String card = idCard.replaceAll("[\\(|\\)]", ""); + int sum; + if (card.length() == 9) { + sum = ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 9 + ((int) card.substring(1, 2).toUpperCase().toCharArray()[0] - 55) * 8; + card = card.substring(1, 9); + } else { + sum = 522 + ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 8; + } + String mid = card.substring(1, 7); + String end = card.substring(7, 8); + char[] chars = mid.toCharArray(); + int iflag = 7; + for (char value : chars) { + sum = sum + Integer.parseInt(String.valueOf(value)) * iflag; + iflag--; + } + if (end.equalsIgnoreCase("A")) { + sum = sum + 10; + } else { + sum = sum + Integer.parseInt(end); + } + return (sum % 11 == 0); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateHKCard"); + } + return false; + } + + /** + * 判断 10 位数的身份证号, 是否合法 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static String[] validateIdCard10(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + String[] info = new String[3]; + info[0] = "N"; // 默认未知地区 + info[1] = "N"; // 默认未知性别 + info[2] = "false"; // 默认非法 + try { + // 属于 8, 9, 10 长度范围内 + if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 + info[0] = "台湾"; + String char2 = idCard.substring(1, 2); + if (char2.equals("1")) { + info[1] = "M"; + } else if (char2.equals("2")) { + info[1] = "F"; + } else { + info[1] = "N"; + info[2] = "false"; + return info; + } + info[2] = validateTWCard(idCard) ? "true" : "false"; + } else if (idCard.matches("^[1|5|7][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 + info[0] = "澳门"; + info[1] = "N"; + // TODO + } else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 + info[0] = "香港"; + info[1] = "N"; + info[2] = validateHKCard(idCard) ? "true" : "false"; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateIdCard10"); + } + return info; + } + + /** + * 验证身份证是否合法 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return false; + String card = idCard.trim(); + if (validateIdCard18(card)) return true; + if (validateIdCard15(card)) return true; + String[] cardArrays = validateIdCard10(card); + return (cardArrays != null && "true".equals(cardArrays[2])); + } + + /** + * 根据身份编号获取年龄 + * @param idCard 身份编号 + * @return 年龄 + */ + public static int getAgeByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return 0; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + String year = idCardStr.substring(6, 10); + // 获取当前年份 + int currentYear = Calendar.getInstance().get(Calendar.YEAR); + // 当前年份 ( 出生年份 ) + return currentYear - Integer.parseInt(year); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAgeByIdCard"); + } + return 0; + } + + /** + * 根据身份编号获取生日 + * @param idCard 身份编号 + * @return 生日 (yyyyMMdd) + */ + public static String getBirthByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + return idCardStr.substring(6, 14); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBirthByIdCard"); + } + return null; + } + + /** + * 根据身份编号获取生日 + * @param idCard 身份编号 + * @return 生日 (yyyyMMdd) + */ + public static String getBirthdayByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1-$2-$3"); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBirthdayByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 年份 ) + * @param idCard 身份编号 + * @return 生日 (yyyy) + */ + public static String getYearByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(0, 4); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getYearByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 月份 ) + * @param idCard 身份编号 + * @return 生日 (MM) + */ + public static String getMonthByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(4, 6); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMonthByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 天数 ) + * @param idCard 身份编号 + * @return 生日 (dd) + */ + public static String getDateByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(6, 8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDateByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取性别 + * @param idCard 身份编号 + * @return 性别 男 (M)、女 (F)、未知 (N) + */ + public static String getGenderByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + // 获取第 17 位性别信息 + String cardNumber = idCardStr.substring(16, 17); + // 奇数为男, 偶数为女 + return (Integer.parseInt(cardNumber) % 2 == 0) ? "F" : "M"; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenderByIdCard"); + } + // 默认未知 + return "N"; + } + + /** + * 根据身份编号获取户籍省份 + * @param idCard 身份编码 + * @return 省级编码 + */ + public static String getProvinceByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + // 身份证长度 + int idCardLength = idCard.length(); + // 属于 15 位身份证、或 18 位身份证 + if (idCardLength == CHINA_ID_MIN_LENGTH || idCardLength == CHINA_ID_MAX_LENGTH) { + return sCityCodeMaps.get(idCard.substring(0, 2)); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProvinceByIdCard"); + } + return null; + } + + /** + * 将身份证的每位和对应位的加权因子相乘之后, 再获取和值 + * @param data byte[] 数据 + * @return 身份证编码, 加权引子 + */ + public static int getPowerSum(final int[] data) { + if (data == null) return 0; + int len = data.length; + if (len == 0) return 0; + int powerLength = POWER.length; + int sum = 0; + if (powerLength == len) { + for (int i = 0; i < len; i++) { + for (int j = 0; j < powerLength; j++) { + if (i == j) { + sum = sum + data[i] * POWER[j]; + } + } + } + } + return sum; + } + + /** + * 将 POWER 和值与 11 取模获取余数进行校验码判断 + * @param sum {@link IDCardUtils#getPowerSum} + * @return 校验位 + */ + public static String getCheckCode18(final int sum) { + String code = ""; + switch (sum % 11) { + case 10: + code = "2"; + break; + case 9: + code = "3"; + break; + case 8: + code = "4"; + break; + case 7: + code = "5"; + break; + case 6: + code = "6"; + break; + case 5: + code = "7"; + break; + case 4: + code = "8"; + break; + case 3: + code = "9"; + break; + case 2: + code = "x"; + break; + case 1: + code = "0"; + break; + case 0: + code = "1"; + break; + } + return code; + } + + // ========== + // = 私有方法 = + // ========== + + /** + * 将字符数组转换成数字数组 + * @param data char[] + * @return int[] + */ + private static int[] convertCharToInt(final char[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + int[] arrays = new int[len]; + for (int i = 0; i < len; i++) { + arrays[i] = Integer.parseInt(String.valueOf(data[i])); + } + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertCharToInt"); + } + return null; + } + + /** + * 验证小于当前日期 是否有效 + * @param yearData 待校验的日期 ( 年 ) + * @param monthData 待校验的日期 ( 月 1-12) + * @param dayData 待校验的日期 ( 日 ) + * @return {@code true} yes, {@code false} no + */ + private static boolean validateDateSmallerThenNow( + final int yearData, + final int monthData, + final int dayData + ) { + int year = Calendar.getInstance().get(Calendar.YEAR); + int datePerMonth; + int MIN = 1930; + if (yearData < MIN || yearData >= year) { + return false; + } + if (monthData < 1 || monthData > 12) { + return false; + } + switch (monthData) { + case 4: + case 6: + case 9: + case 11: + datePerMonth = 30; + break; + case 2: + boolean dm = (yearData % 4 == 0 && yearData % 100 != 0 || yearData % 400 == 0) && yearData > MIN; + datePerMonth = dm ? 29 : 28; + break; + default: + datePerMonth = 31; + } + return (dayData >= 1) && (dayData <= datePerMonth); + } + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + private static boolean isNumber(final String str) { + return !StringUtils.isEmpty(str) && str.matches("^[0-9]*$"); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java b/lib/DevApp/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java new file mode 100644 index 0000000000..9f46ef93c2 --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java @@ -0,0 +1,248 @@ +package dev.utils.common.validator; + +/** + * detail: 检验联系 ( 手机号码、座机 ) 工具类 + * @author Ttt + *
+ *     @see 
+ *     

+ * 验证手机号码是否正确 + * 移动: 134 135 136 137 138 139 147 148 150 151 152 157 158 159 172 178 182 183 184 187 188 195 198 + * 联通: 130 131 132 145 146 155 156 166 167 171 175 176 185 186 196 + * 电信: 133 149 153 173 174 177 180 181 189 190 191 193 199 + * 广电: 192 + * 虚拟运营商: 162 165 167 170 171 + *
+ */ +public final class ValiToPhoneUtils { + + private ValiToPhoneUtils() { + } + + /** + * 中国手机号码格式验证 ( 简单手机号码校验 ) + *
+     *     在输入可以调用该方法, 点击发送验证码, 使用 isPhone
+     *     简单手机号码校验 校验手机号码的长度和 1 开头 ( 是否 11 位 )
+     * 
+ * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneSimple(final String phone) { + return ValidatorUtils.match(CHAIN_PHONE_SIMPLE, phone); + } + + /** + * 是否中国手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhone(final String phone) { + return ValidatorUtils.match(CHINA_PHONE_PATTERN, phone); + } + + /** + * 是否中国移动手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaMobile(final String phone) { + return ValidatorUtils.match(CHINA_MOBILE_PATTERN, phone); + } + + /** + * 是否中国联通手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaUnicom(final String phone) { + return ValidatorUtils.match(CHINA_UNICOM_PATTERN, phone); + } + + /** + * 是否中国电信手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaTelecom(final String phone) { + return ValidatorUtils.match(CHINA_TELECOM_PATTERN, phone); + } + + /** + * 是否中国广电手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaBroadcast(final String phone) { + return ValidatorUtils.match(CHINA_BROADCAST_PATTERN, phone); + } + + /** + * 是否中国虚拟运营商手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaVirtual(final String phone) { + return ValidatorUtils.match(CHINA_VIRTUAL_PATTERN, phone); + } + + // = + + /** + * 是否中国香港手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaHkMobile(final String phone) { + return ValidatorUtils.match(CHINA_HK_PHONE_PATTERN, phone); + } + + /** + * 验证电话号码的格式 + * @param phone 待校验的号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneCallNum(final String phone) { + return ValidatorUtils.match(PHONE_CALL_PATTERN, phone); + } + + // ============== + // = 手机号码判断 = + // ============== + + // 简单手机号码校验 校验手机号码的长度和 1 开头 ( 是否 11 位 ) + public static final String CHAIN_PHONE_SIMPLE = "^(?:\\+86)?1\\d{10}$"; + + // 中国手机号码正则 + public static final String CHINA_PHONE_PATTERN; + + // 中国移动号码正则 + public static final String CHINA_MOBILE_PATTERN; + + // 中国联通号码正则 + public static final String CHINA_UNICOM_PATTERN; + + // 中国电信号码正则 + public static final String CHINA_TELECOM_PATTERN; + + // 中国广电号码正则 + public static final String CHINA_BROADCAST_PATTERN; + + // 中国虚拟运营商号码正则 + public static final String CHINA_VIRTUAL_PATTERN; + + // 中国香港手机号码正则 香港手机号码 8 位数, 5|6|8|9 开头 + 7 位任意数 + public static final String CHINA_HK_PHONE_PATTERN = "^(5|6|8|9)\\d{7}$"; + + // ========== + // = 座机判断 = + // ========== + + // 座机电话格式验证 + public static final String PHONE_CALL_PATTERN = "^(?:\\(\\d{3,4}\\)|\\d{3,4}-)?\\d{7,8}(?:-\\d{1,4})?$"; + + static { + + StringBuilder builder; + + // ========== + // = 中国移动 = + // ========== + + // 中国移动: 134 135 136 137 138 139 147 148 150 151 152 157 158 159 172 178 182 183 184 187 188 195 198 + builder = new StringBuilder(); + builder.append("^13[4,5,6,7,8,9]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[7,8]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[0,1,2,7,8,9]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^17[2,8]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[2,3,4,7,8]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[5,8]{1}\\d{8}$"); // 19 开头 + CHINA_MOBILE_PATTERN = builder.toString(); + + // ========== + // = 中国联通 = + // ========== + + // 中国联通: 130 131 132 145 146 155 156 166 167 171 175 176 185 186 196 + builder = new StringBuilder(); + builder.append("^13[0,1,2]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[5,6]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[5,6]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^16[6,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[1,5,6]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[5,6]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[6]{1}\\d{8}$"); // 19 开头 + CHINA_UNICOM_PATTERN = builder.toString(); + + // ========== + // = 中国电信 = + // ========== + + // 中国电信: 133 149 153 173 174 177 180 181 189 190 191 193 199 + builder = new StringBuilder(); + builder.append("^13[3]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[9]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[3]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^17[3,4,7]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[0,1,9]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[0,1,3,9]{1}\\d{8}$"); // 19 开头 + CHINA_TELECOM_PATTERN = builder.toString(); + + // ========== + // = 中国广电 = + // ========== + + // 中国广电: 192 + builder = new StringBuilder(); + builder.append("^19[2]{1}\\d{8}$"); // 19 开头 + CHINA_BROADCAST_PATTERN = builder.toString(); + + // ============ + // = 虚拟运营商 = + // ============ + + // 虚拟运营商: 162 165 167 170 171 + builder = new StringBuilder(); + builder.append("^16[2,5,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[0,1]{1}\\d{8}$"); // 17 开头 + CHINA_VIRTUAL_PATTERN = builder.toString(); + + // ========== + // = 中国手机 = + // ========== + + // 中国手机: 130 131 132 133 134 135 136 137 138 139 145 146 147 148 149 150 151 152 153 155 156 157 158 159 162 165 166 167 167 170 171 171 172 173 174 175 176 177 178 180 181 182 183 184 185 186 187 188 189 190 191 192 193 195 196 198 199 + builder = new StringBuilder(); + builder.append("^13[0,1,2,3,4,5,6,7,8,9]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[5,6,7,8,9]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[0,1,2,3,5,6,7,8,9]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^16[2,5,6,7,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[0,1,1,2,3,4,5,6,7,8]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[0,1,2,3,4,5,6,7,8,9]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[0,1,2,3,5,6,8,9]{1}\\d{8}$"); // 19 开头 + CHINA_PHONE_PATTERN = builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevApp/src/main/java/dev/utils/common/validator/ValidatorUtils.java b/lib/DevApp/src/main/java/dev/utils/common/validator/ValidatorUtils.java new file mode 100644 index 0000000000..352a0eb85a --- /dev/null +++ b/lib/DevApp/src/main/java/dev/utils/common/validator/ValidatorUtils.java @@ -0,0 +1,214 @@ +package dev.utils.common.validator; + +import java.util.regex.Pattern; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 校验工具类 + * @author Ttt + */ +public final class ValidatorUtils { + + private ValidatorUtils() { + } + + // 日志 TAG + private static final String TAG = ValidatorUtils.class.getSimpleName(); + + /** + * 通用匹配函数 + * @param regex 正则表达式 + * @param input 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean match( + final String regex, + final String input + ) { + if (!StringUtils.isEmpty(input)) { + try { + return Pattern.matches(regex, input); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "match"); + } + } + return false; + } + + // = + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumber(final String str) { + return match(DevFinal.REGEX.NUMBER, str); + } + + /** + * 检验数字或包含小数点 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberDecimal(final String str) { + return match(DevFinal.REGEX.NUMBER_OR_DECIMAL, str); + } + + /** + * 判断字符串是不是全是字母 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLetter(final String str) { + return match(DevFinal.REGEX.LETTER, str); + } + + /** + * 判断字符串是不是包含数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContainNumber(final String str) { + return match(DevFinal.REGEX.CONTAIN_NUMBER, str); + } + + /** + * 判断字符串是不是只含字母和数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberLetter(final String str) { + return match(DevFinal.REGEX.NUMBER_OR_LETTER, str); + } + + /** + * 检验特殊符号 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSpec(final String str) { + return match(DevFinal.REGEX.SPECIAL, str); + } + + /** + * 检验微信号 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWx(final String str) { + return match(DevFinal.REGEX.WX, str); + } + + /** + * 检验真实姓名 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRealName(final String str) { + return match(DevFinal.REGEX.REALNAME, str); + } + + /** + * 校验昵称 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNickName(final String str) { + return match(DevFinal.REGEX.NICKNAME, str); + } + + /** + * 校验用户名 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUserName(final String str) { + return match(DevFinal.REGEX.USERNAME, str); + } + + /** + * 校验密码 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPassword(final String str) { + return match(DevFinal.REGEX.PASSWORD, str); + } + + /** + * 校验邮箱 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmail(final String str) { + return match(DevFinal.REGEX.EMAIL, str); + } + + /** + * 校验 URL + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUrl(final String str) { + if (!StringUtils.isEmpty(str)) { + return match(DevFinal.REGEX.URL, str.toLowerCase()); + } + return false; + } + + /** + * 校验 IP 地址 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isIPAddress(final String str) { + return match(DevFinal.REGEX.IP_ADDRESS, str); + } + + /** + * 校验汉字 ( 无符号, 纯汉字 ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChinese(final String str) { + return match(DevFinal.REGEX.CHINESE, str); + } + + /** + * 判断字符串是不是全是中文 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChineseAll(final String str) { + return match(DevFinal.REGEX.CHINESE_ALL, str); + } + + /** + * 判断字符串中包含中文、包括中文字符标点等 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContainChinese(final String str) { + if (!StringUtils.isEmpty(str)) { + try { + int length = str.length(); + if (length != 0) { + char[] dChar = str.toCharArray(); + for (int i = 0; i < length; i++) { + boolean flag = String.valueOf(dChar[i]).matches(DevFinal.REGEX.CHINESE_ALL2); + if (flag) { + return true; + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isContainChinese"); + } + } + return false; + } +} \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_fade_in.xml b/lib/DevApp/src/main/res/anim/dev_fade_in.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_fade_in.xml rename to lib/DevApp/src/main/res/anim/dev_fade_in.xml index 64f3143081..085f43f030 100644 --- a/DevLibUtils/src/main/res/anim/dev_fade_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_fade_in.xml @@ -9,4 +9,4 @@ android:fromAlpha="0" android:toAlpha="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_fade_out.xml b/lib/DevApp/src/main/res/anim/dev_fade_out.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_fade_out.xml rename to lib/DevApp/src/main/res/anim/dev_fade_out.xml index 950232a1f4..8637b4fa80 100644 --- a/DevLibUtils/src/main/res/anim/dev_fade_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_fade_out.xml @@ -9,4 +9,4 @@ android:fromAlpha="1.0" android:toAlpha="0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_magnify_fade_in.xml b/lib/DevApp/src/main/res/anim/dev_magnify_fade_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_magnify_fade_in.xml rename to lib/DevApp/src/main/res/anim/dev_magnify_fade_in.xml index 06b6879f91..e619b8554c 100644 --- a/DevLibUtils/src/main/res/anim/dev_magnify_fade_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_magnify_fade_in.xml @@ -18,4 +18,4 @@ android:toXScale="1.0" android:toYScale="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_magnify_fade_out.xml b/lib/DevApp/src/main/res/anim/dev_magnify_fade_out.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_magnify_fade_out.xml rename to lib/DevApp/src/main/res/anim/dev_magnify_fade_out.xml index 759a277972..9798d7e03a 100644 --- a/DevLibUtils/src/main/res/anim/dev_magnify_fade_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_magnify_fade_out.xml @@ -18,4 +18,4 @@ android:toXScale="1.5" android:toYScale="1.5" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_magnify_left_top_in.xml b/lib/DevApp/src/main/res/anim/dev_magnify_left_top_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_magnify_left_top_in.xml rename to lib/DevApp/src/main/res/anim/dev_magnify_left_top_in.xml index 9549309ca1..bb6c2826b5 100644 --- a/DevLibUtils/src/main/res/anim/dev_magnify_left_top_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_magnify_left_top_in.xml @@ -18,4 +18,4 @@ android:toXScale="1.0" android:toYScale="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_magnify_right_top_in.xml b/lib/DevApp/src/main/res/anim/dev_magnify_right_top_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_magnify_right_top_in.xml rename to lib/DevApp/src/main/res/anim/dev_magnify_right_top_in.xml index 5a92bb0bc9..ed38e059e9 100644 --- a/DevLibUtils/src/main/res/anim/dev_magnify_right_top_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_magnify_right_top_in.xml @@ -18,4 +18,4 @@ android:toXScale="1.0" android:toYScale="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_down_in.xml b/lib/DevApp/src/main/res/anim/dev_push_down_in.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_down_in.xml rename to lib/DevApp/src/main/res/anim/dev_push_down_in.xml index 55e69555ff..74e59a10c0 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_down_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_down_in.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_down_out.xml b/lib/DevApp/src/main/res/anim/dev_push_down_out.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_down_out.xml rename to lib/DevApp/src/main/res/anim/dev_push_down_out.xml index cc927f5270..ff0c42ddbd 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_down_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_down_out.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="100%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_left_in.xml b/lib/DevApp/src/main/res/anim/dev_push_left_in.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_left_in.xml rename to lib/DevApp/src/main/res/anim/dev_push_left_in.xml index e96e21a89e..76ca76124a 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_left_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_left_in.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_left_out.xml b/lib/DevApp/src/main/res/anim/dev_push_left_out.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_left_out.xml rename to lib/DevApp/src/main/res/anim/dev_push_left_out.xml index ccc492de40..b4049fc73c 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_left_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_left_out.xml @@ -11,4 +11,4 @@ android:toXDelta="-100%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_right_in.xml b/lib/DevApp/src/main/res/anim/dev_push_right_in.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_right_in.xml rename to lib/DevApp/src/main/res/anim/dev_push_right_in.xml index 3f9e684cbb..2e635b9f7e 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_right_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_right_in.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_right_out.xml b/lib/DevApp/src/main/res/anim/dev_push_right_out.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_right_out.xml rename to lib/DevApp/src/main/res/anim/dev_push_right_out.xml index c2217a9f31..7c55ac6e53 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_right_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_right_out.xml @@ -11,4 +11,4 @@ android:toXDelta="100%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_up_in.xml b/lib/DevApp/src/main/res/anim/dev_push_up_in.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_up_in.xml rename to lib/DevApp/src/main/res/anim/dev_push_up_in.xml index b27fa695dc..1f6c556212 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_up_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_up_in.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="0%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_push_up_out.xml b/lib/DevApp/src/main/res/anim/dev_push_up_out.xml similarity index 97% rename from DevLibUtils/src/main/res/anim/dev_push_up_out.xml rename to lib/DevApp/src/main/res/anim/dev_push_up_out.xml index 247d2e5491..159d8e3eb7 100644 --- a/DevLibUtils/src/main/res/anim/dev_push_up_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_push_up_out.xml @@ -11,4 +11,4 @@ android:toXDelta="0%p" android:toYDelta="-100%p" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_rotate_fade_in.xml b/lib/DevApp/src/main/res/anim/dev_rotate_fade_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_rotate_fade_in.xml rename to lib/DevApp/src/main/res/anim/dev_rotate_fade_in.xml index b708128df4..4e6b8018c5 100644 --- a/DevLibUtils/src/main/res/anim/dev_rotate_fade_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_rotate_fade_in.xml @@ -25,4 +25,4 @@ android:toXScale="1.0" android:toYScale="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_rotate_fade_out.xml b/lib/DevApp/src/main/res/anim/dev_rotate_fade_out.xml similarity index 99% rename from DevLibUtils/src/main/res/anim/dev_rotate_fade_out.xml rename to lib/DevApp/src/main/res/anim/dev_rotate_fade_out.xml index 7687847008..e71b9bd4d2 100644 --- a/DevLibUtils/src/main/res/anim/dev_rotate_fade_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_rotate_fade_out.xml @@ -25,4 +25,4 @@ android:toXScale=".5" android:toYScale=".5" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_shrink_fade_in.xml b/lib/DevApp/src/main/res/anim/dev_shrink_fade_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_shrink_fade_in.xml rename to lib/DevApp/src/main/res/anim/dev_shrink_fade_in.xml index eba4dabe62..eed81f8060 100644 --- a/DevLibUtils/src/main/res/anim/dev_shrink_fade_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_shrink_fade_in.xml @@ -18,4 +18,4 @@ android:toXScale="1.0" android:toYScale="1.0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_shrink_fade_out.xml b/lib/DevApp/src/main/res/anim/dev_shrink_fade_out.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_shrink_fade_out.xml rename to lib/DevApp/src/main/res/anim/dev_shrink_fade_out.xml index e3a66b09a8..b342569743 100644 --- a/DevLibUtils/src/main/res/anim/dev_shrink_fade_out.xml +++ b/lib/DevApp/src/main/res/anim/dev_shrink_fade_out.xml @@ -18,4 +18,4 @@ android:toXScale=".5" android:toYScale=".5" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_shrink_left_bottom_in.xml b/lib/DevApp/src/main/res/anim/dev_shrink_left_bottom_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_shrink_left_bottom_in.xml rename to lib/DevApp/src/main/res/anim/dev_shrink_left_bottom_in.xml index 1655c14634..d78afd2bd9 100644 --- a/DevLibUtils/src/main/res/anim/dev_shrink_left_bottom_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_shrink_left_bottom_in.xml @@ -18,4 +18,4 @@ android:toXScale="0" android:toYScale="0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/anim/dev_shrink_right_bottom_in.xml b/lib/DevApp/src/main/res/anim/dev_shrink_right_bottom_in.xml similarity index 98% rename from DevLibUtils/src/main/res/anim/dev_shrink_right_bottom_in.xml rename to lib/DevApp/src/main/res/anim/dev_shrink_right_bottom_in.xml index 80d4ad96c4..f2edbe1638 100644 --- a/DevLibUtils/src/main/res/anim/dev_shrink_right_bottom_in.xml +++ b/lib/DevApp/src/main/res/anim/dev_shrink_right_bottom_in.xml @@ -18,4 +18,4 @@ android:toXScale="0" android:toYScale="0" /> - + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_frame.png b/lib/DevApp/src/main/res/drawable-hdpi/dev_toast_frame.9.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-hdpi/dev_toast_frame.png rename to lib/DevApp/src/main/res/drawable-hdpi/dev_toast_frame.9.png diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_clear_white.png b/lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_error_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_clear_white.png rename to lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_error_white.png diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_info_outline_white.png b/lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_info_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_info_outline_white.png rename to lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_info_white.png diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_check_white.png b/lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_success_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_check_white.png rename to lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_success_white.png diff --git a/DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_warning_outline_white.png b/lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_warning_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-hdpi/dev_toast_ic_warning_outline_white.png rename to lib/DevApp/src/main/res/drawable-hdpi/dev_toast_icon_warning_white.png diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_frame.png b/lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_frame.9.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_frame.png rename to lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_frame.9.png diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_clear_white.png b/lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_error_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_clear_white.png rename to lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_error_white.png diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_info_outline_white.png b/lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_info_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_info_outline_white.png rename to lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_info_white.png diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_check_white.png b/lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_success_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_check_white.png rename to lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_success_white.png diff --git a/DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_warning_outline_white.png b/lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_warning_white.png similarity index 100% rename from DevLibUtils/src/main/res/drawable-xhdpi/dev_toast_ic_warning_outline_white.png rename to lib/DevApp/src/main/res/drawable-xhdpi/dev_toast_icon_warning_white.png diff --git a/DevLibUtils/src/main/res/drawable/dialog_transparent.9.png b/lib/DevApp/src/main/res/drawable/dialog_transparent.9.png similarity index 100% rename from DevLibUtils/src/main/res/drawable/dialog_transparent.9.png rename to lib/DevApp/src/main/res/drawable/dialog_transparent.9.png diff --git a/lib/DevApp/src/main/res/layout/dev_toast_layout.xml b/lib/DevApp/src/main/res/layout/dev_toast_layout.xml new file mode 100644 index 0000000000..0b54c34f6c --- /dev/null +++ b/lib/DevApp/src/main/res/layout/dev_toast_layout.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/DevLibUtils/src/main/res/raw/dev_beep.ogg b/lib/DevApp/src/main/res/raw/dev_beep.ogg similarity index 100% rename from DevLibUtils/src/main/res/raw/dev_beep.ogg rename to lib/DevApp/src/main/res/raw/dev_beep.ogg diff --git a/lib/DevApp/src/main/res/values-zh-rCN/strings.xml b/lib/DevApp/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..f86e2542b5 --- /dev/null +++ b/lib/DevApp/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,27 @@ + + + + + 应用包名 + 应用 MD5签名 + 应用版本号(内部判断) + 应用版本名(用户展示) + 安装包地址 + SHA1 + SHA256 + 首次安装时间 + 最近更新时间 + App 最低支持版本 + App 兼容sdk版本 + APK 安装包大小 + 有效时间 + 签名是否过期 + 已过期 + 未过期 + 证书发布方 + 证书版本号 + 证书算法名称 + 证书算法OID + 证书DER编码 + 证书机器码 + \ No newline at end of file diff --git a/lib/DevApp/src/main/res/values/color.xml b/lib/DevApp/src/main/res/values/color.xml new file mode 100644 index 0000000000..6d80640ed5 --- /dev/null +++ b/lib/DevApp/src/main/res/values/color.xml @@ -0,0 +1,377 @@ + + + + + + #00000000 + + + #000000 + + #19000000 + #33000000 + #4c000000 + #66000000 + #7f000000 + #99000000 + #b2000000 + #cc000000 + #e5000000 + + + #ffffff + + #19ffffff + #33ffffff + #4cffffff + #66ffffff + #7fffffff + #99ffffff + #b2ffffff + #ccffffff + #e5ffffff + + + #808080 + + #19808080 + #33808080 + #4c808080 + #66808080 + #7f808080 + #99808080 + #b2808080 + #cc808080 + #e5808080 + + + #ff0000 + + #0000ff + + #0080ff + + #ffff00 + + #ffd700 + + #ffc0cb + + #00ffff + + #cccccc + + #8f8f8f + + + + #111111 + #222222 + #333333 + #444444 + #555555 + #666666 + #777777 + #888888 + #999999 + + #f0f0f0 + #f1f1f1 + #f2f2f2 + #f3f3f3 + #f4f4f4 + #f5f5f5 + #f6f6f6 + #f7f7f7 + #f8f8f8 + #f9f9f9 + + #e0e0e0 + #e1e1e1 + #e2e2e2 + #e3e3e3 + #e4e4e4 + #e5e5e5 + #e6e6e6 + #e7e7e7 + #e8e8e8 + #e9e9e9 + + #d0d0d0 + #d1d1d1 + #d2d2d2 + #d3d3d3 + #d4d4d4 + #d5d5d5 + #d6d6d6 + #d7d7d7 + #d8d8d8 + #d9d9d9 + + + + + + + #fffff0 + + #ffffe0 + + #fffafa + + #fffaf0 + + #fffacd + + #fff8dc + + #fff5ee + + #fff0f5 + + #ffefd5 + + #ffebcd + + #ffe4e1 + + #ffe4c4 + + #ffe4b5 + + #ffdead + + #ffdab9 + + #ffb6c1 + + #ffa500 + + #ffa07a + + #ff8c00 + + #ff7f50 + + #ff69b4 + + #ff6347 + + #ff4500 + + #ff1493 + + #ff00ff + + #fdf5e6 + + #fafad2 + + #faf0e6 + + #faebd7 + + #fa8072 + + #f8f8ff + + #f5fffa + + #f5f5f5 + + #f5f5dc + + #f5deb3 + + #f4a460 + + #f0ffff + + #f0fff0 + + #f0f8ff + + #f0e68c + + #f08080 + + #eee8aa + + #ee82ee + + #e9967a + + #e6e6fa + + #e0ffff + + #deb887 + + #dda0dd + + #dcdcdc + + #dc143c + + #db7093 + + #daa520 + + #da70d6 + + #d8bfd8 + + #d3d3d3 + + #d2b48c + + #d2691e + + #cd853f + + #cd5c5c + + #c71585 + + #c0c0c0 + + #bdb76b + + #bc8f8f + + #ba55d3 + + #b8860b + + #b22222 + + #b0e0e6 + + #b0c4de + + #afeeee + + #adff2f + + #add8e6 + + #a9a9a9 + + #a52a2a + + #a0522d + + #9932cc + + #98fb98 + + #9400d3 + + #9370db + + #90ee90 + + #8fbc8f + + #8b4513 + + #8b008b + + #8b0000 + + #8a2be2 + + #87cefa + + #87ceeb + + #808080 + + #808000 + + #800080 + + #800000 + + #7fffd4 + + #7fff00 + + #7cfc00 + + #7b68ee + + #778899 + + #708090 + + #6b8e23 + + #6a5acd + + #696969 + + #66cdaa + + #6495ed + + #5f9ea0 + + #556b2f + + #4b0082 + + #48d1cc + + #483d8b + + #4682b4 + + #4169e1 + + #40e0d0 + + #3cb371 + + #32cd32 + + #2f4f4f + + #2e8b57 + + #228b22 + + #20b2aa + + #1e90ff + + #191970 + + #00ffff + + #00ff7f + + #00ff00 + + #00fa9a + + #00ced1 + + #00bfff + + #008b8b + + #008080 + + #008000 + + #006400 + + #0000cd + + #00008b + + #000080 + + #99cc33 + \ No newline at end of file diff --git a/lib/DevApp/src/main/res/values/strings.xml b/lib/DevApp/src/main/res/values/strings.xml new file mode 100644 index 0000000000..23285f7bb3 --- /dev/null +++ b/lib/DevApp/src/main/res/values/strings.xml @@ -0,0 +1,27 @@ + + + to + + App Pack name + App md5 signatures + App version code + App version name + Apk file path + SHA1 + SHA256 + First install time + Last update time + MinSdkVersion + TargetSdkVersion + Apk length + Effective time + Signature whether expire + Expired + Not expired + Certificate publisher + Certificate number + Certificate algorithm name + Certificate algorithm OID + Certificate DER code + Certificate UUID + \ No newline at end of file diff --git a/lib/DevApp/src/main/res/values/styles.xml b/lib/DevApp/src/main/res/values/styles.xml new file mode 100644 index 0000000000..bbf4036f4e --- /dev/null +++ b/lib/DevApp/src/main/res/values/styles.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/DevApp/src/main/res/values/unified.xml b/lib/DevApp/src/main/res/values/unified.xml new file mode 100644 index 0000000000..1839def9d6 --- /dev/null +++ b/lib/DevApp/src/main/res/values/unified.xml @@ -0,0 +1,6 @@ + + + 24.0dp + 8.0dp + 8.0dp + \ No newline at end of file diff --git a/lib/DevApp/src/main/res/xml/dev_app_provider_paths.xml b/lib/DevApp/src/main/res/xml/dev_app_provider_paths.xml new file mode 100644 index 0000000000..0c40bb34af --- /dev/null +++ b/lib/DevApp/src/main/res/xml/dev_app_provider_paths.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/DevApp/utils_readme/USE_CONFIG.md b/lib/DevApp/utils_readme/USE_CONFIG.md new file mode 100644 index 0000000000..cff328d118 --- /dev/null +++ b/lib/DevApp/utils_readme/USE_CONFIG.md @@ -0,0 +1,78 @@ +## 初始化 + +```java +/** + * detail: 全局 Application + * @author Ttt + */ +public class BaseApplication + extends Application { + + // 日志 TAG + private final String LOG_TAG = BaseApplication.class.getSimpleName(); + + @Override + public void onCreate() { + super.onCreate(); + +// // 初始化工具类 - 可不调用, 在 DevUtils FileProviderDevApp 中已初始化, 无需主动调用 +// DevUtils.init(this.getApplicationContext()); + // = 初始化日志配置 = + // 设置默认 Logger 配置 + LogConfig logConfig = new LogConfig(); + logConfig.logLevel = LogLevel.DEBUG; + logConfig.tag = LOG_TAG; + logConfig.sortLog = true; // 美化日志, 边框包围 + logConfig.methodCount = 0; + DevLogger.initialize(logConfig); + // 打开 lib 内部日志 - 线上环境, 不调用方法 + DevUtils.openLog(); + DevUtils.openDebug(); + } +} +``` + +# 配置与使用相关 - [目录][目录] + +## [DevLogger 日志工具类文档][DevLogger 日志工具类文档] + +## [DevToast Toast工具类文档][DevToast Toast工具类文档] + +## [ToastTintUtils Toast美化工具类文档][ToastTintUtils Toast美化工具类文档] + +## [SnackbarUtils Snackbar工具类文档][SnackbarUtils Snackbar工具类文档] + +## [FileRecordUtils 日志、异常文件记录保存工具类文档][FileRecordUtils 日志、异常文件记录保存工具类文档] + +## [SharedUtils - SharedPreferences工具类文档][SharedUtils - SharedPreferences工具类文档] + +## [DevCache - 缓存工具类文档][DevCache - 缓存工具类文档] + +## [TimerManager 定时器工具类文档][TimerManager 定时器工具类文档] + +## [DevThreadManager - 线程工具类文档][DevThreadManager - 线程工具类文档] + +## [DevMediaManager 多媒体工具类文档][DevMediaManager 多媒体工具类文档] + +## [WifiHotUtils - Wifi热点工具类文档][WifiHotUtils - Wifi热点工具类文档] + +## [TextViewUtils - 字体计算工具类使用][TextViewUtils - 字体计算工具类使用] + + + + + + +[目录]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use +[DevLogger 日志工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/DevLogger.md +[DevToast Toast工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/DevToast.md +[ToastTintUtils Toast美化工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/ToastTintUtils.md +[SnackbarUtils Snackbar工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/snackbar/SnackbarUtils.md +[FileRecordUtils 日志、异常文件记录保存工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/record/FileRecord.md +[SharedUtils - SharedPreferences工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/share/SharedUtils.md +[DevCache - 缓存工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/cache/DevCache.md +[TimerManager 定时器工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/timer/TimerManager.md +[DevThreadManager - 线程工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/thread/DevThreadManager.md +[DevMediaManager 多媒体工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/media/DevMediaManager.md +[WifiHotUtils - Wifi热点工具类文档]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/wifi/WifiHotUtils.md +[TextViewUtils - 字体计算工具类使用]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/text/TextCalcUse.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/cache/DevCache.md b/lib/DevApp/utils_readme/cache/DevCache.md new file mode 100644 index 0000000000..c3e3da5455 --- /dev/null +++ b/lib/DevApp/utils_readme/cache/DevCache.md @@ -0,0 +1,111 @@ +# Cache 工具类 + +#### 使用演示类 [CacheUse][CacheUse] 介绍了配置参数及使用 + +#### 项目类结构 - [包目录][包目录] + +* 缓存工具类([DevCache][DevCache]):缓存工具类,提供各种保存数据方法 + +* 缓存管理类([DevCacheManager][DevCacheManager]):内部缓存管理类 + +## API 文档 + +* **缓存类 ->** [DevCache.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java) + +| 方法 | 注释 | +| :- | :- | +| newCache | 获取 DevCache | +| getCachePath | 获取缓存地址 | +| remove | 移除数据 | +| removeForKeys | 删除 Key[] 配置、数据文件 | +| contains | 是否存在 key | +| isDue | 判断某个 key 是否过期 | +| clear | 清除全部数据 | +| clearDue | 清除过期数据 | +| clearType | 清除某个类型的全部数据 | +| getItemByKey | 通过 Key 获取 Item | +| getKeys | 获取有效 Key 集合 | +| getPermanentKeys | 获取永久有效 Key 集合 | +| getCount | 获取有效 Key 数量 | +| getSize | 获取有效 Key 占用总大小 | +| put | 保存 int 类型的数据 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getBytes | 获取 byte[] 类型的数据 | +| getBitmap | 获取 Bitmap 类型的数据 | +| getDrawable | 获取 Drawable 类型的数据 | +| getSerializable | 获取 Serializable 类型的数据 | +| getParcelable | 获取 Parcelable 类型的数据 | +| getJSONObject | 获取 JSONObject 类型的数据 | +| getJSONArray | 获取 JSONArray 类型的数据 | +| getKey | 获取存储 Key | +| isPermanent | 是否永久有效 | +| getType | 获取数据存储类型 | +| getSaveTime | 获取保存时间 ( 毫秒 ) | +| getValidTime | 获取有效期 ( 毫秒 ) | +| setType | setType | +| setSaveTime | setSaveTime | +| setValidTime | setValidTime | +| isInt | isInt | +| isLong | isLong | +| isFloat | isFloat | +| isDouble | isDouble | +| isBoolean | isBoolean | +| isString | isString | +| isBytes | isBytes | +| isBitmap | isBitmap | +| isDrawable | isDrawable | +| isSerializable | isSerializable | +| isParcelable | isParcelable | +| isJSONObject | isJSONObject | +| isJSONArray | isJSONArray | + +#### 使用示例 +```java +// 初始化 +CacheVo cacheVo = new CacheVo("测试持久化"); +// 打印信息 +DevEngine.getLog().dTag(TAG, "保存前: %s", cacheVo.toString()); +// 保存数据 +DevCache.newCache().put("ctv", cacheVo, -1); +// 重新获取 +CacheVo ctv = (CacheVo) DevCache.newCache().getSerializable("ctv"); +// 打印获取后的数据 +DevEngine.getLog().dTag(TAG, "保存后: %s", ctv.toString()); +// 设置保存有效时间 5秒 +DevCache.newCache().put("ctva", new CacheVo("测试有效时间"), 1); + +// 保存到指定文件夹下 +DevCache.newCache( + new File(PathUtils.getSDCard().getSDCardPath(), "Cache").getAbsolutePath() +).put("key", "保存数据", -1); + +// 延迟后 +new Thread(new Runnable() { + @Override + public void run() { + try { + // 延迟 1.5 已经过期再去获取 + Thread.sleep(1500); + // 获取数据 + CacheVo ctva = (CacheVo) DevCache.newCache().getSerializable("ctva"); + // 判断是否过期 + DevEngine.getLog().dTag(TAG, "是否过期: %s", (ctva == null)); + } catch (Exception ignored) { + } + } +}).start(); +``` + + + + + +[CacheUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/cache/CacheUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache +[DevCache]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache/DevCache.java +[DevCacheManager]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache/DevCacheManager.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/logger/DevLogger.md b/lib/DevApp/utils_readme/logger/DevLogger.md new file mode 100644 index 0000000000..a36a919600 --- /dev/null +++ b/lib/DevApp/utils_readme/logger/DevLogger.md @@ -0,0 +1,194 @@ +# DevLogger 日志工具类 + +#### 使用演示类 [LoggerUse][LoggerUse] 介绍了配置参数及使用 + +#### 项目类结构 - [包目录][包目录] + +* 日志操作类([DevLogger][DevLogger]):日志操作类(对外公开直接调用),直接 + +* 日志接口([IPrinter][IPrinter]):主要编写可以被外部调用接口,以及可以操作的类型 + +* 日志输出类([LoggerPrinter][LoggerPrinter]):实现日志接口,并且对对应的方法,进行处理,最终打印 + +* 日志设置([LogConfig][LogConfig]):该类主要控制日志输出方式,以及是否输入日志,堆栈方法等、提供常用日志配置快捷获取方法 + +* 日志配置([LogConstants][LogConstants]):该类主要是常量配置信息 + +* 日志级别([LogLevel][LogLevel]):该类主要控制日志级别 + + +#### 框架亮点 + +* 支持全局日志统一配置,以及部分日志个性化配置 + +* 支持无限长度日志打印,无 Logcat 4000 字符限制 + +* 支持可变参数传参,任意个数打印参数 + +* 支持 JSON、XML 字符串解析、格式化打印 + +* 支持无 TAG 快捷打印、以及全局配置 TAG + +* 支持显示行号、线程、类、方法信息等打印,以及堆栈信息跟踪、偏移打印等 + +* 支持全局控制打印级别,防止信息泄露,以及控制是否打印日志 + +* 支持存储日志信息到文件中 (含手机设备信息、应用版本信息),并且可追加顶部、底部信息等 + +* 美化日志,与系统日志打印格式区分,清晰快捷找到关键日志 + +* 支持输出含当前类及行号和函数名等堆栈信息,点击跳转 + + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| other | 使用单次其他日志配置 | +| getLogConfig | 获取日志配置信息 | +| init | 初始化日志配置信息(使用默认配置) | +| d | 打印 Log.DEBUG | +| e | 打印 Log.ERROR | +| w | 打印 Log.WARN | +| i | 打印 Log.INFO | +| v | 打印 Log.VERBOSE | +| wtf | 打印 Log.ASSERT | +| json | 格式化 JSON 格式数据, 并打印 | +| xml | 格式化 XML 格式数据, 并打印 | +| dTag | 打印 Log.DEBUG | +| eTag | 打印 Log.ERROR | +| wTag | 打印 Log.WARN | +| iTag | 打印 Log.INFO | +| vTag | 打印 Log.VERBOSE | +| wtfTag | 打印 Log.ASSERT | +| jsonTag | 格式化 JSON 格式数据, 并打印 | +| xmlTag | 格式化 XML 格式数据, 并打印 | + + +#### 全局配置 + +```java +// = 在 BaseApplication 中调用 = +// 初始化日志配置 +LogConfig logConfig = new LogConfig(); +// 堆栈方法总数(显示经过的方法) +logConfig.methodCount = 3; +// 堆栈方法索引偏移(0 = 最新经过调用的方法信息, 偏移则往上推, 如 1 = 倒数第二条经过调用的方法信息) +logConfig.methodOffset = 0; +// 是否输出全部方法(在特殊情况下, 如想要打印全部经过的方法, 但是不知道经过的总数) +logConfig.outputMethodAll = false; +// 显示日志线程信息(特殊情况, 显示经过的线程信息, 具体情况如上) +logConfig.displayThreadInfo = false; +// 是否排序日志(格式化后) +logConfig.sortLog = false; // 是否美化日志, 边框包围 +// 日志级别 +logConfig.logLevel = LogLevel.DEBUG; +// 设置 TAG (特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下) +logConfig.tag = "BaseLog"; +// 进行初始化配置, 这样设置后, 默认全部日志都使用改配置, 特殊使用 DevLogger.other(config).d(xxx); +DevLogger.initialize(logConfig); +``` + + +#### 配置事项 +```java +// 发布的时候, 默认不需要打印日志则修改为 +LogConfig logConfig = new LogConfig(); +logConfig.logLevel = LogLevel.NONE; // 全部不打印 +DevLogger.initialize(logConfig); // 该方法设置全局默认日志配置 + +// 还有一种情况, 部分日志发布的时候不打印, 但是有部分异常信息需要打印, 则单独使用配置 +LogConfig.getReleaseLogConfig(TAG) // 使用封装好的线上配置都行 +LogConfig.getReleaseLogConfig(TAG, LogLevel) // 使用封装好的线上配置都行 +DevLogger.initialize(LogConfig.getReleaseLogConfig(TAG)); +``` + + +#### 打印日志 +```java +// 无 TAG 快捷打印 (使用全局 LogConfig.tag) +DevLogger.v("测试数据 - v"); +DevLogger.d("测试数据 - d"); +DevLogger.i("测试数据 - i"); +DevLogger.w("测试数据 - w"); +DevLogger.e("错误 - e"); +DevLogger.wtf("测试数据 - wtf"); + +// 使用 自定义 TAG 打印日志 +DevLogger.vTag(tag, "测试数据 - v"); +DevLogger.dTag(tag, "测试数据 - d"); +DevLogger.iTag(tag, "测试数据 - i"); +DevLogger.wTag(tag, "测试数据 - w"); +DevLogger.eTag(tag, "错误 - e"); +DevLogger.wtfTag(tag, "测试数据 - wtf"); + +// 占位符(其他类型, 一样) +DevLogger.d("%s测试占位符数据 d%s", new Object[]{"1.", " - Format"}); +// = +DevLogger.dTag(tag, "%s测试占位符数据 d%s", new Object[]{"1.", " - Format"}); + +// 打印 JSON、XML 格式字符串数据 +// JSON对象 +DevLogger.json(TestData.SMALL_SON_WITH_NO_LINE_BREAK); +DevLogger.jsonTag(tag, TestData.SMALL_SON_WITH_NO_LINE_BREAK); +// XML数据 +DevLogger.xml(TestData.XML_DATA); +DevLogger.xmlTag(tag, TestData.XML_DATA); +``` + + +#### 打印日志(自定义配置) +```java +// 初始化日志配置 +LogConfig logConfig = new LogConfig(); +// 是否排序日志(格式化后) +logConfig.sortLog = true; +// 日志级别 +logConfig.logLevel = LogLevel.DEBUG; +// 设置 TAG (特殊情况使用, 不使用全部的 TAG 时, 如单独输出在某个 TAG 下) +logConfig.tag = "SAD"; +// 打印日志信息 +DevLogger.other(logConfig).e("new Config - e"); +DevLogger.other(logConfig).e(new Exception("报错"), "new Config - e"); +DevLogger.other(logConfig).eTag(tag, "new Config - e"); +DevLogger.other(logConfig).eTag(tag, new Exception("报错"), "new Config - e"); + +// 有 TAG 优先使用自定义 TAG, 无 TAG 才使用 LogConfig.tag +DevLogger.other(logConfig).eTag(tag, "new Config - e"); +``` + + +# 预览 + +***XML、JSON 格式化打印*** + +![][log_xml_json_png] + +***打印堆栈信息*** + +![][log_default_png] + +***打印异常信息*** + +![][log_error_png] + +***正常打印*** + +![][log_other_png] + + + + + +[LoggerUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/logger/LoggerUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger +[DevLogger]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/DevLogger.java +[IPrinter]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/IPrinter.java +[LoggerPrinter]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/LoggerPrinter.java +[LogConfig]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/LogConfig.java +[LogConstants]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/LogConstants.java +[LogLevel]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger/LogLevel.java +[log_xml_json_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/log_xml_json.png +[log_default_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/log_default.png +[log_error_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/log_error.png +[log_other_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/logger/log_other.png \ No newline at end of file diff --git a/lib/DevApp/utils_readme/logger/log_default.png b/lib/DevApp/utils_readme/logger/log_default.png new file mode 100644 index 0000000000..4230f87d49 Binary files /dev/null and b/lib/DevApp/utils_readme/logger/log_default.png differ diff --git a/lib/DevApp/utils_readme/logger/log_error.png b/lib/DevApp/utils_readme/logger/log_error.png new file mode 100644 index 0000000000..47b9b0e842 Binary files /dev/null and b/lib/DevApp/utils_readme/logger/log_error.png differ diff --git a/lib/DevApp/utils_readme/logger/log_other.png b/lib/DevApp/utils_readme/logger/log_other.png new file mode 100644 index 0000000000..214b6b9562 Binary files /dev/null and b/lib/DevApp/utils_readme/logger/log_other.png differ diff --git a/lib/DevApp/utils_readme/logger/log_xml_json.png b/lib/DevApp/utils_readme/logger/log_xml_json.png new file mode 100644 index 0000000000..9055432c82 Binary files /dev/null and b/lib/DevApp/utils_readme/logger/log_xml_json.png differ diff --git a/lib/DevApp/utils_readme/media/DevMediaManager.md b/lib/DevApp/utils_readme/media/DevMediaManager.md new file mode 100644 index 0000000000..0b306d1915 --- /dev/null +++ b/lib/DevApp/utils_readme/media/DevMediaManager.md @@ -0,0 +1,195 @@ +# 多媒体工具类 + +#### 使用演示类 [MediaUse][MediaUse] 介绍了配置参数及使用 + +#### 项目类结构 - [包目录][包目录] + +* 多媒体管理类([DevMediaManager][DevMediaManager]):MediaPlayer 统一管理类,全局使用一个 MediaPlayer + +* 视频播放控制类([DevVideoPlayerControl][DevVideoPlayerControl]):视频播放控制器,快捷播放视频工具类 + + +#### 框架亮点 + +* 单例 MediaPlayer,全局统一管理,防止多个多媒体资源同时存在后台播放 + +* 快捷封装各种通用方法,以及监听事件处理,控制处理 MediaPlayer + +* 支持使用 MediaSet 抽象类设置 MediaPlayer 其他配置方法等,以及是否循环播放等 + +* 支持快捷播放 raw、assets、本地SDCard、http 等路径,多媒体文件 + + +## API 文档 + +* **MediaPlayer 统一管理类 ->** [DevMediaManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevMediaManager 实例 | +| setAudioStreamType | 设置流类型 | +| playPrepareRaw | 播放 Raw 资源 | +| playPrepareAssets | 播放 Assets 资源 | +| playPrepare | 预加载播放 - ( file-path or http/rtsp URL ) http 资源、本地资源 | +| isPlaying | 是否播放中 | +| pause | 暂停操作 | +| stop | 停止操作 - 销毁 MediaPlayer | +| isIgnoreWhat | 是否忽略错误类型 | +| onError | 播放出错回调 | +| onVideoSizeChanged | 视频大小改变通知 | +| onPrepared | 使用 mMediaPlayer.prepareAsync() 异步播放准备成功回调 | +| onCompletion | 视频播放结束回调 | +| onBufferingUpdate | MediaPlayer 缓冲更新回调 | +| onSeekComplete | 滑动加载完成回调 | +| setMediaListener | 设置 MediaPlayer 回调事件 | +| isNullMediaPlayer | 判断 MediaPlayer 是否为 null | +| isNotNullMediaPlayer | 判断 MediaPlayer 是否不为 null | +| getMediaPlayer | 获取 MediaPlayer 对象 | +| setMediaPlayer | 设置 MediaPlayer 对象 | +| setTAG | 设置日志打印 TAG | +| getVolume | 获取播放音量 | +| setVolume | 设置播放音量 | +| getPlayRawId | 获取播放资源 id | +| getPlayUri | 获取播放地址 | +| getVideoWidth | 获取视频宽度 | +| getVideoHeight | 获取视频高度 | +| getCurrentPosition | 获取播放时间 | +| getDuration | 获取资源总时间 | +| getPlayPercent | 获取播放进度百分比 | +| isLooping | 是否循环播放 - 默认不循环 | +| setMediaConfig | 设置播放配置 | + + +* **视频播放控制器 ->** [DevVideoPlayerControl.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java) + +| 方法 | 注释 | +| :- | :- | +| surfaceChanged | Surface 改变通知 | +| surfaceCreated | Surface 创建 | +| surfaceDestroyed | Surface 销毁 | +| onPrepared | 准备完成回调 | +| onCompletion | 播放完成/结束 | +| onBufferingUpdate | 缓存进度 | +| onSeekComplete | 滑动进度加载成功 | +| onError | 异常回调 | +| onVideoSizeChanged | 视频大小改变通知 | +| setMediaListener | 设置播放监听事件 | +| pausePlayer | 暂停播放 | +| stopPlayer | 停止播放 | +| startPlayer | 开始播放 | +| getSurfaceView | 获取 SurfaceView | +| isPlaying | 是否播放中 | +| isAutoPlay | 判断是否自动播放 | +| setAutoPlay | 设置自动播放 | +| getPlayUri | 获取播放地址 | +| getVideoWidth | 获取视频宽度 | +| getVideoHeight | 获取视频高度 | +| getCurrentPosition | 获取播放时间 | +| getDuration | 获取资源总时间 | +| getPlayPercent | 获取播放进度百分比 | + + +#### 使用示例 +```java +// 设置 TAG, 打印日志使用 +DevMediaManager.getInstance().setTAG(TAG); +// 设置音量 +DevMediaManager.getInstance().setVolume(50); +// 设置流类型 +DevMediaManager.getInstance().setAudioStreamType(AudioManager.STREAM_MUSIC); + +// 获取播放音量 +DevMediaManager.getInstance().getVolume(); +// 获取当前播放的地址 +DevMediaManager.getInstance().getPlayUri(); +// 获取播放的资源id +DevMediaManager.getInstance().getPlayRawId(); +// 获取 当前播放时间 +DevMediaManager.getInstance().getCurrentPosition(); +// 获取资源总时间 +DevMediaManager.getInstance().getDuration(); +// 获取播放进度百分比 +DevMediaManager.getInstance().getPlayPercent(); +// 获取 MediaPlayer 对象 +DevMediaManager.getInstance().getMediaPlayer(); + +// 获取播放的视频高度 +DevMediaManager.getInstance().getVideoHeight(); +// 获取播放的视频宽度 +DevMediaManager.getInstance().getVideoWidth(); + +// 是否播放中 +DevMediaManager.getInstance().isPlaying(); +// 停止操作 +DevMediaManager.getInstance().stop(); +// 暂停操作 +DevMediaManager.getInstance().pause(); + +// 设置事件监听 +DevMediaManager.getInstance().setMediaListener(new DevMediaManager.MediaListener() { + @Override + public void onPrepared() { + if (DevMediaManager.getInstance().isNotNullMediaPlayer()) { + // 播放操作 + DevMediaManager.getInstance().getMediaPlayer().start(); + } + } + + @Override + public void onCompletion() { + } + + @Override + public void onBufferingUpdate(int percent) { + } + + @Override + public void onSeekComplete() { + } + + @Override + public void onError(int what, int extra) { + } + + @Override + public void onVideoSizeChanged(int width, int height) { + } +}); + +// = + +// 播放音频 +DevMediaManager.getInstance().playPrepareRaw(R.raw.dev_beep); +DevMediaManager.getInstance().playPrepareAssets("a.mp3"); +DevMediaManager.getInstance().playPrepare(PathUtils.getSDCard().getSDCardPath() + "/a.mp3"); +DevMediaManager.getInstance().playPrepare("http://xxx.mp3"); +DevMediaManager.getInstance().playPrepare(new DevMediaManager.MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) throws Exception { + mediaPlayer.setDataSource("xxx"); + } +}); // 自由设置信息 + +// = + +SurfaceView surfaceView = null; +// 播放视频 +DevVideoPlayerControl control = new DevVideoPlayerControl(surfaceView); +control.startPlayer(PathUtils.getSDCard().getSDCardPath() + "/video_3.mp4"); +control.startPlayer("http://xxx.mp4"); +control.startPlayer(new DevMediaManager.MediaSet() { + @Override + public void setMediaConfig(MediaPlayer mediaPlayer) throws Exception { + mediaPlayer.setDataSource("xxx"); + } +}); // 自由设置信息 +``` + + + + + +[MediaUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/media/MediaUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player +[DevMediaManager]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevMediaManager.java +[DevVideoPlayerControl]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/player/DevVideoPlayerControl.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/record/FileRecord.md b/lib/DevApp/utils_readme/record/FileRecord.md new file mode 100644 index 0000000000..1821b2ef56 --- /dev/null +++ b/lib/DevApp/utils_readme/record/FileRecord.md @@ -0,0 +1,80 @@ +# 日志、异常文件记录保存工具类文档 + +#### 使用演示类 [FileRecordUse][FileRecordUse] 介绍了配置参数及使用 + +#### 项目类结构 + +* 文件记录分析工具类([FileRecordUtils][FileRecordUtils]):用于记录信息方便分析,支持存储目录、时间段保存 + +## API 文档 + +* **文件记录分析工具类 ->** [FileRecordUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSuccessful | 校验记录方法返回字符串是否成功 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| setCallback | 设置文件记录回调 | +| getLogContent | 获取日志内容 | +| record | 记录方法 | + + +* **日志记录配置信息 ->** [RecordConfig.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordConfig.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取配置信息 | +| getStoragePath | 获取存储路径 | +| getFileName | 获取文件名 ( 固定 ) | +| getFolderName | 获取文件夹名 ( 模块名 ) | +| getFileIntervalTime | 获取文件记录间隔时间 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| getFinalPath | 获取文件地址 | + + +* **日志记录插入信息 ->** [RecordInsert.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/RecordInsert.java) + +| 方法 | 注释 | +| :- | :- | +| getFileInfo | getFileInfo | +| setFileInfo | setFileInfo | +| getLogHeader | getLogHeader | +| setLogHeader | setLogHeader | +| getLogTail | getLogTail | +| setLogTail | setLogTail | + +#### 日志、异常文件记录保存使用方法 +```java +String storagePath = PathUtils.getAppExternal().getAppCachePath(); + +// 创建文件夹 ( 以秒为存储单位 ) 创建如: HH_23/MM_13/SS_01 对应文件夹, 并存储到该目录下 +RecordConfig config = RecordConfig.get(storagePath, "Main_Module", RecordConfig.TIME.HH); + +// 创建文件夹 ( 以小时为存储单位 ) 创建如: HH_23 对应文件夹, 并存储到该目录下 +RecordConfig config2 = RecordConfig.get(storagePath, "User_Module", RecordConfig.TIME.HH); + +// 存储到 storagePath/FileRecord/yyyy_MM_dd/FolderName/HH_number/MM_number/SS_number/ 内 +FileRecordUtils.record(config, "日志内容"); + +// 保存错误信息 +NullPointerException nullPointerException = new NullPointerException("报错啦, null 异常啊"); + +// 单独异常 +FileRecordUtils.record(config2, nullPointerException); + +// 异常 + 日志 +FileRecordUtils.record(config2, "第一个日志内容", nullPointerException, "其他日志内容"); +``` + + + + + +[FileRecordUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/record/FileRecordUse.java +[FileRecordUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/share/SharedUtils.md b/lib/DevApp/utils_readme/share/SharedUtils.md new file mode 100644 index 0000000000..0ce3211a36 --- /dev/null +++ b/lib/DevApp/utils_readme/share/SharedUtils.md @@ -0,0 +1,90 @@ +# SharedPreferences 工具类 + +#### 使用演示类 [ShareUse][ShareUse] 介绍了配置参数及使用 + +> 1.apply 没有返回值而 commit 返回 boolean 表明修改是否提交成功 +> 2.apply 是将修改数据原子提交到内存,而后异步真正提交到硬件磁盘,而 commit 是同步的提交到硬件磁盘 +> 3.apply 方法不会提示任何失败的提示 apply 的效率高一些,如果没有必要确认是否提交成功建议使用 apply + +#### 项目类结构 - [包目录][包目录] + +* SharedPreferences 工具类([SPUtils][SPUtils]):SP 操作工具类,实现 IPreferenceHolder 初始化方法 + +* IPreference 持有类([IPreferenceHolder][IPreferenceHolder]):IPreference 持有类,内部返回实现类 IPreference + +* IPreference 接口类([IPreference][IPreference]):主要是正常操作方法接口类 + +* PreferenceImpl 接口实现类([PreferenceImpl][PreferenceImpl]):实现 IPreference 接口,SharedPreferences 操作接口具体实现类 + +* SharedPreferences 快捷使用工具类([SharedUtils][SharedUtils]):内部实现 SPUtils,直接进行使用 put/get 等 + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| put | 保存数据 | +| putAll | 保存 Map 集合(只能是 Integer、Long、Boolean、Float、String、Set) | +| get | 根据 key 获取数据 | +| getAll | 获取全部数据 | +| remove | 移除数据 | +| removeAll | 移除集合的数据 | +| contains | 是否存在 key | +| clear | 清除全部数据 | +| getInt | 获取 int 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getSet | 获取 Set 类型的数据 | + +#### 使用示例 +```java +// 具体实现方法 基于 PreferenceImpl 实现 + +// 存在可调用的方法 IPreference + +// SharedUtils 二次分装 SPUtils, 快捷调用 + +SharedUtils.put("aa", "aa"); +SharedUtils.put("ac", 123); + +// =========== +// = SPUtils = +// =========== + +// 想要自定义 模式, 名字等 +SPUtils.getPreference(DevUtils.getContext()).put("aa", 1); +SPUtils.getPreference(DevUtils.getContext(), "xxx").put("aa", 1); +SPUtils.getPreference(DevUtils.getContext(), "xxxxx", Context.MODE_PRIVATE).put("aa", 1); + + +// 默认值如下 +switch (type) { + case INTEGER: + return preferences.getInt(key, -1); + case FLOAT: + return preferences.getFloat(key, -1F); + case BOOLEAN: + return preferences.getBoolean(key, false); + case LONG: + return preferences.getLong(key, -1L); + case STRING: + return preferences.getString(key, null); + case STRING_SET: + return preferences.getStringSet(key, null); + default: // 默认取出String类型的数据 + return null; +} +``` + + + + + +[ShareUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/share/ShareUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share +[SPUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/SPUtils.java +[IPreferenceHolder]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/IPreferenceHolder.java +[IPreference]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/IPreference.java +[PreferenceImpl]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/PreferenceImpl.java +[SharedUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share/SharedUtils.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/snackbar/SnackbarUtils.md b/lib/DevApp/utils_readme/snackbar/SnackbarUtils.md new file mode 100644 index 0000000000..92b60c6eb5 --- /dev/null +++ b/lib/DevApp/utils_readme/snackbar/SnackbarUtils.md @@ -0,0 +1,462 @@ +# Snackbar 工具类 + +#### 使用演示类 [SnackbarUse][SnackbarUse] 介绍了配置参数及使用 + +#### 项目类结构 + +* Snackbar 工具类([SnackbarUtils][SnackbarUtils]):Snackbar 二次封装工具类 + +* Snackbar Style 样式抽象类(SnackbarUtils.Style):主要决定 Snackbar 显示效果样式,可继承该抽象类,重写需要的方法,并通过 setStyle 设置 + +* Snackbar Style 样式构建类(SnackbarUtils.StyleBuilder):该类继承 SnackbarUtils.Style,并且增加 set 属性方法,并且可通过 Style 创建 StyleBuilder 并引用其样式配置 + +#### 框架亮点 + +* 支持通过 view/window/fragment/activity 构建 Snackbar 且可通过链式调用 + +* 支持自动区分资源 stringId,支持可变参数传参,自动格式化显示文本内容 + +* 使用 WeakReference 防止内存溢出,并且支持 Snackbar 释放处理 + +* 支持 addView,above,bellow,在指定的 view 上/下 方位置显示,以及显示区域自动计算处理 + +* 支持手动关闭 Snackbar,以及 Snackbar 事件监听处理等 + +* 支持自定义样式,可配置样式属性比较全面,高度自由可配置 + +* 支持样式构建引用,在统一的样式上构建使用,并快捷修改全局样式 + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| with | 获取 SnackbarUtils 对象 | +| getStyle | 获取样式 | +| setStyle | 设置样式 | +| getSnackbar | 获取 Snackbar | +| getSnackbarView | 获取 Snackbar View | +| getTextView | 获取 Snackbar TextView ( snackbar_text ) | +| getActionButton | 获取 Snackbar Action Button ( snackbar_action ) | +| addView | 向 Snackbar 布局中添加 View (Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示) | +| setCallback | 设置 Snackbar 展示完成 及 隐藏完成 的监听 | +| setAction | 设置 Action 按钮文字内容及点击监听 | +| dismiss | 关闭 Snackbar | +| showShort | 显示 Short Snackbar | +| showLong | 显示 Long Snackbar | +| showIndefinite | 显示 Indefinite Snackbar (无限时, 一直显示) | +| setSnackbarStyle | 设置 Snackbar 样式配置 | +| getShadowMargin | 获取阴影边距 | +| setShadowMargin | 设置阴影边距 | +| isAutoCalc | 判断是否自动计算边距 (如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示) | +| setAutoCalc | 设置是否自动计算边距 (如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示) | +| above | 设置 Snackbar 显示在指定 View 的上方 | +| bellow | 设置 Snackbar 显示在指定 View 的下方 | + + +### 调用方法 + +#### 获取 SnackbarUtils + +> SnackbarUtils.with(view/window/fragment/activity); + +#### 显示 Snackbar + +> SnackbarUtils.with(view).showShort/showLong/showIndefinite(R.string.xxx、String); + +#### 关闭 Snackbar + +> SnackbarUtils.with(view).dismiss(isSetNull); // 是否销毁 Snackbar + +#### 设置 Action Button + +> SnackbarUtils.with(view).setAction(clickListener, R.string.xxx、String); + +#### 设置样式 + +> SnackbarUtils.with(view).setStyle(style); + + + +### 使用示例 +```java +// ========================================== +// = 只能通过以下四种方式 获取 SnackbarUtils 对象 = +// ========================================== + +SnackbarUtils.with(view); + +SnackbarUtils.with(window); + +SnackbarUtils.with(fragment); + +SnackbarUtils.with(activity); + + +// ============= +// = 获取相关方法 = +// ============= + +// = 获取 View = + +// 获取 Snackbar 底层 View +View snackbarView = SnackbarUtils.with(view).getSnackbarView(); + +// 获取 Snackbar TextView ( snackbar_text ) - 左侧 文本TextView +TextView textView = SnackbarUtils.with(view).getTextView(); + +// 获取 Snackbar Action Button ( snackbar_action ) - 右侧 Button +Button actionButton = SnackbarUtils.with(view).getActionButton(); + +// = + +// 获取 Snackbar 对象 +Snackbar snackbar = SnackbarUtils.with(view).getSnackbar(); + +// 获取 View 阴影边距大小 - View 自带阴影 +int shadowMargin = SnackbarUtils.with(view).getShadowMargin(); + +// 获取 是否自动计算边距 (如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示) +boolean autoCalc = SnackbarUtils.with(view).isAutoCalc(); // 只有调用 above / bellow 该属性才有意义 + +// 获取 Snackbar 显示效果样式配置信息 +SnackbarUtils.StyleBuilder styleBuilder = SnackbarUtils.with(view).getStyle(); + + +// ============= +// = 设置相关方法 = +// ============= + +// 设置 View 阴影边距大小 +SnackbarUtils.with(view).setShadowMargin(2); + +// 设置是否自动计算边距 (如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示) +SnackbarUtils.with(view).setAutoCalc(true); // 只有调用 above / bellow 该属性才有意义 + +// 设置 Snackbar 显示效果样式 +SnackbarUtils snackbarUtils = SnackbarUtils.with(view).setStyle(style); + +// = 快捷设置指定样式效果的 Snackbar = + +// 设置 Snackbar 显示效果为 当前 SnackbarUtils 对象使用的样式 +Snackbar snackbar1 = SnackbarUtils.with(view).setSnackbarStyle(snackbar); + +// 设置 Snackbar 显示效果为 自定义样式效果 +Snackbar snackbar2 = SnackbarUtils.with(view).setSnackbarStyle(snackbar, style); + + +// = 设置 Action Button 文案等 = + +// 设置 Snackbar Action Button(snackbar_action) 文案 +SnackbarUtils.with(view).setAction(R.string.app_name); + +// 设置 Snackbar Action Button(snackbar_action) 文案 - 支持格式化字符串 +SnackbarUtils.with(view).setAction(R.string.app_name, "1", 2); + +// 设置 Snackbar Action Button(snackbar_action) 文案 +SnackbarUtils.with(view).setAction("撤销"); + +// 设置 Snackbar Action Button(snackbar_action) 文案 - 支持格式化字符串 +SnackbarUtils.with(view).setAction("撤销 %s", "3"); + +// 设置 Snackbar Action Button(snackbar_action) 文案以及点击事件 +SnackbarUtils.with(view).setAction(clickListener, R.string.app_name); + +// 设置 Snackbar Action Button(snackbar_action) 文案以及点击事件 +SnackbarUtils.with(view).setAction(clickListener, "撤销"); + + +// = 设置 事件相关 = + +// 设置 Snackbar 展示完成 及 隐藏完成 的监听 +SnackbarUtils.with(view).setCallback(new Snackbar.Callback() { + @Override + public void onShown(Snackbar sb) { + super.onShown(sb); + // Snackbar 显示 + } + + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + super.onDismissed(transientBottomBar, event); + // Snackbar 关闭 + } +}); + +// ========== +// = 操作方法 = +// ========== + +// = 关闭 = + +// 关闭显示 Snackbar +SnackbarUtils.with(view).dismiss(); + +// 关闭显示 Snackbar, 但不销毁 Snackbar +SnackbarUtils.with(view).dismiss(false); + +// = 显示 - 支持 String、R.string.xx 以及格式化字符串 = + +// 显示 Short Snackbar +SnackbarUtils.with(view).showShort("已收藏该消息!"); + +// 显示 Long Snackbar +SnackbarUtils.with(view).showLong("已收藏该消息!"); + +// 显示 Indefinite Snackbar (无限时, 一直显示) +SnackbarUtils.with(view).showIndefinite("已收藏该消息!"); + +// = 显示区域 = + +// 设置是否自动计算边距 (如: 显示在 View 下面, 但是下方距离不够, 自动设置为在 View 上方显示) +// setAutoCalc 只有调用 above / bellow 该属性才有意义 + +// 设置 Snackbar 显示在指定 View 的上方, 并且向上边距 20 +SnackbarUtils.with(view).above(targetView, 20); + +// 设置 Snackbar 显示在指定 View 的下方, 并且向下边距 5 +SnackbarUtils.with(view).bellow(targetView, 5); + +// 向 Snackbar 布局中添加 View (Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示) +SnackbarUtils.with(view).addView(newTextView, 0); + +// 向 Snackbar 布局中添加 View (Google 不建议, 复杂的布局应该使用 DialogFragment 进行展示) +SnackbarUtils.with(view).addView(viewId, 1); + +// = 结合使用 = + +// 只有调用了 showXxx, 才会进行设置样式, 并且显示 +SnackbarUtils.with(view) + .addView(viewId, 0) + .setStyle(new NightStyle()) + .setAction(new View.OnClickListener() { + @Override + public void onClick(View v) { + + } + }, "撤销") + .bellow(targetView, 0) + .setCallback(new Snackbar.Callback() { + @Override + public void onDismissed(Snackbar transientBottomBar, int event) { + } + }).setAutoCalc(true) + .showShort("已收藏该消息!"); + +// = + +// 通过已有样式创建 StyleBuilder 并修改样式效果使用 +SnackbarUtils.StyleBuilder styleBuilder1 = new SnackbarUtils.StyleBuilder(style); +styleBuilder1.setActionColor(Color.RED); +SnackbarUtils.with(view).setStyle(styleBuilder1).showShort("已收藏该消息!"); + +// 修改默认样式中的部分展示效果 +SnackbarUtils snackbarUtils1 = SnackbarUtils.with(view); +SnackbarUtils.StyleBuilder styleBuilder2 = snackbarUtils1.getStyle(); +styleBuilder2.setActionColor(Color.BLACK); +snackbarUtils1.setStyle(styleBuilder2); +``` + + +#### 自定义样式 +```java +/** + * detail: 自定义样式 - 可参照下方实现方法, 进行配置 + * @author Ttt + */ +class NightStyle extends SnackbarUtils.Style { + @Override + public int getTextColor() { + return Color.WHITE; + } + + @Override + public float getRootAlpha() { + return 0.5F; + } +} +``` + + +```java +/** + * detail: 样式相关 + * @author Ttt + */ +SnackbarUtils.Style style = new SnackbarUtils.Style() { + + // ============ + // = RootView = + // ============ + + /** + * RootView 的重心 + * @return + */ + public int getRootGravity() { + return 0; + } + + /** + * RootView 背景圆角大小 + * @return + */ + public float getRootCornerRadius() { + return 0F; + } + + /** + * RootView 背景着色颜色 + * @return + */ + public int getRootBackgroundTintColor() { + return 0; + } + + /** + * RootView 背景图片 + * @return + */ + public Drawable getRootBackground() { + return null; + } + + /** + * RootView margin 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getRootMargin() { + return null; + } + + /** + * RootView 透明度 + * @return + */ + public float getRootAlpha() { + return 1.0F; + } + + // = snackbar_text TextView 相关 = + + /** + * TextView 的重心 + * @return + */ + public int getTextGravity() { + return 0; + } + + /** + * TextView 文本颜色 + * @return + */ + public int getTextColor() { + return 0; + } + + /** + * TextView 字体大小 + * @return + */ + public float getTextSize() { + return 0F; + } + + /** + * TextView 最大行数 + * @return + */ + public int getTextMaxLines() { + return 0; + } + + /** + * TextView Ellipsize 效果 + * @return + */ + public TextUtils.TruncateAt getTextEllipsize() { + return null; + } + + /** + * TextView 字体样式 + * @return + */ + public Typeface getTextTypeface() { + return null; + } + + /** + * TextView padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getTextPadding() { + return null; + } + + // = snackbar_action Button 相关 = + + /** + * Action Button 的重心 + * @return + */ + public int getActionGravity() { + return 0; + } + + /** + * Action Button 文本颜色 + * @return + */ + public int getActionColor() { + return 0; + } + + /** + * Action Button 字体大小 + * @return + */ + public float getActionSize() { + return 0F; + } + + /** + * Action Button padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + public int[] getActionPadding() { + return null; + } + + /** + * Action Button 背景圆角大小 + * @return + */ + public float getActionCornerRadius() { + return 0F; + } + + /** + * Action Button 背景着色颜色 + * @return + */ + public int getActionBackgroundTintColor() { + return 0; + } + + /** + * Action Button 背景图片 + * @return + */ + public Drawable getActionBackground() { + return null; + } +}; +``` + + + + + +[SnackbarUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/snackbar/SnackbarUse.java +[SnackbarUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/SnackbarUtils.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/thread/DevThreadManager.md b/lib/DevApp/utils_readme/thread/DevThreadManager.md new file mode 100644 index 0000000000..f91031e7a2 --- /dev/null +++ b/lib/DevApp/utils_readme/thread/DevThreadManager.md @@ -0,0 +1,73 @@ +# 线程工具类 + +#### 使用演示类 [ThreadUse][ThreadUse] 介绍了配置参数及使用 + +#### 项目类结构 - [包目录][包目录] + +* 线程池管理 - 开发类([DevThreadManager][DevThreadManager]):内部封装 DevThreadPool 配置处理,方便直接使用 + +* 线程池 - 开发类([DevThreadPool][DevThreadPool]):具体线程池操作方法、线程处理等 + +## API 文档 + +* **线程池管理 - 开发类 ->** [DevThreadManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevThreadManager 实例 | +| initConfig | 初始化配置信息 | +| putConfig | 添加配置信息 | +| removeConfig | 移除配置信息 | + + +* **线程池 - 开发类 ->** [DevThreadPool.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java) + +| 方法 | 注释 | +| :- | :- | +| execute | 加入到线程池任务队列 | +| shutdown | shutdown 会等待所有提交的任务执行完成, 不管是正在执行还是保存在任务队列中的已提交任务 | +| shutdownNow | shutdownNow会尝试中断正在执行的任务(其主要是中断一些指定方法如sleep方法), 并且停止执行等待队列中提交的任务 | +| isShutdown | 判断线程池是否已关闭 = isShutDown当调用shutdown()方法后返回为 true | +| isTerminated | 若关闭后所有任务都已完成, 则返回 true. | +| awaitTermination | 请求关闭、发生超时或者当前线程中断 | +| submit | 提交一个Callable任务用于执行 | +| invokeAll | 执行给定的任务 | +| invokeAny | 执行给定的任务 | +| schedule | 延迟执行Runnable命令 | +| scheduleWithFixedRate | 延迟并循环执行命令 | +| scheduleWithFixedDelay | 延迟并以固定休息时间循环执行命令 | + +#### 使用示例 +```java +Runnable runnable = new Runnable() { + @Override + public void run() { + + } +}; + +// = 优先判断 10个线程数, 的线程池是否存在, 不存在则创建, 存在则复用 = +DevThreadManager.getInstance(10).execute(runnable); + +// 与上面 传入 int 是完全不同的线程池 +DevThreadManager.getInstance("10").execute(runnable); + +// 可以先增加配置 +DevThreadManager.putConfig("QPQP", new DevThreadPool(DevThreadPool.DevThreadPoolType.CALC_CPU)); +// 使用配置的信息 +DevThreadManager.getInstance("QPQP").execute(runnable); + + +DevThreadManager.putConfig("QQQQQQ", 10); +// 使用配置的信息 +DevThreadManager.getInstance("QQQQQQ").execute(runnable); +``` + + + + + +[ThreadUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/thread/ThreadUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread +[DevThreadManager]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadManager.java +[DevThreadPool]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/common/thread/DevThreadPool.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/timer/TimerManager.md b/lib/DevApp/utils_readme/timer/TimerManager.md new file mode 100644 index 0000000000..60ee7283ae --- /dev/null +++ b/lib/DevApp/utils_readme/timer/TimerManager.md @@ -0,0 +1,130 @@ +# 定时器工具类 + +#### 使用演示类 [TimerActivity][TimerActivity] 介绍了配置参数及使用 + +```java +/** + * 主要是为了控制整个项目的定时器, 防止定时器混乱、忘记关闭等情况, 以及减少初始化等操作代码 + */ +``` + +#### 项目类结构 + +* 定时器管理类([TimerManager][TimerManager]):定时器管理类 + +* 定时器([DevTimer][DevTimer]):定时器封装类,配合定时器管理类使用全局控制 + + +#### 框架亮点 + +* 控制整个项目定时器,防止定时器混乱、忘记关闭等情况,统一控制管理 + +* 内部自动添加定时器到集合中,便于项目控制处理 + +* 支持关闭指定 tag timer、all timer,获取指定 Timer + +* 内部封装 Timer,支持获取执行次数、是否无限循环、TAG 标记等通用功能 + +## API 文档 + +* **定时器 ->** [DevTimer.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java) + +| 方法 | 注释 | +| :- | :- | +| getTag | 获取 TAG | +| getUUID | 获取 UUID HashCode | +| getDelay | 获取延迟时间 ( 多少毫秒后开始执行 ) | +| getPeriod | 获取循环时间 ( 每隔多少毫秒执行一次 ) | +| isRunning | 判断是否运行中 | +| isMarkSweep | 是否标记清除 | +| getTriggerNumber | 获取已经触发的次数 | +| getTriggerLimit | 获取允许触发的上限次数 | +| isTriggerEnd | 是否触发结束 ( 到达最大次数 ) | +| isInfinite | 是否无限循环 | +| setHandler | 设置 UI Handler | +| setCallback | 设置回调事件 | +| start | 运行定时器 | +| stop | 关闭定时器 | +| setTag | setTag | +| setDelay | setDelay | +| setPeriod | setPeriod | +| getLimit | getLimit | +| setLimit | setLimit | +| build | build | +| callback | 触发回调方法 | + + +* **定时器管理类 ->** [TimerManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java) + +| 方法 | 注释 | +| :- | :- | +| addContainsChecker | 添加包含校验 | +| getSize | 获取全部定时器总数 | +| recycle | 回收定时器资源 | +| getTimer | 获取对应 TAG 定时器 ( 优先获取符合的 ) | +| getTimers | 获取对应 TAG 定时器集合 | +| closeAll | 关闭全部定时器 | +| closeAllNotRunning | 关闭所有未运行的定时器 | +| closeAllInfinite | 关闭所有无限循环的定时器 | +| closeAllTag | 关闭所有对应 TAG 定时器 | +| closeAllUUID | 关闭所有对应 UUID 定时器 | + + +#### 使用示例 +```java +DevTimer timer = new DevTimer.Builder(1500L) + .setDelay(100L) // 延迟时间 ( 多少毫秒后开始执行 ) + .setPeriod(1500L) // 循环时间 ( 每隔多少毫秒执行一次 ) + .setTag(TAG) // 定时器 TAG + .setLimit(19) // 触发次数上限 ( 负数为无限循环 ) + .build(); // 构建定时器 +timer.setCallback(new DevTimer.Callback() { + @Override + public void callback( + DevTimer timer, + int number, + boolean end, + boolean infinite + ) { + DevEngine.getLog().dTag(TAG, "是否 UI 线程: %s", HandlerUtils.isMainThread()); + } +}); +// 设置了 Handler 则属于 UI 线程触发回调 +timer.setHandler(mUiHandler); +// 运行定时器 +timer.start(); +// 关闭定时器 +timer.stop(); + +int uuid = 0; +// 关闭所有对应 UUID 定时器 +TimerManager.closeAllUUID(uuid); +// 关闭所有对应 TAG 定时器 +TimerManager.closeAllTag(TAG); +// 关闭所有无限循环的定时器 +TimerManager.closeAllInfinite(); +// 关闭所有未运行的定时器 +TimerManager.closeAllNotRunning(); +// 关闭全部定时器 +TimerManager.closeAll(); +// 回收定时器资源 +TimerManager.recycle(); +// 获取全部定时器总数 +TimerManager.getSize(); +// 获取对应 UUID 定时器 ( 优先获取符合的 ) +TimerManager.getTimer(uuid); +// 获取对应 TAG 定时器 ( 优先获取符合的 ) +TimerManager.getTimer(TAG); +// 获取对应 UUID 定时器集合 +TimerManager.getTimers(uuid); +// 获取对应 TAG 定时器集合 +TimerManager.getTimers(TAG); +``` + + + + + +[TimerActivity]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/feature/other_function/timer/TimerActivity.kt +[TimerManager]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/TimerManager.java +[DevTimer]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/timer/DevTimer.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/toast/DevToast.md b/lib/DevApp/utils_readme/toast/DevToast.md new file mode 100644 index 0000000000..f4499dbeb5 --- /dev/null +++ b/lib/DevApp/utils_readme/toast/DevToast.md @@ -0,0 +1,267 @@ +# Toast 工具类 + +#### 使用演示类 [DevToastUse][DevToastUse] 介绍了配置参数及使用 + +> 1. 支持子线程弹出 Toast,可通过开关配置 +> 2. 内部解决 Android 7.1.1 崩溃问题 +> 3. 已处理 部分 ROM 如魅族、小米、三星等关闭应用通知,无法显示 Toast 问题 + +#### 项目类结构 - [包目录][包目录] + +* Toast 工具类([DevToast][DevToast]):Toast 工具类(对外公开直接调用),直接调用 IToastImpl 类方法 + +* Toast 接口([IToast][IToast]):主要编写 Operate 操作接口、Style 样式接口、Filter 过滤接口 + +* Toast 接口实现方法([IToastImpl][IToastImpl]):实现 Toast.Operate 接口,并且对对应的方法,进行处理 + +* Toast 工厂模式([ToastFactory][ToastFactory]):用于生成适配不同 Android 版本对应的 Toast,以及解决无通知权限显示 Toast + +* Toast 默认样式([DefaultToastStyle][DefaultToastStyle]):该类实现 IToast.Style 用于配置自定义 Toast + + +#### 框架亮点 + +* 支持无通知权限 Toast 弹出 + +* 支持不分主次线程都可以弹出 Toast,并可通过开关控制 + +* 支持自动区分资源 stringId,支持可变参数传参,自动格式化 Toast 内容 + +* 使用单例 Toast,避免频繁弹出造成不良的用户体验 + +* 支持自动适配,根据不同 Android 版本、是否有通知权限,生成不同的 Toast 对象 + +* 支持自定义 View 扩展,通过 setView,重新自定义 Toast 布局 + +* 支持自定义样式:Toast(Gravity、背景、圆角、边距等)、TextView(样式、颜色、大小、Ellipsize等) + +* 支持全局配置样式:可通过配置全局通用样式,或单独 Toast 特殊样式,实现整个应用统一替换/设置 + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| reset | 重置默认参数 | +| setUseHandler | 设置是否使用 Handler 显示 Toast | +| setNullText | 设置 Text 为 null 的文本 | +| setTextLength | 设置 Toast 文案长度转换 显示时间 | +| init | Application 初始化调用 (内部已调用) | +| style | 使用单次 Toast 样式配置 | +| defaultStyle | 使用默认 Toast 样式 | +| getToastStyle | 获取 Toast 样式配置 | +| initStyle | 初始化 Toast 样式配置 | +| initToastFilter | 初始化 Toast 过滤器 | +| setView | 设置 Toast 显示的 View | +| show | 显示 Toast | +| cancel | 取消当前显示的 Toast | + + +#### 配置相关 + +```java +// 初始化 Toast - DevUtils 内部已经调用 +DevToast.initialize(application); // 必须调用 + +// 初始化 Toast 样式 - 全局通用 +// DevToast.initStyle(new IToast.Style() {}); // 可以实现 IToast.Style 接口, 参照 DefaultToastStyle + +// 当 Toast 内容为 null 时, 显示的内容 +DevToast.setNullText("text is null"); + +// 是否设置 Handler 显示 Toast - 默认 true, 支持子线程显示 Toast +DevToast.setUseHandler(true); + +// 设置文本长度限制, 超过设置的位数则 为 LENGTH_LONG +DevToast.setTextLength(15); + +// 支持自定义 View - 可不配置, 默认使用系统 Toast View +DevToast.setView(view); +DevToast.setView(viewId); + +// 配置 Toast 过滤, 判断是否显示 Toast、以及内容处理 +// DevToast.initToastFilter(new IToast.Filter() {}); + +// 恢复默认配置 +DevToast.reset(); +``` + + +#### 使用示例 +```java +// 显示 Toast +DevToast.show(view); +DevToast.show(R.string.app_name); +DevToast.show("Toast"); // initStyle - Toast + +// 使用特殊样式 - 默认统一全局样式, style 则为 这个 Toast 单独为这个样式 +DevToast.style(new TempStyle()).show("tempStyle - Toast"); + +// 获取 当前全局使用的样式 +DevToast.getToastStyle(); + +// 获取默认样式 +DevToast.defaultStyle(); + +// 关闭正在显示的 Toast +DevToast.cancel(); +``` + + +#### 自定义样式 +```java +/** + * 自定义实现样式 + */ +private static class TempStyle implements IToast.Style { + + /** + * Toast 的重心 + * @return + */ + @Override + public int getGravity() { + return 0; + } + + /** + * X轴偏移 + * @return + */ + @Override + public int getXOffset() { + return 0; + } + + /** + * Y轴偏移 + * @return + */ + @Override + public int getYOffset() { + return 0; + } + + /** + * 获取水平边距 + * @return + */ + @Override + public int getHorizontalMargin() { + return 0; + } + + /** + * 获取垂直边距 + * @return + */ + @Override + public int getVerticalMargin() { + return 0; + } + + /** + * Toast Z轴坐标阴影 + * @return + */ + @Override + public int getZ() { + return 0; + } + + /** + * 圆角大小 + * @return + */ + @Override + public float getCornerRadius() { + return 5F; + } + + /** + * 背景着色颜色 + * @return + */ + @Override + public int getBackgroundTintColor() { + return 0xB2000000; + } + + /** + * 背景图片 + * @return + */ + @Override + public Drawable getBackground() { + return null; + } + + // = TextView 相关 = + + /** + * 文本颜色 + * @return + */ + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 字体大小 + * @return + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 最大行数 + * @return + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * Ellipsize 效果 + * @return + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 字体样式 + * @return + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * TextView padding 边距 - new int[] { left, top, right, bottom } + * @return + */ + @Override + public int[] getPadding() { + return new int[] { 25, 10, 25, 10 }; + } +} +``` + + + + + +[DevToastUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/toast/DevToastUse.java +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster +[DevToast]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DevToast.java +[IToast]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToast.java +[IToastImpl]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/IToastImpl.java +[ToastFactory]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/ToastFactory.java +[DefaultToastStyle]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/toaster/DefaultToastStyle.java \ No newline at end of file diff --git a/lib/DevApp/utils_readme/toast/ToastTintUtils.md b/lib/DevApp/utils_readme/toast/ToastTintUtils.md new file mode 100644 index 0000000000..0e04926148 --- /dev/null +++ b/lib/DevApp/utils_readme/toast/ToastTintUtils.md @@ -0,0 +1,283 @@ +# Toast 美化工具类 + +#### 使用演示类 [ToastTintUse][ToastTintUse] 介绍了配置参数及使用 + +> 1. 支持子线程弹出 Toast,可通过开关配置 +> 2. 内部解决 Android 7.1.1 崩溃问题 +> 3. 但无处理 部分 ROM 如魅族、小米、三星等关闭应用通知,无法显示 Toast 问题 + +#### 项目类结构 + +* Toast 工具类([ToastTintUtils][ToastTintUtils]):Toast 美化工具类 + + +#### 框架亮点 + +* 支持不分主次线程都可以弹出 Toast,并可通过开关控制 + +* 支持自动区分资源 stringId,支持可变参数传参,自动格式化 Toast 内容 + +* 支持自动适配,根据不同 Android 版本,生成不同的 Toast 对象 + +* 支持自定义样式:Toast(Gravity、背景、圆角、边距等)、TextView(样式、颜色、大小、Ellipsize等) + +* 默认几种样式 Toast (normal、info、warning、success、error),并且支持统一变更设置样式、以及自定义样式 + +* 原始 Toast 基础上封装美化,并且可通过 Style 控制 Toast 效果,实现全局配置 Style 效果 + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| reset | 重置默认参数 | +| setToastFilter | 设置 Toast 过滤器 | +| setUseHandler | 设置是否使用 Handler 显示 Toast | +| setNullText | 设置 Text 为 null 的文本 | +| setUseConfig | 设置是否使用配置 | +| setGravity | 设置 Toast 显示在屏幕上的位置 | +| setMargin | 设置边距 | +| getDefaultStyle | 获取默认样式 | +| getNormalStyle | 获取 Normal 样式 | +| getInfoStyle | 获取 Info 样式 | +| getWarningStyle | 获取 Warning 样式 | +| getErrorStyle | 获取 Error 样式 | +| getSuccessStyle | 获取 Success 样式 | +| setNormalStyle | 设置 Normal 样式 | +| setInfoStyle | 设置 Info 样式 | +| setWarningStyle | 设置 Warning 样式 | +| setErrorStyle | 设置 Error 样式 | +| setSuccessStyle | 设置 Success 样式 | +| getInfoDrawable | 获取 Info 样式 icon | +| getWarningDrawable | 获取 Warning 样式 icon | +| getErrorDrawable | 获取 Error 样式 icon | +| getSuccessDrawable | 获取 Success 样式 icon | +| normal | normal 样式 Toast | +| info | info 样式 Toast | +| warning | warning 样式 Toast | +| error | error 样式 Toast | +| success | success 样式 Toast | +| custom | custom Toast | + + +#### 配置相关 + +```java +// 获取默认样式 +ToastTintUtils.getDefaultStyle(); + +// 获取 Normal 样式 +ToastTintUtils.getNormalStyle(); +// 设置 Normal 样式 +ToastTintUtils.setNormalStyle(style); + +// 获取 Error 样式 +ToastTintUtils.getErrorStyle(); +// 设置 Error 样式 +ToastTintUtils.setErrorStyle(style); +// 获取 Error 样式 小图标 +ToastTintUtils.getErrorDrawable(); + +// 获取 Warning 样式 +ToastTintUtils.getWarningStyle(); +// 设置 Warning 样式 +ToastTintUtils.setWarningStyle(style); +// 获取 Warning 样式 小图标 +ToastTintUtils.getWarningDrawable(); + +// 获取 Success 样式 +ToastTintUtils.getSuccessStyle(); +// 设置 Success 样式 +ToastTintUtils.setSuccessStyle(style); +// 获取 Success 样式 小图标 +ToastTintUtils.getSuccessDrawable(); + +// 获取 Info 样式 +ToastTintUtils.getInfoStyle(); +// 设置 Info 样式 +ToastTintUtils.setInfoStyle(style); +// 获取 Info 样式 小图标 +ToastTintUtils.getInfoDrawable(); + +// 是否使用配置 - 如 Gravity、HorizontalMargin、VerticalMargin +ToastTintUtils.setUseConfig(true); + +// 设置 Gravity +ToastTintUtils.setGravity(Gravity.BOTTOM, 0, 0); + +// 当 Toast 内容为 null 时, 显示的内容 +ToastTintUtils.setNullText("text is null"); + +// 是否设置 Handler 显示 Toast - 默认 true, 支持子线程显示 Toast +ToastTintUtils.setUseHandler(true); + +// 设置 HorizontalMargin、VerticalMargin 边距 +ToastTintUtils.setMargin(0F, 0F); + +// 配置 Toast 过滤, 判断是否显示 Toast、以及内容处理 +// ToastTintUtils.setToastFilter(new ToastTintUtils.Filter() {}); + +// 恢复默认配置 +ToastTintUtils.reset(); +``` + + +#### 使用示例 +```java +// 显示 Success 样式 Toast +ToastTintUtils.success("Success Style Toast"); + +// 显示 Error 样式 Toast +ToastTintUtils.error("Error Style Toast"); + +// 显示 Info 样式 Toast +ToastTintUtils.info("Info Style Toast"); + +// 显示 Normal 样式 Toast +ToastTintUtils.normal("Normal Style Toast"); + +// 显示 Warning 样式 Toast +ToastTintUtils.warning("Warning Style Toast"); + +// 显示 Custom 样式 Toast +ToastTintUtils.custom(style, "Custom Style Toast"); + +// 显示 Custom 样式 Toast, 自定义小图标 +ToastTintUtils.custom(new TempStyle(), "Custom Style Toast", iconDrawable); +``` + +# 预览 + +***Success 样式 Toast*** + +![][toast_success_png] + +***Error 样式 Toast*** + +![][toast_error_png] + +***Info 样式 Toast*** + +![][toast_info_png] + +***Normal 样式 Toast*** + +![][toast_normal_png] + +***Warning 样式 Toast*** + +![][toast_warning_png] + +***Custom 样式 Toast*** + +![][toast_custom_png] + + +#### 自定义样式 +```java +/** + * 自定义实现样式 + * {@link ToastTintUtils.SuccessStyle} + * {@link ToastTintUtils.ErrorStyle} + * {@link ToastTintUtils.InfoStyle} + * {@link ToastTintUtils.WarningStyle} + * {@link ToastTintUtils.NormalStyle} + * {@link ToastTintUtils.DefaultStyle} + */ +private static class TempStyle implements ToastTintUtils.Style { + + /** + * 文本颜色 + * @return + */ + @Override + public int getTextColor() { + return Color.WHITE; + } + + /** + * 字体大小 + * @return + */ + @Override + public float getTextSize() { + return 16F; + } + + /** + * 背景着色颜色 + * @return + */ + @Override + public int getBackgroundTintColor() { + return 0; + } + + /** + * 背景图片 + * @return + */ + @Override + public Drawable getBackground() { + return null; + } + + /** + * 最大行数 + * @return + */ + @Override + public int getMaxLines() { + return 0; + } + + /** + * Ellipsize 效果 + * @return + */ + @Override + public TextUtils.TruncateAt getEllipsize() { + return null; + } + + /** + * 字体样式 + * @return + */ + @Override + public Typeface getTypeface() { + // return Typeface.create("sans-serif-condensed", Typeface.NORMAL); + return null; + } + + /** + * 获取图标着色颜色 + * @return + */ + @Override + public int getTintIconColor() { + return Color.WHITE; + } + + /** + * 是否渲染图标 - getTintIconColor() 着色渲染 + * @return + */ + @Override + public boolean isTintIcon() { + return false; + } +} +``` + + + + + +[ToastTintUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/toast/ToastTintUse.java +[ToastTintUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/toast/ToastTintUtils.java +[toast_success_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/success.png +[toast_error_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/error.png +[toast_info_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/info.png +[toast_normal_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/normal.png +[toast_warning_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/warning.png +[toast_custom_png]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/utils_readme/toast/toast_tint/custom.png \ No newline at end of file diff --git a/lib/DevApp/utils_readme/toast/toast_tint/custom.png b/lib/DevApp/utils_readme/toast/toast_tint/custom.png new file mode 100644 index 0000000000..b3e55948eb Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/custom.png differ diff --git a/lib/DevApp/utils_readme/toast/toast_tint/error.png b/lib/DevApp/utils_readme/toast/toast_tint/error.png new file mode 100644 index 0000000000..721686e0ce Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/error.png differ diff --git a/lib/DevApp/utils_readme/toast/toast_tint/info.png b/lib/DevApp/utils_readme/toast/toast_tint/info.png new file mode 100644 index 0000000000..bda47cf413 Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/info.png differ diff --git a/lib/DevApp/utils_readme/toast/toast_tint/normal.png b/lib/DevApp/utils_readme/toast/toast_tint/normal.png new file mode 100644 index 0000000000..a4240b2311 Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/normal.png differ diff --git a/lib/DevApp/utils_readme/toast/toast_tint/success.png b/lib/DevApp/utils_readme/toast/toast_tint/success.png new file mode 100644 index 0000000000..3e7594e64b Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/success.png differ diff --git a/lib/DevApp/utils_readme/toast/toast_tint/warning.png b/lib/DevApp/utils_readme/toast/toast_tint/warning.png new file mode 100644 index 0000000000..1c4f52b86c Binary files /dev/null and b/lib/DevApp/utils_readme/toast/toast_tint/warning.png differ diff --git a/lib/DevApp/utils_readme/wifi/WifiHotUtils.md b/lib/DevApp/utils_readme/wifi/WifiHotUtils.md new file mode 100644 index 0000000000..8a19ae1605 --- /dev/null +++ b/lib/DevApp/utils_readme/wifi/WifiHotUtils.md @@ -0,0 +1,92 @@ +# Wifi 热点工具类 + +#### 使用演示类 [WifiHotUse][WifiHotUse] 介绍了配置参数及使用 + +> 1. Android 8.0 开始,热点操作方法,已经变更 - https://blog.csdn.net/bukker/article/details/78649504 +> 2. Android 7.1 系统以上不支持自动开启热点,需要手动开启热点 - https://www.jianshu.com/p/9dbb02c3e21f + +#### 项目类结构 + +* Wifi 热点工具类([WifiHotUtils][WifiHotUtils]):Wifi 热点工具类,内部适配不同 Android 版本 api + +## API 文档 + +| 方法 | 注释 | +| :- | :- | +| createWifiConfigToAp | 创建 Wifi 热点配置(支持 无密码/WPA2 PSK) | +| startWifiAp | 开启 Wifi 热点 | +| closeWifiAp | 关闭 Wifi 热点 | +| getWifiApState | 获取 Wifi 热点状态 | +| getWifiApConfiguration | 获取 Wifi 热点配置信息 | +| setWifiApConfiguration | 设置 Wifi 热点配置信息 | +| isOpenWifiAp | 判断是否打开 Wifi 热点 | +| closeWifiApCheck | 关闭 Wifi 热点(判断当前状态) | +| isConnectHot | 是否有设备连接热点 | +| getHotspotServiceIp | 获取热点主机 IP 地址 | +| getHotspotAllotIp | 获取连接上的子网关热点 IP(一个) | +| getConnectHotspotMsg | 获取连接的热点信息 | +| getHotspotSplitIpMask | 获取热点拼接后的 IP 网关掩码 | +| getApWifiSSID | 获取 Wifi 热点名 | +| getApWifiPwd | 获取 Wifi 热点密码 | +| setOnWifiAPListener | 设置 Wifi 热点监听事件 | +| onStarted | 开启热点回调 | +| onStopped | 关闭热点回调 | +| onFailed | 失败回调 | + +#### 使用示例 +```java +// 所需权限 +// +// +// +// +// +// + +final WifiHotUtils wifiHotUtils = new WifiHotUtils(); + +// 有密码 +WifiConfiguration wifiConfiguration = WifiHotUtils.createWifiConfigToAp("WifiHot_AP", "123456789"); + +// 无密码 +wifiConfiguration = WifiHotUtils.createWifiConfigToAp("WifiHot_AP", null); + +// 开启热点(兼容8.0) 7.1 跳转到热点页面, 需手动开启(但是配置信息使用上面的 WifiConfig) +wifiHotUtils.startWifiAp(wifiConfiguration); + +// 关闭热点 +wifiHotUtils.closeWifiAp(); + +// = 8.0 特殊处理 = + +// 8.0 以后热点是针对应用开启, 并且必须强制使用随机生成的 WifiConfig 信息, 无法替换 + +// 如果应用开启了热点, 然后后台清空内存, 对应的热点会关闭, 应用开启的热点是系统随机的, 不影响系统设置中的热点配置信息 + +if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + wifiHotUtils.setOnWifiAPListener(new WifiHotUtils.OnWifiAPListener() { + @Override + public void onStarted(WifiConfiguration wifiConfig) { + String ssid = wifiHotUtils.getApWifiSSID(); + String pwd = wifiHotUtils.getApWifiPwd(); + } + + @Override + public void onStopped() { + + } + + @Override + public void onFailed(int reason) { + + } + }); +} +``` + + + + + +[WifiHotUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/utils_use/wifi/WifiHotUse.java +[WifiHotUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/wifi/WifiHotUtils.java \ No newline at end of file diff --git a/lib/DevAssist/.gitignore b/lib/DevAssist/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevAssist/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevAssist/CHANGELOG.md b/lib/DevAssist/CHANGELOG.md new file mode 100644 index 0000000000..f3b3a0336f --- /dev/null +++ b/lib/DevAssist/CHANGELOG.md @@ -0,0 +1,234 @@ +Change Log +========== + +Version 1.3.8 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +* `[Add]` 新增 DevDataAdapter#onAttachedToRecyclerView 方法初始化处理 + +Version 1.3.7 *(2022-08-07)* +---------------------------- + +* `[Add]` DevTimerAssist 定时器辅助类 + +Version 1.3.6 *(2022-07-04)* +---------------------------- + +* `[Update]` 更新 IMediaEngine 接口,新增 openPreview 方法、更新 Path 返回为 Uri + +Version 1.3.5 *(2022-06-02)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.3.4 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +* `[Update]` 更新 DevPage 变量命名 DF_XXX 为 DEF_XXX + +Version 1.3.3 *(2022-03-20)* +---------------------------- + +* `[Update]` 更新 PageAssist 同步使用 DevPage 默认页数信息 + +Version 1.3.2 *(2022-01-23)* +---------------------------- + +* `[Add]` DevVariableExt 变量操作基类扩展类 + +Version 1.3.1 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.3.0 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.2.9 *(2021-12-20)* +---------------------------- + +* `[Add]` CommonState 通用状态类 + +* `[Add]` DevHistory 历史数据记录功类 + +* `[Add]` DevPage#getDefault 方法 + +* `[Add]` DevDataAdapter#initialize 方法 + +* `[Add]` DevDataAdapter 相关类默认初始化处理 + +Version 1.2.8 *(2021-11-26)* +---------------------------- + +* `[Add]` EditTextWatcherAssist#OtherListener 其他事件触发扩展抽象类 + +Version 1.2.7 *(2021-10-06)* +---------------------------- + +* `[Add]` DevDataAdapter#getRecyclerView、setRecyclerView、bindAdapter + +Version 1.2.6 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +* `[Add]` DevDataList、DevDataListExt + +* `[Add]` DevEngineAssist、DevBarCodeEngine 条形码模块 + +* `[Delete]` DevHttpEngine + +Version 1.2.5 *(2021-06-28)* +---------------------------- + +* `[Update]` DevStorageEngine 支持 OnInsertListener 插入结果监听 + +* `[Add]` DevSource Bitmap、Drawable 字段 + +Version 1.2.4 *(2021-06-21)* +---------------------------- + +* `[Update]` DevStorageEngine 为 DevKeyValueEngine 用于键值对存储 Engine + +* `[Add]` DevSource mInputStream 字段 + +* `[Add]` DevStorageEngine 用于多媒体资源存储 ( 高低版本适配 ) Engine + +Version 1.2.2-3 *(2021-06-04)* +---------------------------- + +* `[Add]` DevAnalyticsEngine、DevPushEngine、DevShareEngine + +Version 1.2.1 *(2021-05-19)* +---------------------------- + +* `[Update]` 修改 Function 为 DevFunction + +Version 1.2.0 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.1.9 *(2021-03-31)* +---------------------------- + +* `[Add]` DevDataAdapterExt2 + +Version 1.1.8 *(2021-03-27)* +---------------------------- + +* `[Add]` DevDataAdapter#getActivity、setActivity、parentContext + +* `[Add]` DevPage#getConfig、getConfigPage、getConfigPageSize + +Version 1.1.7 *(2021-03-24)* +---------------------------- + +* `[Update]` DevDataAdapterExt + +Version 1.1.6 *(2021-03-23)* +---------------------------- + +* `[Add]` DataManager#isLastPositionAndGreaterThanOrEqual、addLists 方法 + +* `[Add]` DevDataAdapter Context 构造函数 + +* `[Add]` DevDataAdapterExt + +Version 1.1.5 *(2021-03-19)* +---------------------------- + +* `[Add]` DevCacheEngine + +Version 1.1.4 *(2021-03-16)* +---------------------------- + +* `[Add]` DevStorageEngine + +Version 1.1.3 *(2021-03-12)* +---------------------------- + +* `[Add]` DevCompressEngine、DevMediaEngine + +Version 1.1.2 *(2021-03-10)* +---------------------------- + +* `[Update]` 修改 Engine Config 接口名 + +Version 1.1.1 *(2021-03-10)* +---------------------------- + +* `[Add]` DevPermissionEngine + +* `[Update]` 修改 Engine Config 接口名 + +Version 1.1.0 *(2021-03-02)* +---------------------------- + +* `[Add]` Function Operation Utils + +Version 1.0.9 *(2021-02-15)* +---------------------------- + +* `[Add]` DevDataAdapter ( DataManager RecyclerView Adapter ) + +* `[Add]` BitmapListener、DrawableListener + +* `[Update]` DevSource、IImageEngine + +Version 1.0.8 *(2021-02-08)* +---------------------------- + +* `[Update]` 重新调整包名、类名以及重新部分代码 + +Version 1.0.7 *(2020-12-28)* +---------------------------- + +* `[Add]` EditTextSearchAssist EditText 搜索辅助类 + +Version 1.0.6 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Update]` 修改 CallBack 相关代码为 Callback + +Version 1.0.5 *(2020-11-15)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.0.4 *(2020-07-19)* +---------------------------- + +* `[Delete]` PageAssist 删除 PageNumReady 属性及相关方法 + +Version 1.0.3 *(2019-12-25)* +---------------------------- + +* `[Add]` IJSONEngine#isJSONObject、isJSONArray + +* `[Add]` IMultiSelectEdit#getSelectSize、getDataCount + +* `[Update]` RequestStatusAssist 为 RequestStateAssist 以及方法名 Status 修改为 State + +Version 1.0.2 *(2019-09-19)* +---------------------------- + +* `[Update]` 修改 AbstractCallBack、AdapterDataAssist 部分方法返回值 ( 返回当前对象,方便链式调用 ) + +Version 1.0.1 *(2019-09-12)* +---------------------------- + +* `[Update]` DevJSONEngine#toJsonIndent 删除 param indent ( 缩进单位 ) + +Version 1.0.0 *(2019-09-03)* +---------------------------- + +* Initial release diff --git a/lib/DevAssist/README.md b/lib/DevAssist/README.md new file mode 100644 index 0000000000..d9c5a79836 --- /dev/null +++ b/lib/DevAssist/README.md @@ -0,0 +1,1625 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevAssist:1.3.8' +``` + +## 目录结构 + +``` +- dev | 根目录 + - adapter | 适配器相关 + - assist | 快捷功能辅助类 + - base | 实体类基类相关 + - data | 数据操作 + - entry | KeyValue 实体类 + - multiselect | 多选编辑操作 + - number | 数值操作 + - state | 状态相关 + - callback | 接口回调相关 + - engine | 兼容 Engine + - analytics | Analytics Engine 数据统计 ( 埋点 ) + - barcode | BarCode Engine 条形码、二维码处理 + - listener | 条形码、二维码操作回调事件 + - cache | Cache Engine 有效期键值对缓存 + - compress | Image Compress Engine 图片压缩 + - listener | 图片压缩回调事件 + - image | Image Engine 图片加载、下载、转格式等 + - listener | 图片加载监听事件 + - json | JSON Engine 映射 + - keyvalue | KeyValue Engine 键值对存储 + - log | Log Engine 日志打印 + - media | Media Selector Engine 多媒体资源选择 + - permission | Permission Engine 权限申请 + - push | Push Engine 推送平台处理 + - share | Share Engine 分享平台处理 + - listener | 分享回调事件 + - storage | Storage Engine 外部、内部文件存储 + - listener | Storage 存储结果事件 + - function | 快捷方法执行相关 +``` + + +## 事项 + +- 部分 API 更新不及时或有遗漏等,`具体以对应的工具类为准` + +- [检测代码规范、注释内容排版,API 文档生成](https://github.com/afkT/JavaDoc) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/CHANGELOG.md) + +## API + + +- dev | 根目录 + - [adapter](#devadapter) | 适配器相关 + - [assist](#devassist) | 快捷功能辅助类 + - [base](#devbase) | 实体类基类相关 + - [data](#devbasedata) | 数据操作 + - [entry](#devbaseentry) | KeyValue 实体类 + - [multiselect](#devbasemultiselect) | 多选编辑操作 + - [number](#devbasenumber) | 数值操作 + - [state](#devbasestate) | 状态相关 + - [callback](#devcallback) | 接口回调相关 + - [engine](#devengine) | 兼容 Engine + - [analytics](#devengineanalytics) | Analytics Engine 数据统计 ( 埋点 ) + - [barcode](#devenginebarcode) | BarCode Engine 条形码、二维码处理 + - [listener](#devenginebarcodelistener) | 条形码、二维码操作回调事件 + - [cache](#devenginecache) | Cache Engine 有效期键值对缓存 + - [compress](#devenginecompress) | Image Compress Engine 图片压缩 + - [listener](#devenginecompresslistener) | 图片压缩回调事件 + - [image](#devengineimage) | Image Engine 图片加载、下载、转格式等 + - [listener](#devengineimagelistener) | 图片加载监听事件 + - [json](#devenginejson) | JSON Engine 映射 + - [keyvalue](#devenginekeyvalue) | KeyValue Engine 键值对存储 + - [log](#devenginelog) | Log Engine 日志打印 + - [media](#devenginemedia) | Media Selector Engine 多媒体资源选择 + - [permission](#devenginepermission) | Permission Engine 权限申请 + - [push](#devenginepush) | Push Engine 推送平台处理 + - [share](#devengineshare) | Share Engine 分享平台处理 + - [listener](#devenginesharelistener) | 分享回调事件 + - [storage](#devenginestorage) | Storage Engine 外部、内部文件存储 + - [listener](#devenginestoragelistener) | Storage 存储结果事件 + - [function](#devfunction) | 快捷方法执行相关 + + +## **`dev`** + + +* **开发辅助类 ->** [DevAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/DevAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getDevAssistVersionCode | 获取 DevAssist 版本号 | +| getDevAssistVersion | 获取 DevAssist 版本 | +| getDevAppVersionCode | 获取 DevApp 版本号 | +| getDevAppVersion | 获取 DevApp 版本 | + + +## **`dev.adapter`** + + +* **DataManager RecyclerView Adapter ->** [DevDataAdapter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapter.java) + +| 方法 | 注释 | +| :- | :- | +| getContext | 获取 Context | +| setContext | 设置 Context | +| getActivity | 获取 Activity | +| setActivity | 设置 Activity | +| parentContext | 通过 ViewGroup 设置 Context | +| getItemCount | getItemCount | +| onAttachedToRecyclerView | onAttachedToRecyclerView | +| onDetachedFromRecyclerView | onDetachedFromRecyclerView | +| getRecyclerView | getRecyclerView | +| setRecyclerView | setRecyclerView | +| bindAdapter | bindAdapter | +| initialize | initialize | +| getDataList | getDataList | +| getDataArrayList | getDataArrayList | +| getDataSize | getDataSize | +| getDataItem | getDataItem | +| getDataItemPosition | getDataItemPosition | +| getFirstData | getFirstData | +| getLastData | getLastData | +| getLastPosition | getLastPosition | +| isDataEmpty | isDataEmpty | +| isDataNotEmpty | isDataNotEmpty | +| isFirstPosition | isFirstPosition | +| isLastPosition | isLastPosition | +| isLastPositionAndGreaterThanOrEqual | isLastPositionAndGreaterThanOrEqual | +| equalsFirstData | equalsFirstData | +| equalsLastData | equalsLastData | +| equalsPositionData | equalsPositionData | +| addData | addData | +| addDataAt | addDataAt | +| addDatas | addDatas | +| addDatasAt | addDatasAt | +| addDatasChecked | addDatasChecked | +| addDatasCheckedAt | addDatasCheckedAt | +| addLists | addLists | +| removeData | removeData | +| removeDataAt | removeDataAt | +| removeDatas | removeDatas | +| replaceData | replaceData | +| replaceDataAt | replaceDataAt | +| swipePosition | swipePosition | +| contains | contains | +| clearDataList | clearDataList | +| setDataList | setDataList | +| notifyDataChanged | notifyDataChanged | +| notifyElementChanged | notifyElementChanged | + + +* **DataManager RecyclerView Adapter Extend ->** [DevDataAdapterExt.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt.java) + +| 方法 | 注释 | +| :- | :- | +| getCallback | 获取通用回调 | +| setCallback | 设置通用回调 | +| getItemCallback | 获取通用 Item Click 回调 | +| setItemCallback | 设置通用 Item Click 回调 | +| getObject | 获取通用 Object | +| setObject | 设置通用 Object | +| getPage | 获取 Page 实体类 | +| setPage | 设置 Page 实体类 | +| getFlags | 获取标记值计算存储 ( 位运算符 ) 实体类 | +| setFlags | 设置标记值计算存储 ( 位运算符 ) 实体类 | +| getState | 获取通用状态实体类 | +| setState | 设置通用状态实体类 | +| getRequestState | 获取请求状态实体类 | +| setRequestState | 设置请求状态实体类 | +| getTextWatcherAssist | 获取 EditText 输入监听辅助类 | +| setTextWatcherAssist | 设置 EditText 输入监听辅助类 | +| getMultiSelectMap | 获取多选辅助类 | +| setMultiSelectMap | 设置多选辅助类 | + + +* **DataManager RecyclerView Adapter Extend ->** [DevDataAdapterExt2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt2.java) + +| 方法 | 注释 | +| :- | :- | +| isNotifyAdapter | 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) | +| setNotifyAdapter | 设置是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) | +| isEditState | isEditState | +| setEditState | setEditState | +| toggleEditState | toggleEditState | +| clearSelectAll | clearSelectAll | +| isSelectAll | isSelectAll | +| isSelect | isSelect | +| isNotSelect | isNotSelect | +| getSelectSize | getSelectSize | +| getDataCount | getDataCount | +| selectAll | selectAll | +| inverseSelect | inverseSelect | +| getMultiSelectKey | 获取多选标记 Key | + + +* **DataManager List ->** [DevDataList.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/adapter/DevDataList.java) + +| 方法 | 注释 | +| :- | :- | +| getContext | 获取 Context | +| setContext | 设置 Context | +| getActivity | 获取 Activity | +| setActivity | 设置 Activity | +| parentContext | 通过 ViewGroup 设置 Context | +| getItemCount | getItemCount | +| getDataList | getDataList | +| getDataArrayList | getDataArrayList | +| getDataSize | getDataSize | +| getDataItem | getDataItem | +| getDataItemPosition | getDataItemPosition | +| getFirstData | getFirstData | +| getLastData | getLastData | +| getLastPosition | getLastPosition | +| isDataEmpty | isDataEmpty | +| isDataNotEmpty | isDataNotEmpty | +| isFirstPosition | isFirstPosition | +| isLastPosition | isLastPosition | +| isLastPositionAndGreaterThanOrEqual | isLastPositionAndGreaterThanOrEqual | +| equalsFirstData | equalsFirstData | +| equalsLastData | equalsLastData | +| equalsPositionData | equalsPositionData | +| addData | addData | +| addDataAt | addDataAt | +| addDatas | addDatas | +| addDatasAt | addDatasAt | +| addDatasChecked | addDatasChecked | +| addDatasCheckedAt | addDatasCheckedAt | +| addLists | addLists | +| removeData | removeData | +| removeDataAt | removeDataAt | +| removeDatas | removeDatas | +| replaceData | replaceData | +| replaceDataAt | replaceDataAt | +| swipePosition | swipePosition | +| contains | contains | +| clearDataList | clearDataList | +| setDataList | setDataList | +| getCallback | 获取通用回调 | +| setCallback | 设置通用回调 | +| getItemCallback | 获取通用 Item Click 回调 | +| setItemCallback | 设置通用 Item Click 回调 | +| getObject | 获取通用 Object | +| setObject | 设置通用 Object | +| getPage | 获取 Page 实体类 | +| setPage | 设置 Page 实体类 | +| getFlags | 获取标记值计算存储 ( 位运算符 ) 实体类 | +| setFlags | 设置标记值计算存储 ( 位运算符 ) 实体类 | +| getState | 获取通用状态实体类 | +| setState | 设置通用状态实体类 | +| getRequestState | 获取请求状态实体类 | +| setRequestState | 设置请求状态实体类 | +| getTextWatcherAssist | 获取 EditText 输入监听辅助类 | +| setTextWatcherAssist | 设置 EditText 输入监听辅助类 | +| getMultiSelectMap | 获取多选辅助类 | +| setMultiSelectMap | 设置多选辅助类 | + + +* **DataManager List Extend ->** [DevDataListExt.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/adapter/DevDataListExt.java) + +| 方法 | 注释 | +| :- | :- | +| isNotifyAdapter | 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) | +| setNotifyAdapter | 设置是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) | +| isEditState | isEditState | +| setEditState | setEditState | +| toggleEditState | toggleEditState | +| clearSelectAll | clearSelectAll | +| isSelectAll | isSelectAll | +| isSelect | isSelect | +| isNotSelect | isNotSelect | +| getSelectSize | getSelectSize | +| getDataCount | getDataCount | +| selectAll | selectAll | +| inverseSelect | inverseSelect | +| getMultiSelectKey | 获取多选标记 Key | + + +## **`dev.assist`** + + +* **数据辅助类 ->** [DataAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/DataAssist.java) + +| 方法 | 注释 | +| :- | :- | +| setDataChanged | 设置数据改变通知 | +| getDataSource | 获取 DataSource Object | +| getDataList | 获取 List Data | +| getDataArrayList | 获取 ArrayList Data | +| getDataSize | 获取 List Size | +| getDataItem | 获取 List Position Data | +| getDataItemPosition | 获取 Value Position | +| getFirstData | 获取 First Data | +| getLastData | 获取 Last Data | +| getLastPosition | 获取 Last Position | +| isDataEmpty | 判断 List Size 是否为 0 | +| isDataNotEmpty | 判断 List Size 是否大于 0 | +| isFirstPosition | 判断是否 First Position | +| isLastPosition | 判断是否 Last Position | +| isLastPositionAndGreaterThanOrEqual | 判断是否 Last Position 且大于等于指定 size | +| equalsFirstData | 判断 First Value 是否一致 | +| equalsLastData | 判断 Last Value 是否一致 | +| equalsPositionData | 判断 Position Value 是否一致 | +| addData | 添加数据 | +| addDataAt | 添加数据 | +| addDatas | 添加数据集 | +| addDatasAt | 添加数据集 | +| addDatasChecked | 添加数据集 ( 进行校验 ) | +| addDatasCheckedAt | 添加数据集 ( 进行校验 ) | +| addLists | 添加数据集 ( 判断是追加还是重置 ) | +| removeData | 移除数据 | +| removeDataAt | 移除数据 | +| removeDatas | 移除数据集 | +| replaceData | 替换数据 | +| replaceDataAt | 替换数据 | +| swipePosition | 数据中两个索引 Data 互换位置 | +| contains | 是否存在 Data | +| clearDataList | 清空全部数据 | +| setDataList | 设置 List Data | +| notifyDataChanged | 通知数据改变 | +| notifyElementChanged | 通知某个数据改变 | + + +* **定时器辅助类 ->** [DevTimerAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/DevTimerAssist.java) + +| 方法 | 注释 | +| :- | :- | +| setTag | 设置 TAG | +| setHandler | 设置 UI Handler | +| setCallback | 设置回调事件 | +| getTimer | 获取定时器 | +| getDuration | 获取剩余总时长 ( 毫秒 ) | +| start | 运行定时器 | +| stop | 关闭定时器 | +| callback | 触发回调方法 | + + +* **EditText 搜索辅助类 ->** [EditTextSearchAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/EditTextSearchAssist.java) + +| 方法 | 注释 | +| :- | :- | +| remove | 移除消息 | +| post | 发送消息 ( 功能由该方法实现 ) | +| setDelayMillis | 设置搜索延迟时间 | +| setCallback | 设置搜索回调接口 | +| bindEditText | 绑定 EditText 输入事件 | +| callback | 搜索回调 | + + +* **解决 Adapter 多个 Item 存在 EditText 监听输入问题 ->** [EditTextWatcherAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/EditTextWatcherAssist.java) + +| 方法 | 注释 | +| :- | :- | +| bindListener | 绑定事件 | +| onFocusChange | 焦点触发方法 | +| beforeTextChanged | 在文本变化前调用 | +| afterTextChanged | 在文本变化后调用 | + + +* **数量控制辅助类 ->** [NumberControlAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/NumberControlAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getNumber | 获取 DevNumber Object | +| isMinNumber | 判断当前数量, 是否等于最小值 | +| isLessThanMinNumber | 判断数量, 是否小于最小值 | +| isGreaterThanMinNumber | 判断数量, 是否大于最小值 | +| isMaxNumber | 判断当前数量, 是否等于最大值 | +| isLessThanMaxNumber | 判断数量, 是否小于最大值 | +| isGreaterThanMaxNumber | 判断数量, 是否大于最大值 | +| getMinNumber | 获取最小值 | +| setMinNumber | 设置最小值 | +| getMaxNumber | 获取最大值 | +| setMaxNumber | 设置最大值 | +| setMinMaxNumber | 设置最小值、最大值 | +| getCurrentNumber | 获取当前数量 | +| setCurrentNumber | 设置当前数量 | +| getResetNumber | 获取重置数量 | +| setResetNumber | 设置重置数量 | +| isAllowNegative | 获取是否允许设置为负数 | +| setAllowNegative | 设置是否允许设置为负数 | +| numberChange | 数量改变通知 | +| addNumber | 添加数量 ( 默认累加 1 ) | +| subtractionNumber | 减少数量 ( 默认累减 1 ) | +| getNumberListener | 获取数量监听事件接口 | +| setNumberListener | 设置数量监听事件接口 | + + +* **Page 辅助类 ->** [PageAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/assist/PageAssist.java) + +| 方法 | 注释 | +| :- | :- | +| initPageConfig | 初始化全局分页配置 | +| reset | 重置操作 | +| getPage | 获取当前页数 | +| setPage | 设置当前页数 | +| equalsPage | 判断当前页数是否一致 | +| getConfig | 获取页数配置信息 | +| getConfigPage | 获取配置初始页页数 | +| getConfigPageSize | 获取配置每页请求条数 | +| getPageSize | 获取每页请求条数 | +| equalsPageSize | 判断每页请求条数是否一致 | +| isLastPage | 判断是否最后一页 | +| setLastPage | 设置是否最后一页 | +| calculateLastPage | 计算是否最后一页 ( 并同步更新 ) | +| isFirstPage | 判断是否第一页 | +| canNextPage | 判断是否允许请求下一页 | +| getNextPage | 获取下一页页数 | +| nextPage | 累加当前页数 ( 下一页 ) | +| isLessThanPageSize | 判断是否小于每页请求条数 | +| response | 请求响应处理 | + + +## **`dev.base`** + + +* **数据源操作实体类 ->** [DevDataSource.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevDataSource.java) + +| 方法 | 注释 | +| :- | :- | +| getDataList | 获取 List Data | +| getDataArrayList | 获取 ArrayList Data | +| getDataSize | 获取 List Size | +| getDataItem | 获取 List Position Data | +| getDataItemPosition | 获取 Value Position | +| getFirstData | 获取 First Data | +| getLastData | 获取 Last Data | +| getLastPosition | 获取 Last Position | +| isDataEmpty | 判断 List Size 是否为 0 | +| isDataNotEmpty | 判断 List Size 是否大于 0 | +| isFirstPosition | 判断是否 First Position | +| isLastPosition | 判断是否 Last Position | +| isLastPositionAndGreaterThanOrEqual | 判断是否 Last Position 且大于等于指定 size | +| equalsFirstData | 判断 First Value 是否一致 | +| equalsLastData | 判断 Last Value 是否一致 | +| equalsPositionData | 判断 Position Value 是否一致 | +| addData | 添加数据 | +| addDataAt | 添加数据 | +| addDatas | 添加数据集 | +| addDatasAt | 添加数据集 | +| addDatasChecked | 添加数据集 ( 进行校验 ) | +| addDatasCheckedAt | 添加数据集 ( 进行校验 ) | +| addLists | 添加数据集 ( 判断是追加还是重置 ) | +| removeData | 移除数据 | +| removeDataAt | 移除数据 | +| removeDatas | 移除数据集 | +| replaceData | 替换数据 | +| replaceDataAt | 替换数据 | +| swipePosition | 数据中两个索引 Data 互换位置 | +| contains | 是否存在 Data | +| clearDataList | 清空全部数据 | +| setDataList | 设置 List Data | + + +* **Key-Value Entry ->** [DevEntry.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevEntry.java) + +| 方法 | 注释 | +| :- | :- | +| getKey | 获取 Key | +| setKey | 设置 Key | +| getValue | 获取 Value | +| setValue | 设置 Value | +| equalsKey | 判断 Key 是否一致 | +| equalsValue | 判断 Value 是否一致 | + + +* **历史数据记录功类 ->** [DevHistory.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevHistory.java) + +| 方法 | 注释 | +| :- | :- | +| getCurrent | 获取当前数据 | +| setCurrent | 设置当前数据 | +| getListener | 获取方法事件触发接口 | +| setListener | 设置方法事件触发接口 | +| cleanCurrent | 清空当前数据 | +| reset | 重置操作 | +| clearBack | 清空回退栈数据 | +| sizeBack | 获取回退栈数据条数 | +| isEmptyBack | 是否不存在回退栈数据 | +| canGoBack | 是否能够执行回退操作 | +| addBack | 添加到回退栈 | +| getBack | 获取上一条回退栈数据 | +| goBack | 前往上一条回退栈数据 | +| toStringBack | 进行回退栈数据顺序拼接字符串 | +| clearForward | 清空前进栈数据 | +| sizeForward | 获取前进栈数据条数 | +| isEmptyForward | 是否不存在前进栈数据 | +| canGoForward | 是否能够执行前进操作 | +| addForward | 添加到前进栈 | +| getForward | 获取下一条前进栈数据 | +| goForward | 前往下一条前进栈数据 | +| toStringForward | 进行前进栈数据顺序拼接字符串 | +| accept | 是否允许添加 | +| changeCurrent | 当前数据改变通知 | +| clear | 清空数据回调 | +| add | 添加数据到栈内 | +| acceptCurrentToList | 是否允许 Current 添加到列表中 | + + +* **Intent 传参读写辅助类 ->** [DevIntent.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevIntent.java) + +| 方法 | 注释 | +| :- | :- | +| with | 创建 DevIntent | +| insert | 插入数据 | +| reader | 读取数据并存储 | +| getDataMaps | 获取存储数据 Map | +| containsKey | 是否存在 Key | +| containsValue | 是否存在 Value | +| isNullValue | 对应 Key 保存的 Value 是否为 null | +| put | 保存数据 | +| putAll | 保存集合数据 | +| remove | 移除数据 | +| removeAll | 移除集合数据 | +| get | 获取对应 Key 保存的 Value | +| clear | 清空数据 | +| clearNull | 清除 null 数据 | +| clearNullKey | 清除 null Key 数据 | +| clearNullValue | 清除 null Value 数据 | +| clearEmpty | 清除 empty 数据 | +| clearEmptyKey | 清除 empty Key 数据 | +| clearEmptyValue | 清除 empty Value 数据 | + + +* **数量实体类 ->** [DevNumber.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevNumber.java) + +| 方法 | 注释 | +| :- | :- | +| isMinNumber | 判断当前数量, 是否等于最小值 | +| isLessThanMinNumber | 判断数量, 是否小于最小值 | +| isGreaterThanMinNumber | 判断数量, 是否大于最小值 | +| isMaxNumber | 判断当前数量, 是否等于最大值 | +| isLessThanMaxNumber | 判断数量, 是否小于最大值 | +| isGreaterThanMaxNumber | 判断数量, 是否大于最大值 | +| getMinNumber | 获取最小值 | +| setMinNumber | 设置最小值 | +| getMaxNumber | 获取最大值 | +| setMaxNumber | 设置最大值 | +| setMinMaxNumber | 设置最小值、最大值 | +| getCurrentNumber | 获取当前数量 | +| setCurrentNumber | 设置当前数量 | +| getResetNumber | 获取重置数量 | +| setResetNumber | 设置重置数量 | +| isAllowNegative | 获取是否允许设置为负数 | +| setAllowNegative | 设置是否允许设置为负数 | +| numberChange | 数量改变通知 | +| addNumber | 添加数量 ( 默认累加 1 ) | +| subtractionNumber | 减少数量 ( 默认累减 1 ) | +| getNumberListener | 获取数量监听事件接口 | +| setNumberListener | 设置数量监听事件接口 | + + +* **通用 Object ->** [DevObject.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevObject.java) + +| 方法 | 注释 | +| :- | :- | +| getUUID | 获取 UUID | +| getObject | 获取 Object | +| setObject | 设置 Object | +| getTag | 获取标记 TAG | +| convertTag | 转换标记 TAG | +| setTag | 设置标记 TAG | +| getModelId | 获取 Model id | +| setModelId | 设置 Model id | +| getCode | 获取 Code | +| setCode | 设置 Code | +| getType | 获取 Type | +| setType | 设置 Type | +| getState | 获取 State | +| setState | 设置 State | +| getTokenUUID | 获取 Token UUID | +| setTokenUUID | 设置 Token UUID | +| randomTokenUUID | 重置随机 Token UUID | +| equalsObject | 判断 Object 是否一致 | +| equalsTag | 判断 TAG 是否一致 | +| equalsModelId | 判断 Model id 是否一致 | +| equalsCode | 判断 Code 是否一致 | +| equalsType | 判断 Type 是否一致 | +| equalsState | 判断 State 是否一致 | +| equalsTokenUUID | 判断 Token UUID 是否一致 | +| isCorrect | 校验数据正确性 | + + +* **Page 实体类 ->** [DevPage.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevPage.java) + +| 方法 | 注释 | +| :- | :- | +| reset | 重置操作 | +| getPage | 获取当前页数 | +| setPage | 设置当前页数 | +| equalsPage | 判断当前页数是否一致 | +| getConfig | 获取页数配置信息 | +| getConfigPage | 获取配置初始页页数 | +| getConfigPageSize | 获取配置每页请求条数 | +| getPageSize | 获取每页请求条数 | +| equalsPageSize | 判断每页请求条数是否一致 | +| isLastPage | 判断是否最后一页 | +| setLastPage | 设置是否最后一页 | +| calculateLastPage | 计算是否最后一页 ( 并同步更新 ) | +| isFirstPage | 判断是否第一页 | +| canNextPage | 判断是否允许请求下一页 | +| getNextPage | 获取下一页页数 | +| nextPage | 累加当前页数 ( 下一页 ) | +| isLessThanPageSize | 判断是否小于每页请求条数 | +| response | 请求响应处理 | +| getDefault | 获取默认配置 Page 实体类 | + + +* **资源来源通用类 ->** [DevSource.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevSource.java) + +| 方法 | 注释 | +| :- | :- | +| create | create | +| createWithPath | createWithPath | +| isUrl | isUrl | +| isUri | isUri | +| isBytes | isBytes | +| isResource | isResource | +| isFile | isFile | +| isInputStream | isInputStream | +| isDrawable | isDrawable | +| isBitmap | isBitmap | +| isSource | 是否有效资源 | + + +* **变量操作基类 ->** [DevVariable.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevVariable.java) + +| 方法 | 注释 | +| :- | :- | +| getVariables | 获取全部变量数据 | +| clearVariables | 清空全部变量数据 | +| putVariables | 保存变量数据集合 | +| getVariablesSize | 获取变量总数 | +| isVariables | 判断是否存在变量数据 | +| isVariableValue | 判断是否存在变量 ( 通过 value 判断 ) | +| removeVariableValue | 删除指定变量数据 | +| removeVariableValueAll | 删除指定变量数据 ( 符合条件的全部 value ) | +| isVariable | 判断是否存在变量 ( 通过 key 判断 ) | +| putVariable | 保存变量数据 | +| removeVariable | 移除指定变量数据 ( 通过 key 判断 ) | +| toggle | 切换变量数据存储状态 | +| getVariableValue | 通过 key 获取 value | +| getVariableValueConvert | 通过 key 获取 value | +| getVariableValues | 获取变量数据 value list | +| getVariableValuesToReverse | 获取变量数据 value list ( 倒序 ) | +| getVariableKey | 通过 value 获取 key | +| getVariableKeys | 获取变量数据 key list | +| getVariableKeysToReverse | 获取变量数据 key list ( 倒序 ) | + + +* **变量操作基类扩展类 ->** [DevVariableExt.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/DevVariableExt.java) + +| 方法 | 注释 | +| :- | :- | +| getCreator | 获取变量创建器 | +| setCreator | 设置变量创建器 | +| getVariable | 获取变量操作基类 | +| getVariableValue | 通过 key 获取 value | +| create | 创建存储值 | + + +## **`dev.base.data`** + + +* **数据改变通知 ->** [DataChanged.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/data/DataChanged.java) + +| 方法 | 注释 | +| :- | :- | +| notifyDataChanged | 通知数据改变 | +| notifyElementChanged | 通知某个数据改变 | + + +* **数据管理接口 ->** [DataManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/data/DataManager.java) + +| 方法 | 注释 | +| :- | :- | +| getDataList | 获取 List Data | +| getDataArrayList | 获取 ArrayList Data | +| getDataSize | 获取 List Size | +| getDataItem | 获取 List Position Data | +| getDataItemPosition | 获取 Value Position | +| getFirstData | 获取 First Data | +| getLastData | 获取 Last Data | +| getLastPosition | 获取 Last Position | +| isDataEmpty | 判断 List Size 是否为 0 | +| isDataNotEmpty | 判断 List Size 是否大于 0 | +| isFirstPosition | 判断是否 First Position | +| isLastPosition | 判断是否 Last Position | +| isLastPositionAndGreaterThanOrEqual | 判断是否 Last Position 且大于等于指定 size | +| equalsFirstData | 判断 First Value 是否一致 | +| equalsLastData | 判断 Last Value 是否一致 | +| equalsPositionData | 判断 Position Value 是否一致 | +| addData | 添加数据 | +| addDataAt | 添加数据 | +| addDatas | 添加数据集 | +| addDatasAt | 添加数据集 | +| addDatasChecked | 添加数据集 ( 进行校验 ) | +| addDatasCheckedAt | 添加数据集 ( 进行校验 ) | +| addLists | 添加数据集 ( 判断是追加还是重置 ) | +| removeData | 移除数据 | +| removeDataAt | 移除数据 | +| removeDatas | 移除数据集 | +| replaceData | 替换数据 | +| replaceDataAt | 替换数据 | +| swipePosition | 数据中两个索引 Data 互换位置 | +| contains | 是否存在 Data | +| clearDataList | 清空全部数据 | +| setDataList | 设置 List Data | + + +## **`dev.base.entry`** + + +## **`dev.base.multiselect`** + + +* **List 多选实体类 ->** [DevMultiSelectList.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectList.java) + +| 方法 | 注释 | +| :- | :- | +| clearSelects | 清空全部选中数据 | +| getSelectSize | 获取选中的数据条数 | +| getSelects | 获取选中的数据集合 | +| putSelects | 通过集合添加选中数据 | +| isSelect | 判断是否存在选中的数据 | +| isSelectValue | 判断是否选中 ( 通过 value 判断 ) | +| unselectValue | 设置非选中 | +| unselectValueAll | 设置非选中 ( 符合条件的全部 value ) | +| select | 设置选中操作 | +| unselect | 设置非选中操作 | +| toggle | 切换选中状态 | +| getSelectValues | 获取选中的数据集合 | +| getSelectValuesToReverse | 获取选中的数据集合 ( 倒序 ) | +| getSelectValue | 获取选中的数据 | +| getSelectValueToPosition | 获取选中的数据所在的索引 | + + +* **Map 多选实体类 ->** [DevMultiSelectMap.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectMap.java) + +| 方法 | 注释 | +| :- | :- | +| clearSelects | 清空全部选中数据 | +| getSelectSize | 获取选中的数据条数 | +| getSelects | 获取选中的数据集合 | +| putSelects | 通过集合添加选中数据 | +| isSelect | 判断是否存在选中的数据 | +| isSelectValue | 判断是否选中 ( 通过 value 判断 ) | +| unselectValue | 设置非选中 | +| unselectValueAll | 设置非选中 ( 符合条件的全部 value ) | +| isSelectKey | 判断是否选中 ( 通过 key 判断 ) | +| select | 设置选中操作 | +| unselect | 设置非选中操作 | +| toggle | 切换选中状态 | +| getSelectValue | 通过 key 获取 value | +| getSelectValues | 获取选中的数据集合 | +| getSelectValuesToReverse | 获取选中的数据集合 ( 倒序 ) | +| getSelectKey | 通过 value 获取 key | +| getSelectKeys | 获取选中的数据集合 | +| getSelectKeysToReverse | 获取选中的数据集合 ( 倒序 ) | + + +* **多选操作接口 ( 基类 ) ->** [IMultiSelect.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelect.java) + +| 方法 | 注释 | +| :- | :- | +| clearSelects | 清空全部选中数据 | +| getSelectSize | 获取选中的数据条数 | +| getSelects | 获取选中的数据集合 | +| putSelects | 通过集合添加选中数据 | +| isSelect | 判断是否存在选中的数据 | +| isSelectValue | 判断是否选中 ( 通过 value 判断 ) | +| unselectValue | 设置非选中 | +| unselectValueAll | 设置非选中 ( 符合条件的全部 value ) | + + +* **多选编辑接口 ->** [IMultiSelectEdit.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectEdit.java) + +| 方法 | 注释 | +| :- | :- | +| isEditState | 是否编辑状态 | +| setEditState | 设置编辑状态 | +| toggleEditState | 切换编辑状态 | +| selectAll | 全选 | +| clearSelectAll | 清空全选 ( 非全选 ) | +| inverseSelect | 反选 | +| isSelectAll | 判断是否全选 | +| isSelect | 判断是否存在选中的数据 | +| isNotSelect | 判断是否不存在选中的数据 | +| getSelectSize | 获取选中的数据条数 | +| getDataCount | 获取数据总数 | + + +* **多选操作接口 ( List ) ->** [IMultiSelectToList.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToList.java) + +| 方法 | 注释 | +| :- | :- | +| isSelect | 判断是否选中 ( 通过 value 判断 ) | +| select | 设置选中操作 | +| unselect | 设置非选中操作 | +| toggle | 切换选中状态 | +| getSelectValues | 获取选中的数据集合 | +| getSelectValuesToReverse | 获取选中的数据集合 ( 倒序 ) | +| getSelectValue | 获取选中的数据 | +| getSelectValueToPosition | 获取选中的数据所在的索引 | + + +* **多选操作接口 ( Map ) ->** [IMultiSelectToMap.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToMap.java) + +| 方法 | 注释 | +| :- | :- | +| isSelect | 判断是否选中 ( 如果未选中, 则设置为选中 ) | +| isSelectKey | 判断是否选中 ( 通过 key 判断 ) | +| select | 设置选中操作 | +| unselect | 设置非选中操作 | +| toggle | 切换选中状态 | +| getSelectValue | 通过 key 获取 value | +| getSelectValues | 获取选中的数据集合 | +| getSelectValuesToReverse | 获取选中的数据集合 ( 倒序 ) | +| getSelectKey | 通过 value 获取 key | +| getSelectKeys | 获取选中的数据集合 | +| getSelectKeysToReverse | 获取选中的数据集合 ( 倒序 ) | + + +## **`dev.base.number`** + + +* **数量监听事件接口 ->** [INumberListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/number/INumberListener.java) + +| 方法 | 注释 | +| :- | :- | +| onPrepareChanged | 数量准备变化通知 | +| onNumberChanged | 数量变化通知 | + + +* **数量操作接口 ->** [INumberOperate.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/number/INumberOperate.java) + +| 方法 | 注释 | +| :- | :- | +| isMinNumber | 判断当前数量是否等于最小值 | +| isLessThanMinNumber | 判断数量是否小于最小值 | +| isGreaterThanMinNumber | 判断数量是否大于最小值 | +| isMaxNumber | 判断当前数量是否等于最大值 | +| isLessThanMaxNumber | 判断数量是否小于最大值 | +| isGreaterThanMaxNumber | 判断数量是否大于最大值 | +| getMinNumber | 获取最小值 | +| setMinNumber | 设置最小值 | +| getMaxNumber | 获取最大值 | +| setMaxNumber | 设置最大值 | +| setMinMaxNumber | 设置最小值、最大值 | +| getCurrentNumber | 获取当前数量 | +| setCurrentNumber | 设置当前数量 | +| getResetNumber | 获取重置数量 | +| setResetNumber | 设置重置数量 | +| isAllowNegative | 获取是否允许设置为负数 | +| setAllowNegative | 设置是否允许设置为负数 | +| numberChange | 数量改变通知 | +| addNumber | 添加数量 ( 默认累加 1 ) | +| subtractionNumber | 减少数量 ( 默认累减 1 ) | +| getNumberListener | 获取数量监听事件接口 | +| setNumberListener | 设置数量监听事件接口 | + + +## **`dev.base.state`** + + +* **通用状态类 ->** [CommonState.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/state/CommonState.java) + +| 方法 | 注释 | +| :- | :- | +| getType | 获取操作类型 | +| setType | 设置操作类型 | +| equalsType | 判断操作类型是否一致 | +| getUUID | 获取操作 UUID | +| randomUUID | 获取操作 UUID ( 随机生成并赋值 ) | +| equalsUUID | 判断 UUID 是否一致 | +| getState | 获取 State | +| setState | 设置 State | +| equalsState | 判断 State 是否一致 | +| isNormal | 判断是否默认状态 ( 暂未进行操作 ) | +| isIng | 判断是否操作中 | +| isSuccess | 判断是否操作成功 | +| isFail | 判断是否操作失败 | +| isError | 判断是否操作异常 | +| isStart | 判断是否开始操作 | +| isRestart | 判断是否重新开始操作 | +| isEnd | 判断是否操作结束 | +| isPause | 判断是否操作暂停 | +| isResume | 判断是否操作恢复 ( 继续 ) | +| isStop | 判断是否操作停止 | +| isCancel | 判断是否操作取消 | +| isCreate | 判断是否创建 | +| isDestroy | 判断是否销毁 | +| isRecycle | 判断是否回收 | +| isInit | 判断是否初始化 | +| isEnabled | 判断是否已打开 | +| isEnabling | 判断是否正在打开 | +| isDisabled | 判断是否已关闭 | +| isDisabling | 判断是否正在关闭 | +| isConnected | 判断是否连接成功 | +| isConnecting | 判断是否连接中 | +| isDisconnected | 判断是否连接失败、断开 | +| isSuspended | 判断是否暂停、延迟 | +| isUnknown | 判断是否未知 | +| isInsert | 判断是否新增 | +| isDelete | 判断是否删除 | +| isUpdate | 判断是否更新 | +| isSelect | 判断是否查询 | +| isEncrypt | 判断是否加密 | +| isDecrypt | 判断是否解密 | +| isReset | 判断是否重置 | +| isClose | 判断是否关闭 | +| isOpen | 判断是否打开 | +| isExit | 判断是否退出 | +| setNormal | 设置状态为默认状态 ( 暂未进行操作 ) | +| setIng | 设置状态为操作中 | +| setSuccess | 设置状态为操作成功 | +| setFail | 设置状态为操作失败 | +| setError | 设置状态为操作异常 | +| setStart | 设置状态为开始操作 | +| setRestart | 设置状态为重新开始操作 | +| setEnd | 设置状态为操作结束 | +| setPause | 设置状态为操作暂停 | +| setResume | 设置状态为操作恢复 ( 继续 ) | +| setStop | 设置状态为操作停止 | +| setCancel | 设置状态为操作取消 | +| setCreate | 设置状态为创建 | +| setDestroy | 设置状态为销毁 | +| setRecycle | 设置状态为回收 | +| setInit | 设置状态为初始化 | +| setEnabled | 设置状态为已打开 | +| setEnabling | 设置状态为正在打开 | +| setDisabled | 设置状态为已关闭 | +| setDisabling | 设置状态为正在关闭 | +| setConnected | 设置状态为连接成功 | +| setConnecting | 设置状态为连接中 | +| setDisconnected | 设置状态为连接失败、断开 | +| setSuspended | 设置状态为暂停、延迟 | +| setUnknown | 设置状态为未知 | +| setInsert | 设置状态为新增 | +| setDelete | 设置状态为删除 | +| setUpdate | 设置状态为更新 | +| setSelect | 设置状态为查询 | +| setEncrypt | 设置状态为加密 | +| setDecrypt | 设置状态为解密 | +| setReset | 设置状态为重置 | +| setClose | 设置状态为关闭 | +| setOpen | 设置状态为打开 | +| setExit | 设置状态为退出 | + + +* **请求状态类 ->** [RequestState.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/base/state/RequestState.java) + +| 方法 | 注释 | +| :- | :- | +| getType | 获取请求类型 | +| setType | 设置请求类型 | +| equalsType | 判断请求类型是否一致 | +| getRequestUUID | 获取请求 UUID | +| randomRequestUUID | 获取请求 UUID ( 随机生成并赋值 ) | +| equalsRequestUUID | 判断 UUID 是否一致 | +| getState | 获取 State | +| setState | 设置 State | +| equalsState | 判断 State 是否一致 | +| isRequestNormal | 判断是否默认状态 ( 暂未进行操作 ) | +| isRequestIng | 判断是否请求中 | +| isRequestSuccess | 判断是否请求成功 | +| isRequestFail | 判断是否请求失败 | +| isRequestError | 判断是否请求异常 | +| isRequestStart | 判断是否请求开始 | +| isRequestRestart | 判断是否重新请求 | +| isRequestEnd | 判断是否请求结束 | +| isRequestPause | 判断是否请求暂停 | +| isRequestResume | 判断是否请求恢复 ( 继续 ) | +| isRequestStop | 判断是否请求停止 | +| isRequestCancel | 判断是否请求取消 | +| setRequestNormal | 设置状态为默认状态 ( 暂未进行操作 ) | +| setRequestIng | 设置状态为请求中 | +| setRequestSuccess | 设置状态为请求成功 | +| setRequestFail | 设置状态为请求失败 | +| setRequestError | 设置状态为请求异常 | +| setRequestStart | 设置状态为请求开始 | +| setRequestRestart | 设置状态为重新请求 | +| setRequestEnd | 设置状态为请求结束 | +| setRequestPause | 设置状态为请求暂停 | +| setRequestResume | 设置状态为请求恢复 ( 继续 ) | +| setRequestStop | 设置状态为请求停止 | +| setRequestCancel | 设置状态为请求取消 | + + +## **`dev.callback`** + + +* **Dev 通用回调 ->** [DevCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/callback/DevCallback.java) + +| 方法 | 注释 | +| :- | :- | +| callback | 回调方法 | +| filter | 过滤处理 | +| isFilter | 判断是否过滤 | +| compare | 对比判断 | + + +* **通用 Click 回调 ->** [DevClickCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/callback/DevClickCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onClick | 点击回调 | +| onLongClick | 长按回调 | + + +* **通用 Dialog 回调 ->** [DevDialogCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/callback/DevDialogCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onDialogNotify | 特殊通知 | +| onDialogShow | show 通知 | +| onDialogDismiss | dismiss 通知 | +| onDialogStart | start 通知 | +| onDialogResume | resume 通知 | +| onDialogPause | pause 通知 | +| onDialogStop | stop 通知 | +| onDialogDestroy | destroy 通知 | + + +* **通用 Item Click 回调 ->** [DevItemClickCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/callback/DevItemClickCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onItemClick | 点击 Item 回调 | +| onItemLongClick | 长按 Item 回调 | + + +* **通用结果回调类 ->** [DevResultCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/callback/DevResultCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 结果回调通知 | +| onError | 异常回调通知 | +| onFailure | 失败回调通知 | + + +## **`dev.engine`** + + +* **DevEngine Generic Assist ->** [DevEngineAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/DevEngineAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +## **`dev.engine.analytics`** + + +* **Analytics Engine ->** [DevAnalyticsEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/analytics/DevAnalyticsEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Analytics Engine 接口 ->** [IAnalyticsEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/analytics/IAnalyticsEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| register | 绑定 | +| unregister | 解绑 | +| track | 数据统计 ( 埋点 ) 方法 | + + +## **`dev.engine.barcode`** + + +* **BarCode Engine ->** [DevBarCodeEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/DevBarCodeEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **BarCode Engine 接口 ->** [IBarCodeEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/IBarCodeEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| getConfig | 获取 BarCode Engine Config | +| encodeBarCode | 编码 ( 生成 ) 条码图片 | +| encodeBarCodeSync | 编码 ( 生成 ) 条码图片 | +| decodeBarCode | 解码 ( 解析 ) 条码图片 | +| decodeBarCodeSync | 解码 ( 解析 ) 条码图片 | +| addIconToBarCode | 添加 Icon 到条码图片上 | + + +## **`dev.engine.barcode.listener`** + + +* **条码解码 ( 解析 ) 回调 ->** [BarCodeDecodeCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeDecodeCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 条码解码 ( 解析 ) 回调 | + + +* **条码编码 ( 生成 ) 回调 ->** [BarCodeEncodeCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeEncodeCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 条码编码 ( 生成 ) 回调 | + + +## **`dev.engine.cache`** + + +* **Cache Engine ->** [DevCacheEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/cache/DevCacheEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Cache Engine 接口 ->** [ICacheEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/cache/ICacheEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取 Cache Engine Config | +| remove | 移除数据 | +| removeForKeys | 移除数组的数据 | +| contains | 是否存在 key | +| isDue | 判断某个 key 是否过期 | +| clear | 清除全部数据 | +| clearDue | 清除过期数据 | +| clearType | 清除某个类型的全部数据 | +| getItemByKey | 通过 Key 获取 Item | +| getKeys | 获取有效 Key 集合 | +| getPermanentKeys | 获取永久有效 Key 集合 | +| getCount | 获取有效 Key 数量 | +| getSize | 获取有效 Key 占用总大小 | +| put | 保存 int 类型的数据 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getBytes | 获取 byte[] 类型的数据 | +| getBitmap | 获取 Bitmap 类型的数据 | +| getDrawable | 获取 Drawable 类型的数据 | +| getSerializable | 获取 Serializable 类型的数据 | +| getParcelable | 获取 Parcelable 类型的数据 | +| getJSONObject | 获取 JSONObject 类型的数据 | +| getJSONArray | 获取 JSONArray 类型的数据 | +| getEntity | 获取指定类型对象 | + + +## **`dev.engine.compress`** + + +* **Image Compress Engine ->** [DevCompressEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/DevCompressEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Image Compress Engine 接口 ->** [ICompressEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/ICompressEngine.java) + +| 方法 | 注释 | +| :- | :- | +| compress | 压缩方法 | + + +## **`dev.engine.compress.listener`** + + +* **压缩过滤接口 ->** [CompressFilter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/CompressFilter.java) + +| 方法 | 注释 | +| :- | :- | +| apply | 根据路径判断是否进行压缩 | + + +* **压缩回调接口 ->** [OnCompressListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnCompressListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始压缩前调用 | +| onSuccess | 压缩成功后调用 | +| onError | 当压缩过程出现问题时触发 | +| onComplete | 压缩完成 ( 压缩结束 ) | + + +* **修改压缩图片文件名接口 ->** [OnRenameListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnRenameListener.java) + +| 方法 | 注释 | +| :- | :- | +| rename | 压缩前调用该方法用于修改压缩后文件名 | + + +## **`dev.engine.image`** + + +* **Image Engine ->** [DevImageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/DevImageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Image Engine 接口 ->** [IImageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/IImageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| pause | pause | +| resume | resume | +| preload | preload | +| clear | clear | +| clearDiskCache | clearDiskCache | +| clearMemoryCache | clearMemoryCache | +| clearAllCache | clearAllCache | +| lowMemory | lowMemory | +| display | display | +| loadImage | loadImage | +| loadImageThrows | loadImageThrows | +| loadBitmap | loadBitmap | +| loadBitmapThrows | loadBitmapThrows | +| loadDrawable | loadDrawable | +| loadDrawableThrows | loadDrawableThrows | +| convertImageFormat | convertImageFormat | + + +## **`dev.engine.image.listener`** + + +* **Bitmap 加载事件 ->** [BitmapListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/BitmapListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | getTranscodeType | + + +* **转换图片格式存储接口 ->** [ConvertStorage.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/ConvertStorage.java) + +| 方法 | 注释 | +| :- | :- | +| convert | 转换图片格式并存储 | + + +* **Drawable 加载事件 ->** [DrawableListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/DrawableListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | getTranscodeType | + + +* **图片加载事件 ->** [LoadListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/LoadListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | 获取转码类型 | +| onStart | 开始加载 | +| onResponse | 响应回调 | +| onFailure | 失败回调 | + + +* **转换图片格式回调接口 ->** [OnConvertListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/OnConvertListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始转换前调用 | +| onSuccess | 转换成功后调用 | +| onError | 当转换过程出现问题时触发 | +| onComplete | 转换完成 ( 转换结束 ) | + + +## **`dev.engine.json`** + + +* **JSON Engine ->** [DevJSONEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/json/DevJSONEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **JSON Engine 接口 ->** [IJSONEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/json/IJSONEngine.java) + +| 方法 | 注释 | +| :- | :- | +| toJson | 将对象转换为 JSON String | +| fromJson | 将 JSON String 映射为指定类型对象 | +| isJSON | 判断字符串是否 JSON 格式 | +| isJSONObject | 判断字符串是否 JSON Object 格式 | +| isJSONArray | 判断字符串是否 JSON Array 格式 | +| toJsonIndent | JSON String 缩进处理 | + + +## **`dev.engine.keyvalue`** + + +* **KeyValue Engine ->** [DevKeyValueEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/keyvalue/DevKeyValueEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Key-Value Engine 接口 ->** [IKeyValueEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/keyvalue/IKeyValueEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取 Key-Value Engine Config | +| remove | 移除数据 | +| removeForKeys | 移除数组的数据 | +| contains | 是否存在 key | +| clear | 清除全部数据 | +| putInt | 保存 int 类型的数据 | +| putLong | 保存 long 类型的数据 | +| putFloat | 保存 float 类型的数据 | +| putDouble | 保存 double 类型的数据 | +| putBoolean | 保存 boolean 类型的数据 | +| putString | 保存 String 类型的数据 | +| putEntity | 保存指定类型对象 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getEntity | 获取指定类型对象 | + + +## **`dev.engine.log`** + + +* **Log Engine ->** [DevLogEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/log/DevLogEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Log Engine 接口 ->** [ILogEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/log/ILogEngine.java) + +| 方法 | 注释 | +| :- | :- | +| isPrintLog | 判断是否打印日志 | +| d | 打印 Log.DEBUG | +| e | 打印 Log.ERROR | +| w | 打印 Log.WARN | +| i | 打印 Log.INFO | +| v | 打印 Log.VERBOSE | +| wtf | 打印 Log.ASSERT | +| json | 格式化 JSON 格式数据, 并打印 | +| xml | 格式化 XML 格式数据, 并打印 | +| dTag | 打印 Log.DEBUG | +| eTag | 打印 Log.ERROR | +| wTag | 打印 Log.WARN | +| iTag | 打印 Log.INFO | +| vTag | 打印 Log.VERBOSE | +| wtfTag | 打印 Log.ASSERT | +| jsonTag | 格式化 JSON 格式数据, 并打印 | +| xmlTag | 格式化 XML 格式数据, 并打印 | + + +## **`dev.engine.media`** + + +* **Media Selector Engine ->** [DevMediaEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/media/DevMediaEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Media Selector Engine 接口 ->** [IMediaEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/media/IMediaEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取全局配置 | +| setConfig | 设置全局配置 | +| openCamera | 打开相册拍照 | +| openGallery | 打开相册选择 | +| openPreview | 打开相册预览 | +| deleteCacheDirFile | 删除缓存文件 | +| deleteAllCacheDirFile | 删除全部缓存文件 | +| isMediaSelectorResult | 是否图片选择 ( onActivityResult ) | +| getSelectors | 获取 Media Selector Data List | +| getSelectorUris | 获取 Media Selector Uri List | +| getSingleSelector | 获取 Single Media Selector Data | +| getSingleSelectorUri | 获取 Single Media Selector Uri | + + +## **`dev.engine.permission`** + + +* **Permission Engine ->** [DevPermissionEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/permission/DevPermissionEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Permission Engine 接口 ->** [IPermissionEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/permission/IPermissionEngine.java) + +| 方法 | 注释 | +| :- | :- | +| isGranted | 判断是否授予了权限 | +| shouldShowRequestPermissionRationale | 获取拒绝权限询问勾选状态 | +| getDeniedPermissionStatus | 获取拒绝权限询问状态集合 | +| againRequest | 再次请求处理操作 | +| request | 请求权限 | +| onGranted | 授权通过权限回调 | +| onDenied | 授权未通过权限回调 | + + +## **`dev.engine.push`** + + +* **Push Engine ->** [DevPushEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/push/DevPushEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Push Engine 接口 ->** [IPushEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/push/IPushEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| register | 绑定 | +| unregister | 解绑 | +| onReceiveServicePid | 推送进程启动通知 | +| onReceiveClientId | 初始化 Client Id 成功通知 | +| onReceiveDeviceToken | 设备 ( 厂商 ) Token 通知 | +| onReceiveOnlineState | 在线状态变化通知 | +| onReceiveCommandResult | 命令回执通知 | +| onNotificationMessageArrived | 推送消息送达通知 | +| onNotificationMessageClicked | 推送消息点击通知 | +| onReceiveMessageData | 透传消息送达通知 | +| convertMessage | 传入 Object 转换 Engine Message | + + +## **`dev.engine.share`** + + +* **Share Engine ->** [DevShareEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/DevShareEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Share Engine 接口 ->** [IShareEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/IShareEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| openMinApp | 打开小程序 | +| shareMinApp | 分享小程序 | +| shareUrl | 分享链接 | +| shareImage | 分享图片 | +| shareImageList | 分享多张图片 | +| shareText | 分享文本 | +| shareVideo | 分享视频 | +| shareMusic | 分享音乐 | +| shareEmoji | 分享表情 | +| shareFile | 分享文件 | +| share | 分享操作 ( 通用扩展 ) | +| onActivityResult | 部分平台 Activity onActivityResult 额外调用处理 | + + +## **`dev.engine.share.listener`** + + +* **分享回调 ->** [ShareListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/listener/ShareListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始分享 | +| onResult | 分享成功 | +| onError | 分享失败 | +| onCancel | 取消分享 | + + +## **`dev.engine.storage`** + + +* **Storage Engine ->** [DevStorageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/DevStorageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Storage Engine 接口 ->** [IStorageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/IStorageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| insertImageToExternal | 插入一张图片到外部存储空间 ( SDCard ) | +| insertVideoToExternal | 插入一条视频到外部存储空间 ( SDCard ) | +| insertAudioToExternal | 插入一条音频到外部存储空间 ( SDCard ) | +| insertDownloadToExternal | 插入一条文件资源到外部存储空间 ( SDCard ) | +| insertMediaToExternal | 插入一条多媒体资源到外部存储空间 ( SDCard ) | +| insertImageToInternal | 插入一张图片到内部存储空间 | +| insertVideoToInternal | 插入一条视频到内部存储空间 | +| insertAudioToInternal | 插入一条音频到内部存储空间 | +| insertDownloadToInternal | 插入一条文件资源到内部存储空间 | +| insertMediaToInternal | 插入一条多媒体资源到内部存储空间 | + + +## **`dev.engine.storage.listener`** + + +* **插入多媒体资源事件 ->** [OnInsertListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/listener/OnInsertListener.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 插入多媒体资源结果方法 | + + +## **`dev.function`** + + +* **执行方法类 ->** [DevFunction.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/function/DevFunction.java) + +| 方法 | 注释 | +| :- | :- | +| operation | 获取 Operation | +| object | 设置 Object | +| tryCatch | 捕获异常处理 | +| thread | 后台线程执行 | +| threadPool | 后台线程池执行 | +| threadCatch | 后台线程执行 | +| threadPoolCatch | 后台线程池执行 | +| method | method | +| error | error | +| getObject | 获取 Object | +| setObject | 设置 Object | \ No newline at end of file diff --git a/lib/DevAssist/build.gradle b/lib/DevAssist/build.gradle new file mode 100644 index 0000000000..028cb9c88c --- /dev/null +++ b/lib/DevAssist/build.gradle @@ -0,0 +1,34 @@ +apply from: rootProject.file(files.lib_app_gradle) + +android.defaultConfig { + versionCode versions.dev_assist_versionCode + versionName versions.dev_assist_versionName + // DevAssist Module Version + buildConfigField "int", "DevAssist_VersionCode", "${versions.dev_assist_versionCode}" + buildConfigField "String", "DevAssist_Version", "\"${versions.dev_assist_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_app + } else { + // 编译时使用 + api project(':DevApp') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevAssist/proguard-rules.pro b/lib/DevAssist/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/DevAssist/project.properties b/lib/DevAssist/project.properties new file mode 100644 index 0000000000..f6913a5026 --- /dev/null +++ b/lib/DevAssist/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevAssist +project.groupId=io.github.afkt +project.artifactId=DevAssist +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevAssist \ No newline at end of file diff --git a/lib/DevAssist/src/main/AndroidManifest.xml b/lib/DevAssist/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..b65e2d7ace --- /dev/null +++ b/lib/DevAssist/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/DevAssist.java b/lib/DevAssist/src/main/java/dev/DevAssist.java new file mode 100644 index 0000000000..04c2d15e71 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/DevAssist.java @@ -0,0 +1,77 @@ +package dev; + +import dev.assist.BuildConfig; + +/** + * detail: 开发辅助类 + * @author Ttt + *
+ *     GitHub
+ *     @see 
+ *     DevApp Api
+ *     @see 
+ *     DevAssist Api
+ *     @see 
+ *     DevBase README
+ *     @see 
+ *     DevBaseMVVM README
+ *     @see 
+ *     DevMVVM README
+ *     @see 
+ *     DevEngine README
+ *     @see 
+ *     DevHttpCapture Api
+ *     @see 
+ *     DevHttpManager Api
+ *     @see 
+ *     DevRetrofit Api
+ *     @see 
+ *     DevWidget Api
+ *     @see 
+ *     DevEnvironment Api
+ *     @see 
+ *     DevJava Api
+ *     @see 
+ * 
+ */ +public final class DevAssist { + + private DevAssist() { + } + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevAssist 版本号 + * @return DevAssist versionCode + */ + public static int getDevAssistVersionCode() { + return BuildConfig.DevAssist_VersionCode; + } + + /** + * 获取 DevAssist 版本 + * @return DevAssist versionName + */ + public static String getDevAssistVersion() { + return BuildConfig.DevAssist_Version; + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + public static int getDevAppVersionCode() { + return BuildConfig.DevApp_VersionCode; + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + public static String getDevAppVersion() { + return BuildConfig.DevApp_Version; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapter.java b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapter.java new file mode 100644 index 0000000000..442ac0a3f6 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapter.java @@ -0,0 +1,436 @@ +package dev.adapter; + +import android.app.Activity; +import android.content.Context; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import dev.assist.DataAssist; +import dev.base.data.DataChanged; +import dev.base.data.DataManager; +import dev.utils.app.ActivityUtils; + +/** + * detail: DataManager RecyclerView Adapter + * @author Ttt + */ +public abstract class DevDataAdapter + extends RecyclerView.Adapter + implements DataManager, + DataChanged { + + // 数据辅助类 + protected DataAssist mAssist = new DataAssist<>(this); + // Context + protected Context mContext; + // Activity + protected Activity mActivity; + // RecyclerView + protected RecyclerView mRecyclerView; + + public DevDataAdapter() { + } + + public DevDataAdapter(Context context) { + this.mContext = context; + this.mActivity = ActivityUtils.getActivity(context); + } + + public DevDataAdapter(Activity activity) { + this.mContext = activity; + this.mActivity = activity; + } + + public DevDataAdapter(RecyclerView recyclerView) { + bindAdapter(recyclerView); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取 Context + * @return {@link Context} + */ + public Context getContext() { + return mContext; + } + + /** + * 设置 Context + * @param context {@link Context} + * @return {@link DevDataAdapter} + */ + public DevDataAdapter setContext(final Context context) { + this.mContext = context; + return this; + } + + /** + * 获取 Activity + * @return {@link Activity} + */ + public Activity getActivity() { + return mActivity; + } + + /** + * 设置 Activity + * @param activity {@link Activity} + * @return {@link DevDataAdapter} + */ + public DevDataAdapter setActivity(Activity activity) { + this.mActivity = activity; + return this; + } + + /** + * 通过 ViewGroup 设置 Context + *
+     *     在 {@link #onCreateViewHolder(ViewGroup, int)} 中调用, 传入 ViewGroup
+     * 
+ * @param parent {@link ViewGroup} + * @return {@link DevDataAdapter} + */ + public DevDataAdapter parentContext(ViewGroup parent) { + if (parent != null && mContext == null) { + this.mContext = parent.getContext(); + } + return this; + } + + // ======================== + // = RecyclerView.Adapter = + // ======================== + + @Override + public int getItemCount() { + return getDataSize(); + } + + @Override + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + super.onAttachedToRecyclerView(recyclerView); + initialize(recyclerView, true); + } + + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + super.onDetachedFromRecyclerView(recyclerView); + } + + // ================ + // = RecyclerView = + // ================ + + public RecyclerView getRecyclerView() { + return mRecyclerView; + } + + public DevDataAdapter setRecyclerView(RecyclerView recyclerView) { + this.mRecyclerView = recyclerView; + return this; + } + + public DevDataAdapter bindAdapter(RecyclerView recyclerView) { + return bindAdapter(recyclerView, true); + } + + public DevDataAdapter bindAdapter( + RecyclerView recyclerView, + boolean set + ) { + initialize(recyclerView, set); + if (recyclerView != null) { + recyclerView.setAdapter(this); + } + return this; + } + + // ========= + // = 初始化 = + // ========= + + public DevDataAdapter initialize(RecyclerView recyclerView) { + return initialize(recyclerView, true); + } + + public DevDataAdapter initialize( + RecyclerView recyclerView, + boolean set + ) { + if (recyclerView != null) { + // 进行设置 Context、Activity + if (mContext == null) { + mContext = recyclerView.getContext(); + } + if (mActivity == null) { + mActivity = ActivityUtils.getActivity(recyclerView.getContext()); + } + } + if (set) setRecyclerView(recyclerView); + return this; + } + + // =============== + // = DataManager = + // =============== + + // ========== + // = 获取相关 = + // ========== + + @Override + public List getDataList() { + return mAssist.getDataList(); + } + + @Override + public ArrayList getDataArrayList() { + return mAssist.getDataArrayList(); + } + + @Override + public int getDataSize() { + return mAssist.getDataSize(); + } + + @Override + public T getDataItem(int position) { + return mAssist.getDataItem(position); + } + + @Override + public int getDataItemPosition(T value) { + return mAssist.getDataItemPosition(value); + } + + @Override + public T getFirstData() { + return mAssist.getFirstData(); + } + + @Override + public T getLastData() { + return mAssist.getLastData(); + } + + @Override + public int getLastPosition() { + return mAssist.getLastPosition(); + } + + // ========== + // = 快捷方法 = + // ========== + + @Override + public boolean isDataEmpty() { + return mAssist.isDataEmpty(); + } + + @Override + public boolean isDataNotEmpty() { + return mAssist.isDataNotEmpty(); + } + + @Override + public boolean isFirstPosition(int position) { + return mAssist.isFirstPosition(position); + } + + @Override + public boolean isLastPosition(int position) { + return mAssist.isLastPosition(position); + } + + @Override + public boolean isLastPosition( + int position, + int size + ) { + return mAssist.isLastPosition(position, size); + } + + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value + ) { + return mAssist.isLastPositionAndGreaterThanOrEqual(position, value); + } + + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value, + int size + ) { + return mAssist.isLastPositionAndGreaterThanOrEqual(position, value, size); + } + + @Override + public boolean equalsFirstData(T value) { + return mAssist.equalsFirstData(value); + } + + @Override + public boolean equalsLastData(T value) { + return mAssist.equalsLastData(value); + } + + @Override + public boolean equalsPositionData( + int position, + T value + ) { + return mAssist.equalsPositionData(position, value); + } + + // ===== + // = 增 = + // ===== + + @Override + public boolean addData(T value) { + return mAssist.addData(value); + } + + @Override + public boolean addDataAt( + int position, + T value + ) { + return mAssist.addDataAt(position, value); + } + + @Override + public boolean addDatas(Collection collection) { + return mAssist.addDatas(collection); + } + + @Override + public boolean addDatasAt( + int position, + Collection collection + ) { + return mAssist.addDatasAt(position, collection); + } + + @Override + public boolean addDatasChecked(Collection collection) { + return mAssist.addDatasChecked(collection); + } + + @Override + public boolean addDatasCheckedAt( + int position, + Collection collection + ) { + return mAssist.addDatasCheckedAt(position, collection); + } + + @Override + public boolean addLists( + boolean append, + Collection collection + ) { + return mAssist.addLists(append, collection); + } + + // ===== + // = 删 = + // ===== + + @Override + public boolean removeData(T value) { + return mAssist.removeData(value); + } + + @Override + public T removeDataAt(int position) { + return mAssist.removeDataAt(position); + } + + @Override + public boolean removeDatas(Collection collection) { + return mAssist.removeDatas(collection); + } + + // ===== + // = 改 = + // ===== + + @Override + public boolean replaceData( + T oldValue, + T newValue + ) { + return mAssist.replaceData(oldValue, newValue); + } + + @Override + public boolean replaceDataAt( + int position, + T value + ) { + return mAssist.replaceDataAt(position, value); + } + + @Override + public boolean swipePosition( + int fromPosition, + int toPosition + ) { + return mAssist.swipePosition(fromPosition, toPosition); + } + + @Override + public boolean contains(T value) { + return mAssist.contains(value); + } + + @Override + public void clearDataList() { + mAssist.clearDataList(); + } + + @Override + public void clearDataList(boolean notify) { + mAssist.clearDataList(notify); + } + + @Override + public boolean setDataList(Collection collection) { + return mAssist.setDataList(collection); + } + + @Override + public boolean setDataList( + Collection collection, + boolean notify + ) { + return mAssist.setDataList(collection, notify); + } + + // ========== + // = 通知方法 = + // ========== + + @Override + public void notifyDataChanged() { + notifyDataSetChanged(); + } + + @Override + public void notifyElementChanged(T value) { + notifyDataSetChanged(); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt.java b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt.java new file mode 100644 index 0000000000..b6dc4c466c --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt.java @@ -0,0 +1,246 @@ +package dev.adapter; + +import android.app.Activity; +import android.content.Context; + +import androidx.recyclerview.widget.RecyclerView; + +import dev.assist.EditTextWatcherAssist; +import dev.base.DevObject; +import dev.base.DevPage; +import dev.base.multiselect.DevMultiSelectMap; +import dev.base.state.CommonState; +import dev.base.state.RequestState; +import dev.callback.DevCallback; +import dev.callback.DevItemClickCallback; +import dev.utils.common.assist.FlagsValue; + +/** + * detail: DataManager RecyclerView Adapter Extend + * @author Ttt + *
+ *     在 {@link DevDataAdapter} 基础上
+ *     新增: Object 存储、分页信息、通用回调、Item 回调、输入监听辅助类、多选辅助类 功能变量
+ * 
+ */ +public abstract class DevDataAdapterExt + extends DevDataAdapter { + + public DevDataAdapterExt() { + } + + public DevDataAdapterExt(Context context) { + super(context); + } + + public DevDataAdapterExt(Activity activity) { + super(activity); + } + + // ============= + // = 对外公开方法 = + // ============= + + // 通用回调 + protected DevCallback mCallback; + // 通用 Item Click 回调 + protected DevItemClickCallback mItemCallback; + // 通用 Object + protected DevObject mObject = new DevObject<>(); + // Page 实体类 + protected DevPage mPage = DevPage.getDefault(); + // 标记值计算存储 ( 位运算符 ) + protected FlagsValue mFlags = new FlagsValue(); + // 通用状态 + protected CommonState mState = new CommonState<>(); + // 请求状态 + protected RequestState mRequestState = new RequestState<>(); + // EditText 输入监听辅助类 + protected EditTextWatcherAssist mTextWatcherAssist = new EditTextWatcherAssist<>(); + // 多选辅助类 + protected DevMultiSelectMap mMultiSelectMap = new DevMultiSelectMap<>(); + + /** + * 获取通用回调 + * @return {@link DevCallback} + */ + public DevCallback getCallback() { + return mCallback; + } + + /** + * 设置通用回调 + * @param callback {@link DevCallback} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setCallback(final DevCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * 获取通用 Item Click 回调 + * @return {@link DevItemClickCallback} + */ + public DevItemClickCallback getItemCallback() { + return mItemCallback; + } + + /** + * 设置通用 Item Click 回调 + * @param itemCallback {@link DevItemClickCallback} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setItemCallback(final DevItemClickCallback itemCallback) { + this.mItemCallback = itemCallback; + return this; + } + + /** + * 获取通用 Object + * @return {@link DevObject} + */ + public DevObject getObject() { + return mObject; + } + + /** + * 设置通用 Object + * @param object {@link DevObject} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setObject(final DevObject object) { + this.mObject = object; + return this; + } + + /** + * 获取 Page 实体类 + * @return {@link DevPage} + */ + public DevPage getPage() { + return mPage; + } + + /** + * 设置 Page 实体类 + * @param pageConfig 页数配置信息 + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setPage(final DevPage.PageConfig pageConfig) { + return setPage(new DevPage<>(pageConfig)); + } + + /** + * 设置 Page 实体类 + * @param page 页数 + * @param pageSize 每页请求条数 + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setPage( + final int page, + final int pageSize + ) { + return setPage(new DevPage<>(page, pageSize)); + } + + /** + * 设置 Page 实体类 + * @param page Page 实体类 + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setPage(final DevPage page) { + this.mPage = page; + return this; + } + + /** + * 获取标记值计算存储 ( 位运算符 ) 实体类 + * @return {@link FlagsValue} + */ + public FlagsValue getFlags() { + return mFlags; + } + + /** + * 设置标记值计算存储 ( 位运算符 ) 实体类 + * @param flags {@link FlagsValue} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setFlags(final FlagsValue flags) { + this.mFlags = flags; + return this; + } + + /** + * 获取通用状态实体类 + * @return {@link CommonState} + */ + public CommonState getState() { + return mState; + } + + /** + * 设置通用状态实体类 + * @param state {@link CommonState} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setState(final CommonState state) { + this.mState = state; + return this; + } + + /** + * 获取请求状态实体类 + * @return {@link RequestState} + */ + public RequestState getRequestState() { + return mRequestState; + } + + /** + * 设置请求状态实体类 + * @param requestState {@link RequestState} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setRequestState(final RequestState requestState) { + this.mRequestState = requestState; + return this; + } + + /** + * 获取 EditText 输入监听辅助类 + * @return {@link EditTextWatcherAssist} + */ + public EditTextWatcherAssist getTextWatcherAssist() { + return mTextWatcherAssist; + } + + /** + * 设置 EditText 输入监听辅助类 + * @param textWatcherAssist {@link EditTextWatcherAssist} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setTextWatcherAssist(final EditTextWatcherAssist textWatcherAssist) { + this.mTextWatcherAssist = textWatcherAssist; + return this; + } + + /** + * 获取多选辅助类 + * @return {@link DevMultiSelectMap} + */ + public DevMultiSelectMap getMultiSelectMap() { + return mMultiSelectMap; + } + + /** + * 设置多选辅助类 + * @param multiSelectMap {@link DevMultiSelectMap} + * @return {@link DevDataAdapterExt} + */ + public DevDataAdapterExt setMultiSelectMap(final DevMultiSelectMap multiSelectMap) { + this.mMultiSelectMap = multiSelectMap; + return this; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt2.java b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt2.java new file mode 100644 index 0000000000..1e85542e70 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/adapter/DevDataAdapterExt2.java @@ -0,0 +1,167 @@ +package dev.adapter; + +import android.app.Activity; +import android.content.Context; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.LinkedHashMap; +import java.util.List; + +import dev.base.multiselect.IMultiSelectEdit; + +/** + * detail: DataManager RecyclerView Adapter Extend + * @author Ttt + *
+ *     在 {@link DevDataAdapterExt} 基础上实现多选辅助
+ * 
+ */ +public abstract class DevDataAdapterExt2 + extends DevDataAdapterExt + implements IMultiSelectEdit> { + + public DevDataAdapterExt2() { + } + + public DevDataAdapterExt2(Context context) { + super(context); + } + + public DevDataAdapterExt2(Activity activity) { + super(activity); + } + + // ============= + // = 对外公开方法 = + // ============= + + // 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + protected boolean mNotifyAdapter = true; + + /** + * 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isNotifyAdapter() { + return mNotifyAdapter; + } + + /** + * 设置是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + * @param notifyAdapter {@code true} yes, {@code false} no + * @return {@link DevDataAdapterExt2} + */ + public DevDataAdapterExt2 setNotifyAdapter(boolean notifyAdapter) { + mNotifyAdapter = notifyAdapter; + return this; + } + + // ==================== + // = IMultiSelectEdit = + // ==================== + + // 是否编辑状态 + protected boolean mEdit; + + @Override + public boolean isEditState() { + return mEdit; + } + + @Override + public DevDataAdapterExt2 setEditState(boolean isEdit) { + this.mEdit = isEdit; + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public DevDataAdapterExt2 toggleEditState() { + return setEditState(!mEdit); + } + + @Override + public DevDataAdapterExt2 clearSelectAll() { + mMultiSelectMap.clearSelects(); + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public boolean isSelectAll() { + int size = mMultiSelectMap.getSelectSize(); + if (size == 0) return false; + return getItemCount() == size; + } + + @Override + public boolean isSelect() { + return mMultiSelectMap.isSelect(); + } + + @Override + public boolean isNotSelect() { + return !mMultiSelectMap.isSelect(); + } + + @Override + public int getSelectSize() { + return mMultiSelectMap.getSelectSize(); + } + + @Override + public int getDataCount() { + return getItemCount(); + } + + // = + + @Override + public DevDataAdapterExt2 selectAll() { + LinkedHashMap maps = new LinkedHashMap<>(); + for (int i = 0, len = getDataCount(); i < len; i++) { + T item = getDataItem(i); + maps.put(getMultiSelectKey(item, i), item); + } + mMultiSelectMap.putSelects(maps); + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public DevDataAdapterExt2 inverseSelect() { + if (isNotSelect()) return selectAll(); + + List keys = mMultiSelectMap.getSelectKeys(); + + LinkedHashMap maps = new LinkedHashMap<>(); + for (int i = 0, len = getDataCount(); i < len; i++) { + T item = getDataItem(i); + maps.put(getMultiSelectKey(item, i), item); + } + mMultiSelectMap.putSelects(maps); + + for (String key : keys) { + mMultiSelectMap.unselect(key); + } + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + // = + + /** + * 获取多选标记 Key + *
+     *     用于 {@link #selectAll()}、{@link #inverseSelect()}
+     * 
+ * @param item 泛型实体类 Item + * @param position 索引 + * @return 多选标记 Key + */ + public abstract String getMultiSelectKey( + T item, + int position + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/adapter/DevDataList.java b/lib/DevAssist/src/main/java/dev/adapter/DevDataList.java new file mode 100644 index 0000000000..5165b23573 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/adapter/DevDataList.java @@ -0,0 +1,573 @@ +package dev.adapter; + +import android.app.Activity; +import android.content.Context; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import dev.assist.DataAssist; +import dev.assist.EditTextWatcherAssist; +import dev.base.DevObject; +import dev.base.DevPage; +import dev.base.data.DataChanged; +import dev.base.data.DataManager; +import dev.base.multiselect.DevMultiSelectMap; +import dev.base.state.CommonState; +import dev.base.state.RequestState; +import dev.callback.DevCallback; +import dev.callback.DevItemClickCallback; +import dev.utils.common.assist.FlagsValue; + +/** + * detail: DataManager List + * @author Ttt + *
+ *     适用于不方便继承 {@link DevDataAdapter} 及扩展类, 但提供相同功能 DataManager
+ * 
+ */ +public abstract class DevDataList + implements DataManager, + DataChanged { + + // 数据辅助类 + protected DataAssist mAssist = new DataAssist<>(this); + // Context + protected Context mContext; + // Activity + protected Activity mActivity; + // 通用回调 + protected DevCallback mCallback; + // 通用 Item Click 回调 + protected DevItemClickCallback mItemCallback; + // 通用 Object + protected DevObject mObject = new DevObject<>(); + // Page 实体类 + protected DevPage mPage = DevPage.getDefault(); + // 标记值计算存储 ( 位运算符 ) + protected FlagsValue mFlags = new FlagsValue(); + // 通用状态 + protected CommonState mState = new CommonState<>(); + // 请求状态 + protected RequestState mRequestState = new RequestState<>(); + // EditText 输入监听辅助类 + protected EditTextWatcherAssist mTextWatcherAssist = new EditTextWatcherAssist<>(); + // 多选辅助类 + protected DevMultiSelectMap mMultiSelectMap = new DevMultiSelectMap<>(); + + public DevDataList() { + } + + public DevDataList(final DevPage.PageConfig pageConfig) { + setPage(pageConfig); + } + + public DevDataList( + final int page, + final int pageSize + ) { + setPage(page, pageSize); + } + + public DevDataList(final DevPage page) { + setPage(page); + } + + // ================== + // = DevDataAdapter = + // ================== + + // =========== + // = get/set = + // =========== + + /** + * 获取 Context + * @return {@link Context} + */ + public Context getContext() { + return mContext; + } + + /** + * 设置 Context + * @param context {@link Context} + * @return {@link DevDataList} + */ + public DevDataList setContext(final Context context) { + this.mContext = context; + return this; + } + + /** + * 获取 Activity + * @return {@link Activity} + */ + public Activity getActivity() { + return mActivity; + } + + /** + * 设置 Activity + * @param activity {@link Activity} + * @return {@link DevDataList} + */ + public DevDataList setActivity(Activity activity) { + this.mActivity = activity; + return this; + } + + /** + * 通过 ViewGroup 设置 Context + *
+     *     在 {@link androidx.recyclerview.widget.RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)} 中调用, 传入 ViewGroup
+     * 
+ * @param parent {@link ViewGroup} + * @return {@link DevDataList} + */ + public DevDataList parentContext(ViewGroup parent) { + if (parent != null && mContext == null) { + this.mContext = parent.getContext(); + } + return this; + } + + // ======================== + // = RecyclerView.Adapter = + // ======================== + + public int getItemCount() { + return getDataSize(); + } + + // =============== + // = DataManager = + // =============== + + // ========== + // = 获取相关 = + // ========== + + @Override + public List getDataList() { + return mAssist.getDataList(); + } + + @Override + public ArrayList getDataArrayList() { + return mAssist.getDataArrayList(); + } + + @Override + public int getDataSize() { + return mAssist.getDataSize(); + } + + @Override + public T getDataItem(int position) { + return mAssist.getDataItem(position); + } + + @Override + public int getDataItemPosition(T value) { + return mAssist.getDataItemPosition(value); + } + + @Override + public T getFirstData() { + return mAssist.getFirstData(); + } + + @Override + public T getLastData() { + return mAssist.getLastData(); + } + + @Override + public int getLastPosition() { + return mAssist.getLastPosition(); + } + + // ========== + // = 快捷方法 = + // ========== + + @Override + public boolean isDataEmpty() { + return mAssist.isDataEmpty(); + } + + @Override + public boolean isDataNotEmpty() { + return mAssist.isDataNotEmpty(); + } + + @Override + public boolean isFirstPosition(int position) { + return mAssist.isFirstPosition(position); + } + + @Override + public boolean isLastPosition(int position) { + return mAssist.isLastPosition(position); + } + + @Override + public boolean isLastPosition( + int position, + int size + ) { + return mAssist.isLastPosition(position, size); + } + + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value + ) { + return mAssist.isLastPositionAndGreaterThanOrEqual(position, value); + } + + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value, + int size + ) { + return mAssist.isLastPositionAndGreaterThanOrEqual(position, value, size); + } + + @Override + public boolean equalsFirstData(T value) { + return mAssist.equalsFirstData(value); + } + + @Override + public boolean equalsLastData(T value) { + return mAssist.equalsLastData(value); + } + + @Override + public boolean equalsPositionData( + int position, + T value + ) { + return mAssist.equalsPositionData(position, value); + } + + // ===== + // = 增 = + // ===== + + @Override + public boolean addData(T value) { + return mAssist.addData(value); + } + + @Override + public boolean addDataAt( + int position, + T value + ) { + return mAssist.addDataAt(position, value); + } + + @Override + public boolean addDatas(Collection collection) { + return mAssist.addDatas(collection); + } + + @Override + public boolean addDatasAt( + int position, + Collection collection + ) { + return mAssist.addDatasAt(position, collection); + } + + @Override + public boolean addDatasChecked(Collection collection) { + return mAssist.addDatasChecked(collection); + } + + @Override + public boolean addDatasCheckedAt( + int position, + Collection collection + ) { + return mAssist.addDatasCheckedAt(position, collection); + } + + @Override + public boolean addLists( + boolean append, + Collection collection + ) { + return mAssist.addLists(append, collection); + } + + // ===== + // = 删 = + // ===== + + @Override + public boolean removeData(T value) { + return mAssist.removeData(value); + } + + @Override + public T removeDataAt(int position) { + return mAssist.removeDataAt(position); + } + + @Override + public boolean removeDatas(Collection collection) { + return mAssist.removeDatas(collection); + } + + // ===== + // = 改 = + // ===== + + @Override + public boolean replaceData( + T oldValue, + T newValue + ) { + return mAssist.replaceData(oldValue, newValue); + } + + @Override + public boolean replaceDataAt( + int position, + T value + ) { + return mAssist.replaceDataAt(position, value); + } + + @Override + public boolean swipePosition( + int fromPosition, + int toPosition + ) { + return mAssist.swipePosition(fromPosition, toPosition); + } + + @Override + public boolean contains(T value) { + return mAssist.contains(value); + } + + @Override + public void clearDataList() { + mAssist.clearDataList(); + } + + @Override + public void clearDataList(boolean notify) { + mAssist.clearDataList(notify); + } + + @Override + public boolean setDataList(Collection collection) { + return mAssist.setDataList(collection); + } + + @Override + public boolean setDataList( + Collection collection, + boolean notify + ) { + return mAssist.setDataList(collection, notify); + } + + // ===================== + // = DevDataAdapterExt = + // ===================== + + /** + * 获取通用回调 + * @return {@link DevCallback} + */ + public DevCallback getCallback() { + return mCallback; + } + + /** + * 设置通用回调 + * @param callback {@link DevCallback} + * @return {@link DevDataList} + */ + public DevDataList setCallback(final DevCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * 获取通用 Item Click 回调 + * @return {@link DevItemClickCallback} + */ + public DevItemClickCallback getItemCallback() { + return mItemCallback; + } + + /** + * 设置通用 Item Click 回调 + * @param itemCallback {@link DevItemClickCallback} + * @return {@link DevDataList} + */ + public DevDataList setItemCallback(final DevItemClickCallback itemCallback) { + this.mItemCallback = itemCallback; + return this; + } + + /** + * 获取通用 Object + * @return {@link DevObject} + */ + public DevObject getObject() { + return mObject; + } + + /** + * 设置通用 Object + * @param object {@link DevObject} + * @return {@link DevDataList} + */ + public DevDataList setObject(final DevObject object) { + this.mObject = object; + return this; + } + + /** + * 获取 Page 实体类 + * @return {@link DevPage} + */ + public DevPage getPage() { + return mPage; + } + + /** + * 设置 Page 实体类 + * @param pageConfig 页数配置信息 + * @return {@link DevDataList} + */ + public DevDataList setPage(final DevPage.PageConfig pageConfig) { + return setPage(new DevPage<>(pageConfig)); + } + + /** + * 设置 Page 实体类 + * @param page 页数 + * @param pageSize 每页请求条数 + * @return {@link DevDataList} + */ + public DevDataList setPage( + final int page, + final int pageSize + ) { + return setPage(new DevPage<>(page, pageSize)); + } + + /** + * 设置 Page 实体类 + * @param page Page 实体类 + * @return {@link DevDataList} + */ + public DevDataList setPage(final DevPage page) { + this.mPage = page; + return this; + } + + /** + * 获取标记值计算存储 ( 位运算符 ) 实体类 + * @return {@link FlagsValue} + */ + public FlagsValue getFlags() { + return mFlags; + } + + /** + * 设置标记值计算存储 ( 位运算符 ) 实体类 + * @param flags {@link FlagsValue} + * @return {@link DevDataList} + */ + public DevDataList setFlags(final FlagsValue flags) { + this.mFlags = flags; + return this; + } + + /** + * 获取通用状态实体类 + * @return {@link CommonState} + */ + public CommonState getState() { + return mState; + } + + /** + * 设置通用状态实体类 + * @param state {@link CommonState} + * @return {@link DevDataList} + */ + public DevDataList setState(final CommonState state) { + this.mState = state; + return this; + } + + /** + * 获取请求状态实体类 + * @return {@link RequestState} + */ + public RequestState getRequestState() { + return mRequestState; + } + + /** + * 设置请求状态实体类 + * @param requestState {@link RequestState} + * @return {@link DevDataList} + */ + public DevDataList setRequestState(final RequestState requestState) { + this.mRequestState = requestState; + return this; + } + + /** + * 获取 EditText 输入监听辅助类 + * @return {@link EditTextWatcherAssist} + */ + public EditTextWatcherAssist getTextWatcherAssist() { + return mTextWatcherAssist; + } + + /** + * 设置 EditText 输入监听辅助类 + * @param textWatcherAssist {@link EditTextWatcherAssist} + * @return {@link DevDataList} + */ + public DevDataList setTextWatcherAssist(final EditTextWatcherAssist textWatcherAssist) { + this.mTextWatcherAssist = textWatcherAssist; + return this; + } + + /** + * 获取多选辅助类 + * @return {@link DevMultiSelectMap} + */ + public DevMultiSelectMap getMultiSelectMap() { + return mMultiSelectMap; + } + + /** + * 设置多选辅助类 + * @param multiSelectMap {@link DevMultiSelectMap} + * @return {@link DevDataList} + */ + public DevDataList setMultiSelectMap(final DevMultiSelectMap multiSelectMap) { + this.mMultiSelectMap = multiSelectMap; + return this; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/adapter/DevDataListExt.java b/lib/DevAssist/src/main/java/dev/adapter/DevDataListExt.java new file mode 100644 index 0000000000..741d1045ac --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/adapter/DevDataListExt.java @@ -0,0 +1,174 @@ +package dev.adapter; + +import java.util.LinkedHashMap; +import java.util.List; + +import dev.base.DevPage; +import dev.base.multiselect.IMultiSelectEdit; + +/** + * detail: DataManager List Extend + * @author Ttt + *
+ *     在 {@link DevDataList} 基础上添加 {@link DevDataAdapterExt2} 功能
+ * 
+ */ +public abstract class DevDataListExt + extends DevDataList + implements IMultiSelectEdit> { + + public DevDataListExt() { + } + + public DevDataListExt(DevPage.PageConfig pageConfig) { + super(pageConfig); + } + + public DevDataListExt( + int page, + int pageSize + ) { + super(page, pageSize); + } + + public DevDataListExt(DevPage page) { + super(page); + } + + // ====================== + // = DevDataAdapterExt2 = + // ====================== + + // ============= + // = 对外公开方法 = + // ============= + + // 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + protected boolean mNotifyAdapter = true; + + /** + * 是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isNotifyAdapter() { + return mNotifyAdapter; + } + + /** + * 设置是否通知适配器 ( 通用: 如多选操作后是否通知适配器 ) + * @param notifyAdapter {@code true} yes, {@code false} no + * @return {@link DevDataListExt} + */ + public DevDataListExt setNotifyAdapter(boolean notifyAdapter) { + mNotifyAdapter = notifyAdapter; + return this; + } + + // ==================== + // = IMultiSelectEdit = + // ==================== + + // 是否编辑状态 + protected boolean mEdit; + + @Override + public boolean isEditState() { + return mEdit; + } + + @Override + public DevDataListExt setEditState(boolean isEdit) { + this.mEdit = isEdit; + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public DevDataListExt toggleEditState() { + return setEditState(!mEdit); + } + + @Override + public DevDataListExt clearSelectAll() { + mMultiSelectMap.clearSelects(); + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public boolean isSelectAll() { + int size = mMultiSelectMap.getSelectSize(); + if (size == 0) return false; + return getItemCount() == size; + } + + @Override + public boolean isSelect() { + return mMultiSelectMap.isSelect(); + } + + @Override + public boolean isNotSelect() { + return !mMultiSelectMap.isSelect(); + } + + @Override + public int getSelectSize() { + return mMultiSelectMap.getSelectSize(); + } + + @Override + public int getDataCount() { + return getItemCount(); + } + + // = + + @Override + public DevDataListExt selectAll() { + LinkedHashMap maps = new LinkedHashMap<>(); + for (int i = 0, len = getDataCount(); i < len; i++) { + T item = getDataItem(i); + maps.put(getMultiSelectKey(item, i), item); + } + mMultiSelectMap.putSelects(maps); + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + @Override + public DevDataListExt inverseSelect() { + if (isNotSelect()) return selectAll(); + + List keys = mMultiSelectMap.getSelectKeys(); + + LinkedHashMap maps = new LinkedHashMap<>(); + for (int i = 0, len = getDataCount(); i < len; i++) { + T item = getDataItem(i); + maps.put(getMultiSelectKey(item, i), item); + } + mMultiSelectMap.putSelects(maps); + + for (String key : keys) { + mMultiSelectMap.unselect(key); + } + if (mNotifyAdapter) mAssist.notifyDataChanged(); + return this; + } + + // = + + /** + * 获取多选标记 Key + *
+     *     用于 {@link #selectAll()}、{@link #inverseSelect()}
+     * 
+ * @param item 泛型实体类 Item + * @param position 索引 + * @return 多选标记 Key + */ + public abstract String getMultiSelectKey( + T item, + int position + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/DataAssist.java b/lib/DevAssist/src/main/java/dev/assist/DataAssist.java new file mode 100644 index 0000000000..1011b732c2 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/DataAssist.java @@ -0,0 +1,554 @@ +package dev.assist; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import dev.base.DevDataSource; +import dev.base.data.DataChanged; +import dev.base.data.DataManager; + +/** + * detail: 数据辅助类 + * @author Ttt + *
+ *     实现 {@link DataManager}, 每个接口方法直接通过调用 {@link DataAssist} 已实现同名方法即可
+ * 
+ */ +public class DataAssist + implements DataManager, + DataChanged { + + // DataSource Object + private final DevDataSource mData = new DevDataSource<>(); + + // 数据改变通知 + private DataChanged mDataChanged; + + public DataAssist() { + } + + public DataAssist(final DataChanged dataChanged) { + this.mDataChanged = dataChanged; + } + + /** + * 设置数据改变通知 + * @param dataChanged {@link DataChanged} + * @return {@link DataAssist} + */ + public DataAssist setDataChanged(final DataChanged dataChanged) { + this.mDataChanged = dataChanged; + return this; + } + + /** + * 获取 DataSource Object + * @return {@link DevDataSource} + */ + public DevDataSource getDataSource() { + return mData; + } + + // ========== + // = 获取相关 = + // ========== + + /** + * 获取 List Data + * @return {@link List} + */ + @Override + public List getDataList() { + return mData.getDataList(); + } + + /** + * 获取 ArrayList Data + * @return {@link ArrayList} + */ + @Override + public ArrayList getDataArrayList() { + return mData.getDataArrayList(); + } + + /** + * 获取 List Size + * @return List.size() + */ + @Override + public int getDataSize() { + return mData.getDataSize(); + } + + /** + * 获取 List Position Data + * @param position 索引 + * @return {@link T} + */ + @Override + public T getDataItem(int position) { + return mData.getDataItem(position); + } + + /** + * 获取 Value Position + * @param value {@link T} + * @return position + */ + @Override + public int getDataItemPosition(T value) { + return mData.getDataItemPosition(value); + } + + /** + * 获取 First Data + * @return {@link T} + */ + @Override + public T getFirstData() { + return mData.getFirstData(); + } + + /** + * 获取 Last Data + * @return {@link T} + */ + @Override + public T getLastData() { + return mData.getLastData(); + } + + /** + * 获取 Last Position + * @return Last Position + */ + @Override + public int getLastPosition() { + return mData.getLastPosition(); + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断 List Size 是否为 0 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isDataEmpty() { + return mData.isDataEmpty(); + } + + /** + * 判断 List Size 是否大于 0 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isDataNotEmpty() { + return mData.isDataNotEmpty(); + } + + /** + * 判断是否 First Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isFirstPosition(int position) { + return mData.isFirstPosition(position); + } + + /** + * 判断是否 Last Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPosition(int position) { + return mData.isLastPosition(position); + } + + /** + * 判断是否 Last Position + * @param position 索引 + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPosition( + int position, + int size + ) { + return mData.isLastPosition(position, size); + } + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value + ) { + return mData.isLastPositionAndGreaterThanOrEqual(position, value); + } + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value, + int size + ) { + return mData.isLastPositionAndGreaterThanOrEqual(position, value, size); + } + + /** + * 判断 First Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsFirstData(T value) { + return mData.equalsFirstData(value); + } + + /** + * 判断 Last Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsLastData(T value) { + return mData.equalsLastData(value); + } + + /** + * 判断 Position Value 是否一致 + * @param position 索引 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsPositionData( + int position, + T value + ) { + return mData.equalsPositionData(position, value); + } + + // ===== + // = 增 = + // ===== + + /** + * 添加数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addData(T value) { + if (mData.addData(value)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDataAt( + int position, + T value + ) { + if (mData.addDataAt(position, value)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatas(Collection collection) { + if (mData.addDatas(collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据集 + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasAt( + int position, + Collection collection + ) { + if (mData.addDatasAt(position, collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据集 ( 进行校验 ) + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasChecked(Collection collection) { + if (mData.addDatasChecked(collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据集 ( 进行校验 ) + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasCheckedAt( + int position, + Collection collection + ) { + if (mData.addDatasCheckedAt(position, collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 添加数据集 ( 判断是追加还是重置 ) + * @param append {@code true} {@link #addDatas} {@code false} {@link #setDataList} + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addLists( + boolean append, + Collection collection + ) { + if (mData.addLists(append, collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + // ===== + // = 删 = + // ===== + + /** + * 移除数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean removeData(T value) { + if (mData.removeData(value)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 移除数据 + * @param position 索引 + * @return remove position value + */ + @Override + public T removeDataAt(int position) { + T value = mData.removeDataAt(position); + notifyDataChanged(); + return value; + } + + /** + * 移除数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean removeDatas(Collection collection) { + if (mData.removeDatas(collection)) { + notifyDataChanged(); + return true; + } + return false; + } + + // ===== + // = 改 = + // ===== + + /** + * 替换数据 + * @param oldValue 旧的 Value + * @param newValue 新的 Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean replaceData( + T oldValue, + T newValue + ) { + if (mData.replaceData(oldValue, newValue)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 替换数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean replaceDataAt( + int position, + T value + ) { + if (mData.replaceDataAt(position, value)) { + notifyDataChanged(); + return true; + } + return false; + } + + // = + + /** + * 数据中两个索引 Data 互换位置 + * @param fromPosition 待换位置索引 + * @param toPosition 替换后位置索引 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean swipePosition( + int fromPosition, + int toPosition + ) { + if (mData.swipePosition(fromPosition, toPosition)) { + notifyDataChanged(); + return true; + } + return false; + } + + /** + * 是否存在 Data + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean contains(T value) { + return mData.contains(value); + } + + /** + * 清空全部数据 + */ + @Override + public void clearDataList() { + mData.clearDataList(); + notifyDataChanged(); + } + + /** + * 清空全部数据 + * @param notify 是否进行通知 + */ + @Override + public void clearDataList(boolean notify) { + mData.clearDataList(notify); + if (notify) notifyDataChanged(); + } + + /** + * 设置 List Data + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean setDataList(Collection collection) { + boolean result = mData.setDataList(collection); + notifyDataChanged(); + return result; + } + + /** + * 设置 List Data + * @param collection {@link Collection} + * @param notify 是否进行通知 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean setDataList( + Collection collection, + boolean notify + ) { + boolean result = mData.setDataList(collection, notify); + if (notify) notifyDataChanged(); + return result; + } + + // ========== + // = 通知方法 = + // ========== + + /** + * 通知数据改变 + */ + @Override + public void notifyDataChanged() { + if (mDataChanged != null) { + mDataChanged.notifyDataChanged(); + } + } + + /** + * 通知某个数据改变 + * @param value {@link T} + */ + @Override + public void notifyElementChanged(T value) { + if (mDataChanged != null) { + mDataChanged.notifyElementChanged(value); + } + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/DevTimerAssist.java b/lib/DevAssist/src/main/java/dev/assist/DevTimerAssist.java new file mode 100644 index 0000000000..9a4cf72cd6 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/DevTimerAssist.java @@ -0,0 +1,215 @@ +package dev.assist; + +import android.os.Handler; + +import java.util.concurrent.atomic.AtomicLong; + +import dev.utils.app.timer.DevTimer; + +/** + * detail: 定时器辅助类 + * @author Ttt + *
+ *     在 {@link DevTimer} 基础上实现, 经过指定时长后进行通知处理
+ * 
+ */ +public class DevTimerAssist { + + // 总时长 ( 毫秒 ) + private final AtomicLong mDuration = new AtomicLong(); + // 循环时间 ( 每隔多少毫秒执行一次 ) + private final long mPeriod; + // 定时器 TAG + private String mTag; + // UI Handler + private Handler mHandler; + // 回调方法 + private Callback mCallback; + // 当前定时器 + private DevTimer mTimer; + + /** + * 构造函数 + * @param duration 总时长 + */ + public DevTimerAssist(long duration) { + this(duration, 100L); + } + + /** + * 构造函数 + * @param duration 总时长 + * @param period 循环时间 ( 每隔多少毫秒执行一次 ) + */ + public DevTimerAssist( + long duration, + long period + ) { + this.mDuration.set(duration); + this.mPeriod = period; + } + + /** + * detail: 回调接口 + * @author Ttt + */ + public interface Callback { + + /** + * 触发回调方法 + * @param assist 定时器辅助类 + * @param number 触发次数 + * @param end 是否结束 + * @param duration 剩余总时长 ( 毫秒 ) + */ + void callback( + DevTimerAssist assist, + int number, + boolean end, + long duration + ); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 设置 TAG + * @param tag TAG + * @return {@link DevTimerAssist} + */ + public DevTimerAssist setTag(final String tag) { + if (mTimer == null) { + mTag = tag; + } + return this; + } + + /** + * 设置 UI Handler + *
+     *     如果没设置 Handler 回调方法则属于非 UI 线程
+     * 
+ * @param handler {@link Handler} + * @return {@link DevTimerAssist} + */ + public DevTimerAssist setHandler(final Handler handler) { + if (mTimer == null) { + mHandler = handler; + } + return this; + } + + /** + * 设置回调事件 + * @param callback {@link Callback} + * @return {@link DevTimerAssist} + */ + public DevTimerAssist setCallback(final Callback callback) { + if (mTimer == null) { + mCallback = callback; + } + return this; + } + + /** + * 获取定时器 + * @return {@link DevTimer} + */ + public DevTimer getTimer() { + if (mTimer == null) { + mTimer = createTimer(); + } + return mTimer; + } + + /** + * 获取剩余总时长 ( 毫秒 ) + * @return 剩余总时长 ( 毫秒 ) + */ + public long getDuration() { + return mDuration.get(); + } + + // ========== + // = 核心方法 = + // ========== + + /** + * 运行定时器 + * @return {@link DevTimerAssist} + */ + public DevTimerAssist start() { + if (isDurationEnd()) return this; + getTimer().start(); + return this; + } + + /** + * 关闭定时器 + * @return {@link DevTimerAssist} + */ + public DevTimerAssist stop() { + getTimer().stop(); + return this; + } + + // ========== + // = 内部方法 = + // ========== + + // 内部 Callback + private final DevTimer.Callback mInnerCallback = new DevTimer.Callback() { + @Override + public void callback( + DevTimer timer, + int number, + boolean end, + boolean infinite + ) { + long result = mDuration.addAndGet(-mPeriod); + if (result <= 0L) { + timer.stop(); + if (mCallback != null) { + mCallback.callback( + DevTimerAssist.this, number, true, result + ); + } + return; + } + if (mCallback != null) { + mCallback.callback( + DevTimerAssist.this, number, false, result + ); + } + } + }; + + /** + * 创建定时器 + * @return {@link DevTimer} + */ + private DevTimer createTimer() { + return new DevTimer.Builder(mPeriod, mPeriod) + .setTag(mTag).build() + .setCallback(mInnerCallback) + .setHandler(mHandler); + } + + /** + * 是否总时长已结束 + * @return {@code true} yes, {@code false} no + */ + private boolean isDurationEnd() { + long result = mDuration.get(); + if (result <= 0L) { + getTimer().stop(); + if (mCallback != null) { + mCallback.callback(this, 0, true, result); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/EditTextSearchAssist.java b/lib/DevAssist/src/main/java/dev/assist/EditTextSearchAssist.java new file mode 100644 index 0000000000..c94ccdbffb --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/EditTextSearchAssist.java @@ -0,0 +1,174 @@ +package dev.assist; + +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +import dev.utils.app.assist.DelayAssist; + +/** + * detail: EditText 搜索辅助类 + * @author Ttt + *
+ *     用于 EditText 输入实时搜索
+ *     降低每触发一次 {@link TextWatcher#onTextChanged} 进行一次查询频率
+ *     

+ * 使用: + * 第一种 + * 在 {@link TextWatcher#onTextChanged} 中调用 {@link #post(CharSequence)} + * 第二种 + * 调用 {@link #setCallback(SearchCallback, EditText)} 传入需要监听的 EditText + * 或使用 {@link #bindEditText(EditText)} 绑定 EditText 输入事件 + *
+ */ +public class EditTextSearchAssist { + + // 搜索回调 + private SearchCallback mCallback; + // 延迟触发回调类 + private final DelayAssist mDelayAssist = new DelayAssist(object -> { + if (mCallback != null && object instanceof CharSequence) { + mCallback.callback((CharSequence) object); + } + }); + + // ========== + // = 构造函数 = + // ========== + + public EditTextSearchAssist() { + this(DelayAssist.DELAY_MILLIS, null); + } + + public EditTextSearchAssist(long delayMillis) { + this(delayMillis, null); + } + + public EditTextSearchAssist(SearchCallback callback) { + this(DelayAssist.DELAY_MILLIS, callback); + } + + public EditTextSearchAssist( + long delayMillis, + SearchCallback callback + ) { + mCallback = callback; + mDelayAssist.setDelayMillis(delayMillis); + } + + /** + * detail: 搜索回调接口 + * @author Ttt + */ + public interface SearchCallback { + + /** + * 搜索回调 + * @param content 搜索内容 + */ + void callback(CharSequence content); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 移除消息 + */ + public void remove() { + mDelayAssist.remove(); + } + + /** + * 发送消息 ( 功能由该方法实现 ) + * @param text 输入内容 + */ + public void post(final CharSequence text) { + mDelayAssist.post(text); + } + + // = + + /** + * 设置搜索延迟时间 + * @param delayMillis 延迟时间 + * @return {@link EditTextSearchAssist} + */ + public EditTextSearchAssist setDelayMillis(final long delayMillis) { + mDelayAssist.setDelayMillis(delayMillis); + return this; + } + + /** + * 设置搜索回调接口 + * @param callback {@link SearchCallback} + * @return {@link EditTextSearchAssist} + */ + public EditTextSearchAssist setCallback(final SearchCallback callback) { + mCallback = callback; + return this; + } + + /** + * 设置搜索回调接口并监听 EditText 输入 + * @param callback {@link SearchCallback} + * @param editText {@link EditText} + * @return {@link EditTextSearchAssist} + */ + public EditTextSearchAssist setCallback( + final SearchCallback callback, + final EditText editText + ) { + bindEditText(editText); + return setCallback(callback); + } + + /** + * 绑定 EditText 输入事件 + * @param editText {@link EditText} + * @return {@link EditTextSearchAssist} + */ + public EditTextSearchAssist bindEditText(final EditText editText) { + if (editText != null) { + initTextWatcher(); + editText.removeTextChangedListener(mTextWatcher); + editText.addTextChangedListener(mTextWatcher); + } + return this; + } + + // ========== + // = 内部逻辑 = + // ========== + + private TextWatcher mTextWatcher; + + private void initTextWatcher() { + if (mTextWatcher != null) return; + mTextWatcher = new TextWatcher() { + @Override + public void onTextChanged( + CharSequence text, + int start, + int before, + int count + ) { + post(text); + } + + @Override + public void beforeTextChanged( + CharSequence text, + int start, + int count, + int after + ) { + } + + @Override + public void afterTextChanged(Editable text) { + } + }; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/EditTextWatcherAssist.java b/lib/DevAssist/src/main/java/dev/assist/EditTextWatcherAssist.java new file mode 100644 index 0000000000..a4a060bc21 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/EditTextWatcherAssist.java @@ -0,0 +1,343 @@ +package dev.assist; + +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; + +/** + * detail: 解决 Adapter 多个 Item 存在 EditText 监听输入问题 + * @author Ttt + */ +public class EditTextWatcherAssist { + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 绑定事件 + * @param text 待设置文本 + * @param position 索引 + * @param editText EditText + * @param listener 输入监听回调事件 + */ + public void bindListener( + final CharSequence text, + final int position, + final EditText editText, + final InputListener listener + ) { + bindListener(text, position, editText, null, listener, null); + } + + /** + * 绑定事件 + * @param text 待设置文本 + * @param position 索引 + * @param editText EditText + * @param object Object + * @param listener 输入监听回调事件 + * @param otherListener 其他事件触发扩展抽象类 + */ + public void bindListener( + final CharSequence text, + final int position, + final EditText editText, + final T object, + final InputListener listener, + final OtherListener otherListener + ) { + if (editText != null) { + // 设置内容 + editText.setText(text); + // 清空焦点 + editText.clearFocus(); + // 设置获取焦点事件 + editText.setOnFocusChangeListener(new FocusListener( + position, editText, object, listener, otherListener + )); + } + } + + // ============= + // = 内部判断方法 = + // ============= + + // ================================= + // = 处理 Adapter Item ( EditText ) = + // ================================= + + // Text 改变事件 + private TextWatcher mTextWatcher; + // 获得焦点的 EditText + private EditText mFocusEdit; + // 获得焦点的索引 + private int mFocusPos; + + /** + * 焦点改变 + * @param editText EditText + * @param position 索引 + */ + private void focusChange( + final EditText editText, + final int position + ) { + if (mTextWatcher != null) { + if (mFocusEdit != null) { + mFocusEdit.removeTextChangedListener(mTextWatcher); + } + mTextWatcher = null; + } + // 保存获得焦点的 EditText + mFocusEdit = editText; + // 保存索引 + mFocusPos = position; + } + + // = + + /** + * detail: 焦点事件监听 + * @author Ttt + */ + private class FocusListener + implements View.OnFocusChangeListener { + + // 当前索引 + private final int position; + // EditText + private final EditText editText; + // Object + private final T object; + // 输入监听事件 + private final InputListener listener; + // 其他事件触发扩展抽象类 + private final OtherListener otherListener; + + /** + * 构造函数 + * @param position 索引 + * @param editText EditText + * @param object Object + * @param listener 输入监听回调事件 + * @param otherListener 其他事件触发扩展抽象类 + */ + public FocusListener( + int position, + EditText editText, + T object, + InputListener listener, + OtherListener otherListener + ) { + this.position = position; + this.editText = editText; + this.object = object; + this.listener = listener; + this.otherListener = otherListener; + } + + @Override + public void onFocusChange( + View v, + boolean hasFocus + ) { + if (mFocusPos == position) { + if (otherListener != null) { + otherListener.onFocusChange( + true, hasFocus, + editText, position, object + ); + } + } + + if (hasFocus) { + // 获得焦点设置 View 操作 + focusChange(editText, position); + // 判断是否为 null + if (mTextWatcher == null) { + mTextWatcher = new TextWatcher() { + @Override + public void onTextChanged( + CharSequence text, + int start, + int before, + int count + ) { + if (mFocusPos == position) { + if (otherListener != null) { + otherListener.onTextChanged( + text, start, before, count, + editText, position, object + ); + } + + if (listener != null) { // 触发回调 + listener.onTextChanged(text, editText, position, object); + } + } + } + + @Override + public void beforeTextChanged( + CharSequence text, + int start, + int count, + int after + ) { + if (mFocusPos == position) { + if (otherListener != null) { + otherListener.beforeTextChanged( + text, start, count, after, + editText, position, object + ); + } + } + } + + @Override + public void afterTextChanged(Editable text) { + if (mFocusPos == position) { + if (otherListener != null) { + otherListener.afterTextChanged( + text, editText, position, object + ); + } + } + } + }; + } + if (mFocusEdit != null) { // 增加监听 + mFocusEdit.addTextChangedListener(mTextWatcher); + } + } else { // 失去焦点, 清空操作 + focusChange(null, -1); + } + if (mFocusPos == position) { + if (otherListener != null) { + otherListener.onFocusChange( + false, hasFocus, + editText, position, object + ); + } + } + } + } + + // ========== + // = 事件相关 = + // ========== + + /** + * detail: 输入监听回调事件 + * @param 泛型 + * @author Ttt + */ + public interface InputListener { + + /** + * 文本改变监听 + * @param text 改变文本 + * @param editText EditText + * @param position 索引 + * @param object Object + */ + void onTextChanged( + CharSequence text, + EditText editText, + int position, + T object + ); + } + + /** + * detail: 其他事件触发扩展抽象类 + * @param 泛型 + * @author Ttt + */ + public static abstract class OtherListener { + + // ========================= + // = OnFocusChangeListener = + // ========================= + + /** + * 焦点触发方法 + * @param before {@code true} 进入方法先触发, {@code false} 逻辑处理后再次触发 + * @param hasFocus 是否获取焦点 + * @param editText EditText + * @param position 索引 + * @param object Object + */ + public void onFocusChange( + boolean before, + boolean hasFocus, + EditText editText, + int position, + T object + ) { + } + + // =============== + // = TextWatcher = + // =============== + + /** + * 在文本变化前调用 + * @param text 修改之前的文字 + * @param start 字符串中即将发生修改的位置 + * @param count 字符串中即将被修改的文字的长度, 如果是新增的话则为 0 + * @param after 被修改的文字修改之后的长度, 如果是删除的话则为 0 + * @param editText EditText + * @param position 索引 + * @param object Object + */ + public void beforeTextChanged( + CharSequence text, + int start, + int count, + int after, + EditText editText, + int position, + T object + ) { + } + + /** + * 在文本变化后调用 + * @param text 改变后的字符串 + * @param start 有变动的字符串的位置 + * @param before 被改变的字符串长度, 如果是新增则为 0 + * @param count 添加的字符串长度, 如果是删除则为 0 + * @param editText EditText + * @param position 索引 + * @param object Object + */ + public void onTextChanged( + CharSequence text, + int start, + int before, + int count, + EditText editText, + int position, + T object + ) { + } + + /** + * 在文本变化后调用 + * @param text 修改后的文字 + * @param editText EditText + * @param position 索引 + * @param object Object + */ + public void afterTextChanged( + Editable text, + EditText editText, + int position, + T object + ) { + } + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/NumberControlAssist.java b/lib/DevAssist/src/main/java/dev/assist/NumberControlAssist.java new file mode 100644 index 0000000000..61dea0f284 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/NumberControlAssist.java @@ -0,0 +1,327 @@ +package dev.assist; + +import dev.base.DevNumber; +import dev.base.number.INumberListener; +import dev.base.number.INumberOperate; + +/** + * detail: 数量控制辅助类 + * @author Ttt + *
+ *     主要用于数量加减限制, 如: 购物车数量加减
+ * 
+ */ +public class NumberControlAssist + implements INumberOperate { + + // Number Object + private final DevNumber mNumber; + + public NumberControlAssist() { + mNumber = new DevNumber<>(); + } + + public NumberControlAssist(final int minNumber) { + mNumber = new DevNumber<>(minNumber); + } + + public NumberControlAssist( + final int minNumber, + final int maxNumber + ) { + mNumber = new DevNumber<>(minNumber, maxNumber); + } + + /** + * 获取 DevNumber Object + * @return {@link DevNumber} + */ + public DevNumber getNumber() { + return mNumber; + } + + // ================== + // = INumberOperate = + // ================== + + /** + * 判断当前数量, 是否等于最小值 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMinNumber() { + return mNumber.isMinNumber(); + } + + /** + * 判断数量, 是否等于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMinNumber(final int number) { + return mNumber.isMinNumber(number); + } + + /** + * 判断数量, 是否小于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLessThanMinNumber(final int number) { + return mNumber.isLessThanMinNumber(number); + } + + /** + * 判断数量, 是否大于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isGreaterThanMinNumber(final int number) { + return mNumber.isGreaterThanMinNumber(number); + } + + // = + + /** + * 判断当前数量, 是否等于最大值 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMaxNumber() { + return mNumber.isMaxNumber(); + } + + /** + * 判断数量, 是否等于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMaxNumber(final int number) { + return mNumber.isMaxNumber(number); + } + + /** + * 判断数量, 是否小于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLessThanMaxNumber(final int number) { + return mNumber.isLessThanMaxNumber(number); + } + + /** + * 判断数量, 是否大于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isGreaterThanMaxNumber(final int number) { + return mNumber.isGreaterThanMaxNumber(number); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取最小值 + * @return 最小值 + */ + @Override + public int getMinNumber() { + return mNumber.getMinNumber(); + } + + /** + * 设置最小值 + * @param minNumber 最小值 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setMinNumber(final int minNumber) { + mNumber.setMinNumber(minNumber); + return this; + } + + // = + + /** + * 获取最大值 + * @return 最大值 + */ + @Override + public int getMaxNumber() { + return mNumber.getMaxNumber(); + } + + /** + * 设置最大值 + * @param maxNumber 最大值 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setMaxNumber(final int maxNumber) { + mNumber.setMaxNumber(maxNumber); + return this; + } + + /** + * 设置最小值、最大值 + * @param minNumber 最小值 + * @param maxNumber 最大值 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setMinMaxNumber( + final int minNumber, + final int maxNumber + ) { + mNumber.setMinMaxNumber(minNumber, maxNumber); + return this; + } + + // = + + /** + * 获取当前数量 + * @return 当前数量 + */ + @Override + public int getCurrentNumber() { + return mNumber.getCurrentNumber(); + } + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setCurrentNumber(final int currentNumber) { + mNumber.setCurrentNumber(currentNumber); + return this; + } + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @param isTriggerListener 是否触发事件 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setCurrentNumber( + final int currentNumber, + final boolean isTriggerListener + ) { + mNumber.setCurrentNumber(currentNumber, isTriggerListener); + return this; + } + + // = + + /** + * 获取重置数量 + * @return 重置数量 + */ + @Override + public int getResetNumber() { + return mNumber.getResetNumber(); + } + + /** + * 设置重置数量 + * @param resetNumber 重置数量 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setResetNumber(final int resetNumber) { + mNumber.setResetNumber(resetNumber); + return this; + } + + // = + + /** + * 获取是否允许设置为负数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isAllowNegative() { + return mNumber.isAllowNegative(); + } + + /** + * 设置是否允许设置为负数 + * @param allowNegative {@code true} yes, {@code false} no + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setAllowNegative(final boolean allowNegative) { + mNumber.setAllowNegative(allowNegative); + return this; + } + + // ============= + // = 数量变化方法 = + // ============= + + /** + * 数量改变通知 + * @param number 变化基数数量 + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist numberChange(final int number) { + mNumber.numberChange(number); + return this; + } + + /** + * 添加数量 ( 默认累加 1 ) + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist addNumber() { + mNumber.addNumber(); + return this; + } + + /** + * 减少数量 ( 默认累减 1 ) + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist subtractionNumber() { + mNumber.subtractionNumber(); + return this; + } + + // ======= + // = 事件 = + // ======= + + /** + * 获取数量监听事件接口 + * @return {@link INumberListener} + */ + @Override + public INumberListener getNumberListener() { + return mNumber.getNumberListener(); + } + + /** + * 设置数量监听事件接口 + * @param listener {@link INumberListener} + * @return {@link NumberControlAssist} + */ + @Override + public NumberControlAssist setNumberListener(final INumberListener listener) { + mNumber.setNumberListener(listener); + return this; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/assist/PageAssist.java b/lib/DevAssist/src/main/java/dev/assist/PageAssist.java new file mode 100644 index 0000000000..fdbf189e07 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/assist/PageAssist.java @@ -0,0 +1,239 @@ +package dev.assist; + +import dev.base.DevPage; +import dev.base.state.RequestState; + +/** + * detail: Page 辅助类 + * @author Ttt + */ +public class PageAssist + extends RequestState { + + // 全局页数配置 + public static int DEF_PAGE = DevPage.DEF_PAGE; + // 全局每页请求条数配置 + public static int DEF_PAGE_SIZE = DevPage.DEF_PAGE_SIZE; + + // Page Object + private final DevPage mPage; + + public PageAssist() { + this(DEF_PAGE, DEF_PAGE_SIZE); + } + + public PageAssist(final DevPage.PageConfig pageConfig) { + this(pageConfig.page, pageConfig.pageSize); + } + + public PageAssist( + final int page, + final int pageSize + ) { + super(); + mPage = new DevPage<>(page, pageSize); + } + + // = + + /** + * 初始化全局分页配置 + * @param page 页数 + * @param pageSize 每页请求条数 + */ + public static void initPageConfig( + final int page, + final int pageSize + ) { + PageAssist.DEF_PAGE = page; + PageAssist.DEF_PAGE_SIZE = pageSize; + } + + /** + * 重置操作 + * @return {@link PageAssist} + */ + public PageAssist reset() { + mPage.reset(); + setRequestNormal(); + return this; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取当前页数 + * @return 当前页数 + */ + public int getPage() { + return mPage.getPage(); + } + + /** + * 设置当前页数 + * @param page 当前页数 + * @return {@link PageAssist} + */ + public PageAssist setPage(final int page) { + mPage.setPage(page); + return this; + } + + /** + * 判断当前页数是否一致 + * @param page 待校验当前页数 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsPage(final int page) { + return mPage.equalsPage(page); + } + + // = + + /** + * 获取页数配置信息 + * @return {@link DevPage.PageConfig} + */ + public DevPage.PageConfig getConfig() { + return mPage.getConfig(); + } + + /** + * 获取配置初始页页数 + * @return 初始页页数 + */ + public int getConfigPage() { + return mPage.getConfigPage(); + } + + /** + * 获取配置每页请求条数 + * @return 每页请求条数 + */ + public int getConfigPageSize() { + return mPage.getConfigPageSize(); + } + + // = + + /** + * 获取每页请求条数 + * @return 每页请求条数 + */ + public int getPageSize() { + return mPage.getPageSize(); + } + + /** + * 判断每页请求条数是否一致 + * @param pageSize 待校验每页请求条数 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsPageSize(final int pageSize) { + return mPage.equalsPageSize(pageSize); + } + + // = + + /** + * 判断是否最后一页 + * @return {@code true} yes, {@code false} no + */ + public boolean isLastPage() { + return mPage.isLastPage(); + } + + /** + * 设置是否最后一页 + * @param lastPage 是否最后一页 + * @return {@link PageAssist} + */ + public PageAssist setLastPage(final boolean lastPage) { + mPage.setLastPage(lastPage); + return this; + } + + /** + * 计算是否最后一页 ( 并同步更新 ) + * @param size 数据条数 + * @return {@link PageAssist} + */ + public PageAssist calculateLastPage(final int size) { + mPage.calculateLastPage(size); + return this; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断是否第一页 + * @return {@code true} yes, {@code false} no + */ + public boolean isFirstPage() { + return mPage.isFirstPage(); + } + + /** + * 判断是否允许请求下一页 + * @return {@code true} yes, {@code false} no + */ + public boolean canNextPage() { + return mPage.canNextPage(); + } + + /** + * 获取下一页页数 + * @return 下一页页数 + */ + public int getNextPage() { + return mPage.getNextPage(); + } + + /** + * 累加当前页数 ( 下一页 ) + * @return {@link PageAssist} + */ + public PageAssist nextPage() { + mPage.nextPage(); + return this; + } + + /** + * 判断是否小于每页请求条数 + * @param size 数据条数 + * @return {@code true} yes, {@code false} no + */ + public boolean isLessThanPageSize(final int size) { + return mPage.isLessThanPageSize(size); + } + + // = + + /** + * 请求响应处理 + * @param refresh 是否刷新操作 + * @return {@link PageAssist} + */ + public PageAssist response(final boolean refresh) { + mPage.response(refresh); + return this; + } + + /** + * 请求响应处理 + * @param refresh 是否刷新操作 + * @param lastPage 是否最后一页 + * @return {@link PageAssist} + */ + public PageAssist response( + final boolean refresh, + final boolean lastPage + ) { + mPage.response(refresh, lastPage); + return this; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevDataSource.java b/lib/DevAssist/src/main/java/dev/base/DevDataSource.java new file mode 100644 index 0000000000..22b0554268 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevDataSource.java @@ -0,0 +1,583 @@ +package dev.base; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import dev.base.data.DataManager; +import dev.utils.LogPrintUtils; +import dev.utils.common.ObjectUtils; + +/** + * detail: 数据源操作实体类 + * @author Ttt + */ +public class DevDataSource + extends DevObject + implements DataManager { + + // 日志 TAG + private static final String TAG = DevDataSource.class.getSimpleName(); + + // List Data + private final List mList = new ArrayList<>(); + + public DevDataSource() { + } + + public DevDataSource(final T object) { + super(object); + } + + public DevDataSource( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 获取相关 = + // ========== + + /** + * 获取 List Data + * @return {@link List} + */ + @Override + public List getDataList() { + return mList; + } + + /** + * 获取 ArrayList Data + * @return {@link ArrayList} + */ + @Override + public ArrayList getDataArrayList() { + return new ArrayList<>(mList); + } + + /** + * 获取 List Size + * @return List.size() + */ + @Override + public int getDataSize() { + return mList.size(); + } + + /** + * 获取 List Position Data + * @param position 索引 + * @return {@link T} + */ + @Override + public T getDataItem(int position) { + if (position < 0) return null; + try { + return mList.get(position); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDataItem"); + } + return null; + } + + /** + * 获取 Value Position + * @param value {@link T} + * @return position + */ + @Override + public int getDataItemPosition(T value) { + try { + return mList.indexOf(value); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getDataItemPosition"); + } + return -1; + } + + /** + * 获取 First Data + * @return {@link T} + */ + @Override + public T getFirstData() { + return getDataItem(0); + } + + /** + * 获取 Last Data + * @return {@link T} + */ + @Override + public T getLastData() { + return getDataItem(getLastPosition()); + } + + /** + * 获取 Last Position + * @return Last Position + */ + @Override + public int getLastPosition() { + int size = getDataSize(); + return (size == 0) ? 0 : size - 1; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断 List Size 是否为 0 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isDataEmpty() { + return getDataSize() == 0; + } + + /** + * 判断 List Size 是否大于 0 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isDataNotEmpty() { + return !isDataEmpty(); + } + + /** + * 判断是否 First Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isFirstPosition(int position) { + return position == 0; + } + + /** + * 判断是否 Last Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPosition(int position) { + return isLastPosition(position, getDataSize()); + } + + /** + * 判断是否 Last Position + * @param position 索引 + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPosition( + int position, + int size + ) { + return position >= 0 && size >= 1 && size - position == 1; + } + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value + ) { + return isLastPositionAndGreaterThanOrEqual(position, value, getDataSize()); + } + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value, + int size + ) { + return size >= value && isLastPosition(position, size); + } + + /** + * 判断 First Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsFirstData(T value) { + return value != null && ObjectUtils.equals(getFirstData(), value); + } + + /** + * 判断 Last Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsLastData(T value) { + return value != null && ObjectUtils.equals(getLastData(), value); + } + + /** + * 判断 Position Value 是否一致 + * @param position 索引 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equalsPositionData( + int position, + T value + ) { + return value != null && ObjectUtils.equals(getDataItem(position), value); + } + + // ===== + // = 增 = + // ===== + + /** + * 添加数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addData(T value) { + try { + mList.add(value); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addData"); + } + return false; + } + + /** + * 添加数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDataAt( + int position, + T value + ) { + if (position < 0) return false; + try { + mList.add(position, value); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addDataAt"); + } + return false; + } + + /** + * 添加数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatas(Collection collection) { + if (collection == null) return false; + try { + mList.addAll(collection); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addDatas"); + } + return false; + } + + /** + * 添加数据集 + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasAt( + int position, + Collection collection + ) { + if (position < 0) return false; + if (collection == null) return false; + try { + mList.addAll(position, collection); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addDatasAt"); + } + return false; + } + + /** + * 添加数据集 ( 进行校验 ) + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasChecked(Collection collection) { + if (collection == null) return false; + try { + List lists = new ArrayList<>(); + for (T value : collection) { + if (value != null) { + lists.add(value); + } + } + mList.addAll(lists); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addDatasChecked"); + } + return false; + } + + /** + * 添加数据集 ( 进行校验 ) + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addDatasCheckedAt( + int position, + Collection collection + ) { + if (position < 0) return false; + if (collection == null) return false; + try { + List lists = new ArrayList<>(); + for (T value : collection) { + if (value != null) { + lists.add(value); + } + } + mList.addAll(position, lists); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "addDatasCheckedAt"); + } + return false; + } + + /** + * 添加数据集 ( 判断是追加还是重置 ) + * @param append {@code true} {@link #addDatas} {@code false} {@link #setDataList} + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean addLists( + boolean append, + Collection collection + ) { + if (append) { + return addDatas(collection); + } else { + return setDataList(collection); + } + } + + // ===== + // = 删 = + // ===== + + /** + * 移除数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean removeData(T value) { + try { + return mList.remove(value); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeData"); + } + return false; + } + + /** + * 移除数据 + * @param position 索引 + * @return remove position value + */ + @Override + public T removeDataAt(int position) { + if (position < 0) return null; + try { + return mList.remove(position); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeDataAt"); + } + return null; + } + + /** + * 移除数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean removeDatas(Collection collection) { + if (collection == null) return false; + try { + mList.removeAll(collection); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "removeDatas"); + } + return false; + } + + // ===== + // = 改 = + // ===== + + /** + * 替换数据 + * @param oldValue 旧的 Value + * @param newValue 新的 Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean replaceData( + T oldValue, + T newValue + ) { + if (contains(oldValue)) { + return replaceDataAt(getDataItemPosition(oldValue), newValue); + } + return false; + } + + /** + * 替换数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean replaceDataAt( + int position, + T value + ) { + if (position < 0) return false; + try { + mList.set(position, value); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "replaceDataAt"); + } + return false; + } + + // = + + /** + * 数据中两个索引 Data 互换位置 + * @param fromPosition 待换位置索引 + * @param toPosition 替换后位置索引 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean swipePosition( + int fromPosition, + int toPosition + ) { + if (fromPosition != toPosition && fromPosition >= 0 && toPosition >= 0) { + try { + Collections.swap(mList, fromPosition, toPosition); + return true; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "swipePosition"); + } + } + return false; + } + + /** + * 是否存在 Data + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean contains(T value) { + if (value == null) return false; + try { + return mList.contains(value); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "contains"); + } + return false; + } + + /** + * 清空全部数据 + */ + @Override + public void clearDataList() { + mList.clear(); + } + + /** + * 清空全部数据 + * @param notify 是否进行通知 + */ + @Override + public void clearDataList(boolean notify) { + mList.clear(); + } + + /** + * 设置 List Data + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean setDataList(Collection collection) { + mList.clear(); + if (collection != null) { + mList.addAll(collection); + return true; + } + return false; + } + + /** + * 设置 List Data + * @param collection {@link Collection} + * @param notify 是否进行通知 + * @return {@code true} success, {@code false} fail + */ + @Override + public boolean setDataList( + Collection collection, + boolean notify + ) { + mList.clear(); + if (collection != null) { + mList.addAll(collection); + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevEntry.java b/lib/DevAssist/src/main/java/dev/base/DevEntry.java new file mode 100644 index 0000000000..3f6d591231 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevEntry.java @@ -0,0 +1,92 @@ +package dev.base; + +import dev.utils.common.ObjectUtils; + +/** + * detail: Key-Value Entry + * @author Ttt + */ +public class DevEntry { + + // key + public K mKey; + // value + public V mValue; + + public DevEntry() { + } + + public DevEntry(final K key) { + this.mKey = key; + } + + public DevEntry( + final K key, + final V value + ) { + this.mKey = key; + this.mValue = value; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Key + * @return Key + */ + public K getKey() { + return mKey; + } + + /** + * 设置 Key + * @param key Key + * @return {@link DevEntry} + */ + public DevEntry setKey(final K key) { + this.mKey = key; + return this; + } + + /** + * 获取 Value + * @return Value + */ + public V getValue() { + return mValue; + } + + /** + * 设置 Value + * @param value Value + * @return {@link DevEntry} + */ + public DevEntry setValue(final V value) { + this.mValue = value; + return this; + } + + // ========== + // = 判断方法 = + // ========== + + /** + * 判断 Key 是否一致 + * @param key 待校验 Key + * @return {@code true} yes, {@code false} no + */ + public boolean equalsKey(final K key) { + return key != null && ObjectUtils.equals(this.mKey, key); + } + + /** + * 判断 Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + public boolean equalsValue(final V value) { + return value != null && ObjectUtils.equals(this.mValue, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevHistory.java b/lib/DevAssist/src/main/java/dev/base/DevHistory.java new file mode 100644 index 0000000000..a001f73391 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevHistory.java @@ -0,0 +1,799 @@ +package dev.base; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +import dev.utils.DevFinal; +import dev.utils.LogPrintUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 历史数据记录功类 + * @author Ttt + *
+ *     不判 null ( null 默认可加入 )
+ *     可通过 {@link Listener#accept(boolean, Object)} 进行拦截
+ *     

+ * 需注意的是, 首次设置 Current 会添加 null 进入回退栈 + * 可按上方进行拦截, 判断数量是否为 0 且为 null + *
+ */ +public class DevHistory { + + // 日志 TAG + private final String TAG = DevHistory.class.getSimpleName(); + // 回退栈 + private final Stack mBack = new Stack<>(); + // 前进栈 + private final Stack mForward = new Stack<>(); + // 当前数据 + private T mCurrent; + // 方法事件触发接口 + private Listener mListener; + + // ========== + // = 构造函数 = + // ========== + + public DevHistory() { + } + + public DevHistory(final Listener listener) { + this.mListener = listener; + } + + // ========== + // = 接口回调 = + // ========== + + /** + * detail: 方法事件触发接口 + * @author Ttt + */ + public interface Listener { + + /** + * 是否允许添加 + *
+         *     调用 {@link #addBack(Object)}、{@link #addForward(Object)} 触发
+         * 
+ * @param back {@code true} 回退栈操作, {@code false} 前进栈操作 + * @param value 待添加数据 + * @return {@code true} 允许, {@code false} 不允许 + */ + boolean accept( + boolean back, + T value + ); + + /** + * 当前数据改变通知 + *
+         *     调用 {@link #setCurrent(Object)} 触发
+         * 
+ * @param beforeCurrent 操作前的当前数据 + * @param afterCurrent 操作后的当前数据 + */ + void changeCurrent( + T beforeCurrent, + T afterCurrent + ); + + /** + * 清空数据回调 + *
+         *     调用 {@link #clearBack()}、{@link #clearForward()} 触发
+         * 
+ * @param back {@code true} 清空回退栈数据, {@code false} 清空前进栈数据 + */ + void clear(boolean back); + + /** + * 添加数据到栈内 + *
+         *     调用 {@link #addBack(Object)}、{@link #addForward(Object)} 触发
+         * 
+ * @param back {@code true} 回退栈, {@code false} 前进栈 + * @param value 已添加数据 + */ + void add( + boolean back, + T value + ); + + /** + * 是否允许 Current 添加到列表中 + *
+         *     调用 {@link #goBack(int)}、{@link #getForward(int)} 触发
+         * 
+ * @param back {@code true} 回退操作 ( 添加到前进栈 ), {@code false} 前进操作 ( 添加到回退栈 ) + * @param beforeCurrent 操作前的当前数据 + * @return {@code true} yes, {@code false} no + */ + boolean acceptCurrentToList( + boolean back, + T beforeCurrent + ); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取当前数据 + * @return 当前数据 + */ + public T getCurrent() { + return mCurrent; + } + + /** + * 设置当前数据 + * @param current 当前数据 + * @return {@link DevHistory} + */ + public DevHistory setCurrent(final T current) { + return setCurrent(current, Action.SET_CURRENT); + } + + /** + * 获取方法事件触发接口 + * @return 方法事件触发接口 + */ + public Listener getListener() { + return mListener; + } + + /** + * 设置方法事件触发接口 + * @param listener 方法事件触发接口 + * @return {@link DevHistory} + */ + public DevHistory setListener(final Listener listener) { + this.mListener = listener; + return this; + } + + /** + * 清空当前数据 + * @return {@link DevHistory} + */ + public DevHistory cleanCurrent() { + return setCurrent(null, Action.SET_NULL); + } + + /** + * 重置操作 + * @return {@link DevHistory} + */ + public DevHistory reset() { + return reset(true); + } + + /** + * 重置操作 + * @param setNull 是否设置 Current 为 null + * @return {@link DevHistory} + */ + public DevHistory reset(final boolean setNull) { + clearBack().clearForward(); + if (setNull) cleanCurrent(); + return this; + } + + // ============ + // = 回退栈相关 = + // ============ + + /** + * 清空回退栈数据 + * @return {@link DevHistory} + */ + public DevHistory clearBack() { + return clear(Type.BACK); + } + + /** + * 获取回退栈数据条数 + * @return 回退栈数据条数 + */ + public int sizeBack() { + return size(Type.BACK); + } + + /** + * 是否不存在回退栈数据 + * @return {@code true} yes, {@code false} no + */ + public boolean isEmptyBack() { + return isEmpty(Type.BACK); + } + + /** + * 是否能够执行回退操作 + * @return {@code true} yes, {@code false} no + */ + public boolean canGoBack() { + return canGoBack(1); + } + + /** + * 是否能够执行回退操作 + * @param index 索引 + * @return {@code true} yes, {@code false} no + */ + public boolean canGoBack(final int index) { + return canGo(Type.BACK, index); + } + + /** + * 添加到回退栈 + * @param value 待添加数据 + * @return {@code true} success, {@code false} fail + */ + public boolean addBack(final T value) { + return add(Type.BACK, value); + } + + /** + * 获取上一条回退栈数据 + * @return 上一条回退栈数据 + */ + public T getBack() { + return getBack(1); + } + + /** + * 获取指定索引回退栈数据 + * @param index 索引 + * @return 指定索引回退栈数据 + */ + public T getBack(final int index) { + return get(Type.BACK, index); + } + + /** + * 前往上一条回退栈数据 + * @return 上一条回退栈数据 + */ + public T goBack() { + return goBack(1); + } + + /** + * 前往指定索引回退栈数据 + * @param index 索引 + * @return 指定索引回退栈数据 + */ + public T goBack(final int index) { + return gotoBack(index); + } + + /** + * 进行回退栈数据顺序拼接字符串 + * @return 回退栈数据顺序拼接字符串 + */ + public String toStringBack() { + return toString(Type.BACK, false, " > ", false); + } + + /** + * 进行回退栈数据顺序拼接字符串 + * @param appendCurrent 是否追加当前数据 + * @param symbol 拼接符号 + * @param reverse 是否进行反转 + * @return 回退栈数据顺序拼接字符串 + */ + public String toStringBack( + final boolean appendCurrent, + final String symbol, + final boolean reverse + ) { + return toString(Type.BACK, appendCurrent, symbol, reverse); + } + + // ============ + // = 前进栈相关 = + // ============ + + /** + * 清空前进栈数据 + * @return {@link DevHistory} + */ + public DevHistory clearForward() { + return clear(Type.FORWARD); + } + + /** + * 获取前进栈数据条数 + * @return 前进栈数据条数 + */ + public int sizeForward() { + return size(Type.FORWARD); + } + + /** + * 是否不存在前进栈数据 + * @return {@code true} yes, {@code false} no + */ + public boolean isEmptyForward() { + return isEmpty(Type.FORWARD); + } + + /** + * 是否能够执行前进操作 + * @return {@code true} yes, {@code false} no + */ + public boolean canGoForward() { + return canGoForward(1); + } + + /** + * 是否能够执行前进操作 + * @param index 索引 + * @return {@code true} yes, {@code false} no + */ + public boolean canGoForward(final int index) { + return canGo(Type.FORWARD, index); + } + + /** + * 添加到前进栈 + * @param value 待添加数据 + * @return {@code true} success, {@code false} fail + */ + public boolean addForward(final T value) { + return add(Type.FORWARD, value); + } + + /** + * 获取下一条前进栈数据 + * @return 下一条前进栈数据 + */ + public T getForward() { + return getForward(1); + } + + /** + * 获取指定索引前进栈数据 + * @param index 索引 + * @return 指定索引前进栈数据 + */ + public T getForward(final int index) { + return get(Type.FORWARD, index); + } + + /** + * 前往下一条前进栈数据 + * @return 下一条前进栈数据 + */ + public T goForward() { + return goForward(1); + } + + /** + * 前往指定索引前进栈数据 + * @param index 索引 + * @return 指定索引前进栈数据 + */ + public T goForward(final int index) { + return gotoForward(index); + } + + /** + * 进行前进栈数据顺序拼接字符串 + * @return 前进栈数据顺序拼接字符串 + */ + public String toStringForward() { + return toString(Type.FORWARD, false, " > ", true); + } + + /** + * 进行前进栈数据顺序拼接字符串 + * @param appendCurrent 是否追加当前数据 + * @param symbol 拼接符号 + * @param reverse 是否进行反转 + * @return 前进栈数据顺序拼接字符串 + */ + public String toStringForward( + final boolean appendCurrent, + final String symbol, + final boolean reverse + ) { + return toString(Type.FORWARD, appendCurrent, symbol, reverse); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 计算 ( 回退、前进 ) 操作真实索引 + *
+     *     需传入正整数, 如前往 ( 回退、前进 ) 上、下一条数据则传入 1
+     *     会自动取绝对值, 并获取长度 - index 位置数据
+     * 
+ * @param size 数据条数 + * @param index 索引 + * @return 操作真实索引 + */ + private int calculateRealIndex( + final int size, + final int index + ) { + if (size == 0) return -1; + int realIndex = size - Math.abs(index); + if (realIndex < 0) return -1; + if (realIndex == size) return -1; + return realIndex; + } + + /** + * 设置当前数据 + * @param current Current Data + * @param operate 操作类型 + * @return {@link DevHistory} + */ + private DevHistory setCurrent( + final T current, + final Action operate + ) { + switch (operate) { + case SET_NULL: // 设置为 null 操作 + break; + case SET_CURRENT: // 正常设置当前数据操作 + // 清空前进栈数据 + clearForward(); + // 追加当前数据到回退栈数据中 + addBack(mCurrent); + break; + case GO_BACK: // 回退操作 + // 清空前进栈数据 + clearForward(); + break; + case GO_FORWARD: // 前进操作 + break; + } + T beforeCurrent = this.mCurrent; + this.mCurrent = current; + // 当前数据改变通知 + if (mListener != null) { + mListener.changeCurrent(beforeCurrent, current); + } + return this; + } + + /** + * detail: 操作行为 + * @author Ttt + */ + private enum Action { + + // 设置为 null 操作 + SET_NULL, + + // 正常设置当前数据操作 + SET_CURRENT, + + // 回退操作 + GO_BACK, + + // 前进操作 + GO_FORWARD, + } + + /** + * detail: 操作类型 + * @author Ttt + */ + private enum Type { + + // 回退数据相关 + BACK(true), + + // 前进数据相关 + FORWARD(false); + + protected boolean type; + + Type(boolean type) { + this.type = type; + } + } + + // = + + /** + * 清空栈内数据 + * @param operate 操作类型 + * @return {@link DevHistory} + */ + private DevHistory clear(final Type operate) { + switch (operate) { + case BACK: + mBack.clear(); + break; + case FORWARD: + mForward.clear(); + break; + } + if (mListener != null) { + mListener.clear(operate.type); + } + return this; + } + + /** + * 获取栈内数据条数 + * @param operate 操作类型 + * @return 栈内数据条数 + */ + private int size(final Type operate) { + switch (operate) { + case BACK: + return mBack.size(); + case FORWARD: + return mForward.size(); + } + return 0; + } + + /** + * 是否不存在栈内数据 + * @param operate 操作类型 + * @return {@code true} yes, {@code false} no + */ + private boolean isEmpty(final Type operate) { + return size(operate) == 0; + } + + /** + * 是否能够执行指定操作 + * @param operate 操作类型 + * @param index 索引 + * @return {@code true} yes, {@code false} no + */ + private boolean canGo( + final Type operate, + final int index + ) { + return calculateRealIndex(size(operate), index) >= 0; + } + + /** + * 添加数据到栈内 + * @param operate 操作类型 + * @param value 待添加数据 + * @return {@code true} success, {@code false} fail + */ + private boolean add( + final Type operate, + final T value + ) { + if (mListener != null) { + if (!mListener.accept(operate.type, value)) { + return false; + } + } + switch (operate) { + case BACK: + mBack.add(value); + break; + case FORWARD: + mForward.add(value); + break; + } + if (mListener != null) { + mListener.add(operate.type, value); + } + return true; + } + + /** + * 获取指定索引栈内数据 + *
+     *     索引传入值可以看该方法注释
+     *     {@link #calculateRealIndex(int, int)}
+     * 
+ * @param operate 操作类型 + * @param index 索引 + * @return 指定索引栈内数据 + */ + private T get( + final Type operate, + final int index + ) { + int realIndex; + switch (operate) { + case BACK: + realIndex = calculateRealIndex(sizeBack(), index); + if (realIndex < 0) return null; + try { + return mBack.get(realIndex); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get - Back"); + } + case FORWARD: + realIndex = calculateRealIndex(sizeForward(), index); + if (realIndex < 0) return null; + try { + return mForward.get(realIndex); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "get - Forward"); + } + } + return null; + } + + /** + * 前往指定索引回退栈数据 + *
+     *     索引传入值可以看该方法注释
+     *     {@link #calculateRealIndex(int, int)}
+     *     

+ * 例如目前 A、B、C、D、E、F、G ( Current ) + * mBack 存储了 A、B、C、D、E、F 六条数据 + * mForward 存储了 0 条数据 ( 倒序 ) + * 该方法传入 3 ( 6 - 3 = 3 => D ) 前往 D 的位置 + * 这个时候会设置 D 为 Current + * mBack 存储变更为 A、B、C + * mForward 存储变更为 G ( beforeCurrent )、F、E + *

+ * 每次操作都会清空前进栈 ( mForward ) 数据, 再进行添加新数据 + * mForward 为什么进行倒序存储主要是为了复用 {@link #calculateRealIndex(int, int)} 方法, 统一逻辑 + *

+ * 注: 需要 {@link #canGoBack(int)} 能够执行回退操作 + * 否则直接返回 null 且不会对任何数据、状态进行变更 + *
+ * @param index 索引 + * @return 指定索引回退栈数据 + */ + private T gotoBack(final int index) { + int size = sizeBack(); + int realIndex = calculateRealIndex(size, index); + if (realIndex < 0) return null; + T beforeCurrent = mCurrent; + // 获取准备回退数据并设置为当前数据 + T backValue = getBack(index); + setCurrent(backValue, Action.GO_BACK); + // 待添加前进栈数据 + List lists = new ArrayList<>(); + // 是否需要把 beforeCurrent 添加到前进栈中 + if (mListener != null) { + if (mListener.acceptCurrentToList(true, beforeCurrent)) { + lists.add(beforeCurrent); + } + } else { + lists.add(beforeCurrent); + } + try { + // 添加回退索引位置到结尾之间的数据并进行降序排序 + List temps = mBack.subList(realIndex + 1, size); + Collections.reverse(temps); + lists.addAll(temps); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "gotoBack - subList"); + } + for (int i = realIndex; i < size; i++) { + try { + // 一直进行移除同一索引 ( 数据长度递减 ) + mBack.remove(realIndex); + } catch (Exception ignored) { + } + } + mForward.addAll(lists); + return backValue; + } + + /** + * 前往指定索引前进栈数据 + *
+     *     索引传入值可以看该方法注释
+     *     {@link #calculateRealIndex(int, int)}
+     *     

+ * 例如目前 A、B、C、D ( Current )、E、F、G + * mBack 存储了 A、B、C 三条数据 + * mForward 存储了 G、F、E 三条数据 ( 倒序 ) + * 该方法传入 2 ( 3 - 2 = 1 => F ) 前往 F 的位置 + * 这个时候会设置 F 为 Current + * mBack 存储变更为 A、B、C、D、E + * mForward 存储变更为 G + *

+ * 每次操作都会清空前进栈 ( mForward ) 数据, 再进行添加新数据 + * mForward 为什么进行倒序存储主要是为了复用 {@link #calculateRealIndex(int, int)} 方法, 统一逻辑 + *

+ * 注: 需要 {@link #canGoForward(int)} 能够执行前进操作 + * 否则直接返回 null 且不会对任何数据、状态进行变更 + *
+ * @param index 索引 + * @return 指定索引前进栈数据 + */ + private T gotoForward(final int index) { + int size = sizeForward(); + int realIndex = calculateRealIndex(size, index); + if (realIndex < 0) return null; + T beforeCurrent = mCurrent; + // 获取准备前进数据并设置为当前数据 + T forwardValue = getForward(index); + setCurrent(forwardValue, Action.GO_FORWARD); + // 待添加回退栈数据 + List lists = new ArrayList<>(); + try { + // 添加前进索引位置到结尾之间的数据并进行降序排序 + List temps = mForward.subList(realIndex + 1, size); + Collections.reverse(temps); + lists.addAll(temps); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "gotoForward - subList"); + } + // 是否需要把 beforeCurrent 添加到回退栈中 + if (mListener != null) { + if (mListener.acceptCurrentToList(false, beforeCurrent)) { + lists.add(beforeCurrent); + } + } else { + lists.add(beforeCurrent); + } + for (int i = realIndex; i < size; i++) { + try { + // 一直进行移除同一索引 ( 数据长度递减 ) + mForward.remove(realIndex); + } catch (Exception ignored) { + } + } + Collections.reverse(lists); + mBack.addAll(lists); + return forwardValue; + } + + /** + * 进行栈数据顺序拼接字符串 + * @param operate 操作类型 + * @param appendCurrent 是否追加当前数据 + * @param symbol 拼接符号 + * @param reverse 是否进行反转 + * @return 栈数据顺序拼接字符串 + */ + private String toString( + final Type operate, + final boolean appendCurrent, + final String symbol, + final boolean reverse + ) { + List lists = new ArrayList<>(); + switch (operate) { + case BACK: + lists.addAll(mBack); + if (appendCurrent) { + lists.add(mCurrent); + } + break; + case FORWARD: + if (appendCurrent) { + lists.add(mCurrent); + } + lists.addAll(mForward); + break; + } + if (reverse) Collections.reverse(lists); + // 获取拼接符号 + String spliceSymbol = StringUtils.checkValue( + DevFinal.SYMBOL.NULL, symbol + ); + StringBuilder builder = new StringBuilder(); + for (T value : lists) { + try { + builder.append(value) + .append(spliceSymbol); + } catch (Exception e) { + builder.append("null(error)") + .append(spliceSymbol); + } + } + String content = builder.toString(); + return StringUtils.clearEndsWith(content, spliceSymbol); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevIntent.java b/lib/DevAssist/src/main/java/dev/base/DevIntent.java new file mode 100644 index 0000000000..316ce38969 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevIntent.java @@ -0,0 +1,334 @@ +package dev.base; + +import android.content.Intent; +import android.os.Bundle; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.common.StringUtils; + +/** + * detail: Intent 传参读写辅助类 + * @author Ttt + *
+ *     统一存储为 String 需要转换其他类型则可通过
+ *     {@link dev.utils.common.ConvertUtils} 进行转换
+ *     或者自行通过 JSON 映射实体类等
+ *     

+ * 可存储 key、value 为 null 数据 ( 提供方法清除 null 数据 ) + *

+ * 自动读取 Intent、Bundle 数据进行填充 + * 仅支持 String、Integer、Long、Double、Float、Boolean 类型 + * 并自动存储为 String + *

+ * 通过 {@link #insert()} 可将 Map 数据插入到 Intent、Bundle 中 + *
+ */ +public class DevIntent { + + // 存储数据 Map + private final LinkedHashMap mDataMaps = new LinkedHashMap<>(); + + private DevIntent() { + } + + // ========== + // = 静态方法 = + // ========== + + /** + * 创建 DevIntent + * @return {@link DevIntent} + */ + public static DevIntent with() { + return new DevIntent(); + } + + /** + * 创建 DevIntent + * @param intent {@link Intent} + * @return {@link DevIntent} + */ + public static DevIntent with(final Intent intent) { + return new DevIntent().reader(intent); + } + + /** + * 创建 DevIntent + * @param bundle {@link Bundle} + * @return {@link DevIntent} + */ + public static DevIntent with(final Bundle bundle) { + return new DevIntent().reader(bundle); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 插入数据 + * @param intent {@link Intent} + * @return {@link Intent} + */ + public Intent insert(final Intent intent) { + if (intent != null) { + for (Map.Entry entry : mDataMaps.entrySet()) { + intent.putExtra(entry.getKey(), entry.getValue()); + } + } + return intent; + } + + /** + * 插入数据 + * @return {@link Bundle} + */ + public Bundle insert() { + return insert(new Bundle()); + } + + /** + * 插入数据 + * @param bundle {@link Bundle} + * @return {@link Bundle} + */ + public Bundle insert(final Bundle bundle) { + if (bundle != null) { + for (Map.Entry entry : mDataMaps.entrySet()) { + bundle.putString(entry.getKey(), entry.getValue()); + } + } + return bundle; + } + + // = + + /** + * 读取数据并存储 + * @param intent {@link Intent} + * @return {@link DevIntent} + */ + public DevIntent reader(final Intent intent) { + if (intent != null) { + return reader(intent.getExtras()); + } + return this; + } + + /** + * 读取数据并存储 + * @param bundle {@link Bundle} + * @return {@link DevIntent} + */ + public DevIntent reader(final Bundle bundle) { + if (bundle != null) { + for (String key : bundle.keySet()) { + Object value = bundle.get(key); + if (value == null) { + continue; + } + if (value instanceof String) { + put(key, String.valueOf(value)); + } else if (value instanceof Integer) { + put(key, String.valueOf(value)); + } else if (value instanceof Long) { + put(key, String.valueOf(value)); + } else if (value instanceof Double) { + put(key, String.valueOf(value)); + } else if (value instanceof Float) { + put(key, String.valueOf(value)); + } else if (value instanceof Boolean) { + put(key, String.valueOf(value)); + } + } + } + return this; + } + + // ======= + // = 通用 = + // ======= + + /** + * 获取存储数据 Map + * @return 存储数据 Map + */ + public Map getDataMaps() { + return mDataMaps; + } + + /** + * 是否存在 Key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + public boolean containsKey(final String key) { + return mDataMaps.containsKey(key); + } + + /** + * 是否存在 Value + * @param value 保存的 value + * @return {@code true} yes, {@code false} no + */ + public boolean containsValue(final String value) { + return mDataMaps.containsValue(value); + } + + /** + * 对应 Key 保存的 Value 是否为 null + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + public boolean isNullValue(final String key) { + return get(key) == null; + } + + /** + * 保存数据 + * @param key 保存的 key + * @param value 保存的 value + * @return {@link DevIntent} + */ + public DevIntent put( + final String key, + final String value + ) { + mDataMaps.put(key, value); + return this; + } + + /** + * 保存集合数据 + * @param map {@link Map} + * @return {@link DevIntent} + */ + public DevIntent putAll(final Map map) { + if (map != null) mDataMaps.putAll(map); + return this; + } + + /** + * 移除数据 + * @param key 保存的 key + * @return {@link DevIntent} + */ + public DevIntent remove(final String key) { + mDataMaps.remove(key); + return this; + } + + /** + * 移除集合数据 + * @param keys 保存的 key 集合 + * @return {@link DevIntent} + */ + public DevIntent removeAll(final List keys) { + if (keys != null) { + for (String key : keys) { + mDataMaps.remove(key); + } + } + return this; + } + + /** + * 获取对应 Key 保存的 Value + * @param key 保存的 key + * @return 保存的 value + */ + public String get(final String key) { + return mDataMaps.get(key); + } + + /** + * 清空数据 + * @return {@link DevIntent} + */ + public DevIntent clear() { + mDataMaps.clear(); + return this; + } + + // = + + /** + * 清除 null 数据 + *
+     *     key、value 只要其中一个为 null 就清除
+     * 
+ * @return {@link DevIntent} + */ + public DevIntent clearNull() { + return clearNullKey().clearNullValue(); + } + + /** + * 清除 null Key 数据 + * @return {@link DevIntent} + */ + public DevIntent clearNullKey() { + return remove(null); + } + + /** + * 清除 null Value 数据 + *
+     *     value 只要为 null 就清除
+     * 
+ * @return {@link DevIntent} + */ + public DevIntent clearNullValue() { + Iterator> iterator = mDataMaps.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (entry.getValue() == null) { + iterator.remove(); + } + } + return this; + } + + // = + + /** + * 清除 empty 数据 + *
+     *     key、value 只要其中一个为 empty ( null、"" ) 就清除
+     * 
+ * @return {@link DevIntent} + */ + public DevIntent clearEmpty() { + return clearEmptyKey().clearEmptyValue(); + } + + /** + * 清除 empty Key 数据 + * @return {@link DevIntent} + */ + public DevIntent clearEmptyKey() { + return remove(null).remove(""); + } + + /** + * 清除 empty Value 数据 + *
+     *     value 只要为 empty ( null、"" ) 就清除
+     * 
+ * @return {@link DevIntent} + */ + public DevIntent clearEmptyValue() { + Iterator> iterator = mDataMaps.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (StringUtils.isEmpty(entry.getValue())) { + iterator.remove(); + } + } + return this; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevNumber.java b/lib/DevAssist/src/main/java/dev/base/DevNumber.java new file mode 100644 index 0000000000..e6d2e2c5d0 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevNumber.java @@ -0,0 +1,386 @@ +package dev.base; + +import dev.base.number.INumberListener; +import dev.base.number.INumberOperate; + +/** + * detail: 数量实体类 + * @author Ttt + */ +public class DevNumber + extends DevObject + implements INumberOperate> { + + // 最小值 + private int mMinNumber = 1; + // 最大值 + private int mMaxNumber = Integer.MAX_VALUE; + // 当前数量 + private int mCurrentNumber = 1; + // 重置数量 ( 出现异常情况, 则使用该变量赋值 ) + private int mResetNumber = 1; + // 是否允许设置为负数 + private boolean mAllowNegative = false; + // 数量监听事件接口 + private INumberListener mNumberListener; + + public DevNumber() { + } + + public DevNumber(final int minNumber) { + this.mMinNumber = minNumber; + } + + public DevNumber( + final int minNumber, + final int maxNumber + ) { + this.mMinNumber = minNumber; + this.mMaxNumber = maxNumber; + } + + // ================== + // = INumberOperate = + // ================== + + /** + * 判断当前数量, 是否等于最小值 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMinNumber() { + return isMinNumber(mCurrentNumber); + } + + /** + * 判断数量, 是否等于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMinNumber(final int number) { + return number == mMinNumber; + } + + /** + * 判断数量, 是否小于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLessThanMinNumber(final int number) { + return number < mMinNumber; + } + + /** + * 判断数量, 是否大于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isGreaterThanMinNumber(final int number) { + return number > mMinNumber; + } + + // = + + /** + * 判断当前数量, 是否等于最大值 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMaxNumber() { + return isMaxNumber(mCurrentNumber); + } + + /** + * 判断数量, 是否等于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isMaxNumber(final int number) { + return number == mMaxNumber; + } + + /** + * 判断数量, 是否小于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isLessThanMaxNumber(final int number) { + return number < mMaxNumber; + } + + /** + * 判断数量, 是否大于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isGreaterThanMaxNumber(final int number) { + return number > mMaxNumber; + } + + // =========== + // = get/set = + // =========== + + /** + * 获取最小值 + * @return 最小值 + */ + @Override + public int getMinNumber() { + return mMinNumber; + } + + /** + * 设置最小值 + *
+     *     内部判断了, 是否大于 mMaxNumber, 属于的话则自动赋值 mMaxNumber
+     * 
+ * @param minNumber 最小值 + * @return {@link DevNumber} + */ + @Override + public DevNumber setMinNumber(final int minNumber) { + int number = minNumber; + // 如果不允许为负数, 并且设置最小值为负数, 则设置为重置数量 + if (!mAllowNegative && number < 0) { + number = mResetNumber; + } + if (number > mMaxNumber) { + number = mMaxNumber; + } + this.mMinNumber = number; + return this; + } + + // = + + /** + * 获取最大值 + * @return 最大值 + */ + @Override + public int getMaxNumber() { + return mMaxNumber; + } + + /** + * 设置最大值 + *
+     *     内部判断了, 是否小于 mMinNumber, 属于的话则自动赋值 mMinNumber
+     *     特殊情况 ( 修改为负数 ), 最好先调用 setMinNumber, 在调用 setMaxNumber
+     * 
+ * @param maxNumber 最大值 + * @return {@link DevNumber} + */ + @Override + public DevNumber setMaxNumber(final int maxNumber) { + this.mMaxNumber = Math.max(maxNumber, mMinNumber); + return this; + } + + /** + * 设置最小值、最大值 + * @param minNumber 最小值 + * @param maxNumber 最大值 + * @return {@link DevNumber} + */ + @Override + public DevNumber setMinMaxNumber( + final int minNumber, + final int maxNumber + ) { + return setMinNumber(minNumber).setMaxNumber(maxNumber); + } + + // = + + /** + * 获取当前数量 + * @return 当前数量 + */ + @Override + public int getCurrentNumber() { + return mCurrentNumber; + } + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @return {@link DevNumber} + */ + @Override + public DevNumber setCurrentNumber(final int currentNumber) { + return setCurrentNumber(currentNumber, true); + } + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @param isTriggerListener 是否触发事件 + * @return {@link DevNumber} + */ + @Override + public DevNumber setCurrentNumber( + final int currentNumber, + final boolean isTriggerListener + ) { + int number = currentNumber; + if (number < mMinNumber) { + number = mMinNumber; + } else if (number > mMaxNumber) { + number = mMaxNumber; + } + // 判断是否添加 + boolean isAdd = (number > mCurrentNumber); + // 重新赋值 + this.mCurrentNumber = number; + // 判断是否触发事件 + if (isTriggerListener) { + if (mNumberListener != null) { + mNumberListener.onNumberChanged(isAdd, number); + } + } + return this; + } + + // = + + /** + * 获取重置数量 + * @return 重置数量 + */ + @Override + public int getResetNumber() { + return mResetNumber; + } + + /** + * 设置重置数量 + * @param resetNumber 重置数量 + * @return {@link DevNumber} + */ + @Override + public DevNumber setResetNumber(final int resetNumber) { + // 防止出现负数 + this.mResetNumber = (!mAllowNegative && mResetNumber < 0) ? 1 : resetNumber; + return this; + } + + // = + + /** + * 获取是否允许设置为负数 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isAllowNegative() { + return mAllowNegative; + } + + /** + * 设置是否允许设置为负数 + * @param allowNegative {@code true} yes, {@code false} no + * @return {@link DevNumber} + */ + @Override + public DevNumber setAllowNegative(final boolean allowNegative) { + this.mAllowNegative = allowNegative; + // 进行检查更新 + checkUpdate(); + return this; + } + + // ============= + // = 数量变化方法 = + // ============= + + /** + * 数量改变通知 + * @param number 变化基数数量 + * @return {@link DevNumber} + */ + @Override + public DevNumber numberChange(final int number) { + if (mNumberListener != null) { + // 计算之后的数量 + int afterNumber = (mCurrentNumber + number); + // 判断是否添加, 大于等于当前数量, 表示添加 + boolean isAdd = afterNumber >= mCurrentNumber; + // 进行判断 + if (mNumberListener.onPrepareChanged(isAdd, mCurrentNumber, afterNumber)) { + // 进行设置当前数量 + setCurrentNumber(afterNumber, true); + } + } + return this; + } + + /** + * 添加数量 ( 默认累加 1 ) + * @return {@link DevNumber} + */ + @Override + public DevNumber addNumber() { + return numberChange(1); + } + + /** + * 减少数量 ( 默认累减 1 ) + * @return {@link DevNumber} + */ + @Override + public DevNumber subtractionNumber() { + return numberChange(-1); + } + + // ======= + // = 事件 = + // ======= + + /** + * 获取数量监听事件接口 + * @return {@link INumberListener} + */ + @Override + public INumberListener getNumberListener() { + return mNumberListener; + } + + /** + * 设置数量监听事件接口 + * @param listener {@link INumberListener} + * @return {@link DevNumber} + */ + @Override + public DevNumber setNumberListener(final INumberListener listener) { + this.mNumberListener = listener; + return this; + } + + // ============= + // = 内部判断方法 = + // ============= + + /** + * 检查更新处理 + */ + private void checkUpdate() { + if (!mAllowNegative && mResetNumber < 0) { + mResetNumber = 1; + } + if (!mAllowNegative && mMinNumber < 0) { + mMinNumber = mResetNumber; + } + if (!mAllowNegative && mMaxNumber < 0) { + mMaxNumber = mResetNumber; + } + // 重置最大值 + setMaxNumber(mMaxNumber); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevObject.java b/lib/DevAssist/src/main/java/dev/base/DevObject.java new file mode 100644 index 0000000000..bf1796ae05 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevObject.java @@ -0,0 +1,315 @@ +package dev.base; + +import java.io.Serializable; +import java.util.UUID; + +import dev.utils.common.ObjectUtils; + +/** + * detail: 通用 Object + * @author Ttt + */ +public class DevObject + implements Serializable { + + // uuid ( 一定程度上唯一 ) + private final int mUUID = UUID.randomUUID().hashCode(); + // Object + private T mObject; + // 标记 TAG + private Object mTag; + // model id + private int mModelId; + // code String + private String mCode; + // Type + private int mType; + // State + private int mState; + // Token uuid + private long mTokenUUID = UUID.randomUUID().hashCode(); + + public DevObject() { + } + + public DevObject(final T object) { + this.mObject = object; + } + + public DevObject( + final T object, + final Object tag + ) { + this.mObject = object; + this.mTag = tag; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 UUID + * @return random UUID HashCode + */ + public final int getUUID() { + return mUUID; + } + + /** + * 获取 Object + * @return Object + */ + public T getObject() { + return mObject; + } + + /** + * 设置 Object + * @param object T Class Object + * @return {@link DevObject} + */ + public DevObject setObject(final T object) { + this.mObject = object; + return this; + } + + /** + * 获取标记 TAG + * @return 标记 TAG + */ + public Object getTag() { + return mTag; + } + + /** + * 转换标记 TAG + * @param 泛型 + * @return Object convert T object + */ + public CTO convertTag() { + try { + return (CTO) mTag; + } catch (Exception ignored) { + } + return null; + } + + /** + * 设置标记 TAG + * @param tag TAG + * @return {@link DevObject} + */ + public DevObject setTag(final Object tag) { + this.mTag = tag; + return this; + } + + /** + * 获取 Model id + * @return model id + */ + public int getModelId() { + return mModelId; + } + + /** + * 设置 Model id + * @param modelId model id + * @return {@link DevObject} + */ + public DevObject setModelId(final int modelId) { + this.mModelId = modelId; + return this; + } + + /** + * 获取 Code + * @return Code + */ + public String getCode() { + return mCode; + } + + /** + * 设置 Code + * @param code Code + * @return {@link DevObject} + */ + public DevObject setCode(final String code) { + this.mCode = code; + return this; + } + + /** + * 设置 Code + * @param code Code + * @return {@link DevObject} + */ + public DevObject setCode(final int code) { + return setCode(String.valueOf(code)); + } + + /** + * 获取 Type + * @return Type + */ + public int getType() { + return mType; + } + + /** + * 设置 Type + * @param type Type + * @return {@link DevObject} + */ + public DevObject setType(final int type) { + this.mType = type; + return this; + } + + /** + * 获取 State + * @return State + */ + public int getState() { + return mState; + } + + /** + * 设置 State + * @param state State + * @return {@link DevObject} + */ + public DevObject setState(final int state) { + this.mState = state; + return this; + } + + /** + * 获取 Token UUID + * @return Token UUID + */ + public long getTokenUUID() { + return mTokenUUID; + } + + /** + * 设置 Token UUID + * @param uuid token UUID + * @return {@link DevObject} + */ + public DevObject setTokenUUID(final long uuid) { + this.mTokenUUID = uuid; + return this; + } + + /** + * 重置随机 Token UUID + * @return Token UUID + */ + public long randomTokenUUID() { + mTokenUUID = UUID.randomUUID().hashCode(); + return mTokenUUID; + } + + // ========== + // = 判断方法 = + // ========== + + /** + * 判断 Object 是否一致 + * @param object 待校验 Object + * @return {@code true} yes, {@code false} no + */ + public boolean equalsObject(final T object) { + return object != null && ObjectUtils.equals(this.mObject, object); + } + + /** + * 判断 TAG 是否一致 + * @param tag 待校验 TAG + * @return {@code true} yes, {@code false} no + */ + public boolean equalsTag(final Object tag) { + return tag != null && ObjectUtils.equals(this.mTag, tag); + } + + /** + * 判断 Model id 是否一致 + * @param modelId 待校验 Model id + * @return {@code true} yes, {@code false} no + */ + public boolean equalsModelId(final int modelId) { + return this.mModelId == modelId; + } + + /** + * 判断 Code 是否一致 + * @param code 待校验 Code + * @return {@code true} yes, {@code false} no + */ + public boolean equalsCode(final int code) { + return equalsCode(String.valueOf(code)); + } + + /** + * 判断 Code 是否一致 + * @param code 待校验 Code + * @return {@code true} yes, {@code false} no + */ + public boolean equalsCode(final String code) { + return code != null && ObjectUtils.equals(this.mCode, code); + } + + /** + * 判断 Type 是否一致 + * @param type 待校验 Type + * @return {@code true} yes, {@code false} no + */ + public boolean equalsType(final int type) { + return this.mType == type; + } + + /** + * 判断 State 是否一致 + * @param state 待校验 State + * @return {@code true} yes, {@code false} no + */ + public boolean equalsState(final int state) { + return this.mState == state; + } + + /** + * 判断 Token UUID 是否一致 + * @param uuid 待校验 Token UUID + * @return {@code true} yes, {@code false} no + */ + public boolean equalsTokenUUID(final long uuid) { + return this.mTokenUUID == uuid; + } + + // ========== + // = 校验方法 = + // ========== + + /** + * 校验数据正确性 + *
+     *     根据需要重写
+     * 
+ * @return {@code true} correct, {@code false} error + */ + public boolean isCorrect() { + return false; + } + + /** + * 校验数据正确性 + * @param data {@link DevObject} + * @return {@code true} correct, {@code false} error + */ + public static boolean isCorrect(final DevObject data) { + return data != null && data.isCorrect(); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevPage.java b/lib/DevAssist/src/main/java/dev/base/DevPage.java new file mode 100644 index 0000000000..4482caea41 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevPage.java @@ -0,0 +1,267 @@ +package dev.base; + +/** + * detail: Page 实体类 + * @author Ttt + */ +public class DevPage + extends DevObject { + + // 页数配置 + private final PageConfig config; + // 当前页数 ( 已成功 ) + private int mPage; + // 是否最后一页 + private boolean mLastPage; + + public DevPage(final PageConfig pageConfig) { + this(pageConfig.page, pageConfig.pageSize); + } + + public DevPage( + final int page, + final int pageSize + ) { + config = new PageConfig(page, pageSize); + // 设置页数信息 + mPage = config.page; + } + + /** + * detail: 页数配置信息 + * @author Ttt + */ + public static class PageConfig { + + // 页数配置 + public final int page; + // 每页请求条数配置 + public final int pageSize; + + public PageConfig( + final int page, + final int pageSize + ) { + this.page = page; + this.pageSize = pageSize; + } + } + + /** + * 重置操作 + * @return {@link DevPage} + */ + public DevPage reset() { + return setPage(config.page).setLastPage(false); + } + + /** + * 重置操作 + * @param reset 是否进行重置 ( 方便判断是否刷新进行调用 ) + * @return {@link DevPage} + */ + public DevPage reset(final boolean reset) { + if (reset) reset(); + return this; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取当前页数 + * @return 当前页数 + */ + public int getPage() { + return mPage; + } + + /** + * 设置当前页数 + * @param page 当前页数 + * @return {@link DevPage} + */ + public DevPage setPage(final int page) { + mPage = page; + return this; + } + + /** + * 判断当前页数是否一致 + * @param page 待校验当前页数 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsPage(final int page) { + return mPage == page; + } + + // = + + /** + * 获取页数配置信息 + * @return {@link PageConfig} + */ + public PageConfig getConfig() { + return config; + } + + /** + * 获取配置初始页页数 + * @return 初始页页数 + */ + public int getConfigPage() { + return config.page; + } + + /** + * 获取配置每页请求条数 + * @return 每页请求条数 + */ + public int getConfigPageSize() { + return config.pageSize; + } + + // = + + /** + * 获取每页请求条数 + * @return 每页请求条数 + */ + public int getPageSize() { + return config.pageSize; + } + + /** + * 判断每页请求条数是否一致 + * @param pageSize 待校验每页请求条数 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsPageSize(final int pageSize) { + return config.pageSize == pageSize; + } + + // = + + /** + * 判断是否最后一页 + * @return {@code true} yes, {@code false} no + */ + public boolean isLastPage() { + return mLastPage; + } + + /** + * 设置是否最后一页 + * @param lastPage 是否最后一页 + * @return {@link DevPage} + */ + public DevPage setLastPage(final boolean lastPage) { + mLastPage = lastPage; + return this; + } + + /** + * 计算是否最后一页 ( 并同步更新 ) + * @param size 数据条数 + * @return {@link DevPage} + */ + public DevPage calculateLastPage(final int size) { + return setLastPage(isLessThanPageSize(size)); + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断是否第一页 + * @return {@code true} yes, {@code false} no + */ + public boolean isFirstPage() { + return mPage == config.page; + } + + /** + * 判断是否允许请求下一页 + * @return {@code true} yes, {@code false} no + */ + public boolean canNextPage() { + return !mLastPage; // 非最后一页则可以请求 + } + + /** + * 获取下一页页数 + * @return 下一页页数 + */ + public int getNextPage() { + return mPage + 1; + } + + /** + * 累加当前页数 ( 下一页 ) + * @return {@link DevPage} + */ + public DevPage nextPage() { + return setPage(mPage + 1); + } + + /** + * 判断是否小于每页请求条数 + *
+     *     如果小于每页请求条数, 也表明已经没有下一页
+     * 
+ * @param size 数据条数 + * @return {@code true} yes, {@code false} no + */ + public boolean isLessThanPageSize(final int size) { + return size < config.pageSize; + } + + // = + + /** + * 请求响应处理 + * @param refresh 是否刷新操作 + * @return {@link DevPage} + */ + public DevPage response(final boolean refresh) { + // 刷新重置操作 + if (refresh) reset(); + // 累加当前页数 ( 下一页 ) + return nextPage(); + } + + /** + * 请求响应处理 + * @param refresh 是否刷新操作 + * @param lastPage 是否最后一页 + * @return {@link DevPage} + */ + public DevPage response( + final boolean refresh, + final boolean lastPage + ) { + return response(refresh).setLastPage(lastPage); + } + + // =========== + // = Default = + // =========== + + // 默认页数配置 + public static final int DEF_PAGE = 1; + // 默认每页请求条数配置 + public static final int DEF_PAGE_SIZE = 10; + + /** + * 获取默认配置 Page 实体类 + * @param 泛型 + * @return {@link DevPage} + */ + public static DevPage getDefault() { + return new DevPage<>( + DEF_PAGE, DEF_PAGE_SIZE + ); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevSource.java b/lib/DevAssist/src/main/java/dev/base/DevSource.java new file mode 100644 index 0000000000..fcb2a051f0 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevSource.java @@ -0,0 +1,147 @@ +package dev.base; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import java.io.File; +import java.io.InputStream; + +/** + * detail: 资源来源通用类 + * @author Ttt + */ +public class DevSource { + + public final String mUrl; + public final Uri mUri; + public final byte[] mBytes; + public final int mResource; + public final File mFile; + public final InputStream mInputStream; + public final Drawable mDrawable; + public final Bitmap mBitmap; + + public DevSource( + String url, + Uri uri, + byte[] bytes, + int resource, + File file, + InputStream inputStream, + Drawable drawable, + Bitmap bitmap + ) { + this.mUrl = url; + this.mUri = uri; + this.mBytes = bytes; + this.mResource = resource; + this.mFile = file; + this.mInputStream = inputStream; + this.mDrawable = drawable; + this.mBitmap = bitmap; + } + + public static DevSource create(final String url) { + return new DevSource( + url, null, null, 0, + null, null, null, null + ); + } + + public static DevSource create(final Uri uri) { + return new DevSource( + null, uri, null, 0, + null, null, null, null + ); + } + + public static DevSource create(final byte[] bytes) { + return new DevSource( + null, null, bytes, 0, + null, null, null, null + ); + } + + public static DevSource create(final int resource) { + return new DevSource( + null, null, null, resource, + null, null, null, null + ); + } + + public static DevSource create(final File file) { + return new DevSource( + null, null, null, 0, + file, null, null, null + ); + } + + public static DevSource create(final InputStream inputStream) { + return new DevSource( + null, null, null, 0, + null, inputStream, null, null + ); + } + + public static DevSource create(final Drawable drawable) { + return new DevSource( + null, null, null, 0, + null, null, drawable, null + ); + } + + public static DevSource create(final Bitmap bitmap) { + return new DevSource( + null, null, null, 0, + null, null, null, bitmap + ); + } + + public static DevSource createWithPath(final String path) { + return create(path != null ? new File(path) : null); + } + + // = + + public boolean isUrl() { + return mUrl != null; + } + + public boolean isUri() { + return mUri != null; + } + + public boolean isBytes() { + return mBytes != null; + } + + public boolean isResource() { + return mResource != 0; + } + + public boolean isFile() { + return mFile != null; + } + + public boolean isInputStream() { + return mInputStream != null; + } + + public boolean isDrawable() { + return mDrawable != null; + } + + public boolean isBitmap() { + return mBitmap != null; + } + + /** + * 是否有效资源 + * @return {@code true} yes, {@code false} no + */ + public boolean isSource() { + return isUrl() || isUri() || isBytes() || isResource() + || isFile() || isInputStream() || isDrawable() || isBitmap(); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevState.java b/lib/DevAssist/src/main/java/dev/base/DevState.java new file mode 100644 index 0000000000..1250892a11 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevState.java @@ -0,0 +1,23 @@ +package dev.base; + +/** + * detail: 状态实体类 + * @author Ttt + */ +public class DevState + extends DevObject { + + public DevState() { + } + + public DevState(final T object) { + super(object); + } + + public DevState( + final T object, + final Object tag + ) { + super(object, tag); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevVariable.java b/lib/DevAssist/src/main/java/dev/base/DevVariable.java new file mode 100644 index 0000000000..8f424f914c --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevVariable.java @@ -0,0 +1,285 @@ +package dev.base; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * detail: 变量操作基类 + * @author Ttt + *
+ *     内部封装逻辑, 对外提供快捷方法
+ *     减少逻辑实现代码
+ * 
+ */ +public class DevVariable { + + // 存储数据 Map + private final LinkedHashMap mLinkedHashMap = new LinkedHashMap<>(); + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取全部变量数据 + * @return {@link LinkedHashMap} + */ + public LinkedHashMap getVariables() { + return mLinkedHashMap; + } + + /** + * 清空全部变量数据 + * @return {@link DevVariable} + */ + public DevVariable clearVariables() { + mLinkedHashMap.clear(); + return this; + } + + /** + * 保存变量数据集合 + * @param collection {@link LinkedHashMap} + * @return {@link DevVariable} + */ + public DevVariable putVariables(final Map collection) { + if (collection != null) mLinkedHashMap.putAll(collection); + return this; + } + + /** + * 获取变量总数 + * @return 变量总数 + */ + public int getVariablesSize() { + return mLinkedHashMap.size(); + } + + /** + * 判断是否存在变量数据 + * @return {@code true} yes, {@code false} no + */ + public boolean isVariables() { + return getVariablesSize() != 0; + } + + // = + + /** + * 判断是否存在变量 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + public boolean isVariableValue(final V value) { + return mLinkedHashMap.containsValue(value); + } + + /** + * 删除指定变量数据 + * @param value Value + * @return {@link DevVariable} + */ + public DevVariable removeVariableValue(final V value) { + Iterator iterator = mLinkedHashMap.values().iterator(); + while (iterator.hasNext()) { + V v = iterator.next(); + if (v == value) { + iterator.remove(); + break; + } + } + return this; + } + + /** + * 删除指定变量数据 ( 符合条件的全部 value ) + * @param value Value + * @return {@link DevVariable} + */ + public DevVariable removeVariableValueAll(final V value) { + Iterator iterator = mLinkedHashMap.values().iterator(); + while (iterator.hasNext()) { + V v = iterator.next(); + if (v == value) { + iterator.remove(); + } + } + return this; + } + + // = + + /** + * 判断是否存在变量 ( 通过 key 判断 ) + * @param key Key + * @return {@code true} yes, {@code false} no + */ + public boolean isVariable(final K key) { + return mLinkedHashMap.containsKey(key); + } + + /** + * 判断是否存在变量 ( 如果不存在, 则保存 ) + * @param key Key + * @param value Value + * @return {@code true} yes, {@code false} no + */ + public boolean isVariable( + final K key, + final V value + ) { + if (!isVariable(key)) { + mLinkedHashMap.put(key, value); + return false; + } + return true; + } + + // = + + /** + * 保存变量数据 + * @param key Key + * @param value Value + * @return {@link DevVariable} + */ + public DevVariable putVariable( + final K key, + final V value + ) { + mLinkedHashMap.put(key, value); + return this; + } + + /** + * 保存变量数据 + * @param put {@code true} put, {@code false} remove + * @param key Key + * @param value Value + * @return {@link DevVariable} + */ + public DevVariable putVariable( + final boolean put, + final K key, + final V value + ) { + return put ? putVariable(key, value) : removeVariable(key); + } + + /** + * 移除指定变量数据 ( 通过 key 判断 ) + * @param key Key + * @return {@link DevVariable} + */ + public DevVariable removeVariable(final K key) { + mLinkedHashMap.remove(key); + return this; + } + + /** + * 切换变量数据存储状态 + *
+     *     如果存在则删除、反之则保存
+     * 
+ * @param key Key + * @param value Value + * @return {@link DevVariable} + */ + public DevVariable toggle( + final K key, + final V value + ) { + if (isVariable(key)) { // 移除存在的数据 + mLinkedHashMap.remove(key); + } else { // 保存变量数据 + mLinkedHashMap.put(key, value); + } + return this; + } + + // = + + /** + * 通过 key 获取 value + * @param key Key + * @return Value + */ + public V getVariableValue(final K key) { + return mLinkedHashMap.get(key); + } + + /** + * 通过 key 获取 value + * @param key Key + * @param 泛型 + * @return Value convert T value + */ + public T getVariableValueConvert(final K key) { + try { + return (T) mLinkedHashMap.get(key); + } catch (Exception ignored) { + } + return null; + } + + // = + + /** + * 获取变量数据 value list + * @return {@link List} + */ + public List getVariableValues() { + return new ArrayList<>(mLinkedHashMap.values()); + } + + /** + * 获取变量数据 value list ( 倒序 ) + * @return {@link List} + */ + public List getVariableValuesToReverse() { + List lists = getVariableValues(); + Collections.reverse(lists); + return lists; + } + + // = + + /** + * 通过 value 获取 key + * @param value Value + * @return Key + */ + public K getVariableKey(final V value) { + // 进行循环遍历获取 + for (Map.Entry entry : mLinkedHashMap.entrySet()) { + V v = entry.getValue(); + // 判断是否符合对应的 value + if (v == value) { + return entry.getKey(); + } + } + return null; + } + + /** + * 获取变量数据 key list + * @return {@link List} + */ + public List getVariableKeys() { + return new ArrayList<>(mLinkedHashMap.keySet()); + } + + /** + * 获取变量数据 key list ( 倒序 ) + * @return {@link List} + */ + public List getVariableKeysToReverse() { + List lists = getVariableKeys(); + Collections.reverse(lists); + return lists; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/DevVariableExt.java b/lib/DevAssist/src/main/java/dev/base/DevVariableExt.java new file mode 100644 index 0000000000..78d3df89d3 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/DevVariableExt.java @@ -0,0 +1,110 @@ +package dev.base; + +/** + * detail: 变量操作基类扩展类 + * @author Ttt + *
+ *     {@link DevVariable} 变量操作基类基础上进行扩展
+ *     支持通过接口方式进行创建存储值
+ * 
+ */ +public class DevVariableExt { + + // 变量操作基类 + private final DevVariable mVariable = new DevVariable<>(); + // 变量创建器 + private Creator mCreator = null; + + // ========== + // = 构造函数 = + // ========== + + public DevVariableExt() { + } + + public DevVariableExt(final Creator creator) { + this.mCreator = creator; + } + + // ======== + // = 创建器 = + // ======== + + /** + * detail: 变量创建器 + * @author Ttt + */ + public interface Creator { + + /** + * 创建存储值 + * @param key 存储 key + * @param param 额外参数 + * @return 存储值 + */ + V create( + K key, + P param + ); + } + + /** + * 获取变量创建器 + * @return {@link Creator} + */ + public Creator getCreator() { + return mCreator; + } + + /** + * 设置变量创建器 + * @param creator {@link Creator} + * @return {@link DevVariableExt} + */ + public DevVariableExt setCreator(final Creator creator) { + this.mCreator = creator; + return this; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取变量操作基类 + * @return {@link DevVariable} + */ + public DevVariable getVariable() { + return mVariable; + } + + /** + * 通过 key 获取 value + * @param key Key + * @return Value + */ + public V getVariableValue(final K key) { + return getVariableValue(key, null); + } + + /** + * 通过 key 获取 value + * @param key Key + * @param param 额外参数 + * @return Value + */ + public V getVariableValue( + final K key, + final P param + ) { + V value = mVariable.getVariableValue(key); + if (value != null) return value; + if (mCreator != null) { + value = mCreator.create(key, param); + if (value != null) { + mVariable.putVariable(key, value); + } + } + return value; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/data/DataChanged.java b/lib/DevAssist/src/main/java/dev/base/data/DataChanged.java new file mode 100644 index 0000000000..df63de2d5d --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/data/DataChanged.java @@ -0,0 +1,24 @@ +package dev.base.data; + +/** + * detail: 数据改变通知 + * @param 泛型 + * @author Ttt + */ +public interface DataChanged { + + // ========== + // = 通知方法 = + // ========== + + /** + * 通知数据改变 + */ + void notifyDataChanged(); + + /** + * 通知某个数据改变 + * @param value {@link T} + */ + void notifyElementChanged(T value); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/data/DataManager.java b/lib/DevAssist/src/main/java/dev/base/data/DataManager.java new file mode 100644 index 0000000000..0deb7b548e --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/data/DataManager.java @@ -0,0 +1,326 @@ +package dev.base.data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * detail: 数据管理接口 + * @param 泛型 + * @author Ttt + */ +public interface DataManager { + + // ========== + // = 获取相关 = + // ========== + + /** + * 获取 List Data + * @return {@link List} + */ + List getDataList(); + + /** + * 获取 ArrayList Data + * @return {@link ArrayList} + */ + ArrayList getDataArrayList(); + + /** + * 获取 List Size + * @return List.size() + */ + int getDataSize(); + + /** + * 获取 List Position Data + * @param position 索引 + * @return {@link T} + */ + T getDataItem(int position); + + /** + * 获取 Value Position + * @param value {@link T} + * @return position + */ + int getDataItemPosition(T value); + + /** + * 获取 First Data + * @return {@link T} + */ + T getFirstData(); + + /** + * 获取 Last Data + * @return {@link T} + */ + T getLastData(); + + /** + * 获取 Last Position + * @return Last Position + */ + int getLastPosition(); + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断 List Size 是否为 0 + * @return {@code true} yes, {@code false} no + */ + boolean isDataEmpty(); + + /** + * 判断 List Size 是否大于 0 + * @return {@code true} yes, {@code false} no + */ + boolean isDataNotEmpty(); + + /** + * 判断是否 First Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + boolean isFirstPosition(int position); + + /** + * 判断是否 Last Position + * @param position 索引 + * @return {@code true} yes, {@code false} no + */ + boolean isLastPosition(int position); + + /** + * 判断是否 Last Position + * @param position 索引 + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + boolean isLastPosition( + int position, + int size + ); + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @return {@code true} yes, {@code false} no + */ + boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value + ); + + /** + * 判断是否 Last Position 且大于等于指定 size + * @param position 索引 + * @param value 待判断 size + * @param size 总数 + * @return {@code true} yes, {@code false} no + */ + boolean isLastPositionAndGreaterThanOrEqual( + int position, + int value, + int size + ); + + /** + * 判断 First Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + boolean equalsFirstData(T value); + + /** + * 判断 Last Value 是否一致 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + boolean equalsLastData(T value); + + /** + * 判断 Position Value 是否一致 + * @param position 索引 + * @param value 待校验 Value + * @return {@code true} yes, {@code false} no + */ + boolean equalsPositionData( + int position, + T value + ); + + // ===== + // = 增 = + // ===== + + /** + * 添加数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + boolean addData(T value); + + /** + * 添加数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + boolean addDataAt( + int position, + T value + ); + + /** + * 添加数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean addDatas(Collection collection); + + /** + * 添加数据集 + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean addDatasAt( + int position, + Collection collection + ); + + /** + * 添加数据集 ( 进行校验 ) + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean addDatasChecked(Collection collection); + + /** + * 添加数据集 ( 进行校验 ) + * @param position 索引 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean addDatasCheckedAt( + int position, + Collection collection + ); + + /** + * 添加数据集 ( 判断是追加还是重置 ) + * @param append {@code true} {@link #addDatas} {@code false} {@link #setDataList} + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean addLists( + boolean append, + Collection collection + ); + + // ===== + // = 删 = + // ===== + + /** + * 移除数据 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + boolean removeData(T value); + + /** + * 移除数据 + * @param position 索引 + * @return remove position value + */ + T removeDataAt(int position); + + /** + * 移除数据集 + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean removeDatas(Collection collection); + + // ===== + // = 改 = + // ===== + + /** + * 替换数据 + * @param oldValue 旧的 Value + * @param newValue 新的 Value + * @return {@code true} success, {@code false} fail + */ + boolean replaceData( + T oldValue, + T newValue + ); + + /** + * 替换数据 + * @param position 索引 + * @param value Value + * @return {@code true} success, {@code false} fail + */ + boolean replaceDataAt( + int position, + T value + ); + + // = + + /** + * 数据中两个索引 Data 互换位置 + * @param fromPosition 待换位置索引 + * @param toPosition 替换后位置索引 + * @return {@code true} success, {@code false} fail + */ + boolean swipePosition( + int fromPosition, + int toPosition + ); + + /** + * 是否存在 Data + * @param value Value + * @return {@code true} yes, {@code false} no + */ + boolean contains(T value); + + /** + * 清空全部数据 + */ + void clearDataList(); + + /** + * 清空全部数据 + * @param notify 是否进行通知 + */ + void clearDataList(boolean notify); + + /** + * 设置 List Data + * @param collection {@link Collection} + * @return {@code true} success, {@code false} fail + */ + boolean setDataList(Collection collection); + + /** + * 设置 List Data + * @param collection {@link Collection} + * @param notify 是否进行通知 + * @return {@code true} success, {@code false} fail + */ + boolean setDataList( + Collection collection, + boolean notify + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/entry/DoubleEntry.java b/lib/DevAssist/src/main/java/dev/base/entry/DoubleEntry.java new file mode 100644 index 0000000000..9ddb9c865b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/entry/DoubleEntry.java @@ -0,0 +1,21 @@ +package dev.base.entry; + +import dev.base.DevEntry; + +public class DoubleEntry + extends DevEntry { + + public DoubleEntry() { + } + + public DoubleEntry(final Double key) { + super(key); + } + + public DoubleEntry( + final Double key, + final V value + ) { + super(key, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/entry/FloatEntry.java b/lib/DevAssist/src/main/java/dev/base/entry/FloatEntry.java new file mode 100644 index 0000000000..0605516868 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/entry/FloatEntry.java @@ -0,0 +1,21 @@ +package dev.base.entry; + +import dev.base.DevEntry; + +public class FloatEntry + extends DevEntry { + + public FloatEntry() { + } + + public FloatEntry(final Float key) { + super(key); + } + + public FloatEntry( + final Float key, + final V value + ) { + super(key, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/entry/IntEntry.java b/lib/DevAssist/src/main/java/dev/base/entry/IntEntry.java new file mode 100644 index 0000000000..c74787bbac --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/entry/IntEntry.java @@ -0,0 +1,21 @@ +package dev.base.entry; + +import dev.base.DevEntry; + +public class IntEntry + extends DevEntry { + + public IntEntry() { + } + + public IntEntry(final Integer key) { + super(key); + } + + public IntEntry( + final Integer key, + final V value + ) { + super(key, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/entry/LongEntry.java b/lib/DevAssist/src/main/java/dev/base/entry/LongEntry.java new file mode 100644 index 0000000000..528b500d4c --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/entry/LongEntry.java @@ -0,0 +1,21 @@ +package dev.base.entry; + +import dev.base.DevEntry; + +public class LongEntry + extends DevEntry { + + public LongEntry() { + } + + public LongEntry(final Long key) { + super(key); + } + + public LongEntry( + final Long key, + final V value + ) { + super(key, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/entry/StringEntry.java b/lib/DevAssist/src/main/java/dev/base/entry/StringEntry.java new file mode 100644 index 0000000000..b7e7a5bf06 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/entry/StringEntry.java @@ -0,0 +1,21 @@ +package dev.base.entry; + +import dev.base.DevEntry; + +public class StringEntry + extends DevEntry { + + public StringEntry() { + } + + public StringEntry(final String key) { + super(key); + } + + public StringEntry( + final String key, + final V value + ) { + super(key, value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectList.java b/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectList.java new file mode 100644 index 0000000000..cc19280d9b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectList.java @@ -0,0 +1,269 @@ +package dev.base.multiselect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import dev.base.DevEntry; +import dev.base.DevObject; + +/** + * detail: List 多选实体类 + * @param Value + * @author Ttt + *
+ *     实现 {@link IMultiSelectToList}, 每个接口方法直接通过调用 {@link DevMultiSelectList} 已实现同名方法即可
+ * 
+ */ +public class DevMultiSelectList + extends DevObject + implements IMultiSelectToList, V> { + + // 选中数据集 + private final List mListSelects = new ArrayList<>(); + + // ==================== + // = IBaseMultiSelect = + // ==================== + + /** + * 清空全部选中数据 + */ + @Override + public void clearSelects() { + mListSelects.clear(); + } + + /** + * 获取选中的数据条数 + * @return 选中的数据条数 + */ + @Override + public int getSelectSize() { + return mListSelects.size(); + } + + /** + * 获取选中的数据集合 + * @return 选中的数据集合 + */ + @Override + public List getSelects() { + return mListSelects; + } + + /** + * 通过集合添加选中数据 + * @param collection 集合泛型 + */ + @Override + public void putSelects(final List collection) { + if (collection != null) { + mListSelects.addAll(collection); + } + } + + /** + * 通过集合添加选中数据 + * @param collections 集合 + */ + @Override + public void putSelects(final Collection> collections) { + if (collections != null) { + for (DevEntry entry : collections) { + if (entry != null) { + mListSelects.add(entry.getValue()); + } + } + } + } + + /** + * 判断是否存在选中的数据 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelect() { + return getSelectSize() != 0; + } + + /** + * 判断是否选中 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelectValue(final V value) { + return mListSelects.contains(value); + } + + /** + * 设置非选中 + * @param value Value + */ + @Override + public void unselectValue(final V value) { + mListSelects.remove(value); + } + + /** + * 设置非选中 ( 符合条件的全部 value ) + * @param value Value + */ + @Override + public void unselectValueAll(final V value) { + Iterator iterator = mListSelects.iterator(); + while (iterator.hasNext()) { + V v = iterator.next(); + if (v == value) { + iterator.remove(); + } + } + } + + // ====================== + // = IMultiSelectToList = + // ====================== + + /** + * 判断是否选中 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelect(final V value) { + return isSelectValue(value); + } + + /** + * 设置选中操作 + * @param value Value + */ + @Override + public void select(final V value) { + mListSelects.add(value); + } + + /** + * 设置选中操作 + * @param isSelect 是否选中 + * @param value Value + */ + @Override + public void select( + final boolean isSelect, + final V value + ) { + if (isSelect) { + select(value); + } else { + unselect(value); + } + } + + /** + * 设置选中操作 ( 保存到指定索引 ) + * @param value Value + * @param position 索引 + */ + @Override + public void select( + final V value, + final int position + ) { + if (position >= 0) mListSelects.add(position, value); + } + + /** + * 设置非选中操作 + * @param position 索引 + * @return Value + */ + @Override + public V unselect(final int position) { + if (position >= 0) { + try { + return mListSelects.remove(position); + } catch (Exception ignored) { + } + } + return null; + } + + /** + * 设置非选中操作 + * @param value Value + */ + @Override + public void unselect(final V value) { + mListSelects.remove(value); + } + + /** + * 切换选中状态 + *
+     *     如果选中, 则设为非选中, 反之设为选中
+     * 
+ * @param value Value + */ + @Override + public void toggle(final V value) { + if (mListSelects.contains(value)) { + mListSelects.remove(value); + } else { + mListSelects.add(value); + } + } + + // =============== + // = 获取选中的数据 = + // =============== + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + @Override + public List getSelectValues() { + return new ArrayList<>(mListSelects); + } + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + @Override + public List getSelectValuesToReverse() { + List lists = getSelectValues(); + Collections.reverse(lists); + return lists; + } + + /** + * 获取选中的数据 + * @param position 索引 + * @return Value + */ + @Override + public V getSelectValue(final int position) { + if (position >= 0) { + try { + return mListSelects.get(position); + } catch (Exception ignored) { + } + } + return null; + } + + /** + * 获取选中的数据所在的索引 + * @param value Value + * @return value position + */ + @Override + public int getSelectValueToPosition(final V value) { + return mListSelects.indexOf(value); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectMap.java b/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectMap.java new file mode 100644 index 0000000000..4dd8a9b10d --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/DevMultiSelectMap.java @@ -0,0 +1,308 @@ +package dev.base.multiselect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import dev.base.DevEntry; +import dev.base.DevObject; + +/** + * detail: Map 多选实体类 + * @param Key + * @param Value + * @author Ttt + *
+ *     实现 {@link IMultiSelectToMap}, 每个接口方法直接通过调用 {@link DevMultiSelectMap} 已实现同名方法即可
+ * 
+ */ +public class DevMultiSelectMap + extends DevObject + implements IMultiSelectToMap, K, V> { + + // 选中数据集 + private final LinkedHashMap mMapSelects = new LinkedHashMap<>(); + + // ==================== + // = IBaseMultiSelect = + // ==================== + + /** + * 清空全部选中数据 + */ + @Override + public void clearSelects() { + mMapSelects.clear(); + } + + /** + * 获取选中的数据条数 + * @return 选中的数据条数 + */ + @Override + public int getSelectSize() { + return mMapSelects.size(); + } + + /** + * 获取选中的数据集合 + * @return 选中的数据集合 + */ + @Override + public LinkedHashMap getSelects() { + return mMapSelects; + } + + /** + * 通过集合添加选中数据 + * @param collection 集合泛型 + */ + @Override + public void putSelects(final LinkedHashMap collection) { + if (collection != null) { + mMapSelects.putAll(collection); + } + } + + /** + * 通过集合添加选中数据 + * @param collections 集合 + */ + @Override + public void putSelects(final Collection> collections) { + if (collections != null) { + for (DevEntry entry : collections) { + if (entry != null) { + mMapSelects.put((K) entry.getKey(), entry.getValue()); + } + } + } + } + + /** + * 判断是否存在选中的数据 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelect() { + return getSelectSize() != 0; + } + + /** + * 判断是否选中 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelectValue(final V value) { + return mMapSelects.containsValue(value); + } + + /** + * 设置非选中 + * @param value Value + */ + @Override + public void unselectValue(final V value) { + Iterator iterator = mMapSelects.values().iterator(); + while (iterator.hasNext()) { + V v = iterator.next(); + if (v == value) { + iterator.remove(); + break; + } + } + } + + /** + * 设置非选中 ( 符合条件的全部 value ) + * @param value Value + */ + @Override + public void unselectValueAll(final V value) { + Iterator iterator = mMapSelects.values().iterator(); + while (iterator.hasNext()) { + V v = iterator.next(); + if (v == value) { + iterator.remove(); + } + } + } + + // ===================== + // = IMultiSelectToMap = + // ===================== + + /** + * 判断是否选中 ( 如果未选中, 则设置为选中 ) + * @param key Key + * @param value Value + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelect( + final K key, + final V value + ) { + if (!isSelectKey(key)) { + mMapSelects.put(key, value); + return false; + } + return true; + } + + /** + * 判断是否选中 ( 通过 key 判断 ) + * @param key Key + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean isSelectKey(final K key) { + return mMapSelects.containsKey(key); + } + + /** + * 设置选中操作 + * @param key Key + * @param value Value + */ + @Override + public void select( + final K key, + final V value + ) { + mMapSelects.put(key, value); + } + + /** + * 设置选中操作 + * @param isSelect 是否选中 + * @param key Key + * @param value Value + */ + @Override + public void select( + final boolean isSelect, + final K key, + final V value + ) { + if (isSelect) { + select(key, value); + } else { + unselect(key); + } + } + + /** + * 设置非选中操作 + * @param key Key + */ + @Override + public void unselect(final K key) { + mMapSelects.remove(key); + } + + /** + * 切换选中状态 + *
+     *     如果选中, 则设为非选中, 反之设为选中
+     * 
+ * @param key Key + * @param value Value + */ + @Override + public void toggle( + final K key, + final V value + ) { + if (isSelectKey(key)) { + mMapSelects.remove(key); + } else { + mMapSelects.put(key, value); + } + } + + // =============== + // = 获取选中的数据 = + // =============== + + // ========= + // = Value = + // ========= + + /** + * 通过 key 获取 value + * @param key Key + * @return Value + */ + @Override + public V getSelectValue(final K key) { + return mMapSelects.get(key); + } + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + @Override + public List getSelectValues() { + return new ArrayList<>(mMapSelects.values()); + } + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + @Override + public List getSelectValuesToReverse() { + List lists = getSelectValues(); + Collections.reverse(lists); + return lists; + } + + // ======= + // = Key = + // ======= + + /** + * 通过 value 获取 key + * @param value Value + * @return Key + */ + @Override + public K getSelectKey(final V value) { + // 进行循环遍历获取 + for (Map.Entry entry : mMapSelects.entrySet()) { + V v = entry.getValue(); + // 判断是否符合对应的 value + if (v == value) { + return entry.getKey(); + } + } + return null; + } + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + @Override + public List getSelectKeys() { + return new ArrayList<>(mMapSelects.keySet()); + } + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + @Override + public List getSelectKeysToReverse() { + List lists = getSelectKeys(); + Collections.reverse(lists); + return lists; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelect.java b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelect.java new file mode 100644 index 0000000000..ffc1cf93b6 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelect.java @@ -0,0 +1,68 @@ +package dev.base.multiselect; + +import java.util.Collection; + +import dev.base.DevEntry; + +/** + * detail: 多选操作接口 ( 基类 ) + * @param 集合泛型 + * @param Value + * @author Ttt + */ +public interface IMultiSelect { + + /** + * 清空全部选中数据 + */ + void clearSelects(); + + /** + * 获取选中的数据条数 + * @return 选中的数据条数 + */ + int getSelectSize(); + + /** + * 获取选中的数据集合 + * @return 选中的数据集合 + */ + CollectionT getSelects(); + + /** + * 通过集合添加选中数据 + * @param collection 集合泛型 + */ + void putSelects(CollectionT collection); + + /** + * 通过集合添加选中数据 + * @param collections 集合 + */ + void putSelects(Collection> collections); + + /** + * 判断是否存在选中的数据 + * @return {@code true} yes, {@code false} no + */ + boolean isSelect(); + + /** + * 判断是否选中 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + boolean isSelectValue(V value); + + /** + * 设置非选中 + * @param value Value + */ + void unselectValue(V value); + + /** + * 设置非选中 ( 符合条件的全部 value ) + * @param value Value + */ + void unselectValueAll(V value); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectEdit.java b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectEdit.java new file mode 100644 index 0000000000..ffe07dcb5e --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectEdit.java @@ -0,0 +1,96 @@ +package dev.base.multiselect; + +/** + * detail: 多选编辑接口 + * @param 泛型 + * @author Ttt + *
+ *     实现该接口, 对外支持快捷操作方法
+ *     内部通过 {@link IMultiSelectToList}、{@link IMultiSelectToMap} 实现多选操作功能
+ * 
+ */ +public interface IMultiSelectEdit { + + // ========== + // = 编辑状态 = + // ========== + + /** + * 是否编辑状态 + * @return {@code true} yes, {@code false} no + */ + boolean isEditState(); + + /** + * 设置编辑状态 + * @param isEdit {@code true} yes, {@code false} no + * @return {@link R} + */ + R setEditState(boolean isEdit); + + /** + * 切换编辑状态 + * @return {@link R} + */ + R toggleEditState(); + + // ========== + // = 选择操作 = + // ========== + + /** + * 全选 + * @return {@link R} + */ + R selectAll(); + + /** + * 清空全选 ( 非全选 ) + * @return {@link R} + */ + R clearSelectAll(); + + /** + * 反选 + * @return {@link R} + */ + R inverseSelect(); + + // ========== + // = 判断处理 = + // ========== + + /** + * 判断是否全选 + * @return {@code true} yes, {@code false} no + */ + boolean isSelectAll(); + + /** + * 判断是否存在选中的数据 + * @return {@code true} yes, {@code false} no + */ + boolean isSelect(); + + /** + * 判断是否不存在选中的数据 + * @return {@code true} yes, {@code false} no + */ + boolean isNotSelect(); + + // ======== + // = 数量值 = + // ======== + + /** + * 获取选中的数据条数 + * @return 选中的数据条数 + */ + int getSelectSize(); + + /** + * 获取数据总数 + * @return 数据总数 + */ + int getDataCount(); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToList.java b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToList.java new file mode 100644 index 0000000000..2eb9860518 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToList.java @@ -0,0 +1,106 @@ +package dev.base.multiselect; + +import java.util.List; + +/** + * detail: 多选操作接口 ( List ) + * @param 集合泛型 + * @param Value + * @author Ttt + */ +public interface IMultiSelectToList + extends IMultiSelect { + + /** + * 判断是否选中 ( 通过 value 判断 ) + * @param value Value + * @return {@code true} yes, {@code false} no + */ + boolean isSelect(V value); + + // = + + /** + * 设置选中操作 + * @param value Value + */ + void select(V value); + + /** + * 设置选中操作 + * @param isSelect 是否选中 + * @param value Value + */ + void select( + boolean isSelect, + V value + ); + + /** + * 设置选中操作 ( 保存到指定索引 ) + * @param value Value + * @param position 索引 + */ + void select( + V value, + int position + ); + + // = + + /** + * 设置非选中操作 + * @param position 索引 + * @return Value + */ + V unselect(int position); + + /** + * 设置非选中操作 + * @param value Value + */ + void unselect(V value); + + // = + + /** + * 切换选中状态 + *
+     *     如果选中, 则设为非选中, 反之设为选中
+     * 
+ * @param value Value + */ + void toggle(V value); + + // =============== + // = 获取选中的数据 = + // =============== + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + List getSelectValues(); + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + List getSelectValuesToReverse(); + + // = + + /** + * 获取选中的数据 + * @param position 索引 + * @return Value + */ + V getSelectValue(int position); + + /** + * 获取选中的数据所在的索引 + * @param value Value + * @return value position + */ + int getSelectValueToPosition(V value); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToMap.java b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToMap.java new file mode 100644 index 0000000000..8183d9a4e3 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/multiselect/IMultiSelectToMap.java @@ -0,0 +1,130 @@ +package dev.base.multiselect; + +import java.util.List; +import java.util.Map; + +/** + * detail: 多选操作接口 ( Map ) + * @param 集合泛型 + * @param Key + * @param Value + * @author Ttt + */ +public interface IMultiSelectToMap + extends IMultiSelect { + + /** + * 判断是否选中 ( 如果未选中, 则设置为选中 ) + * @param key Key + * @param value Value + * @return {@code true} yes, {@code false} no + */ + boolean isSelect( + K key, + V value + ); + + /** + * 判断是否选中 ( 通过 key 判断 ) + * @param key Key + * @return {@code true} yes, {@code false} no + */ + boolean isSelectKey(K key); + + // = + + /** + * 设置选中操作 + * @param key Key + * @param value Value + */ + void select( + K key, + V value + ); + + /** + * 设置选中操作 + * @param isSelect 是否选中 + * @param key Key + * @param value Value + */ + void select( + boolean isSelect, + K key, + V value + ); + + // = + + /** + * 设置非选中操作 + * @param key Key + */ + void unselect(K key); + + // = + + /** + * 切换选中状态 + *
+     *     如果选中, 则设为非选中, 反之设为选中
+     * 
+ * @param key Key + * @param value Value + */ + void toggle( + K key, + V value + ); + + // =============== + // = 获取选中的数据 = + // =============== + + // ========= + // = Value = + // ========= + + /** + * 通过 key 获取 value + * @param key Key + * @return Value + */ + V getSelectValue(K key); + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + List getSelectValues(); + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + List getSelectValuesToReverse(); + + // ======= + // = Key = + // ======= + + /** + * 通过 value 获取 key + * @param value Value + * @return Key + */ + K getSelectKey(V value); + + /** + * 获取选中的数据集合 + * @return {@link List} + */ + List getSelectKeys(); + + /** + * 获取选中的数据集合 ( 倒序 ) + * @return {@link List} + */ + List getSelectKeysToReverse(); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/number/INumberListener.java b/lib/DevAssist/src/main/java/dev/base/number/INumberListener.java new file mode 100644 index 0000000000..5237903368 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/number/INumberListener.java @@ -0,0 +1,31 @@ +package dev.base.number; + +/** + * detail: 数量监听事件接口 + * @author Ttt + */ +public interface INumberListener { + + /** + * 数量准备变化通知 + * @param isAdd 是否增加 + * @param curNumber 当前数量 + * @param afterNumber 处理之后的数量 + * @return {@code true} allow, {@code false} prohibit + */ + boolean onPrepareChanged( + boolean isAdd, + int curNumber, + int afterNumber + ); + + /** + * 数量变化通知 + * @param isAdd 是否增加 + * @param curNumber 当前数量 + */ + void onNumberChanged( + boolean isAdd, + int curNumber + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/number/INumberOperate.java b/lib/DevAssist/src/main/java/dev/base/number/INumberOperate.java new file mode 100644 index 0000000000..7a5b22a426 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/number/INumberOperate.java @@ -0,0 +1,202 @@ +package dev.base.number; + +/** + * detail: 数量操作接口 + * @param 泛型 + * @author Ttt + */ +public interface INumberOperate { + + /** + * 判断当前数量是否等于最小值 + * @return {@code true} yes, {@code false} no + */ + boolean isMinNumber(); + + /** + * 判断数量是否等于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isMinNumber(int number); + + /** + * 判断数量是否小于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isLessThanMinNumber(int number); + + /** + * 判断数量是否大于最小值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isGreaterThanMinNumber(int number); + + // = + + /** + * 判断当前数量是否等于最大值 + * @return {@code true} yes, {@code false} no + */ + boolean isMaxNumber(); + + /** + * 判断数量是否等于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isMaxNumber(int number); + + /** + * 判断数量是否小于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isLessThanMaxNumber(int number); + + /** + * 判断数量是否大于最大值 + * @param number Number + * @return {@code true} yes, {@code false} no + */ + boolean isGreaterThanMaxNumber(int number); + + // =========== + // = get/set = + // =========== + + /** + * 获取最小值 + * @return 最小值 + */ + int getMinNumber(); + + /** + * 设置最小值 + * @param minNumber 最小值 + * @return R 泛型返回对象 + */ + R setMinNumber(int minNumber); + + // = + + /** + * 获取最大值 + * @return 最大值 + */ + int getMaxNumber(); + + /** + * 设置最大值 + * @param maxNumber 最大值 + * @return R 泛型返回对象 + */ + R setMaxNumber(int maxNumber); + + /** + * 设置最小值、最大值 + * @param minNumber 最小值 + * @param maxNumber 最大值 + * @return R 泛型返回对象 + */ + R setMinMaxNumber( + int minNumber, + int maxNumber + ); + + // = + + /** + * 获取当前数量 + * @return 当前数量 + */ + int getCurrentNumber(); + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @return R 泛型返回对象 + */ + R setCurrentNumber(int currentNumber); + + /** + * 设置当前数量 + * @param currentNumber 当前数量 + * @param isTriggerListener 是否触发事件 + * @return R 泛型返回对象 + */ + R setCurrentNumber( + int currentNumber, + boolean isTriggerListener + ); + + // = + + /** + * 获取重置数量 + * @return 重置数量 + */ + int getResetNumber(); + + /** + * 设置重置数量 + * @param resetNumber 重置数量 + * @return R 泛型返回对象 + */ + R setResetNumber(int resetNumber); + + // = + + /** + * 获取是否允许设置为负数 + * @return {@code true} yes, {@code false} no + */ + boolean isAllowNegative(); + + /** + * 设置是否允许设置为负数 + * @param allowNegative {@code true} yes, {@code false} no + * @return R 泛型返回对象 + */ + R setAllowNegative(boolean allowNegative); + + // = + + /** + * 数量改变通知 + * @param number 变化基数数量 + * @return R 泛型返回对象 + */ + R numberChange(int number); + + /** + * 添加数量 ( 默认累加 1 ) + * @return R 泛型返回对象 + */ + R addNumber(); + + /** + * 减少数量 ( 默认累减 1 ) + * @return R 泛型返回对象 + */ + R subtractionNumber(); + + // ======= + // = 事件 = + // ======= + + /** + * 获取数量监听事件接口 + * @return {@link INumberListener} + */ + INumberListener getNumberListener(); + + /** + * 设置数量监听事件接口 + * @param iNumberListener {@link INumberListener} + * @return R 泛型返回对象 + */ + R setNumberListener(INumberListener iNumberListener); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/state/CommonState.java b/lib/DevAssist/src/main/java/dev/base/state/CommonState.java new file mode 100644 index 0000000000..07ab575977 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/state/CommonState.java @@ -0,0 +1,677 @@ +package dev.base.state; + +import dev.base.DevState; +import dev.utils.DevFinal; + +/** + * detail: 通用状态类 + * @author Ttt + */ +public class CommonState { + + // State Object + private final DevState mState = new DevState<>(); + + public CommonState() { + setNormal(); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取操作类型 + * @return 操作类型 + */ + public T getType() { + return mState.getObject(); + } + + /** + * 设置操作类型 + * @param type 操作类型 + * @return {@link CommonState} + */ + public CommonState setType(final T type) { + mState.setObject(type); + return this; + } + + /** + * 判断操作类型是否一致 + * @param type 待校验操作类型 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsType(final T type) { + return mState.equalsObject(type); + } + + // = + + /** + * 获取操作 UUID + * @return 操作 UUID + */ + public long getUUID() { + return mState.getTokenUUID(); + } + + /** + * 获取操作 UUID ( 随机生成并赋值 ) + * @return 操作 UUID + */ + public long randomUUID() { + return mState.randomTokenUUID(); + } + + /** + * 判断 UUID 是否一致 + * @param uuid 待校验操作 UUID + * @return {@code true} yes, {@code false} no + */ + public boolean equalsUUID(final long uuid) { + return mState.equalsTokenUUID(uuid); + } + + // = + + /** + * 获取 State + * @return State + */ + public int getState() { + return mState.getState(); + } + + /** + * 设置 State + * @param state State + * @return {@link CommonState} + */ + public CommonState setState(final int state) { + mState.setState(state); + return this; + } + + /** + * 判断 State 是否一致 + * @param state 待校验 State + * @return {@code true} yes, {@code false} no + */ + public boolean equalsState(final int state) { + return mState.equalsState(state); + } + + // ========== + // = 快捷方法 = + // ========== + + // ======= + // = get = + // ======= + + /** + * 判断是否默认状态 ( 暂未进行操作 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isNormal() { + return equalsState(DevFinal.INT.NORMAL); + } + + /** + * 判断是否操作中 + * @return {@code true} yes, {@code false} no + */ + public boolean isIng() { + return equalsState(DevFinal.INT.ING); + } + + /** + * 判断是否操作成功 + * @return {@code true} yes, {@code false} no + */ + public boolean isSuccess() { + return equalsState(DevFinal.INT.SUCCESS); + } + + /** + * 判断是否操作失败 + * @return {@code true} yes, {@code false} no + */ + public boolean isFail() { + return equalsState(DevFinal.INT.FAIL); + } + + /** + * 判断是否操作异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isError() { + return equalsState(DevFinal.INT.ERROR); + } + + /** + * 判断是否开始操作 + * @return {@code true} yes, {@code false} no + */ + public boolean isStart() { + return equalsState(DevFinal.INT.START); + } + + /** + * 判断是否重新开始操作 + * @return {@code true} yes, {@code false} no + */ + public boolean isRestart() { + return equalsState(DevFinal.INT.RESTART); + } + + /** + * 判断是否操作结束 + * @return {@code true} yes, {@code false} no + */ + public boolean isEnd() { + return equalsState(DevFinal.INT.END); + } + + /** + * 判断是否操作暂停 + * @return {@code true} yes, {@code false} no + */ + public boolean isPause() { + return equalsState(DevFinal.INT.PAUSE); + } + + /** + * 判断是否操作恢复 ( 继续 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isResume() { + return equalsState(DevFinal.INT.RESUME); + } + + /** + * 判断是否操作停止 + * @return {@code true} yes, {@code false} no + */ + public boolean isStop() { + return equalsState(DevFinal.INT.STOP); + } + + /** + * 判断是否操作取消 + * @return {@code true} yes, {@code false} no + */ + public boolean isCancel() { + return equalsState(DevFinal.INT.CANCEL); + } + + /** + * 判断是否创建 + * @return {@code true} yes, {@code false} no + */ + public boolean isCreate() { + return equalsState(DevFinal.INT.CREATE); + } + + /** + * 判断是否销毁 + * @return {@code true} yes, {@code false} no + */ + public boolean isDestroy() { + return equalsState(DevFinal.INT.DESTROY); + } + + /** + * 判断是否回收 + * @return {@code true} yes, {@code false} no + */ + public boolean isRecycle() { + return equalsState(DevFinal.INT.RECYCLE); + } + + /** + * 判断是否初始化 + * @return {@code true} yes, {@code false} no + */ + public boolean isInit() { + return equalsState(DevFinal.INT.INIT); + } + + /** + * 判断是否已打开 + * @return {@code true} yes, {@code false} no + */ + public boolean isEnabled() { + return equalsState(DevFinal.INT.ENABLED); + } + + /** + * 判断是否正在打开 + * @return {@code true} yes, {@code false} no + */ + public boolean isEnabling() { + return equalsState(DevFinal.INT.ENABLING); + } + + /** + * 判断是否已关闭 + * @return {@code true} yes, {@code false} no + */ + public boolean isDisabled() { + return equalsState(DevFinal.INT.DISABLED); + } + + /** + * 判断是否正在关闭 + * @return {@code true} yes, {@code false} no + */ + public boolean isDisabling() { + return equalsState(DevFinal.INT.DISABLING); + } + + /** + * 判断是否连接成功 + * @return {@code true} yes, {@code false} no + */ + public boolean isConnected() { + return equalsState(DevFinal.INT.CONNECTED); + } + + /** + * 判断是否连接中 + * @return {@code true} yes, {@code false} no + */ + public boolean isConnecting() { + return equalsState(DevFinal.INT.CONNECTING); + } + + /** + * 判断是否连接失败、断开 + * @return {@code true} yes, {@code false} no + */ + public boolean isDisconnected() { + return equalsState(DevFinal.INT.DISCONNECTED); + } + + /** + * 判断是否暂停、延迟 + * @return {@code true} yes, {@code false} no + */ + public boolean isSuspended() { + return equalsState(DevFinal.INT.SUSPENDED); + } + + /** + * 判断是否未知 + * @return {@code true} yes, {@code false} no + */ + public boolean isUnknown() { + return equalsState(DevFinal.INT.UNKNOWN); + } + + /** + * 判断是否新增 + * @return {@code true} yes, {@code false} no + */ + public boolean isInsert() { + return equalsState(DevFinal.INT.INSERT); + } + + /** + * 判断是否删除 + * @return {@code true} yes, {@code false} no + */ + public boolean isDelete() { + return equalsState(DevFinal.INT.DELETE); + } + + /** + * 判断是否更新 + * @return {@code true} yes, {@code false} no + */ + public boolean isUpdate() { + return equalsState(DevFinal.INT.UPDATE); + } + + /** + * 判断是否查询 + * @return {@code true} yes, {@code false} no + */ + public boolean isSelect() { + return equalsState(DevFinal.INT.SELECT); + } + + /** + * 判断是否加密 + * @return {@code true} yes, {@code false} no + */ + public boolean isEncrypt() { + return equalsState(DevFinal.INT.ENCRYPT); + } + + /** + * 判断是否解密 + * @return {@code true} yes, {@code false} no + */ + public boolean isDecrypt() { + return equalsState(DevFinal.INT.DECRYPT); + } + + /** + * 判断是否重置 + * @return {@code true} yes, {@code false} no + */ + public boolean isReset() { + return equalsState(DevFinal.INT.RESET); + } + + /** + * 判断是否关闭 + * @return {@code true} yes, {@code false} no + */ + public boolean isClose() { + return equalsState(DevFinal.INT.CLOSE); + } + + /** + * 判断是否打开 + * @return {@code true} yes, {@code false} no + */ + public boolean isOpen() { + return equalsState(DevFinal.INT.OPEN); + } + + /** + * 判断是否退出 + * @return {@code true} yes, {@code false} no + */ + public boolean isExit() { + return equalsState(DevFinal.INT.EXIT); + } + + // ======= + // = set = + // ======= + + /** + * 设置状态为默认状态 ( 暂未进行操作 ) + * @return {@link CommonState} + */ + public CommonState setNormal() { + return setState(DevFinal.INT.NORMAL); + } + + /** + * 设置状态为操作中 + * @return {@link CommonState} + */ + public CommonState setIng() { + return setState(DevFinal.INT.ING); + } + + /** + * 设置状态为操作成功 + * @return {@link CommonState} + */ + public CommonState setSuccess() { + return setState(DevFinal.INT.SUCCESS); + } + + /** + * 设置状态为操作失败 + * @return {@link CommonState} + */ + public CommonState setFail() { + return setState(DevFinal.INT.FAIL); + } + + /** + * 设置状态为操作异常 + * @return {@link CommonState} + */ + public CommonState setError() { + return setState(DevFinal.INT.ERROR); + } + + /** + * 设置状态为开始操作 + * @return {@link CommonState} + */ + public CommonState setStart() { + return setState(DevFinal.INT.START); + } + + /** + * 设置状态为重新开始操作 + * @return {@link CommonState} + */ + public CommonState setRestart() { + return setState(DevFinal.INT.RESTART); + } + + /** + * 设置状态为操作结束 + * @return {@link CommonState} + */ + public CommonState setEnd() { + return setState(DevFinal.INT.END); + } + + /** + * 设置状态为操作暂停 + * @return {@link CommonState} + */ + public CommonState setPause() { + return setState(DevFinal.INT.PAUSE); + } + + /** + * 设置状态为操作恢复 ( 继续 ) + * @return {@link CommonState} + */ + public CommonState setResume() { + return setState(DevFinal.INT.RESUME); + } + + /** + * 设置状态为操作停止 + * @return {@link CommonState} + */ + public CommonState setStop() { + return setState(DevFinal.INT.STOP); + } + + /** + * 设置状态为操作取消 + * @return {@link CommonState} + */ + public CommonState setCancel() { + return setState(DevFinal.INT.CANCEL); + } + + /** + * 设置状态为创建 + * @return {@link CommonState} + */ + public CommonState setCreate() { + return setState(DevFinal.INT.CREATE); + } + + /** + * 设置状态为销毁 + * @return {@link CommonState} + */ + public CommonState setDestroy() { + return setState(DevFinal.INT.DESTROY); + } + + /** + * 设置状态为回收 + * @return {@link CommonState} + */ + public CommonState setRecycle() { + return setState(DevFinal.INT.RECYCLE); + } + + /** + * 设置状态为初始化 + * @return {@link CommonState} + */ + public CommonState setInit() { + return setState(DevFinal.INT.INIT); + } + + /** + * 设置状态为已打开 + * @return {@link CommonState} + */ + public CommonState setEnabled() { + return setState(DevFinal.INT.ENABLED); + } + + /** + * 设置状态为正在打开 + * @return {@link CommonState} + */ + public CommonState setEnabling() { + return setState(DevFinal.INT.ENABLING); + } + + /** + * 设置状态为已关闭 + * @return {@link CommonState} + */ + public CommonState setDisabled() { + return setState(DevFinal.INT.DISABLED); + } + + /** + * 设置状态为正在关闭 + * @return {@link CommonState} + */ + public CommonState setDisabling() { + return setState(DevFinal.INT.DISABLING); + } + + /** + * 设置状态为连接成功 + * @return {@link CommonState} + */ + public CommonState setConnected() { + return setState(DevFinal.INT.CONNECTED); + } + + /** + * 设置状态为连接中 + * @return {@link CommonState} + */ + public CommonState setConnecting() { + return setState(DevFinal.INT.CONNECTING); + } + + /** + * 设置状态为连接失败、断开 + * @return {@link CommonState} + */ + public CommonState setDisconnected() { + return setState(DevFinal.INT.DISCONNECTED); + } + + /** + * 设置状态为暂停、延迟 + * @return {@link CommonState} + */ + public CommonState setSuspended() { + return setState(DevFinal.INT.SUSPENDED); + } + + /** + * 设置状态为未知 + * @return {@link CommonState} + */ + public CommonState setUnknown() { + return setState(DevFinal.INT.UNKNOWN); + } + + /** + * 设置状态为新增 + * @return {@link CommonState} + */ + public CommonState setInsert() { + return setState(DevFinal.INT.INSERT); + } + + /** + * 设置状态为删除 + * @return {@link CommonState} + */ + public CommonState setDelete() { + return setState(DevFinal.INT.DELETE); + } + + /** + * 设置状态为更新 + * @return {@link CommonState} + */ + public CommonState setUpdate() { + return setState(DevFinal.INT.UPDATE); + } + + /** + * 设置状态为查询 + * @return {@link CommonState} + */ + public CommonState setSelect() { + return setState(DevFinal.INT.SELECT); + } + + /** + * 设置状态为加密 + * @return {@link CommonState} + */ + public CommonState setEncrypt() { + return setState(DevFinal.INT.ENCRYPT); + } + + /** + * 设置状态为解密 + * @return {@link CommonState} + */ + public CommonState setDecrypt() { + return setState(DevFinal.INT.DECRYPT); + } + + /** + * 设置状态为重置 + * @return {@link CommonState} + */ + public CommonState setReset() { + return setState(DevFinal.INT.RESET); + } + + /** + * 设置状态为关闭 + * @return {@link CommonState} + */ + public CommonState setClose() { + return setState(DevFinal.INT.CLOSE); + } + + /** + * 设置状态为打开 + * @return {@link CommonState} + */ + public CommonState setOpen() { + return setState(DevFinal.INT.OPEN); + } + + /** + * 设置状态为退出 + * @return {@link CommonState} + */ + public CommonState setExit() { + return setState(DevFinal.INT.EXIT); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/base/state/RequestState.java b/lib/DevAssist/src/main/java/dev/base/state/RequestState.java new file mode 100644 index 0000000000..42a7cd1de8 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/base/state/RequestState.java @@ -0,0 +1,309 @@ +package dev.base.state; + +import dev.base.DevState; +import dev.utils.DevFinal; + +/** + * detail: 请求状态类 + * @author Ttt + */ +public class RequestState { + + // State Object + private final DevState mState = new DevState<>(); + + public RequestState() { + setRequestNormal(); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取请求类型 + * @return 请求类型 + */ + public T getType() { + return mState.getObject(); + } + + /** + * 设置请求类型 + * @param type 请求类型 + * @return {@link RequestState} + */ + public RequestState setType(final T type) { + mState.setObject(type); + return this; + } + + /** + * 判断请求类型是否一致 + * @param type 待校验请求类型 + * @return {@code true} yes, {@code false} no + */ + public boolean equalsType(final T type) { + return mState.equalsObject(type); + } + + // = + + /** + * 获取请求 UUID + * @return 请求 UUID + */ + public long getRequestUUID() { + return mState.getTokenUUID(); + } + + /** + * 获取请求 UUID ( 随机生成并赋值 ) + * @return 请求 UUID + */ + public long randomRequestUUID() { + return mState.randomTokenUUID(); + } + + /** + * 判断 UUID 是否一致 + * @param uuid 待校验请求 UUID + * @return {@code true} yes, {@code false} no + */ + public boolean equalsRequestUUID(final long uuid) { + return mState.equalsTokenUUID(uuid); + } + + // = + + /** + * 获取 State + * @return State + */ + public int getState() { + return mState.getState(); + } + + /** + * 设置 State + * @param state State + * @return {@link RequestState} + */ + public RequestState setState(final int state) { + mState.setState(state); + return this; + } + + /** + * 判断 State 是否一致 + * @param state 待校验 State + * @return {@code true} yes, {@code false} no + */ + public boolean equalsState(final int state) { + return mState.equalsState(state); + } + + // ========== + // = 快捷方法 = + // ========== + + // ======= + // = get = + // ======= + + /** + * 判断是否默认状态 ( 暂未进行操作 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestNormal() { + return equalsState(DevFinal.INT.REQUEST_NORMAL); + } + + /** + * 判断是否请求中 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestIng() { + return equalsState(DevFinal.INT.REQUEST_ING); + } + + /** + * 判断是否请求成功 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestSuccess() { + return equalsState(DevFinal.INT.REQUEST_SUCCESS); + } + + /** + * 判断是否请求失败 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestFail() { + return equalsState(DevFinal.INT.REQUEST_FAIL); + } + + /** + * 判断是否请求异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestError() { + return equalsState(DevFinal.INT.REQUEST_ERROR); + } + + /** + * 判断是否请求开始 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestStart() { + return equalsState(DevFinal.INT.REQUEST_START); + } + + /** + * 判断是否重新请求 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestRestart() { + return equalsState(DevFinal.INT.REQUEST_RESTART); + } + + /** + * 判断是否请求结束 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestEnd() { + return equalsState(DevFinal.INT.REQUEST_END); + } + + /** + * 判断是否请求暂停 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestPause() { + return equalsState(DevFinal.INT.REQUEST_PAUSE); + } + + /** + * 判断是否请求恢复 ( 继续 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestResume() { + return equalsState(DevFinal.INT.REQUEST_RESUME); + } + + /** + * 判断是否请求停止 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestStop() { + return equalsState(DevFinal.INT.REQUEST_STOP); + } + + /** + * 判断是否请求取消 + * @return {@code true} yes, {@code false} no + */ + public boolean isRequestCancel() { + return equalsState(DevFinal.INT.REQUEST_CANCEL); + } + + // ======= + // = set = + // ======= + + /** + * 设置状态为默认状态 ( 暂未进行操作 ) + * @return {@link RequestState} + */ + public RequestState setRequestNormal() { + return setState(DevFinal.INT.REQUEST_NORMAL); + } + + /** + * 设置状态为请求中 + * @return {@link RequestState} + */ + public RequestState setRequestIng() { + return setState(DevFinal.INT.REQUEST_ING); + } + + /** + * 设置状态为请求成功 + * @return {@link RequestState} + */ + public RequestState setRequestSuccess() { + return setState(DevFinal.INT.REQUEST_SUCCESS); + } + + /** + * 设置状态为请求失败 + * @return {@link RequestState} + */ + public RequestState setRequestFail() { + return setState(DevFinal.INT.REQUEST_FAIL); + } + + /** + * 设置状态为请求异常 + * @return {@link RequestState} + */ + public RequestState setRequestError() { + return setState(DevFinal.INT.REQUEST_ERROR); + } + + /** + * 设置状态为请求开始 + * @return {@link RequestState} + */ + public RequestState setRequestStart() { + return setState(DevFinal.INT.REQUEST_START); + } + + /** + * 设置状态为重新请求 + * @return {@link RequestState} + */ + public RequestState setRequestRestart() { + return setState(DevFinal.INT.REQUEST_RESTART); + } + + /** + * 设置状态为请求结束 + * @return {@link RequestState} + */ + public RequestState setRequestEnd() { + return setState(DevFinal.INT.REQUEST_END); + } + + /** + * 设置状态为请求暂停 + * @return {@link RequestState} + */ + public RequestState setRequestPause() { + return setState(DevFinal.INT.REQUEST_PAUSE); + } + + /** + * 设置状态为请求恢复 ( 继续 ) + * @return {@link RequestState} + */ + public RequestState setRequestResume() { + return setState(DevFinal.INT.REQUEST_RESUME); + } + + /** + * 设置状态为请求停止 + * @return {@link RequestState} + */ + public RequestState setRequestStop() { + return setState(DevFinal.INT.REQUEST_STOP); + } + + /** + * 设置状态为请求取消 + * @return {@link RequestState} + */ + public RequestState setRequestCancel() { + return setState(DevFinal.INT.REQUEST_CANCEL); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/BaseCallback.java b/lib/DevAssist/src/main/java/dev/callback/BaseCallback.java new file mode 100644 index 0000000000..e0c5b95f92 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/BaseCallback.java @@ -0,0 +1,25 @@ +package dev.callback; + +import dev.base.DevObject; + +/** + * detail: 回调基类 + * @author Ttt + */ +public class BaseCallback + extends DevObject { + + public BaseCallback() { + } + + public BaseCallback(final T object) { + super(object); + } + + public BaseCallback( + final T object, + final Object tag + ) { + super(object, tag); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/DevCallback.java b/lib/DevAssist/src/main/java/dev/callback/DevCallback.java new file mode 100644 index 0000000000..59740b73ef --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/DevCallback.java @@ -0,0 +1,86 @@ +package dev.callback; + +/** + * detail: Dev 通用回调 + * @author Ttt + */ +public class DevCallback + extends BaseCallback { + + public DevCallback() { + } + + public DevCallback(final T object) { + super(object); + } + + public DevCallback( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 通用方法 = + // ========== + + /** + * 回调方法 + */ + public void callback() { + } + + /** + * 回调方法 + * @param value 回调值 + */ + public void callback(T value) { + } + + /** + * 回调方法 + * @param value 回调值 + * @param param 回调参数 + */ + public void callback( + T value, + int param + ) { + } + + // ========== + // = 过滤方法 = + // ========== + + /** + * 过滤处理 + * @param value 待处理值 + * @return 过滤处理的值 + */ + public T filter(T value) { + return value; + } + + /** + * 判断是否过滤 + * @param value 待判断值 + * @return {@code true} yes, {@code false} no + */ + public boolean isFilter(T value) { + return false; + } + + /** + * 对比判断 + * @param value 待对比值 + * @param value1 待对比值 + * @return {@code true} yes, {@code false} no + */ + public boolean compare( + T value, + T value1 + ) { + return false; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/DevClickCallback.java b/lib/DevAssist/src/main/java/dev/callback/DevClickCallback.java new file mode 100644 index 0000000000..27d7f4116b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/DevClickCallback.java @@ -0,0 +1,77 @@ +package dev.callback; + +/** + * detail: 通用 Click 回调 + * @author Ttt + */ +public class DevClickCallback + extends BaseCallback { + + public DevClickCallback() { + } + + public DevClickCallback(final T object) { + super(object); + } + + public DevClickCallback( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 通用方法 = + // ========== + + /** + * 点击回调 + */ + public void onClick() { + } + + /** + * 点击回调 + * @param value 回调值 + */ + public void onClick(T value) { + } + + /** + * 点击回调 + * @param value 回调值 + * @param param 回调参数 + */ + public void onClick( + T value, + int param + ) { + } + + // = + + /** + * 长按回调 + */ + public void onLongClick() { + } + + /** + * 长按回调 + * @param value 回调值 + */ + public void onLongClick(T value) { + } + + /** + * 长按回调 + * @param value 回调值 + * @param param 回调参数 + */ + public void onLongClick( + T value, + int param + ) { + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/DevDialogCallback.java b/lib/DevAssist/src/main/java/dev/callback/DevDialogCallback.java new file mode 100644 index 0000000000..11702cd6c9 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/DevDialogCallback.java @@ -0,0 +1,77 @@ +package dev.callback; + +/** + * detail: 通用 Dialog 回调 + * @author Ttt + */ +public class DevDialogCallback + extends DevClickCallback { + + public DevDialogCallback() { + } + + public DevDialogCallback(final T object) { + super(object); + } + + public DevDialogCallback( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 通知方法 = + // ========== + + /** + * 特殊通知 + */ + public void onDialogNotify() { + } + + /** + * show 通知 + */ + public void onDialogShow() { + } + + /** + * dismiss 通知 + */ + public void onDialogDismiss() { + } + + // = + + /** + * start 通知 + */ + public void onDialogStart() { + } + + /** + * resume 通知 + */ + public void onDialogResume() { + } + + /** + * pause 通知 + */ + public void onDialogPause() { + } + + /** + * stop 通知 + */ + public void onDialogStop() { + } + + /** + * destroy 通知 + */ + public void onDialogDestroy() { + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/DevItemClickCallback.java b/lib/DevAssist/src/main/java/dev/callback/DevItemClickCallback.java new file mode 100644 index 0000000000..29aa051f1f --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/DevItemClickCallback.java @@ -0,0 +1,77 @@ +package dev.callback; + +/** + * detail: 通用 Item Click 回调 + * @author Ttt + */ +public class DevItemClickCallback + extends BaseCallback { + + public DevItemClickCallback() { + } + + public DevItemClickCallback(final T object) { + super(object); + } + + public DevItemClickCallback( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 通用方法 = + // ========== + + /** + * 点击 Item 回调 + */ + public void onItemClick() { + } + + /** + * 点击 Item 回调 + * @param value 回调值 + */ + public void onItemClick(T value) { + } + + /** + * 点击 Item 回调 + * @param value 回调值 + * @param param 回调参数 + */ + public void onItemClick( + T value, + int param + ) { + } + + // = + + /** + * 长按 Item 回调 + */ + public void onItemLongClick() { + } + + /** + * 长按 Item 回调 + * @param value 回调值 + */ + public void onItemLongClick(T value) { + } + + /** + * 长按 Item 回调 + * @param value 回调值 + * @param param 回调参数 + */ + public void onItemLongClick( + T value, + int param + ) { + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/callback/DevResultCallback.java b/lib/DevAssist/src/main/java/dev/callback/DevResultCallback.java new file mode 100644 index 0000000000..51118374d2 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/callback/DevResultCallback.java @@ -0,0 +1,58 @@ +package dev.callback; + +/** + * detail: 通用结果回调类 + * @author Ttt + */ +public abstract class DevResultCallback + extends BaseCallback { + + public DevResultCallback() { + } + + public DevResultCallback(final T object) { + super(object); + } + + public DevResultCallback( + final T object, + final Object tag + ) { + super(object, tag); + } + + // ========== + // = 结果回调 = + // ========== + + /** + * 结果回调通知 + * @param data 返回数据 + * @param message 返回信息 + * @param value 返回值 + */ + public abstract void onResult( + String data, + String message, + T value + ); + + /** + * 异常回调通知 + * @param error 异常信息 + * @param 泛型 + */ + public void onError(E error) { + } + + /** + * 失败回调通知 + * @param fail 失败信息 + * @param errorCode 错误 code + */ + public void onFailure( + String fail, + String errorCode + ) { + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/DevEngineAssist.java b/lib/DevAssist/src/main/java/dev/engine/DevEngineAssist.java new file mode 100644 index 0000000000..4c01ad88d3 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/DevEngineAssist.java @@ -0,0 +1,117 @@ +package dev.engine; + +import java.util.LinkedHashMap; +import java.util.Map; + +import dev.utils.DevFinal; + +/** + * detail: DevEngine Generic Assist + * @author Ttt + */ +public class DevEngineAssist { + + // Engine Map + private final Map mEngineMaps = new LinkedHashMap<>(); + + /** + * 获取 Engine + * @return {@link Engine} + */ + public Engine getEngine() { + return getEngine(DevFinal.STR.DEFAULT); + } + + /** + * 获取 Engine + * @param key key + * @return {@link Engine} + */ + public Engine getEngine(final String key) { + return mEngineMaps.get(key); + } + + /** + * 设置 Engine + * @param engine {@link Engine} + * @return {@link Engine} + */ + public Engine setEngine(final Engine engine) { + return setEngine(DevFinal.STR.DEFAULT, engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link Engine} + * @return {@link Engine} + */ + public Engine setEngine( + final String key, + final Engine engine + ) { + mEngineMaps.put(key, engine); + return engine; + } + + /** + * 移除 Engine + */ + public void removeEngine() { + removeEngine(DevFinal.STR.DEFAULT); + } + + /** + * 移除 Engine + * @param key key + */ + public void removeEngine(final String key) { + mEngineMaps.remove(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 Engine Map + * @return Engine Map + */ + public Map getEngineMaps() { + return mEngineMaps; + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public boolean contains() { + return contains(DevFinal.STR.DEFAULT); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public boolean contains(final String key) { + return mEngineMaps.containsKey(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public boolean isEmpty() { + return isEmpty(DevFinal.STR.DEFAULT); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public boolean isEmpty(final String key) { + return getEngine(key) == null; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/analytics/DevAnalyticsEngine.java b/lib/DevAssist/src/main/java/dev/engine/analytics/DevAnalyticsEngine.java new file mode 100644 index 0000000000..8a1672c34e --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/analytics/DevAnalyticsEngine.java @@ -0,0 +1,125 @@ +package dev.engine.analytics; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Analytics Engine + * @author Ttt + */ +public final class DevAnalyticsEngine { + + private DevAnalyticsEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IAnalyticsEngine} + */ + public static IAnalyticsEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IAnalyticsEngine} + */ + public static IAnalyticsEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IAnalyticsEngine} + * @return {@link IAnalyticsEngine} + */ + public static IAnalyticsEngine setEngine(final IAnalyticsEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IAnalyticsEngine} + * @return {@link IAnalyticsEngine} + */ + public static IAnalyticsEngine setEngine( + final String key, + final IAnalyticsEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/analytics/IAnalyticsEngine.java b/lib/DevAssist/src/main/java/dev/engine/analytics/IAnalyticsEngine.java new file mode 100644 index 0000000000..7a289f3727 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/analytics/IAnalyticsEngine.java @@ -0,0 +1,69 @@ +package dev.engine.analytics; + +import android.app.Application; +import android.content.Context; + +/** + * detail: Analytics Engine 接口 + * @author Ttt + */ +public interface IAnalyticsEngine { + + /** + * detail: Analytics Config + * @author Ttt + */ + class EngineConfig { + } + + /** + * detail: Analytics ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 初始化方法 + * @param application {@link Application} + * @param config Analytics Config + */ + void initialize( + Application application, + Config config + ); + + /** + * 绑定 + * @param context {@link Context} + * @param config Analytics Config + */ + void register( + Context context, + Config config + ); + + /** + * 解绑 + * @param context {@link Context} + * @param config Analytics Config + */ + void unregister( + Context context, + Config config + ); + + // = + + /** + * 数据统计 ( 埋点 ) 方法 + * @param params Analytics ( Data、Params ) Item + * @return {@code true} success, {@code false} fail + */ + boolean track(Item params); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/barcode/DevBarCodeEngine.java b/lib/DevAssist/src/main/java/dev/engine/barcode/DevBarCodeEngine.java new file mode 100644 index 0000000000..7be914203c --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/barcode/DevBarCodeEngine.java @@ -0,0 +1,125 @@ +package dev.engine.barcode; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: BarCode Engine + * @author Ttt + */ +public final class DevBarCodeEngine { + + private DevBarCodeEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IBarCodeEngine} + */ + public static IBarCodeEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IBarCodeEngine} + */ + public static IBarCodeEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IBarCodeEngine} + * @return {@link IBarCodeEngine} + */ + public static IBarCodeEngine setEngine(final IBarCodeEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IBarCodeEngine} + * @return {@link IBarCodeEngine} + */ + public static IBarCodeEngine setEngine( + final String key, + final IBarCodeEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/barcode/IBarCodeEngine.java b/lib/DevAssist/src/main/java/dev/engine/barcode/IBarCodeEngine.java new file mode 100644 index 0000000000..5370c1f356 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/barcode/IBarCodeEngine.java @@ -0,0 +1,122 @@ +package dev.engine.barcode; + +import android.graphics.Bitmap; + +import dev.engine.barcode.listener.BarCodeDecodeCallback; +import dev.engine.barcode.listener.BarCodeEncodeCallback; + +/** + * detail: BarCode Engine 接口 + * @author Ttt + */ +public interface IBarCodeEngine { + + /** + * detail: BarCode Config + * @author Ttt + */ + class EngineConfig { + } + + /** + * detail: BarCode ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + } + + /** + * detail: BarCode Result + * @author Ttt + */ + class EngineResult { + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 初始化方法 + * @param config BarCode Config + */ + void initialize(Config config); + + /** + * 获取 BarCode Engine Config + * @return BarCode Engine Config + */ + Config getConfig(); + + // ========== + // = 生成条码 = + // ========== + + /** + * 编码 ( 生成 ) 条码图片 + * @param params BarCode ( Data、Params ) Item + * @param callback 生成结果回调 + */ + void encodeBarCode( + Item params, + BarCodeEncodeCallback callback + ); + + // = + + /** + * 编码 ( 生成 ) 条码图片 + *
+     *     该方法是耗时操作, 请在子线程中调用
+     * 
+ * @param params BarCode ( Data、Params ) Item + * @return 条码图片 + */ + Bitmap encodeBarCodeSync(Item params) + throws Exception; + + // ========== + // = 解析条码 = + // ========== + + /** + * 解码 ( 解析 ) 条码图片 + * @param bitmap 待解析的条码图片 + * @param callback 解析结果回调 + */ + void decodeBarCode( + Bitmap bitmap, + BarCodeDecodeCallback callback + ); + + /** + * 解码 ( 解析 ) 条码图片 + *
+     *     该方法是耗时操作, 请在子线程中调用
+     * 
+ * @param bitmap 待解析的条码图片 + * @return 解析结果 + */ + Result decodeBarCodeSync(Bitmap bitmap) + throws Exception; + + // ========== + // = 其他功能 = + // ========== + + /** + * 添加 Icon 到条码图片上 + * @param params BarCode ( Data、Params ) Item + * @param src 条码图片 + * @param icon icon + * @return 含 icon 条码图片 + */ + Bitmap addIconToBarCode( + Item params, + Bitmap src, + Bitmap icon + ) + throws Exception; +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeDecodeCallback.java b/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeDecodeCallback.java new file mode 100644 index 0000000000..a3b80345c7 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeDecodeCallback.java @@ -0,0 +1,22 @@ +package dev.engine.barcode.listener; + +import dev.engine.barcode.IBarCodeEngine; + +/** + * detail: 条码解码 ( 解析 ) 回调 + * @author Ttt + */ +public interface BarCodeDecodeCallback { + + /** + * 条码解码 ( 解析 ) 回调 + * @param success 是否成功 + * @param result 识别结果 + * @param error 异常信息 + */ + void onResult( + boolean success, + Result result, + Throwable error + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeEncodeCallback.java b/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeEncodeCallback.java new file mode 100644 index 0000000000..7d4942e01b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeEncodeCallback.java @@ -0,0 +1,22 @@ +package dev.engine.barcode.listener; + +import android.graphics.Bitmap; + +/** + * detail: 条码编码 ( 生成 ) 回调 + * @author Ttt + */ +public interface BarCodeEncodeCallback { + + /** + * 条码编码 ( 生成 ) 回调 + * @param success 是否成功 + * @param bitmap 条码图片 + * @param error 异常信息 + */ + void onResult( + boolean success, + Bitmap bitmap, + Throwable error + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/cache/DevCacheEngine.java b/lib/DevAssist/src/main/java/dev/engine/cache/DevCacheEngine.java new file mode 100644 index 0000000000..03d12b0073 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/cache/DevCacheEngine.java @@ -0,0 +1,125 @@ +package dev.engine.cache; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Cache Engine + * @author Ttt + */ +public final class DevCacheEngine { + + private DevCacheEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link ICacheEngine} + */ + public static ICacheEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link ICacheEngine} + */ + public static ICacheEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link ICacheEngine} + * @return {@link ICacheEngine} + */ + public static ICacheEngine setEngine(final ICacheEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link ICacheEngine} + * @return {@link ICacheEngine} + */ + public static ICacheEngine setEngine( + final String key, + final ICacheEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/cache/ICacheEngine.java b/lib/DevAssist/src/main/java/dev/engine/cache/ICacheEngine.java new file mode 100644 index 0000000000..e46155f296 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/cache/ICacheEngine.java @@ -0,0 +1,623 @@ +package dev.engine.cache; + +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Parcelable; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.Serializable; +import java.lang.reflect.Type; +import java.util.List; + +import dev.utils.common.cipher.Cipher; + +/** + * detail: Cache Engine 接口 + * @author Ttt + */ +public interface ICacheEngine { + + /** + * detail: Cache Config + * @author Ttt + */ + class EngineConfig { + + // 通用加解密中间层 + public final Cipher cipher; + + public EngineConfig(Cipher cipher) { + this.cipher = cipher; + } + } + + /** + * detail: Cache ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + + // 存储 Key + public final String key; + // 存储类型 + public final int type; + // 文件大小 + public final long size; + // 保存时间 ( 毫秒 ) + public final long saveTime; + // 有效期 ( 毫秒 ) + public final long validTime; + // 是否永久有效 + public final boolean isPermanent; + // 是否过期 + public final boolean isDue; + + public EngineItem( + String key, + int type, + long size, + long saveTime, + long validTime, + boolean isPermanent, + boolean isDue + ) { + this.key = key; + this.type = type; + this.size = size; + this.saveTime = saveTime; + this.validTime = validTime; + this.isPermanent = isPermanent; + this.isDue = isDue; + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Cache Engine Config + * @return Cache Config + */ + Config getConfig(); + + // = + + /** + * 移除数据 + * @param key 保存的 key + */ + void remove(String key); + + /** + * 移除数组的数据 + * @param keys 保存的 key 数组 + */ + void removeForKeys(String[] keys); + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + boolean contains(String key); + + /** + * 判断某个 key 是否过期 + *
+     *     如果不存在该 key 也返回过期
+     * 
+ * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + boolean isDue(String key); + + /** + * 清除全部数据 + */ + void clear(); + + /** + * 清除过期数据 + */ + void clearDue(); + + /** + * 清除某个类型的全部数据 + * @param type 类型 + */ + void clearType(int type); + + /** + * 通过 Key 获取 Item + * @param key 保存的 key + * @return Item + */ + Item getItemByKey(String key); + + /** + * 获取有效 Key 集合 + * @return 有效 Key 集合 + */ + List getKeys(); + + /** + * 获取永久有效 Key 集合 + * @return 永久有效 Key 集合 + */ + List getPermanentKeys(); + + /** + * 获取有效 Key 数量 + * @return 有效 Key 数量 + */ + int getCount(); + + /** + * 获取有效 Key 占用总大小 + * @return 有效 Key 占用总大小 + */ + long getSize(); + + // ======= + // = 存储 = + // ======= + + /** + * 保存 int 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + int value, + long validTime + ); + + /** + * 保存 long 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + long value, + long validTime + ); + + /** + * 保存 float 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + float value, + long validTime + ); + + /** + * 保存 double 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + double value, + long validTime + ); + + /** + * 保存 boolean 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + boolean value, + long validTime + ); + + /** + * 保存 String 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + String value, + long validTime + ); + + /** + * 保存 byte[] 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + byte[] value, + long validTime + ); + + /** + * 保存 Bitmap 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + Bitmap value, + long validTime + ); + + /** + * 保存 Drawable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + Drawable value, + long validTime + ); + + /** + * 保存 Serializable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + Serializable value, + long validTime + ); + + /** + * 保存 Parcelable 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + Parcelable value, + long validTime + ); + + /** + * 保存 JSONObject 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + JSONObject value, + long validTime + ); + + /** + * 保存 JSONArray 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + JSONArray value, + long validTime + ); + + /** + * 保存指定类型对象 + * @param key 保存的 key + * @param value 存储的数据 + * @param validTime 有效时间 ( 毫秒 ) 小于等于 0 为永久有效 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + boolean put( + String key, + T value, + long validTime + ); + + // ======= + // = 获取 = + // ======= + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + int getInt(String key); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + long getLong(String key); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + float getFloat(String key); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + double getDouble(String key); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + boolean getBoolean(String key); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + String getString(String key); + + /** + * 获取 byte[] 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + byte[] getBytes(String key); + + /** + * 获取 Bitmap 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + Bitmap getBitmap(String key); + + /** + * 获取 Drawable 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + Drawable getDrawable(String key); + + /** + * 获取 Serializable 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + Object getSerializable(String key); + + /** + * 获取 Parcelable 类型的数据 + * @param key 保存的 key + * @param creator {@link Parcelable.Creator} + * @return 存储的数据 + */ + T getParcelable( + String key, + Parcelable.Creator creator + ); + + /** + * 获取 JSONObject 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + JSONObject getJSONObject(String key); + + /** + * 获取 JSONArray 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + JSONArray getJSONArray(String key); + + /** + * 获取指定类型对象 + * @param key 保存的 key + * @param typeOfT {@link Type} T + * @param 泛型 + * @return instance of type + */ + T getEntity( + String key, + Type typeOfT + ); + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + int getInt( + String key, + int defaultValue + ); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + long getLong( + String key, + long defaultValue + ); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + float getFloat( + String key, + float defaultValue + ); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + double getDouble( + String key, + double defaultValue + ); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + boolean getBoolean( + String key, + boolean defaultValue + ); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + String getString( + String key, + String defaultValue + ); + + /** + * 获取 byte[] 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + byte[] getBytes( + String key, + byte[] defaultValue + ); + + /** + * 获取 Bitmap 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + Bitmap getBitmap( + String key, + Bitmap defaultValue + ); + + /** + * 获取 Drawable 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + Drawable getDrawable( + String key, + Drawable defaultValue + ); + + /** + * 获取 Serializable 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + Object getSerializable( + String key, + Object defaultValue + ); + + /** + * 获取 Parcelable 类型的数据 + * @param key 保存的 key + * @param creator {@link Parcelable.Creator} + * @param defaultValue 默认值 + * @return 存储的数据 + */ + T getParcelable( + String key, + Parcelable.Creator creator, + T defaultValue + ); + + /** + * 获取 JSONObject 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + JSONObject getJSONObject( + String key, + JSONObject defaultValue + ); + + /** + * 获取 JSONArray 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + JSONArray getJSONArray( + String key, + JSONArray defaultValue + ); + + /** + * 获取指定类型对象 + * @param key 保存的 key + * @param typeOfT {@link Type} T + * @param defaultValue 默认值 + * @param 泛型 + * @return instance of type + */ + T getEntity( + String key, + Type typeOfT, + T defaultValue + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/compress/DevCompressEngine.java b/lib/DevAssist/src/main/java/dev/engine/compress/DevCompressEngine.java new file mode 100644 index 0000000000..f3e6b44a34 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/compress/DevCompressEngine.java @@ -0,0 +1,125 @@ +package dev.engine.compress; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Image Compress Engine + * @author Ttt + */ +public final class DevCompressEngine { + + private DevCompressEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link ICompressEngine} + */ + public static ICompressEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link ICompressEngine} + */ + public static ICompressEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link ICompressEngine} + * @return {@link ICompressEngine} + */ + public static ICompressEngine setEngine(final ICompressEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link ICompressEngine} + * @return {@link ICompressEngine} + */ + public static ICompressEngine setEngine( + final String key, + final ICompressEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/compress/ICompressEngine.java b/lib/DevAssist/src/main/java/dev/engine/compress/ICompressEngine.java new file mode 100644 index 0000000000..fe0774f933 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/compress/ICompressEngine.java @@ -0,0 +1,87 @@ +package dev.engine.compress; + +import java.util.List; + +import dev.engine.compress.listener.CompressFilter; +import dev.engine.compress.listener.OnCompressListener; +import dev.engine.compress.listener.OnRenameListener; + +/** + * detail: Image Compress Engine 接口 + * @author Ttt + */ +public interface ICompressEngine { + + /** + * detail: Image Compress Config + * @author Ttt + */ + class EngineConfig { + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 压缩方法 + * @param data 待压缩图片 + * @param config 压缩配置参数 + * @param compressListener 压缩回调接口 + * @return {@code true} success, {@code false} fail + */ + boolean compress( + Object data, + Config config, + OnCompressListener compressListener + ); + + /** + * 压缩方法 + * @param data 待压缩图片 + * @param config 压缩配置参数 + * @param filter 开启压缩条件 + * @param renameListener 压缩前重命名接口 + * @param compressListener 压缩回调接口 + * @return {@code true} success, {@code false} fail + */ + boolean compress( + Object data, + Config config, + CompressFilter filter, + OnRenameListener renameListener, + OnCompressListener compressListener + ); + + // = + + /** + * 压缩方法 + * @param lists 待压缩图片集合 + * @param config 压缩配置参数 + * @param compressListener 压缩回调接口 + * @return {@code true} success, {@code false} fail + */ + boolean compress( + List lists, + Config config, + OnCompressListener compressListener + ); + + /** + * 压缩方法 + * @param lists 待压缩图片集合 + * @param config 压缩配置参数 + * @param filter 开启压缩条件 + * @param renameListener 压缩前重命名接口 + * @param compressListener 压缩回调接口 + * @return {@code true} success, {@code false} fail + */ + boolean compress( + List lists, + Config config, + CompressFilter filter, + OnRenameListener renameListener, + OnCompressListener compressListener + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/compress/listener/CompressFilter.java b/lib/DevAssist/src/main/java/dev/engine/compress/listener/CompressFilter.java new file mode 100644 index 0000000000..153dcae51b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/compress/listener/CompressFilter.java @@ -0,0 +1,15 @@ +package dev.engine.compress.listener; + +/** + * detail: 压缩过滤接口 + * @author Ttt + */ +public interface CompressFilter { + + /** + * 根据路径判断是否进行压缩 + * @param path 文件路径 + * @return {@code true} yes, {@code false} no + */ + boolean apply(String path); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnCompressListener.java b/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnCompressListener.java new file mode 100644 index 0000000000..67622a479d --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnCompressListener.java @@ -0,0 +1,58 @@ +package dev.engine.compress.listener; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * detail: 压缩回调接口 + * @author Ttt + */ +public interface OnCompressListener { + + /** + * 开始压缩前调用 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + void onStart( + int index, + int count + ); + + /** + * 压缩成功后调用 + * @param file 压缩成功文件 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + void onSuccess( + File file, + int index, + int count + ); + + /** + * 当压缩过程出现问题时触发 + * @param error 异常信息 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + void onError( + Throwable error, + int index, + int count + ); + + /** + * 压缩完成 ( 压缩结束 ) + * @param lists 压缩成功存储 List + * @param maps 每个索引对应压缩存储地址 + * @param count 压缩总数 + */ + void onComplete( + List lists, + Map maps, + int count + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnRenameListener.java b/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnRenameListener.java new file mode 100644 index 0000000000..798a79f538 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnRenameListener.java @@ -0,0 +1,15 @@ +package dev.engine.compress.listener; + +/** + * detail: 修改压缩图片文件名接口 + * @author Ttt + */ +public interface OnRenameListener { + + /** + * 压缩前调用该方法用于修改压缩后文件名 + * @param filePath 文件路径 + * @return 返回重命名后的字符串 + */ + String rename(String filePath); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/DevImageEngine.java b/lib/DevAssist/src/main/java/dev/engine/image/DevImageEngine.java new file mode 100644 index 0000000000..0e17a193a8 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/DevImageEngine.java @@ -0,0 +1,125 @@ +package dev.engine.image; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Image Engine + * @author Ttt + */ +public final class DevImageEngine { + + private DevImageEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IImageEngine} + */ + public static IImageEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IImageEngine} + */ + public static IImageEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IImageEngine} + * @return {@link IImageEngine} + */ + public static IImageEngine setEngine(final IImageEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IImageEngine} + * @return {@link IImageEngine} + */ + public static IImageEngine setEngine( + final String key, + final IImageEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/IImageEngine.java b/lib/DevAssist/src/main/java/dev/engine/image/IImageEngine.java new file mode 100644 index 0000000000..bea4f6b5d2 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/IImageEngine.java @@ -0,0 +1,153 @@ +package dev.engine.image; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageView; + +import androidx.fragment.app.Fragment; + +import java.util.List; + +import dev.base.DevSource; +import dev.engine.image.listener.LoadListener; +import dev.engine.image.listener.OnConvertListener; + +/** + * detail: Image Engine 接口 + * @author Ttt + */ +public interface IImageEngine { + + /** + * detail: Image Config + * @author Ttt + */ + class EngineConfig { + } + + // ==================== + // = pause and resume = + // ==================== + + void pause(Fragment fragment); + + void resume(Fragment fragment); + + void pause(Context context); + + void resume(Context context); + + // =========== + // = preload = + // =========== + + void preload(Context context, DevSource source); + + void preload(Context context, DevSource source, Config config); + + // ========= + // = clear = + // ========= + + void clear(View view); + + void clear(Fragment fragment, View view); + + void clearDiskCache(Context context); + + void clearMemoryCache(Context context); + + void clearAllCache(Context context); + + // ========= + // = other = + // ========= + + void lowMemory(Context context); + + // =========== + // = display = + // =========== + + void display(ImageView imageView, String url); + + void display(ImageView imageView, DevSource source); + + void display(ImageView imageView, String url, Config config); + + void display(ImageView imageView, DevSource source, Config config); + + void display(ImageView imageView, String url, LoadListener listener); + + void display(ImageView imageView, DevSource source, LoadListener listener); + + void display(ImageView imageView, String url, Config config, LoadListener listener); + + void display(ImageView imageView, DevSource source, Config config, LoadListener listener); + + // = + + void display(Fragment fragment, ImageView imageView, String url); + + void display(Fragment fragment, ImageView imageView, DevSource source); + + void display(Fragment fragment, ImageView imageView, String url, Config config); + + void display(Fragment fragment, ImageView imageView, DevSource source, Config config); + + void display(Fragment fragment, ImageView imageView, String url, LoadListener listener); + + void display(Fragment fragment, ImageView imageView, DevSource source, LoadListener listener); + + void display(Fragment fragment, ImageView imageView, String url, Config config, LoadListener listener); + + void display(Fragment fragment, ImageView imageView, DevSource source, Config config, LoadListener listener); + + // ======== + // = load = + // ======== + + void loadImage(Context context, DevSource source, Config config, LoadListener listener); + + void loadImage(Fragment fragment, DevSource source, Config config, LoadListener listener); + + T loadImage(Context context, DevSource source, Config config, Class type); + + T loadImageThrows(Context context, DevSource source, Config config, Class type) throws Exception; + + // = + + void loadBitmap(Context context, DevSource source, Config config, LoadListener listener); + + void loadBitmap(Fragment fragment, DevSource source, Config config, LoadListener listener); + + Bitmap loadBitmap(Context context, DevSource source, Config config); + + Bitmap loadBitmapThrows(Context context, DevSource source, Config config) throws Exception; + + // = + + void loadDrawable(Context context, DevSource source, Config config, LoadListener listener); + + void loadDrawable(Fragment fragment, DevSource source, Config config, LoadListener listener); + + Drawable loadDrawable(Context context, DevSource source, Config config); + + Drawable loadDrawableThrows(Context context, DevSource source, Config config) throws Exception; + + // =========== + // = convert = + // =========== + + boolean convertImageFormat( + Context context, List sources, + OnConvertListener listener + ); + + boolean convertImageFormat( + Context context, List sources, + Config config, OnConvertListener listener + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/listener/BitmapListener.java b/lib/DevAssist/src/main/java/dev/engine/image/listener/BitmapListener.java new file mode 100644 index 0000000000..4ad7d6545a --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/listener/BitmapListener.java @@ -0,0 +1,16 @@ +package dev.engine.image.listener; + +import android.graphics.Bitmap; + +/** + * detail: Bitmap 加载事件 + * @author Ttt + */ +public abstract class BitmapListener + implements LoadListener { + + @Override + public final Class getTranscodeType() { + return Bitmap.class; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/listener/ConvertStorage.java b/lib/DevAssist/src/main/java/dev/engine/image/listener/ConvertStorage.java new file mode 100644 index 0000000000..19d5209e6b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/listener/ConvertStorage.java @@ -0,0 +1,35 @@ +package dev.engine.image.listener; + +import android.content.Context; + +import java.io.File; + +import dev.base.DevSource; +import dev.engine.image.IImageEngine; + +/** + * detail: 转换图片格式存储接口 + * @author Ttt + */ +public interface ConvertStorage { + + /** + * 转换图片格式并存储 + * @param context {@link Context} + * @param source 当前待转换资源 + * @param config 配置信息 + * @param index 当前转换索引 + * @param count 转换总数 + * @param task 转存任务标记 + * @return 存储路径 + */ + File convert( + Context context, + DevSource source, + Config config, + int index, + int count, + String task + ) + throws Exception; +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/listener/DrawableListener.java b/lib/DevAssist/src/main/java/dev/engine/image/listener/DrawableListener.java new file mode 100644 index 0000000000..2f78743636 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/listener/DrawableListener.java @@ -0,0 +1,16 @@ +package dev.engine.image.listener; + +import android.graphics.drawable.Drawable; + +/** + * detail: Drawable 加载事件 + * @author Ttt + */ +public abstract class DrawableListener + implements LoadListener { + + @Override + public final Class getTranscodeType() { + return Drawable.class; + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/listener/LoadListener.java b/lib/DevAssist/src/main/java/dev/engine/image/listener/LoadListener.java new file mode 100644 index 0000000000..873b0c11b6 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/listener/LoadListener.java @@ -0,0 +1,43 @@ +package dev.engine.image.listener; + +import dev.base.DevSource; + +/** + * detail: 图片加载事件 + * @param 泛型 + * @author Ttt + */ +public interface LoadListener { + + /** + * 获取转码类型 + * @return {@link TranscodeType#getClass()} + */ + Class getTranscodeType(); + + /** + * 开始加载 + * @param source 数据来源 + */ + void onStart(DevSource source); + + /** + * 响应回调 + * @param source 数据来源 + * @param value 结果数据 + */ + void onResponse( + DevSource source, + TranscodeType value + ); + + /** + * 失败回调 + * @param source 数据来源 + * @param throwable {@link Throwable} + */ + void onFailure( + DevSource source, + Throwable throwable + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/image/listener/OnConvertListener.java b/lib/DevAssist/src/main/java/dev/engine/image/listener/OnConvertListener.java new file mode 100644 index 0000000000..f57d75739e --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/image/listener/OnConvertListener.java @@ -0,0 +1,58 @@ +package dev.engine.image.listener; + +import java.io.File; +import java.util.List; +import java.util.Map; + +/** + * detail: 转换图片格式回调接口 + * @author Ttt + */ +public interface OnConvertListener { + + /** + * 开始转换前调用 + * @param index 当前转换索引 + * @param count 转换总数 + */ + void onStart( + int index, + int count + ); + + /** + * 转换成功后调用 + * @param file 转换成功文件 + * @param index 当前转换索引 + * @param count 转换总数 + */ + void onSuccess( + File file, + int index, + int count + ); + + /** + * 当转换过程出现问题时触发 + * @param error 异常信息 + * @param index 当前转换索引 + * @param count 转换总数 + */ + void onError( + Throwable error, + int index, + int count + ); + + /** + * 转换完成 ( 转换结束 ) + * @param lists 转换成功存储 List + * @param maps 每个索引对应转换存储地址 + * @param count 转换总数 + */ + void onComplete( + List lists, + Map maps, + int count + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/json/DevJSONEngine.java b/lib/DevAssist/src/main/java/dev/engine/json/DevJSONEngine.java new file mode 100644 index 0000000000..f3f0ed572b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/json/DevJSONEngine.java @@ -0,0 +1,125 @@ +package dev.engine.json; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: JSON Engine + * @author Ttt + */ +public final class DevJSONEngine { + + private DevJSONEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IJSONEngine} + */ + public static IJSONEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IJSONEngine} + */ + public static IJSONEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IJSONEngine} + * @return {@link IJSONEngine} + */ + public static IJSONEngine setEngine(final IJSONEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IJSONEngine} + * @return {@link IJSONEngine} + */ + public static IJSONEngine setEngine( + final String key, + final IJSONEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/json/IJSONEngine.java b/lib/DevAssist/src/main/java/dev/engine/json/IJSONEngine.java new file mode 100644 index 0000000000..1f179ec485 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/json/IJSONEngine.java @@ -0,0 +1,158 @@ +package dev.engine.json; + +import java.lang.reflect.Type; + +/** + * detail: JSON Engine 接口 + * @author Ttt + */ +public interface IJSONEngine { + + /** + * detail: JSON Config + * @author Ttt + */ + class EngineConfig { + } + + // ========== + // = 转换方法 = + // ========== + + /** + * 将对象转换为 JSON String + * @param object {@link Object} + * @return JSON String + */ + String toJson(Object object); + + /** + * 将对象转换为 JSON String + * @param object {@link Object} + * @param config 配置信息 + * @return JSON String + */ + String toJson( + Object object, + Config config + ); + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT {@link Class} T + * @param 泛型 + * @return instance of type + */ + T fromJson( + String json, + Class classOfT + ); + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT {@link Class} T + * @param config 配置信息 + * @param 泛型 + * @return instance of type + */ + T fromJson( + String json, + Class classOfT, + Config config + ); + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param typeOfT {@link Type} T + * @param 泛型 + * @return instance of type + */ + T fromJson( + String json, + Type typeOfT + ); + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param typeOfT {@link Type} T + * @param config 配置信息 + * @param 泛型 + * @return instance of type + */ + T fromJson( + String json, + Type typeOfT, + Config config + ); + + // ========== + // = 其他方法 = + // ========== + + /** + * 判断字符串是否 JSON 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + boolean isJSON(String json); + + /** + * 判断字符串是否 JSON Object 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + boolean isJSONObject(String json); + + /** + * 判断字符串是否 JSON Array 格式 + * @param json 待校验 JSON String + * @return {@code true} yes, {@code false} no + */ + boolean isJSONArray(String json); + + // = + + /** + * JSON String 缩进处理 + * @param json JSON String + * @return JSON String + */ + String toJsonIndent(String json); + + /** + * JSON String 缩进处理 + * @param json JSON String + * @param config 配置信息 + * @return JSON String + */ + String toJsonIndent( + String json, + Config config + ); + + /** + * Object 转 JSON String 并进行缩进处理 + * @param object {@link Object} + * @return JSON String + */ + String toJsonIndent(Object object); + + /** + * Object 转 JSON String 并进行缩进处理 + * @param object {@link Object} + * @param config 配置信息 + * @return JSON String + */ + String toJsonIndent( + Object object, + Config config + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/keyvalue/DevKeyValueEngine.java b/lib/DevAssist/src/main/java/dev/engine/keyvalue/DevKeyValueEngine.java new file mode 100644 index 0000000000..985c9be52b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/keyvalue/DevKeyValueEngine.java @@ -0,0 +1,125 @@ +package dev.engine.keyvalue; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: KeyValue Engine + * @author Ttt + */ +public final class DevKeyValueEngine { + + private DevKeyValueEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IKeyValueEngine} + */ + public static IKeyValueEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IKeyValueEngine} + */ + public static IKeyValueEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IKeyValueEngine} + * @return {@link IKeyValueEngine} + */ + public static IKeyValueEngine setEngine(final IKeyValueEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IKeyValueEngine} + * @return {@link IKeyValueEngine} + */ + public static IKeyValueEngine setEngine( + final String key, + final IKeyValueEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/keyvalue/IKeyValueEngine.java b/lib/DevAssist/src/main/java/dev/engine/keyvalue/IKeyValueEngine.java new file mode 100644 index 0000000000..47f2075f97 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/keyvalue/IKeyValueEngine.java @@ -0,0 +1,286 @@ +package dev.engine.keyvalue; + +import java.lang.reflect.Type; + +import dev.utils.common.cipher.Cipher; + +/** + * detail: Key-Value Engine 接口 + * @author Ttt + */ +public interface IKeyValueEngine { + + /** + * detail: Key-Value Config + * @author Ttt + */ + class EngineConfig { + + // 通用加解密中间层 + public final Cipher cipher; + + public EngineConfig( + Cipher cipher + ) { + this.cipher = cipher; + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Key-Value Engine Config + * @return Key-Value Config + */ + Config getConfig(); + + // = + + /** + * 移除数据 + * @param key 保存的 key + */ + void remove(String key); + + /** + * 移除数组的数据 + * @param keys 保存的 key 数组 + */ + void removeForKeys(String[] keys); + + /** + * 是否存在 key + * @param key 保存的 key + * @return {@code true} yes, {@code false} no + */ + boolean contains(String key); + + /** + * 清除全部数据 + */ + void clear(); + + // ======= + // = 存储 = + // ======= + + /** + * 保存 int 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putInt( + String key, + int value + ); + + /** + * 保存 long 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putLong( + String key, + long value + ); + + /** + * 保存 float 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putFloat( + String key, + float value + ); + + /** + * 保存 double 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putDouble( + String key, + double value + ); + + /** + * 保存 boolean 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putBoolean( + String key, + boolean value + ); + + /** + * 保存 String 类型的数据 + * @param key 保存的 key + * @param value 存储的数据 + * @return {@code true} success, {@code false} fail + */ + boolean putString( + String key, + String value + ); + + /** + * 保存指定类型对象 + * @param key 保存的 key + * @param value 存储的数据 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + boolean putEntity( + String key, + T value + ); + + // ======= + // = 获取 = + // ======= + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + int getInt(String key); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + long getLong(String key); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + float getFloat(String key); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + double getDouble(String key); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + boolean getBoolean(String key); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @return 存储的数据 + */ + String getString(String key); + + /** + * 获取指定类型对象 + * @param key 保存的 key + * @param typeOfT {@link Type} T + * @param 泛型 + * @return instance of type + */ + T getEntity( + String key, + Type typeOfT + ); + + // = + + /** + * 获取 int 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + int getInt( + String key, + int defaultValue + ); + + /** + * 获取 long 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + long getLong( + String key, + long defaultValue + ); + + /** + * 获取 float 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + float getFloat( + String key, + float defaultValue + ); + + /** + * 获取 double 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + double getDouble( + String key, + double defaultValue + ); + + /** + * 获取 boolean 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + boolean getBoolean( + String key, + boolean defaultValue + ); + + /** + * 获取 String 类型的数据 + * @param key 保存的 key + * @param defaultValue 默认值 + * @return 存储的数据 + */ + String getString( + String key, + String defaultValue + ); + + /** + * 获取指定类型对象 + * @param key 保存的 key + * @param typeOfT {@link Type} T + * @param defaultValue 默认值 + * @param 泛型 + * @return instance of type + */ + T getEntity( + String key, + Type typeOfT, + T defaultValue + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/log/DevLogEngine.java b/lib/DevAssist/src/main/java/dev/engine/log/DevLogEngine.java new file mode 100644 index 0000000000..f8986afeae --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/log/DevLogEngine.java @@ -0,0 +1,125 @@ +package dev.engine.log; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Log Engine + * @author Ttt + */ +public final class DevLogEngine { + + private DevLogEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link ILogEngine} + */ + public static ILogEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link ILogEngine} + */ + public static ILogEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link ILogEngine} + * @return {@link ILogEngine} + */ + public static ILogEngine setEngine(final ILogEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link ILogEngine} + * @return {@link ILogEngine} + */ + public static ILogEngine setEngine( + final String key, + final ILogEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/log/ILogEngine.java b/lib/DevAssist/src/main/java/dev/engine/log/ILogEngine.java new file mode 100644 index 0000000000..a934da64da --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/log/ILogEngine.java @@ -0,0 +1,232 @@ +package dev.engine.log; + +/** + * detail: Log Engine 接口 + * @author Ttt + */ +public interface ILogEngine { + + /** + * 判断是否打印日志 + * @return {@code true} yes, {@code false} no + */ + boolean isPrintLog(); + + // ============================= + // = 使用默认 TAG ( 日志打印方法 ) = + // ============================= + + /** + * 打印 Log.DEBUG + * @param message 日志信息 + * @param args 格式化参数 + */ + void d( + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param message 日志信息 + * @param args 格式化参数 + */ + void e( + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param throwable 异常 + */ + void e(Throwable throwable); + + /** + * 打印 Log.ERROR + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + void e( + Throwable throwable, + String message, + Object... args + ); + + /** + * 打印 Log.WARN + * @param message 日志信息 + * @param args 格式化参数 + */ + void w( + String message, + Object... args + ); + + /** + * 打印 Log.INFO + * @param message 日志信息 + * @param args 格式化参数 + */ + void i( + String message, + Object... args + ); + + /** + * 打印 Log.VERBOSE + * @param message 日志信息 + * @param args 格式化参数 + */ + void v( + String message, + Object... args + ); + + /** + * 打印 Log.ASSERT + * @param message 日志信息 + * @param args 格式化参数 + */ + void wtf( + String message, + Object... args + ); + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param json JSON 格式字符串 + */ + void json(String json); + + /** + * 格式化 XML 格式数据, 并打印 + * @param xml XML 格式字符串 + */ + void xml(String xml); + + // ============================== + // = 使用自定义 TAG ( 日志打印方法 ) = + // ============================== + + /** + * 打印 Log.DEBUG + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void dTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void eTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + */ + void eTag( + String tag, + Throwable throwable + ); + + /** + * 打印 Log.ERROR + * @param tag 日志 TAG + * @param throwable 异常 + * @param message 日志信息 + * @param args 格式化参数 + */ + void eTag( + String tag, + Throwable throwable, + String message, + Object... args + ); + + /** + * 打印 Log.WARN + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void wTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.INFO + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void iTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.VERBOSE + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void vTag( + String tag, + String message, + Object... args + ); + + /** + * 打印 Log.ASSERT + * @param tag 日志 TAG + * @param message 日志信息 + * @param args 格式化参数 + */ + void wtfTag( + String tag, + String message, + Object... args + ); + + // = + + /** + * 格式化 JSON 格式数据, 并打印 + * @param tag 日志 TAG + * @param json JSON 格式字符串 + */ + void jsonTag( + String tag, + String json + ); + + /** + * 格式化 XML 格式数据, 并打印 + * @param tag 日志 TAG + * @param xml XML 格式字符串 + */ + void xmlTag( + String tag, + String xml + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/media/DevMediaEngine.java b/lib/DevAssist/src/main/java/dev/engine/media/DevMediaEngine.java new file mode 100644 index 0000000000..93cf47a98b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/media/DevMediaEngine.java @@ -0,0 +1,125 @@ +package dev.engine.media; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Media Selector Engine + * @author Ttt + */ +public final class DevMediaEngine { + + private DevMediaEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IMediaEngine} + */ + public static IMediaEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IMediaEngine} + */ + public static IMediaEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IMediaEngine} + * @return {@link IMediaEngine} + */ + public static IMediaEngine setEngine(final IMediaEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IMediaEngine} + * @return {@link IMediaEngine} + */ + public static IMediaEngine setEngine( + final String key, + final IMediaEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/media/IMediaEngine.java b/lib/DevAssist/src/main/java/dev/engine/media/IMediaEngine.java new file mode 100644 index 0000000000..53d55c6141 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/media/IMediaEngine.java @@ -0,0 +1,233 @@ +package dev.engine.media; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import androidx.fragment.app.Fragment; + +import java.util.List; + +/** + * detail: Media Selector Engine 接口 + * @author Ttt + */ +public interface IMediaEngine { + + /** + * detail: Media Selector Config + * @author Ttt + */ + class EngineConfig { + } + + /** + * detail: Media Selector Data + * @author Ttt + */ + class EngineData { + } + + // ========== + // = 配置方法 = + // ========== + + /** + * 获取全局配置 + * @return 全局配置信息 + */ + Config getConfig(); + + /** + * 设置全局配置 + * @param config 新的配置信息 + */ + void setConfig(Config config); + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 打开相册拍照 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + boolean openCamera(Activity activity); + + /** + * 打开相册拍照 + * @param activity {@link Activity} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openCamera( + Activity activity, + Config config + ); + + /** + * 打开相册拍照 + * @param fragment {@link Fragment} + * @return {@code true} success, {@code false} fail + */ + boolean openCamera(Fragment fragment); + + /** + * 打开相册拍照 + * @param fragment {@link Fragment} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openCamera( + Fragment fragment, + Config config + ); + + // = + + /** + * 打开相册选择 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + boolean openGallery(Activity activity); + + /** + * 打开相册选择 + * @param activity {@link Activity} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openGallery( + Activity activity, + Config config + ); + + /** + * 打开相册选择 + * @param fragment {@link Fragment} + * @return {@code true} success, {@code false} fail + */ + boolean openGallery(Fragment fragment); + + /** + * 打开相册选择 + * @param fragment {@link Fragment} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openGallery( + Fragment fragment, + Config config + ); + + // = + + /** + * 打开相册预览 + * @param activity {@link Activity} + * @return {@code true} success, {@code false} fail + */ + boolean openPreview(Activity activity); + + /** + * 打开相册预览 + * @param activity {@link Activity} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openPreview( + Activity activity, + Config config + ); + + /** + * 打开相册预览 + * @param fragment {@link Fragment} + * @return {@code true} success, {@code false} fail + */ + boolean openPreview(Fragment fragment); + + /** + * 打开相册预览 + * @param fragment {@link Fragment} + * @param config 配置信息 + * @return {@code true} success, {@code false} fail + */ + boolean openPreview( + Fragment fragment, + Config config + ); + + // ========== + // = 其他方法 = + // ========== + + /** + * 删除缓存文件 + * @param context {@link Context} + * @param type 类型 ( 图片、视频 ) + */ + void deleteCacheDirFile( + Context context, + int type + ); + + /** + * 删除全部缓存文件 + * @param context {@link Context} + */ + void deleteAllCacheDirFile(Context context); + + /** + * 是否图片选择 ( onActivityResult ) + * @param requestCode 请求 code + * @param resultCode resultCode + * @return {@code true} success, {@code false} fail + */ + boolean isMediaSelectorResult( + int requestCode, + int resultCode + ); + + // = + + /** + * 获取 Media Selector Data List + * @param intent onActivityResult Intent data + * @return Media Selector Data List + */ + List getSelectors(Intent intent); + + /** + * 获取 Media Selector Uri List + * @param intent onActivityResult Intent data + * @param original 是否使用原图 + * @return Media Selector Uri List + */ + List getSelectorUris( + Intent intent, + boolean original + ); + + /** + * 获取 Single Media Selector Data + * @param intent onActivityResult Intent data + * @return Single Media Selector Data + */ + Data getSingleSelector(Intent intent); + + /** + * 获取 Single Media Selector Uri + * @param intent onActivityResult Intent data + * @param original 是否使用原图 + * @return Single Media Selector Uri + */ + Uri getSingleSelectorUri( + Intent intent, + boolean original + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/permission/DevPermissionEngine.java b/lib/DevAssist/src/main/java/dev/engine/permission/DevPermissionEngine.java new file mode 100644 index 0000000000..b9a97813fd --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/permission/DevPermissionEngine.java @@ -0,0 +1,125 @@ +package dev.engine.permission; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Permission Engine + * @author Ttt + */ +public final class DevPermissionEngine { + + private DevPermissionEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IPermissionEngine} + */ + public static IPermissionEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IPermissionEngine} + */ + public static IPermissionEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IPermissionEngine} + * @return {@link IPermissionEngine} + */ + public static IPermissionEngine setEngine(final IPermissionEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IPermissionEngine} + * @return {@link IPermissionEngine} + */ + public static IPermissionEngine setEngine( + final String key, + final IPermissionEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/permission/IPermissionEngine.java b/lib/DevAssist/src/main/java/dev/engine/permission/IPermissionEngine.java new file mode 100644 index 0000000000..a33fcf1714 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/permission/IPermissionEngine.java @@ -0,0 +1,150 @@ +package dev.engine.permission; + +import android.app.Activity; +import android.content.Context; + +import java.util.List; + +/** + * detail: Permission Engine 接口 + * @author Ttt + */ +public interface IPermissionEngine { + + // ============= + // = 请求权限回调 = + // ============= + + /** + * detail: 权限请求回调 + * @author Ttt + */ + interface Callback { + + /** + * 授权通过权限回调 + */ + void onGranted(); + + /** + * 授权未通过权限回调 + *
+         *     判断 deniedList 申请未通过的权限中拒绝状态
+         *     可通过 {@link #getDeniedPermissionStatus(Activity, boolean, String...)} 进行获取
+         *     第二个参数 shouldShow ( boolean )
+         *     {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问
+         * 
+ * @param grantedList 申请通过的权限 + * @param deniedList 申请未通过的权限 + * @param notFoundList 查询不到的权限 ( 包含未注册 ) + */ + void onDenied( + List grantedList, + List deniedList, + List notFoundList + ); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断是否授予了权限 + * @param context {@link Context} + * @param permissions 待判断权限 + * @return {@code true} yes, {@code false} no + */ + boolean isGranted( + Context context, + String... permissions + ); + + /** + * 获取拒绝权限询问勾选状态 + *
+     *     拒绝过一次, 再次申请时, 弹出选择进行拒绝, 获取询问勾选状态
+     *     true 表示没有勾选不再询问, 而 false 则表示勾选了不再询问
+     * 
+ * @param activity {@link Activity} + * @param permissions 待判断权限 + * @return {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问 + */ + boolean shouldShowRequestPermissionRationale( + Activity activity, + String... permissions + ); + + /** + * 获取拒绝权限询问状态集合 + * @param activity {@link Activity} + * @param shouldShow {@code true} 没有勾选不再询问, {@code false} 勾选了不再询问 + * @param permissions 待判断权限 + * @return 拒绝权限询问状态集合 + */ + List getDeniedPermissionStatus( + Activity activity, + boolean shouldShow, + String... permissions + ); + + /** + * 再次请求处理操作 + *
+     *     如果存在拒绝了且不再询问则跳转到应用设置页面
+     *     否则则再次请求拒绝的权限
+     * 
+ * @param activity {@link Activity} + * @param callback 权限请求回调 + * @param deniedList 申请未通过的权限集合 + * @return 0 不符合要求无任何操作、1 再次请求操作、2 跳转到应用设置页面 + */ + int againRequest( + Activity activity, + Callback callback, + List deniedList + ); + + // ============= + // = 权限请求方法 = + // ============= + + /** + * 请求权限 + * @param activity {@link Activity} + * @param permissions 待申请权限 + */ + void request( + Activity activity, + String[] permissions + ); + + /** + * 请求权限 + * @param activity {@link Activity} + * @param permissions 待申请权限 + * @param callback 权限请求回调 + */ + void request( + Activity activity, + String[] permissions, + Callback callback + ); + + /** + * 请求权限 + *
+     *     againRequest 参数为 true 则会调用 {@link #againRequest(Activity, Callback, List)}
+     * 
+ * @param activity {@link Activity} + * @param permissions 待申请权限 + * @param callback 权限请求回调 + * @param againRequest 如果失败是否再次请求 + */ + void request( + Activity activity, + String[] permissions, + Callback callback, + boolean againRequest + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/push/DevPushEngine.java b/lib/DevAssist/src/main/java/dev/engine/push/DevPushEngine.java new file mode 100644 index 0000000000..a0e6cf6bb1 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/push/DevPushEngine.java @@ -0,0 +1,125 @@ +package dev.engine.push; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Push Engine + * @author Ttt + */ +public final class DevPushEngine { + + private DevPushEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IPushEngine} + */ + public static IPushEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IPushEngine} + */ + public static IPushEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IPushEngine} + * @return {@link IPushEngine} + */ + public static IPushEngine setEngine(final IPushEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IPushEngine} + * @return {@link IPushEngine} + */ + public static IPushEngine setEngine( + final String key, + final IPushEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/push/IPushEngine.java b/lib/DevAssist/src/main/java/dev/engine/push/IPushEngine.java new file mode 100644 index 0000000000..661010fd37 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/push/IPushEngine.java @@ -0,0 +1,153 @@ +package dev.engine.push; + +import android.app.Application; +import android.content.Context; + +/** + * detail: Push Engine 接口 + * @author Ttt + */ +public interface IPushEngine { + + /** + * detail: Push Config + * @author Ttt + */ + class EngineConfig { + } + + /** + * detail: Push ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 初始化方法 + * @param application {@link Application} + * @param config Push Config + */ + void initialize( + Application application, + Config config + ); + + /** + * 绑定 + * @param context {@link Context} + * @param config Push Config + */ + void register( + Context context, + Config config + ); + + /** + * 解绑 + * @param context {@link Context} + * @param config Push Config + */ + void unregister( + Context context, + Config config + ); + + // = + + /** + * 推送进程启动通知 + * @param context {@link Context} + * @param pid Push 进程 ID + */ + void onReceiveServicePid( + Context context, + int pid + ); + + /** + * 初始化 Client Id 成功通知 + * @param context {@link Context} + * @param clientId 唯一 ID 用于标识当前应用 + */ + void onReceiveClientId( + Context context, + String clientId + ); + + /** + * 设备 ( 厂商 ) Token 通知 + * @param context {@link Context} + * @param deviceToken 设备 Token + */ + void onReceiveDeviceToken( + Context context, + String deviceToken + ); + + /** + * 在线状态变化通知 + * @param context {@link Context} + * @param online 是否在线 + */ + void onReceiveOnlineState( + Context context, + boolean online + ); + + /** + * 命令回执通知 + * @param context {@link Context} + * @param message Push ( Data、Params ) Item + */ + void onReceiveCommandResult( + Context context, + Item message + ); + + /** + * 推送消息送达通知 + * @param context {@link Context} + * @param message Push ( Data、Params ) Item + */ + void onNotificationMessageArrived( + Context context, + Item message + ); + + /** + * 推送消息点击通知 + * @param context {@link Context} + * @param message Push ( Data、Params ) Item + */ + void onNotificationMessageClicked( + Context context, + Item message + ); + + /** + * 透传消息送达通知 + * @param context {@link Context} + * @param message Push ( Data、Params ) Item + */ + void onReceiveMessageData( + Context context, + Item message + ); + + // =============== + // = 转换 Message = + // =============== + + /** + * 传入 Object 转换 Engine Message + * @param message Message Object + * @return Engine Message + */ + Item convertMessage(Object message); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/share/DevShareEngine.java b/lib/DevAssist/src/main/java/dev/engine/share/DevShareEngine.java new file mode 100644 index 0000000000..6b83f36900 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/share/DevShareEngine.java @@ -0,0 +1,125 @@ +package dev.engine.share; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Share Engine + * @author Ttt + */ +public final class DevShareEngine { + + private DevShareEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IShareEngine} + */ + public static IShareEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IShareEngine} + */ + public static IShareEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IShareEngine} + * @return {@link IShareEngine} + */ + public static IShareEngine setEngine(final IShareEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IShareEngine} + * @return {@link IShareEngine} + */ + public static IShareEngine setEngine( + final String key, + final IShareEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/share/IShareEngine.java b/lib/DevAssist/src/main/java/dev/engine/share/IShareEngine.java new file mode 100644 index 0000000000..4b8676fb58 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/share/IShareEngine.java @@ -0,0 +1,207 @@ +package dev.engine.share; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; + +import dev.engine.share.listener.ShareListener; + +/** + * detail: Share Engine 接口 + * @author Ttt + */ +public interface IShareEngine { + + /** + * detail: Share Config + * @author Ttt + */ + class EngineConfig { + } + + /** + * detail: Share ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 初始化方法 + * @param application {@link Application} + * @param config Share Config + */ + void initialize( + Application application, + Config config + ); + + // ========== + // = 分享操作 = + // ========== + + /** + * 打开小程序 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean openMinApp( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享小程序 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareMinApp( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享链接 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareUrl( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享图片 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareImage( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享多张图片 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareImageList( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享文本 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareText( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享视频 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareVideo( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享音乐 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareMusic( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享表情 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareEmoji( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享文件 + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean shareFile( + Activity activity, + Item params, + ShareListener listener + ); + + /** + * 分享操作 ( 通用扩展 ) + * @param activity {@link Activity} + * @param params Share ( Data、Params ) Item + * @param listener 分享回调 + * @return {@code true} success, {@code false} fail + */ + boolean share( + Activity activity, + Item params, + ShareListener listener + ); + + // = + + /** + * 部分平台 Activity onActivityResult 额外调用处理 + * @param context {@link Context} + * @param requestCode 请求 code + * @param resultCode resultCode + * @param intent {@link Intent} + */ + void onActivityResult( + Context context, + int requestCode, + int resultCode, + Intent intent + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/share/listener/ShareListener.java b/lib/DevAssist/src/main/java/dev/engine/share/listener/ShareListener.java new file mode 100644 index 0000000000..d7252b2b14 --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/share/listener/ShareListener.java @@ -0,0 +1,38 @@ +package dev.engine.share.listener; + +import dev.engine.share.IShareEngine; + +/** + * detail: 分享回调 + * @author Ttt + */ +public interface ShareListener { + + /** + * 开始分享 + * @param params Share ( Data、Params ) Item + */ + void onStart(Item params); + + /** + * 分享成功 + * @param params Share ( Data、Params ) Item + */ + void onResult(Item params); + + /** + * 分享失败 + * @param params Share ( Data、Params ) Item + * @param throwable {@link Throwable} + */ + void onError( + Item params, + Throwable throwable + ); + + /** + * 取消分享 + * @param params Share ( Data、Params ) Item + */ + void onCancel(Item params); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/storage/DevStorageEngine.java b/lib/DevAssist/src/main/java/dev/engine/storage/DevStorageEngine.java new file mode 100644 index 0000000000..7d80f0d9be --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/storage/DevStorageEngine.java @@ -0,0 +1,125 @@ +package dev.engine.storage; + +import java.util.Map; + +import dev.engine.DevEngineAssist; + +/** + * detail: Storage Engine + * @author Ttt + */ +public final class DevStorageEngine { + + private DevStorageEngine() { + } + + private static final DevEngineAssist sAssist = new DevEngineAssist<>(); + + /** + * 获取 Engine + * @return {@link IStorageEngine} + */ + public static IStorageEngine getEngine() { + return sAssist.getEngine(); + } + + /** + * 获取 Engine + * @param key key + * @return {@link IStorageEngine} + */ + public static IStorageEngine getEngine(final String key) { + return sAssist.getEngine(key); + } + + /** + * 设置 Engine + * @param engine {@link IStorageEngine} + * @return {@link IStorageEngine} + */ + public static IStorageEngine setEngine(final IStorageEngine engine) { + return sAssist.setEngine(engine); + } + + /** + * 设置 Engine + * @param key key + * @param engine {@link IStorageEngine} + * @return {@link IStorageEngine} + */ + public static IStorageEngine setEngine( + final String key, + final IStorageEngine engine + ) { + return sAssist.setEngine(key, engine); + } + + /** + * 移除 Engine + */ + public static void removeEngine() { + sAssist.removeEngine(); + } + + /** + * 移除 Engine + * @param key key + */ + public static void removeEngine(final String key) { + sAssist.removeEngine(key); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 DevEngine Generic Assist + * @return DevEngine Generic Assist + */ + public static DevEngineAssist getAssist() { + return sAssist; + } + + /** + * 获取 Engine Map + * @return Engine Map + */ + public static Map getEngineMaps() { + return sAssist.getEngineMaps(); + } + + /** + * 是否存在 Engine + * @return {@code true} yes, {@code false} no + */ + public static boolean contains() { + return sAssist.contains(); + } + + /** + * 是否存在 Engine + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean contains(final String key) { + return sAssist.contains(key); + } + + /** + * 判断 Engine 是否为 null + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty() { + return sAssist.isEmpty(); + } + + /** + * 判断 Engine 是否为 null + * @param key key + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final String key) { + return sAssist.isEmpty(key); + } +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/storage/IStorageEngine.java b/lib/DevAssist/src/main/java/dev/engine/storage/IStorageEngine.java new file mode 100644 index 0000000000..10ff6a594f --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/storage/IStorageEngine.java @@ -0,0 +1,179 @@ +package dev.engine.storage; + +import android.os.Environment; +import android.provider.MediaStore; + +import dev.base.DevSource; +import dev.engine.storage.listener.OnInsertListener; +import dev.utils.app.PathUtils; + +/** + * detail: Storage Engine 接口 + * @author Ttt + *
+ *     关于存储空间及兼容建议查看 {@link PathUtils}
+ *     

+ * 外部存储空间 ( SDCard ) 正常指的是 + * {@link Environment#DIRECTORY_PICTURES} + * {@link Environment#DIRECTORY_DCIM} + * {@link Environment#DIRECTORY_MUSIC} + * {@link Environment#DIRECTORY_DOWNLOADS} + * 适配高版本的 {@link MediaStore} 操作, 可参考 {@link dev.utils.app.MediaStoreUtils} + *

+ * 内部存储空间分 + * 内部存储 : /data/data/package/ 目录 + * 外部存储 ( 私有目录 ) : /storage/emulated/0/Android/data/package/ 目录 + * 具体需要区分存储位置可以在 {@link EngineItem} 子类新增参数判断 + *
+ */ +public interface IStorageEngine { + + /** + * detail: Storage ( Data、Params ) Item + * @author Ttt + */ + class EngineItem { + } + + /** + * detail: Storage Result + * @author Ttt + */ + class EngineResult { + } + + // ============= + // = 对外公开方法 = + // ============= + + // ========== + // = 外部存储 = + // ========== + + /** + * 插入一张图片到外部存储空间 ( SDCard ) + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertImageToExternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条视频到外部存储空间 ( SDCard ) + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertVideoToExternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条音频到外部存储空间 ( SDCard ) + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertAudioToExternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条文件资源到外部存储空间 ( SDCard ) + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertDownloadToExternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条多媒体资源到外部存储空间 ( SDCard ) + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertMediaToExternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + // ========== + // = 内部存储 = + // ========== + + /** + * 插入一张图片到内部存储空间 + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertImageToInternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条视频到内部存储空间 + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertVideoToInternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条音频到内部存储空间 + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertAudioToInternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条文件资源到内部存储空间 + * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertDownloadToInternal( + Item params, + DevSource source, + OnInsertListener listener + ); + + /** + * 插入一条多媒体资源到内部存储空间 + *
+     *     并不局限于多媒体, 如文本存储、其他文件写入等
+     * 
+ * @param params Storage ( Data、Params ) Item + * @param source 数据来源 + * @param listener 插入多媒体资源事件 + */ + void insertMediaToInternal( + Item params, + DevSource source, + OnInsertListener listener + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/engine/storage/listener/OnInsertListener.java b/lib/DevAssist/src/main/java/dev/engine/storage/listener/OnInsertListener.java new file mode 100644 index 0000000000..c6527bbfab --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/engine/storage/listener/OnInsertListener.java @@ -0,0 +1,23 @@ +package dev.engine.storage.listener; + +import dev.base.DevSource; +import dev.engine.storage.IStorageEngine; + +/** + * detail: 插入多媒体资源事件 + * @author Ttt + */ +public interface OnInsertListener { + + /** + * 插入多媒体资源结果方法 + * @param result 存储结果 + * @param params 原始参数 + * @param source 原始数据 + */ + void onResult( + Result result, + Item params, + DevSource source + ); +} \ No newline at end of file diff --git a/lib/DevAssist/src/main/java/dev/function/DevFunction.java b/lib/DevAssist/src/main/java/dev/function/DevFunction.java new file mode 100644 index 0000000000..ad090db74b --- /dev/null +++ b/lib/DevAssist/src/main/java/dev/function/DevFunction.java @@ -0,0 +1,554 @@ +package dev.function; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import dev.base.DevObject; +import dev.utils.LogPrintUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.thread.DevThreadPool; + +/** + * detail: 执行方法类 + * @author Ttt + */ +public final class DevFunction { + + private DevFunction() { + } + + // 默认线程池对象 + private static final ExecutorService sThreadPool = Executors.newFixedThreadPool( + DevThreadPool.getCalcThreads() + ); + + /** + * detail: 方法体 + * @author Ttt + */ + public interface Method { + + void method(Operation operation); + } + + /** + * detail: 方法体 ( 存在异常触发 ) + * @author Ttt + *
+     *     前提属于调用 try-catch 方法
+     * 
+ */ + public interface Method2 + extends Method { + + void error( + Operation operation, + Throwable error + ); + } + + // ========= + // = 包装类 = + // ========= + + /** + * detail: Function 操作包装类 + * @author Ttt + */ + public static final class Operation { + + // 日志 TAG + private final String TAG; + // 存储数据 + private DevObject mObject; + + public Operation() { + this(Operation.class.getSimpleName()); + } + + public Operation(final String tag) { + if (StringUtils.isNotEmpty(tag)) { + this.TAG = tag; + } else { + this.TAG = Operation.class.getSimpleName(); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Object + * @return {@link DevObject} + */ + public DevObject getObject() { + return mObject; + } + + /** + * 设置 Object + * @param object {@link DevObject} + * @return {@link Operation} + */ + public Operation setObject(final DevObject object) { + this.mObject = object; + return this; + } + + // = + + /** + * 获取 Operation + * @return {@link Operation} + */ + public Operation operation() { + return new Operation(); + } + + /** + * 获取 Operation + * @param tag 日志 TAG + * @return {@link Operation} + */ + public Operation operation(final String tag) { + return new Operation(tag); + } + + // ========== + // = 捕获异常 = + // ========== + + /** + * 捕获异常处理 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation tryCatch(final Method method) { + if (method != null) { + try { + method.method(Operation.this); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "tryCatch"); + if (method instanceof Method2) { + ((Method2) method).error(Operation.this, e); + } + } + } + return this; + } + + // ========== + // = 线程方法 = + // ========== + + /** + * 后台线程执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation thread(final Method method) { + return thread(method, 0L); + } + + /** + * 后台线程执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation thread( + final Method method, + final long delayMillis + ) { + if (method != null) { + new Thread(() -> { + if (delayMillis > 0L) { + try { + Thread.sleep(delayMillis); + } catch (Exception ignored) { + } + } + method.method(Operation.this); + }).start(); + } + return this; + } + + // = + + /** + * 后台线程池执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation threadPool(final Method method) { + return threadPool(sThreadPool, method, 0L); + } + + /** + * 后台线程池执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation threadPool( + final Method method, + final long delayMillis + ) { + return threadPool(sThreadPool, method, delayMillis); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation threadPool( + final ExecutorService pool, + final Method method + ) { + return threadPool(pool, method, 0L); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation threadPool( + final ExecutorService pool, + final Method method, + final long delayMillis + ) { + if (pool != null && method != null) { + pool.execute(() -> { + if (delayMillis > 0L) { + try { + Thread.sleep(delayMillis); + } catch (Exception ignored) { + } + } + method.method(Operation.this); + }); + } + return this; + } + + // ================ + // = 线程捕获异常方法 = + // ================ + + /** + * 后台线程执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation threadCatch(final Method method) { + return threadCatch(method, 0L); + } + + /** + * 后台线程执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation threadCatch( + final Method method, + final long delayMillis + ) { + if (method != null) { + new Thread(() -> { + if (delayMillis > 0L) { + try { + Thread.sleep(delayMillis); + } catch (Exception ignored) { + } + } + try { + method.method(Operation.this); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "threadCatch"); + if (method instanceof Method2) { + ((Method2) method).error(Operation.this, e); + } + } + }).start(); + } + return this; + } + + // = + + /** + * 后台线程池执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation threadPoolCatch(final Method method) { + return threadPoolCatch(sThreadPool, method, 0L); + } + + /** + * 后台线程池执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation threadPoolCatch( + final Method method, + final long delayMillis + ) { + return threadPoolCatch(sThreadPool, method, delayMillis); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @return {@link Operation} + */ + public Operation threadPoolCatch( + final ExecutorService pool, + final Method method + ) { + return threadPoolCatch(pool, method, 0L); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public Operation threadPoolCatch( + final ExecutorService pool, + final Method method, + final long delayMillis + ) { + if (pool != null && method != null) { + pool.execute(() -> { + if (delayMillis > 0L) { + try { + Thread.sleep(delayMillis); + } catch (Exception ignored) { + } + } + try { + method.method(Operation.this); + } catch (Throwable e) { + LogPrintUtils.eTag(TAG, e, "threadPoolCatch"); + if (method instanceof Method2) { + ((Method2) method).error(Operation.this, e); + } + } + }); + } + return this; + } + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取 Operation + * @return {@link Operation} + */ + public static Operation operation() { + return new Operation(); + } + + /** + * 获取 Operation + * @param tag 日志 TAG + * @return {@link Operation} + */ + public static Operation operation(final String tag) { + return new Operation(tag); + } + + /** + * 设置 Object + * @param object {@link DevObject} + * @return {@link Operation} + */ + public static Operation object(final DevObject object) { + return new Operation().setObject(object); + } + + // ========== + // = 捕获异常 = + // ========== + + /** + * 捕获异常处理 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation tryCatch(final Method method) { + return new Operation().tryCatch(method); + } + + // ========== + // = 线程方法 = + // ========== + + /** + * 后台线程执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation thread(final Method method) { + return new Operation().thread(method); + } + + /** + * 后台线程执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation thread( + final Method method, + final long delayMillis + ) { + return new Operation().thread(method, delayMillis); + } + + // = + + /** + * 后台线程池执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation threadPool(final Method method) { + return new Operation().threadPool(method); + } + + /** + * 后台线程池执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation threadPool( + final Method method, + final long delayMillis + ) { + return new Operation().threadPool(method, delayMillis); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation threadPool( + final ExecutorService pool, + final Method method + ) { + return new Operation().threadPool(pool, method); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation threadPool( + final ExecutorService pool, + final Method method, + final long delayMillis + ) { + return new Operation().threadPool(pool, method, delayMillis); + } + + // ================ + // = 线程捕获异常方法 = + // ================ + + /** + * 后台线程执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation threadCatch(final Method method) { + return new Operation().threadCatch(method); + } + + /** + * 后台线程执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation threadCatch( + final Method method, + final long delayMillis + ) { + return new Operation().threadCatch(method, delayMillis); + } + + // = + + /** + * 后台线程池执行 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation threadPoolCatch(final Method method) { + return new Operation().threadPoolCatch(method); + } + + /** + * 后台线程池执行 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation threadPoolCatch( + final Method method, + final long delayMillis + ) { + return new Operation().threadPoolCatch(method, delayMillis); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @return {@link Operation} + */ + public static Operation threadPoolCatch( + final ExecutorService pool, + final Method method + ) { + return new Operation().threadPoolCatch(pool, method); + } + + /** + * 后台线程池执行 + * @param pool 线程池 + * @param method 执行方法 + * @param delayMillis 延迟执行时间 ( 毫秒 ) + * @return {@link Operation} + */ + public static Operation threadPoolCatch( + final ExecutorService pool, + final Method method, + final long delayMillis + ) { + return new Operation().threadPoolCatch(pool, method, delayMillis); + } +} \ No newline at end of file diff --git a/lib/DevBase/.gitignore b/lib/DevBase/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevBase/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevBase/CHANGELOG.md b/lib/DevBase/CHANGELOG.md new file mode 100644 index 0000000000..591340d489 --- /dev/null +++ b/lib/DevBase/CHANGELOG.md @@ -0,0 +1,81 @@ +Change Log +========== + +Version 1.1.4 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.3 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.2 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.1 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.6 *(2021-05-19)* +---------------------------- + +* `[Delete]` 移除 DevBase#isCurrentVisible 相关方法 + +Version 1.0.5 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.4 *(2021-02-27)* +---------------------------- + +* `[Add]` DevBaseViewBindingVH ( RecyclerView ViewBinding ViewHolder ) + +Version 1.0.3 *(2021-01-24)* +---------------------------- + +* `[Perf]` 进行代码检测优化 + +* `[Delete]` 移除 isTryViewBindingCatch 方法,以及相关处理代码 + +Version 1.0.2 *(2020-11-26)* +---------------------------- + +* `[Add]` 新增 IDevBaseMethod#preLoad 预加载方法 + +* `[Add]` 新增 DevBaseContentAssist 悬浮容器 FrameLayout 及对应操作方法 + +Version 1.0.1 *(2020-11-15)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.0.0 *(2020-09-02)* +---------------------------- + +* Initial release diff --git a/lib/DevBase/README.md b/lib/DevBase/README.md new file mode 100644 index 0000000000..83dd5ac804 --- /dev/null +++ b/lib/DevBase/README.md @@ -0,0 +1,106 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevBase:1.1.4' +``` + +## 目录结构 + +``` +- dev.base | 根目录 + - able | 基类库接口相关 + - activity | 核心 Base Activity 代码 + - adapter | RecyclerView ViewBinding ViewHolder + - expand | 基于 Base Activity、Fragment 扩展包 + - content | Content Layout 基类 + - mvp | MVP 架构基类 + - viewbinding | ViewBinding 基类 + - fragment | 核心 Base Fragment 代码 + - utils | 依赖工具包 + - assist | 功能辅助类 ( 抽取通用代码 ) +``` + + +## 项目类结构 - [包目录][包目录] + +### 核心代码 + +* 核心 Base Activity([activity][activity]):整个库 Activity 基类都基于该模块代码 + +* 核心 Base Fragment([fragment][fragment]):整个库 Fragment 基类都基于该模块代码 + +### 其他代码 + +* 接口相关([able][able]):对外提供开放方法接口,用于基类可选配置及获取操作 + +* 库依赖工具包([utils、assist][utils、assist]):抽取通用代码工具类、封装相同逻辑代码辅助类 + +### 基于 Base Activity、Fragment 扩展包([expand][expand]) + +* Content Layout 基类([content][content]):通过内置 Layout 作为根布局,方便对全局进行增删 View 控制处理 + +* MVP 架构基类([mvp][mvp]):MVP Contract Lifecycle 架构基类 + +* ViewBinding 基类([viewbinding][viewbinding]):使用 ViewBinding 实现对 View 进行 bind 基类 + +## 设计思路 + +首先整个库 Activity、Fragment 最终实现都是继承 [AbstractDevBaseActivity][AbstractDevBaseActivity]、[AbstractDevBaseFragment][AbstractDevBaseFragment] + +方便对核心代码设计理解及管理控制,并在此基础上实现三个扩展基类 MVP、ViewBinding、Content Layout + +* **ViewBinding 基类** + +> 使用 ViewBinding 代替频繁 findViewById,或替换 [Butter Knife][Butter Knife] +> +> **Butter Knife Attention**: This tool is now deprecated. Please switch to [view binding][view binding] + +* **MVP 架构基类** + +> 使用 MVP Contract 来进行管理,优化代码结构并使用 [Lifecycle][Lifecycle] 解决 MVP 内存泄漏问题 + +* **Content Layout 基类** + +> **核心实现:内置 R.layout.base_content_view 作为 contentView 根布局进行显示** +> +> 并进行动态添加 title、body 等布局 View,以达到能够对全局进行 View 增删显隐控制处理,以及后续需求迭代、维护全局操作 + +各个扩展基类都有实现 MVP、ViewBinding 组合功能,如:`MVPViewBinding`、`ContentMVP`、`ContentViewBinding`、`ContentMVPViewBinding` 组合基类 + +## 其他 + +* 为什么没添加 MVVM 架构基类 + +> 因 MVVM 需要依赖较多库,可能部分项目并不使用 MVVM 作为基础架构,为此新增 [DevBaseMVVM][DevBaseMVVM] 库进行区分,减少库依赖数量,以及 MVVM 架构代码实现设计理解 + +架构只是一种思维方式,不管是 MVC、MVP 还是 MVVM,都只是一种思考问题、解决问题的思维 + +其目的是要解决编程过程中,模块内部高内聚、模块与模块之间低耦合、可维护性、易测试等问题 + +* 混淆 + +> -keep class 包名.databinding.** {*;} + +因为 [ViewBindingUtils][ViewBindingUtils] 是通过反射进行初始化,防止方法 `bind`、`inflate` 被混淆,所以需要忽略自动生成的 ViewBinding 类 + + + + + +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base +[activity]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/activity +[fragment]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/fragment +[able]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/able +[utils、assist]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/utils +[expand]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/expand +[content]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/expand/content +[mvp]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/expand/mvp +[viewbinding]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/expand/viewbinding +[AbstractDevBaseActivity]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/activity/AbstractDevBaseActivity.kt +[AbstractDevBaseFragment]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/fragment/AbstractDevBaseFragment.kt +[Butter Knife]: https://github.com/JakeWharton/butterknife +[view binding]: https://developer.android.com/topic/libraries/view-binding +[Lifecycle]: https://developer.android.com/topic/libraries/architecture/lifecycle +[DevBaseMVVM]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md +[ViewBindingUtils]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/src/main/java/dev/base/utils/ViewBindingUtils.kt \ No newline at end of file diff --git a/lib/DevBase/build.gradle b/lib/DevBase/build.gradle new file mode 100644 index 0000000000..d0884f7877 --- /dev/null +++ b/lib/DevBase/build.gradle @@ -0,0 +1,45 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply from: rootProject.file(files.unified_use_view_binding_gradle) + +android.defaultConfig { + versionCode versions.dev_base_versionCode + versionName versions.dev_base_versionName + // DevBase Module Version + buildConfigField "int", "DevBase_VersionCode", "${versions.dev_base_versionCode}" + buildConfigField "String", "DevBase_Version", "\"${versions.dev_base_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.kotlin.stdlib + api deps.kotlin.core + api deps.kotlin.coroutines + api deps.kotlin.lifecycle_runtime + api deps.kotlin.lifecycle_common_java8 + api deps.kotlin.fragment + api deps.kotlin.activity + api deps.androidx.design + api deps.androidx.appcompat + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_app + } else { + // 编译时使用 + api project(':DevApp') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevBase/proguard-rules.pro b/lib/DevBase/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/DevBase/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/DevBase/project.properties b/lib/DevBase/project.properties new file mode 100644 index 0000000000..fbc233834b --- /dev/null +++ b/lib/DevBase/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevBase +project.groupId=io.github.afkt +project.artifactId=DevBase +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevBase \ No newline at end of file diff --git a/lib/DevBase/src/main/AndroidManifest.xml b/lib/DevBase/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..55c2f2c37e --- /dev/null +++ b/lib/DevBase/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/DevBase.kt b/lib/DevBase/src/main/java/dev/base/DevBase.kt new file mode 100644 index 0000000000..358d6cd2f2 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/DevBase.kt @@ -0,0 +1,71 @@ +package dev.base + +/** + * detail: DevBase + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevBase { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevBase 版本号 + * @return DevBase versionCode + */ + fun getDevBaseVersionCode(): Int { + return BuildConfig.DevBase_VersionCode + } + + /** + * 获取 DevBase 版本 + * @return DevBase versionName + */ + fun getDevBaseVersion(): String { + return BuildConfig.DevBase_Version + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + fun getDevAppVersionCode(): Int { + return BuildConfig.DevApp_VersionCode + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + fun getDevAppVersion(): String { + return BuildConfig.DevApp_Version + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBase.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBase.kt new file mode 100644 index 0000000000..7b8f1cd49a --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBase.kt @@ -0,0 +1,10 @@ +package dev.base.able + +/** + * detail: 基类相关方法 + * @author Ttt + */ +interface IDevBase : IDevBaseConfig, + IDevBaseContent, + IDevBaseMethod, + IDevBaseUIOperation \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseConfig.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseConfig.kt new file mode 100644 index 0000000000..df2560727f --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseConfig.kt @@ -0,0 +1,16 @@ +package dev.base.able + +/** + * detail: 基类配置 + * @author Ttt + */ +interface IDevBaseConfig { + + /** + * 是否进行 Activity 管理控制 + * 可通过 lifecycle 实现 Activity Manager + */ + fun isActivityManager(): Boolean { + return true + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseContent.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseContent.kt new file mode 100644 index 0000000000..8aae8b7027 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseContent.kt @@ -0,0 +1,22 @@ +package dev.base.able + +import android.view.View + +/** + * detail: 基类 Content View 相关方法 + * @author Ttt + */ +interface IDevBaseContent { + + /** + * 获取 Content Layout Id + * @return Content Layout Id + */ + fun baseContentId(): Int + + /** + * 获取 Content Layout View + * @return Content Layout View + */ + fun baseContentView(): View? +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseLayout.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseLayout.kt new file mode 100644 index 0000000000..59b4051343 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseLayout.kt @@ -0,0 +1,29 @@ +package dev.base.able + +import android.view.View + +/** + * detail: 基类 Layout View 相关方法 + * @author Ttt + */ +interface IDevBaseLayout { + + /** + * 获取 Layout Id ( 用于 contentLinear addView ) + * @return Layout Id + */ + fun baseLayoutId(): Int + + /** + * 获取 Layout View ( 用于 contentLinear addView ) + * @return Layout View + */ + fun baseLayoutView(): View? + + /** + * Layout View addView 是否 LayoutParams.MATCH_PARENT + */ + fun isLayoutMatchParent(): Boolean { + return true + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseMethod.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseMethod.kt new file mode 100644 index 0000000000..a02135e973 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseMethod.kt @@ -0,0 +1,56 @@ +package dev.base.able + +/** + * detail: 基类通用方法 + * @author Ttt + */ +interface IDevBaseMethod { + + /** + * 初始化顺序 ( 需主动调用 ) + */ + fun initOrder() { + initView() + initValue() + initListener() + initObserve() + initOther() + } + + /** + * 预加载方法 ( 需主动调用, 预留可选 ) + * 例: + * Activity : [preLoad] ( onCreate ) => [initOrder] ( onCreate ) + * Fragment : [preLoad] ( onCreateView ) => [initOrder] ( onViewCreated ) + */ + fun preLoad() {} + + // ============ + // = 初始化方法 = + // ============ + + /** + * 初始化 View + */ + fun initView() + + /** + * 初始化参数、配置 + */ + fun initValue() + + /** + * 初始化事件 + */ + fun initListener() + + /** + * 初始化观察事件 + */ + fun initObserve() + + /** + * 初始化其他操作 + */ + fun initOther() +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseUIOperation.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseUIOperation.kt new file mode 100644 index 0000000000..daa52f6421 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseUIOperation.kt @@ -0,0 +1,120 @@ +package dev.base.able + +import android.app.Dialog +import android.widget.PopupWindow +import androidx.fragment.app.DialogFragment + +/** + * detail: 基类 UI 交互相关操作方法 + * @author Ttt + */ +interface IDevBaseUIOperation { + + // ========= + // = Toast = + // ========= + + /** + * 显示 Toast + * @param text Toast 提示文本 + * @param formatArgs 格式化参数 + */ + fun showToast( + text: String?, + vararg formatArgs: Any + ) + + /** + * 显示 Toast + * @param resId R.string.id + * @param formatArgs 格式化参数 + */ + fun showToast( + resId: Int, + vararg formatArgs: Any + ) + + // =============== + // = PopupWindow = + // =============== + + /** + * 获取 PopupWindow + * @return [PopupWindow] + */ + fun getDevPopupWindow(): PopupWindow? + + /** + * 设置 PopupWindow + * @param popupWindow [PopupWindow] + * @return [PopupWindow] + */ + fun setDevPopupWindow(popupWindow: T): T + + /** + * 设置 PopupWindow + * @param isClose 是否关闭之前的 PopupWindow + * @param popupWindow [PopupWindow] + * @return [PopupWindow] + */ + fun setDevPopupWindow( + isClose: Boolean, + popupWindow: T + ): T + + // ========== + // = Dialog = + // ========== + + /** + * 获取 Dialog + * @return [Dialog] + */ + fun getDevDialog(): Dialog? + + /** + * 设置 Dialog + * @param dialog [Dialog] + * @return [Dialog] + */ + fun setDevDialog(dialog: T): T + + /** + * 设置 Dialog + * @param isClose 是否关闭之前的 Dialog + * @param dialog [Dialog] + * @return [Dialog] + */ + fun setDevDialog( + isClose: Boolean, + dialog: T + ): T + + // ================== + // = DialogFragment = + // ================== + + /** + * 获取 DialogFragment + * @return [DialogFragment] + */ + fun getDevDialogFragment(): DialogFragment? + + /** + * 设置 DialogFragment + * @param dialog [DialogFragment] + * @return [DialogFragment] + */ + fun setDevDialogFragment(dialog: T): T + + /** + * 设置 DialogFragment + * @param isClose 是否关闭之前的 DialogFragment + * @param dialog [DialogFragment] + * @return [DialogFragment] + */ + fun setDevDialogFragment( + isClose: Boolean, + dialog: T + ): T +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/able/IDevBaseViewBinding.kt b/lib/DevBase/src/main/java/dev/base/able/IDevBaseViewBinding.kt new file mode 100644 index 0000000000..aef5955751 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/able/IDevBaseViewBinding.kt @@ -0,0 +1,42 @@ +package dev.base.able + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding + +/** + * detail: 基类 ViewBinding 接口 + * @author Ttt + */ +interface IDevBaseViewBinding { + + /** + * View Bind 初始化操作 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + */ + fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): VB + + /** + * 获取待 Bind View + */ + fun getBindingView(): View? + + /** + * 是否 Bind View + */ + fun isViewBinding(): Boolean { + return true + } + + /** + * 是否分离 ( 销毁 ) Binding + */ + fun isDetachBinding(): Boolean { + return isViewBinding() + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/activity/AbstractDevBaseActivity.kt b/lib/DevBase/src/main/java/dev/base/activity/AbstractDevBaseActivity.kt new file mode 100644 index 0000000000..041f6182f5 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/activity/AbstractDevBaseActivity.kt @@ -0,0 +1,213 @@ +package dev.base.activity + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.PopupWindow +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.DialogFragment +import dev.base.able.IDevBase +import dev.base.utils.assist.DevBaseAssist +import dev.utils.app.ActivityUtils + +/** + * detail: Activity 抽象基类 + * @author Ttt + */ +abstract class AbstractDevBaseActivity : AppCompatActivity(), + IDevBase { + + // ========== + // = Object = + // ========== + + @JvmField // 日志 TAG ( 根据使用习惯命名大写 ) + protected var TAG = AbstractDevBaseActivity::class.java.simpleName + + @JvmField // Content View + protected var mContentView: View? = null + + @JvmField // DevBase 合并相同代码辅助类 + protected var assist = DevBaseAssist() + + // ========== + // = 生命周期 = + // ========== + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 获取当前类名 + TAG = this.javaClass.simpleName + // 设置数据 + assist + .setTag(TAG) + .setContext(this) + .printLog("onCreate") + // 添加 Activity + if (isActivityManager()) ActivityUtils.getManager().addActivity(this) + // Content View 初始化处理 + contentInit(LayoutInflater.from(this), null) + // 设置 Content View + mContentView?.let { setContentView(it) } + } + + override fun onStart() { + super.onStart() + assist.printLog("onStart") + } + + override fun onRestart() { + super.onRestart() + assist.printLog("onRestart") + } + + override fun onResume() { + super.onResume() + assist.printLog("onResume") + } + + override fun onPause() { + super.onPause() + assist.printLog("onPause") + } + + override fun onStop() { + super.onStop() + assist.printLog("onStop") + } + + override fun onDestroy() { + super.onDestroy() + assist.printLog("onDestroy") + // 移除当前 Activity + if (isActivityManager()) ActivityUtils.getManager().removeActivity(this) + } + + /** + * 返回键点击触发 + * 重新实现该方法必须保留 super.onBackPressed() + */ + override fun onBackPressed() { + super.onBackPressed() + assist.printLog("onBackPressed") + } + + // =================== + // = IDevBaseContent = + // =================== + + /** + * Content View 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + */ + private fun contentInit( + inflater: LayoutInflater, + container: ViewGroup? + ) { + if (mContentView != null) return + // 使用 baseContentId() + if (baseContentId() != 0) { + try { + mContentView = inflater.inflate(baseContentId(), container, false) + } catch (e: Exception) { + assist.printLog(e, "contentInit - baseContentId") + } + } + // 如果 View 等于 null, 则使用 baseContentView() + if (mContentView == null) mContentView = baseContentView() + } + + // ================== + // = IDevBaseMethod = + // ================== + + // ============ + // = 初始化方法 = + // ============ + + override fun initView() { + assist.printLog("initView") + } + + override fun initValue() { + assist.printLog("initValue") + } + + override fun initListener() { + assist.printLog("initListener") + } + + override fun initObserve() { + assist.printLog("initObserve") + } + + override fun initOther() { + assist.printLog("initOther") + } + + // ======================= + // = IDevBaseUIOperation = + // ======================= + + override fun showToast( + text: String?, + vararg formatArgs: Any + ) { + assist.showToast(text, formatArgs) + } + + override fun showToast( + resId: Int, + vararg formatArgs: Any + ) { + assist.showToast(resId, formatArgs) + } + + override fun getDevPopupWindow(): PopupWindow? { + return assist.getDevPopupWindow() + } + + override fun setDevPopupWindow(popupWindow: T): T { + return assist.setDevPopupWindow(popupWindow) + } + + override fun setDevPopupWindow( + isClose: Boolean, + popupWindow: T + ): T { + return assist.setDevPopupWindow(isClose, popupWindow) + } + + override fun getDevDialog(): Dialog? { + return assist.getDevDialog() + } + + override fun setDevDialog(dialog: T): T { + return assist.setDevDialog(dialog) + } + + override fun setDevDialog( + isClose: Boolean, + dialog: T + ): T { + return assist.setDevDialog(isClose, dialog) + } + + override fun getDevDialogFragment(): DialogFragment? { + return assist.getDevDialogFragment() + } + + override fun setDevDialogFragment(dialog: T): T { + return assist.setDevDialogFragment(dialog) + } + + override fun setDevDialogFragment( + isClose: Boolean, + dialog: T + ): T { + return assist.setDevDialogFragment(isClose, dialog) + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/activity/DevBaseActivity.kt b/lib/DevBase/src/main/java/dev/base/activity/DevBaseActivity.kt new file mode 100644 index 0000000000..b08d8a1dc9 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/activity/DevBaseActivity.kt @@ -0,0 +1,12 @@ +package dev.base.activity + +import android.view.View + +/** + * detail: Activity 基类 + * @author Ttt + */ +abstract class DevBaseActivity : AbstractDevBaseActivity(), + View.OnClickListener { + override fun onClick(v: View) {} +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/adapter/DevBaseViewBindingVH.kt b/lib/DevBase/src/main/java/dev/base/adapter/DevBaseViewBindingVH.kt new file mode 100644 index 0000000000..eaceeb39d3 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/adapter/DevBaseViewBindingVH.kt @@ -0,0 +1,79 @@ +package dev.base.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding +import dev.base.utils.ViewBindingUtils + +/** + * detail: RecyclerView ViewBinding ViewHolder + * @author Ttt + */ +class DevBaseViewBindingVH(val binding: VB) : RecyclerView.ViewHolder( + binding.root +) { + companion object { + @JvmStatic + fun create( + clazz: Class, + parent: ViewGroup, + @LayoutRes resource: Int + ) = + DevBaseViewBindingVH( + ViewBindingUtils.viewBinding( + view = LayoutInflater.from(parent.context).inflate(resource, parent, false), + clazz = clazz + ) + ) + + @JvmStatic + fun create( + clazz: Class, + context: Context, + @LayoutRes resource: Int + ) = + DevBaseViewBindingVH( + ViewBindingUtils.viewBinding( + view = LayoutInflater.from(context).inflate(resource, null), + clazz = clazz + ) + ) + + @JvmStatic + fun create( + clazz: Class, + view: View + ) = + DevBaseViewBindingVH( + ViewBindingUtils.viewBinding( + view = view, + clazz = clazz + ) + ) + } +} + +inline fun newBindingViewHolder( + parent: ViewGroup, + @LayoutRes resource: Int +): DevBaseViewBindingVH = + DevBaseViewBindingVH.create(VB::class.java, parent, resource) + +inline fun newBindingViewHolder( + context: Context, + @LayoutRes resource: Int +): DevBaseViewBindingVH = + DevBaseViewBindingVH.create(VB::class.java, context, resource) + +inline fun newBindingViewHolder( + view: View, +): DevBaseViewBindingVH = + DevBaseViewBindingVH.create(VB::class.java, view) + +inline fun newBindingViewHolder( + binding: VB +): DevBaseViewBindingVH = DevBaseViewBindingVH(binding) \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentActivity.kt new file mode 100644 index 0000000000..6088c0e709 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentActivity.kt @@ -0,0 +1,88 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import dev.base.R +import dev.base.able.IDevBaseLayout +import dev.base.activity.DevBaseActivity +import dev.base.utils.assist.DevBaseContentAssist + +/** + * detail: Content Activity 基类 + * @author Ttt + * 内置 R.layout.base_content_view 作为 contentView 并对所需 View 进行 Add + * 设计思路: + * 全局统一使用 R.layout.base_content_view 作为根布局进行显示 + * 并且进行动态添加 title、body 等布局 View + * 能够对全局进行增删 View 控制处理, 以及后期全局需求配置 + */ +abstract class DevBaseContentActivity : DevBaseActivity(), + IDevBaseLayout { + + @JvmField // Layout View + protected var layoutView: View? = null + + @JvmField // DevBase ContentView 填充辅助类 + protected var contentAssist = DevBaseContentAssist() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 绑定 ContentView 填充辅助类 + contentAssist.bind(this) + // Layout View 初始化处理 + layoutInit(layoutInflater, null) + // 添加到 contentLinear + if (isLayoutMatchParent()) { + contentAssist.addContentView( + layoutView, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + ) + } else { + contentAssist.addContentView(layoutView) + } + } + + // =================== + // = IDevBaseContent = + // =================== + + final override fun baseContentId(): Int { + return R.layout.base_content_view + } + + final override fun baseContentView(): View? { + return null + } + + // ================== + // = IDevBaseLayout = + // ================== + + /** + * Layout View 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + */ + private fun layoutInit( + inflater: LayoutInflater, + container: ViewGroup? + ) { + if (layoutView != null) return + // 使用 baseLayoutId() + if (baseLayoutId() != 0) { + try { + layoutView = inflater.inflate(baseLayoutId(), container, false) + } catch (e: Exception) { + assist.printLog(e, "layoutInit - baseLayoutId") + } + } + // 如果 View 等于 null, 则使用 baseLayoutView() + if (layoutView == null) layoutView = baseLayoutView() + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentFragment.kt new file mode 100644 index 0000000000..dd3792de14 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentFragment.kt @@ -0,0 +1,88 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import dev.base.R +import dev.base.able.IDevBaseLayout +import dev.base.fragment.DevBaseFragment +import dev.base.utils.assist.DevBaseContentAssist + +/** + * detail: Content Fragment 基类 + * @author Ttt + */ +abstract class DevBaseContentFragment : DevBaseFragment(), + IDevBaseLayout { + + @JvmField // Layout View + protected var layoutView: View? = null + + @JvmField // DevBase ContentView 填充辅助类 + protected var contentAssist = DevBaseContentAssist() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + // 绑定 ContentView 填充辅助类 + contentAssist.bind(mContentView) + // Layout View 初始化处理 + layoutInit(layoutInflater, null) + // 添加到 contentLinear + if (isLayoutMatchParent()) { + contentAssist.addContentView( + layoutView, + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + ) + } else { + contentAssist.addContentView(layoutView) + } + return mContentView + } + + // =================== + // = IDevBaseContent = + // =================== + + final override fun baseContentId(): Int { + return R.layout.base_content_view + } + + final override fun baseContentView(): View? { + return null + } + + // ================== + // = IDevBaseLayout = + // ================== + + /** + * Layout View 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + */ + private fun layoutInit( + inflater: LayoutInflater, + container: ViewGroup? + ) { + if (layoutView != null) return + // 使用 baseLayoutId() + if (baseLayoutId() != 0) { + try { + layoutView = inflater.inflate(baseLayoutId(), container, false) + } catch (e: Exception) { + assist.printLog(e, "layoutInit - baseLayoutId") + } + } + // 如果 View 等于 null, 则使用 baseLayoutView() + if (layoutView == null) layoutView = baseLayoutView() + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPActivity.kt new file mode 100644 index 0000000000..e9cab9f94a --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPActivity.kt @@ -0,0 +1,30 @@ +package dev.base.expand.content + +import android.os.Bundle +import dev.base.expand.mvp.MVP + +/** + * detail: Content MVP Activity 基类 + * @author Ttt + */ +abstract class DevBaseContentMVPActivity

> : + DevBaseContentActivity() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPFragment.kt new file mode 100644 index 0000000000..b4dce4e974 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPFragment.kt @@ -0,0 +1,30 @@ +package dev.base.expand.content + +import android.os.Bundle +import dev.base.expand.mvp.MVP + +/** + * detail: Content MVP Fragment 基类 + * @author Ttt + */ +abstract class DevBaseContentMVPFragment

> : + DevBaseContentFragment() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingActivity.kt new file mode 100644 index 0000000000..252d02c414 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingActivity.kt @@ -0,0 +1,31 @@ +package dev.base.expand.content + +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import dev.base.expand.mvp.MVP + +/** + * detail: Content MVP Activity ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentMVPViewBindingActivity

, VB : ViewBinding> : + DevBaseContentViewBindingActivity() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingFragment.kt new file mode 100644 index 0000000000..64f0eb33b5 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentMVPViewBindingFragment.kt @@ -0,0 +1,31 @@ +package dev.base.expand.content + +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import dev.base.expand.mvp.MVP + +/** + * detail: Content MVP Fragment ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentMVPViewBindingFragment

, VB : ViewBinding> : + DevBaseContentViewBindingFragment() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingActivity.kt new file mode 100644 index 0000000000..f9349f8265 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingActivity.kt @@ -0,0 +1,47 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.able.IDevBaseViewBinding +import dev.base.utils.ViewBindingUtils + +/** + * detail: Content Activity ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentViewBindingActivity : DevBaseContentActivity(), + IDevBaseViewBinding { + + lateinit var binding: VB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (isViewBinding()) { + // ViewBinding 初始化处理 + binding = viewBinding(layoutInflater, null) + } + } + + // ======================= + // = IDevBaseViewBinding = + // ======================= + + final override fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): VB { + return ViewBindingUtils.viewBindingJavaClass( + inflater, + container, + getBindingView(), + javaClass + ) + } + + final override fun getBindingView(): View? { + return layoutView + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingFragment.kt new file mode 100644 index 0000000000..8be48cc183 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/content/DevBaseContentViewBindingFragment.kt @@ -0,0 +1,58 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.able.IDevBaseViewBinding +import dev.base.utils.ViewBindingUtils + +/** + * detail: Content Fragment ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentViewBindingFragment : DevBaseContentFragment(), + IDevBaseViewBinding { + + private var _binding: VB? = null + val binding: VB get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + if (isViewBinding()) { + // ViewBinding 初始化处理 + _binding = viewBinding(inflater, container) + } + return mContentView + } + + override fun onDestroyView() { + super.onDestroyView() + if (isDetachBinding()) _binding = null + } + + // ======================= + // = IDevBaseViewBinding = + // ======================= + + final override fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): VB { + return ViewBindingUtils.viewBindingJavaClass( + inflater, + container, + getBindingView(), + javaClass + ) + } + + final override fun getBindingView(): View? { + return layoutView + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPActivity.kt new file mode 100644 index 0000000000..d0fea88fce --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPActivity.kt @@ -0,0 +1,31 @@ +package dev.base.expand.mvp + +import android.os.Bundle +import dev.base.activity.DevBaseActivity + +/** + * detail: MVP Activity 基类 + * @author Ttt + * 需要自己实现 Contract ( 契约类 ) 用来管理 View 与 Presenter 的交互 + */ +abstract class DevBaseMVPActivity

> : + DevBaseActivity() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPFragment.kt new file mode 100644 index 0000000000..ff7f77c908 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPFragment.kt @@ -0,0 +1,30 @@ +package dev.base.expand.mvp + +import android.os.Bundle +import dev.base.fragment.DevBaseFragment + +/** + * detail: MVP Fragment 基类 + * @author Ttt + */ +abstract class DevBaseMVPFragment

> : + DevBaseFragment() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingActivity.kt new file mode 100644 index 0000000000..66fdab6241 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingActivity.kt @@ -0,0 +1,31 @@ +package dev.base.expand.mvp + +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import dev.base.expand.viewbinding.DevBaseViewBindingActivity + +/** + * detail: MVP Activity ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseMVPViewBindingActivity

, VB : ViewBinding> : + DevBaseViewBindingActivity() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingFragment.kt new file mode 100644 index 0000000000..74bc785d22 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/mvp/DevBaseMVPViewBindingFragment.kt @@ -0,0 +1,31 @@ +package dev.base.expand.mvp + +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import dev.base.expand.viewbinding.DevBaseViewBindingFragment + +/** + * detail: MVP Fragment ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseMVPViewBindingFragment

, VB : ViewBinding> : + DevBaseViewBindingFragment() { + + // MVP Presenter + lateinit var presenter: P + + override fun onCreate(savedInstanceState: Bundle?) { + // 创建 MVP 模式的 Presenter + presenter = createPresenter() + // lifecycle + lifecycle.addObserver(presenter) + // 初始化操作 + super.onCreate(savedInstanceState) + } + + /** + * 初始化创建 Presenter + * @return [MVP.Presenter] + */ + abstract fun createPresenter(): P +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/mvp/MVP.kt b/lib/DevBase/src/main/java/dev/base/expand/mvp/MVP.kt new file mode 100644 index 0000000000..499bae430e --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/mvp/MVP.kt @@ -0,0 +1,92 @@ +package dev.base.expand.mvp + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner + +/** + * detail: MVP Contract 类 + * @author Ttt + */ +class MVP private constructor() { + + /** + * detail: 空实现 MVPView + * @author Ttt + */ + class ViewImpl : IView + + // ======= + // = MVP = + // ======= + + /** + * detail: MVP 模式的 Model ( 通常作为获取数据 ) + * @author Ttt + */ + interface IModel + + /** + * detail: MVP 模式的 View ( 通过 Presenter 将数据传入到该层, 负责 View 的展示相关 ) + * @author Ttt + */ + interface IView + + /** + * detail: MVP 模式 P 层接口类 + * @author Ttt + */ + interface IPresenter { + + /** + * 设置 View 层与 P 层 关联持有 + * @param view [IView] + */ + fun attachView(view: V) + + /** + * 销毁 View 与 P 层 关联关系 + */ + fun detachView() + } + + /** + * detail: MVP 模式的指挥者 ( 连接 View 和 Model) + * @author Ttt + */ + open class Presenter : IPresenter, + DefaultLifecycleObserver { + + // 是否分离 MVP View + private var detach = true + + constructor(view: V) : this(view, true) + + constructor( + view: V, + detach: Boolean + ) { + this.mvpView = view + this.detach = detach + } + + // IView + @JvmField + protected var mvpView: V? = null + + // IModel + @JvmField + protected var mvpModel: M? = null + + override fun attachView(view: V) { + mvpView = view + } + + override fun detachView() { + mvpView = null + } + + override fun onDestroy(owner: LifecycleOwner) { + if (detach) detachView() + } + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingActivity.kt b/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingActivity.kt new file mode 100644 index 0000000000..824a37317d --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingActivity.kt @@ -0,0 +1,48 @@ +package dev.base.expand.viewbinding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.able.IDevBaseViewBinding +import dev.base.activity.DevBaseActivity +import dev.base.utils.ViewBindingUtils + +/** + * detail: Activity ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseViewBindingActivity : DevBaseActivity(), + IDevBaseViewBinding { + + lateinit var binding: VB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (isViewBinding()) { + // ViewBinding 初始化处理 + binding = viewBinding(layoutInflater, null) + } + } + + // ======================= + // = IDevBaseViewBinding = + // ======================= + + final override fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): VB { + return ViewBindingUtils.viewBindingJavaClass( + inflater, + container, + getBindingView(), + javaClass + ) + } + + final override fun getBindingView(): View? { + return mContentView + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingFragment.kt b/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingFragment.kt new file mode 100644 index 0000000000..138abb33bc --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/expand/viewbinding/DevBaseViewBindingFragment.kt @@ -0,0 +1,59 @@ +package dev.base.expand.viewbinding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.able.IDevBaseViewBinding +import dev.base.fragment.DevBaseFragment +import dev.base.utils.ViewBindingUtils + +/** + * detail: Fragment ViewBinding 基类 + * @author Ttt + */ +abstract class DevBaseViewBindingFragment : DevBaseFragment(), + IDevBaseViewBinding { + + private var _binding: VB? = null + val binding: VB get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + if (isViewBinding()) { + // ViewBinding 初始化处理 + _binding = viewBinding(inflater, container) + } + return mContentView + } + + override fun onDestroyView() { + super.onDestroyView() + if (isDetachBinding()) _binding = null + } + + // ======================= + // = IDevBaseViewBinding = + // ======================= + + final override fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup? + ): VB { + return ViewBindingUtils.viewBindingJavaClass( + inflater, + container, + getBindingView(), + javaClass + ) + } + + final override fun getBindingView(): View? { + return mContentView + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/fragment/AbstractDevBaseFragment.kt b/lib/DevBase/src/main/java/dev/base/fragment/AbstractDevBaseFragment.kt new file mode 100644 index 0000000000..1b36c94ccd --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/fragment/AbstractDevBaseFragment.kt @@ -0,0 +1,245 @@ +package dev.base.fragment + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.PopupWindow +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import dev.base.able.IDevBase +import dev.base.utils.assist.DevBaseAssist + +/** + * detail: Fragment 抽象基类 + * @author Ttt + * androidx 来袭, Fragment 如何更简单的实现懒加载 + * @see https://mp.weixin.qq.com/s/3iZOIy5-F5qfXS22buFxrA + */ +abstract class AbstractDevBaseFragment : Fragment(), + IDevBase { + + // ========== + // = Object = + // ========== + + @JvmField // 日志 TAG ( 根据使用习惯命名大写 ) + protected var TAG = AbstractDevBaseFragment::class.java.simpleName + + @JvmField // Content View + protected var mContentView: View? = null + + @JvmField // DevBase 合并相同代码辅助类 + protected var assist = DevBaseAssist() + + // ========== + // = 生命周期 = + // ========== + + override fun onAttach(context: Context) { + super.onAttach(context) + // 获取当前类名 + TAG = this.javaClass.simpleName + // 设置数据 + assist + .setTag(TAG) + .setContext(context) + .printLog("onAttach") + } + + override fun onDetach() { + super.onDetach() + assist.printLog("onDetach") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + assist.printLog("onCreate") + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + assist.printLog("onCreateView") + + if (mContentView != null) { + // ViewUtils.removeSelfFromParent(mContentView) + val parent = mContentView!!.parent as? ViewGroup + // 删除已经在显示的 View 防止切回来不加载一片空白 + parent?.removeView(mContentView) + mContentView = null + } + // Content View 初始化处理 + contentInit(inflater, container) + return mContentView + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + assist.printLog("onViewCreated") + } + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + assist.printLog("onHiddenChanged - hidden: $hidden") + } + + override fun setUserVisibleHint(isVisibleToUser: Boolean) { + super.setUserVisibleHint(isVisibleToUser) + assist.printLog("setUserVisibleHint") + } + + override fun onStart() { + super.onStart() + assist.printLog("onStart") + } + + override fun onResume() { + super.onResume() + assist.printLog("onResume") + } + + override fun onPause() { + super.onPause() + assist.printLog("onPause") + } + + override fun onStop() { + super.onStop() + assist.printLog("onStop") + } + + override fun onDestroyView() { + super.onDestroyView() + assist.printLog("onDestroyView") + } + + override fun onDestroy() { + super.onDestroy() + assist.printLog("onDestroy") + } + + // =================== + // = IDevBaseContent = + // =================== + + /** + * Content View 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + */ + private fun contentInit( + inflater: LayoutInflater, + container: ViewGroup? + ) { + if (mContentView != null) return + // 使用 baseContentId() + if (baseContentId() != 0) { + try { + mContentView = inflater.inflate(baseContentId(), container, false) + } catch (e: Exception) { + assist.printLog(e, "contentInit - baseContentId") + } + } + // 如果 View 等于 null, 则使用 baseContentView() + if (mContentView == null) mContentView = baseContentView() + } + + // ================== + // = IDevBaseMethod = + // ================== + + // ============ + // = 初始化方法 = + // ============ + + override fun initView() { + assist.printLog("initView") + } + + override fun initValue() { + assist.printLog("initValue") + } + + override fun initListener() { + assist.printLog("initListener") + } + + override fun initObserve() { + assist.printLog("initObserve") + } + + override fun initOther() { + assist.printLog("initOther") + } + + // ======================= + // = IDevBaseUIOperation = + // ======================= + + override fun showToast( + text: String?, + vararg formatArgs: Any + ) { + assist.showToast(text, formatArgs) + } + + override fun showToast( + resId: Int, + vararg formatArgs: Any + ) { + assist.showToast(resId, formatArgs) + } + + override fun getDevPopupWindow(): PopupWindow? { + return assist.getDevPopupWindow() + } + + override fun setDevPopupWindow(popupWindow: T): T { + return assist.setDevPopupWindow(popupWindow) + } + + override fun setDevPopupWindow( + isClose: Boolean, + popupWindow: T + ): T { + return assist.setDevPopupWindow(isClose, popupWindow) + } + + override fun getDevDialog(): Dialog? { + return assist.getDevDialog() + } + + override fun setDevDialog(dialog: T): T { + return assist.setDevDialog(dialog) + } + + override fun setDevDialog( + isClose: Boolean, + dialog: T + ): T { + return assist.setDevDialog(isClose, dialog) + } + + override fun getDevDialogFragment(): DialogFragment? { + return assist.getDevDialogFragment() + } + + override fun setDevDialogFragment(dialog: T): T { + return assist.setDevDialogFragment(dialog) + } + + override fun setDevDialogFragment( + isClose: Boolean, + dialog: T + ): T { + return assist.setDevDialogFragment(isClose, dialog) + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/fragment/DevBaseFragment.kt b/lib/DevBase/src/main/java/dev/base/fragment/DevBaseFragment.kt new file mode 100644 index 0000000000..2e3fe8202e --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/fragment/DevBaseFragment.kt @@ -0,0 +1,12 @@ +package dev.base.fragment + +import android.view.View + +/** + * detail: Fragment 基类 + * @author Ttt + */ +abstract class DevBaseFragment : AbstractDevBaseFragment(), + View.OnClickListener { + override fun onClick(v: View) {} +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/utils/ViewBindingUtils.kt b/lib/DevBase/src/main/java/dev/base/utils/ViewBindingUtils.kt new file mode 100644 index 0000000000..ad97a60651 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/utils/ViewBindingUtils.kt @@ -0,0 +1,88 @@ +package dev.base.utils + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.utils.LogPrintUtils +import java.lang.reflect.ParameterizedType + +/** + * detail: ViewBinding 工具类 + * @author Ttt + */ +object ViewBindingUtils { + + // 日志 TAG + private val TAG = ViewBindingUtils::class.java.simpleName + + /** + * 获取 VB Class + * @param clazz javaClass + */ + fun getClassVB(clazz: Class): Class { + val parameterizedType = clazz.genericSuperclass as ParameterizedType + val types = parameterizedType.actualTypeArguments + for (type in types) { + if (type.toString().endsWith("Binding")) { + return type as Class + } + } + return types[0] as Class + } + + /** + * ViewBinding 初始化处理 ( 通过传入 javaClass ) + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + * @param view 待绑定 View + * @param clazz javaClass + */ + fun viewBindingJavaClass( + inflater: LayoutInflater? = null, + container: ViewGroup? = null, + view: View?, + clazz: Class + ): VB { + try { + return viewBinding( + inflater, container, view, getClassVB(clazz) + ) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewBinding") + } + throw Exception("${clazz.simpleName} viewBinding error") + } + + /** + * ViewBinding 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + * @param view 待绑定 View + * @param clazz VB Class + */ + fun viewBinding( + inflater: LayoutInflater? = null, + container: ViewGroup? = null, + view: View?, + clazz: Class + ): VB { + try { + if (view != null) { + val method = clazz.getMethod("bind", View::class.java) + return method.invoke(null, view) as VB + } else { + val method = clazz.getMethod( + "inflate", + LayoutInflater::class.java, + ViewGroup::class.java, + Boolean::class.java + ) + return method.invoke(null, inflater, container, false) as VB + } + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewBinding") + } + throw Exception("${clazz.simpleName} viewBinding error") + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseAssist.kt b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseAssist.kt new file mode 100644 index 0000000000..fc88922f46 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseAssist.kt @@ -0,0 +1,145 @@ +package dev.base.utils.assist + +import android.app.Dialog +import android.content.Context +import android.widget.PopupWindow +import androidx.fragment.app.DialogFragment +import dev.base.able.IDevBaseUIOperation +import dev.utils.LogPrintUtils +import dev.utils.app.DialogUtils +import dev.utils.app.toast.ToastUtils + +/** + * detail: DevBase 合并相同代码辅助类 + * @author Ttt + */ +class DevBaseAssist : IDevBaseUIOperation { + + // 日志 TAG + private var mTag = DevBaseAssist::class.java.simpleName + + // Context + private var mContext: Context? = null + + // 基类 PopupWindow + private var mDevPopupWindow: PopupWindow? = null + + // 基类 Dialog + private var mDevDialog: Dialog? = null + + // 基类 DialogFragment + private var mDevDialogFragment: DialogFragment? = null + + // = + + fun setContext(context: Context): DevBaseAssist { + this.mContext = context + return this + } + + fun setTag(tag: String): DevBaseAssist { + this.mTag = tag + return this + } + + fun getTag(): String { + return this.mTag + } + + // ========== + // = 日志处理 = + // ========== + + /** + * 统一打印日志 ( 内部封装调用 ) + * @param message 打印内容 + * @return [DevBaseAssist] + */ + fun printLog(message: String): DevBaseAssist { + LogPrintUtils.dTag(mTag, "%s -> %s", mTag, message) + return this + } + + /** + * 统一打印日志 ( 内部封装调用 ) + * @param throwable 异常 + * @param message 打印内容 + * @return [DevBaseAssist] + */ + fun printLog( + throwable: Throwable, + message: String + ): DevBaseAssist { + LogPrintUtils.eTag(mTag, throwable, message) + return this + } + + // ======================= + // = IDevBaseUIOperation = + // ======================= + + override fun showToast( + text: String?, + vararg formatArgs: Any + ) { + ToastUtils.showShort(mContext, text, *formatArgs) + } + + override fun showToast( + resId: Int, + vararg formatArgs: Any + ) { + ToastUtils.showShort(mContext, resId, *formatArgs) + } + + override fun getDevPopupWindow(): PopupWindow? { + return mDevPopupWindow + } + + override fun setDevPopupWindow(popupWindow: T): T { + return setDevPopupWindow(true, popupWindow) + } + + override fun setDevPopupWindow( + isClose: Boolean, + popupWindow: T + ): T { + if (isClose) DialogUtils.closePopupWindow(mDevPopupWindow) + this.mDevPopupWindow = popupWindow + return popupWindow + } + + override fun getDevDialog(): Dialog? { + return mDevDialog + } + + override fun setDevDialog(dialog: T): T { + return setDevDialog(true, dialog) + } + + override fun setDevDialog( + isClose: Boolean, + dialog: T + ): T { + if (isClose) DialogUtils.closeDialog(mDevDialog) + this.mDevDialog = dialog + return dialog + } + + override fun getDevDialogFragment(): DialogFragment? { + return mDevDialogFragment + } + + override fun setDevDialogFragment(dialog: T): T { + return setDevDialogFragment(true, dialog) + } + + override fun setDevDialogFragment( + isClose: Boolean, + dialog: T + ): T { + if (isClose) DialogUtils.closeDialog(mDevDialogFragment) + this.mDevDialogFragment = dialog + return dialog + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseContentAssist.kt b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseContentAssist.kt new file mode 100644 index 0000000000..369befb632 --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseContentAssist.kt @@ -0,0 +1,584 @@ +package dev.base.utils.assist + +import android.app.Activity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import dev.base.R + +/** + * detail: DevBase ContentView 填充辅助类 + * @author Ttt + * 主要用于整个应用使用相同结构 Layout 作为布局 + * 并对各个 Activity Layout 动态添加到 contentLinear + * 其他 Linear、Frame 作用相同, 也可以根据不同页面进行特殊添加 + */ +class DevBaseContentAssist { + + // 是否安全处理 ( 建议跟随 BuildConfig.DEBUG 取反处理, 开发阶段抛出异常 ) + private var mSafe = false + + // 最外层 Layout + @JvmField + var rootLinear: LinearLayout? = null + + // StatusBar Layout + @JvmField + var statusBarLinear: LinearLayout? = null + + // Title Layout + @JvmField + var titleLinear: LinearLayout? = null + + // Body Layout + @JvmField + var bodyFrame: FrameLayout? = null + + // 填充容器 + @JvmField + var contentLinear: LinearLayout? = null + + // 状态布局容器 + @JvmField + var stateLinear: LinearLayout? = null + + // 悬浮容器 + @JvmField + var floatFrame: FrameLayout? = null + + fun bind(activity: Activity): DevBaseContentAssist { + // R.layout.base_content_view + this.rootLinear = activity.findViewById(R.id.vid_root_ll) + this.statusBarLinear = activity.findViewById(R.id.vid_status_bar_ll) + this.titleLinear = activity.findViewById(R.id.vid_title_ll) + this.bodyFrame = activity.findViewById(R.id.vid_body_fl) + this.contentLinear = activity.findViewById(R.id.vid_content_ll) + this.stateLinear = activity.findViewById(R.id.vid_state_ll) + this.floatFrame = activity.findViewById(R.id.vid_float_fl) + return this + } + + fun bind(view: View?): DevBaseContentAssist { + if (view != null) { + // R.layout.base_content_view + this.rootLinear = view.findViewById(R.id.vid_root_ll) + this.statusBarLinear = view.findViewById(R.id.vid_status_bar_ll) + this.titleLinear = view.findViewById(R.id.vid_title_ll) + this.bodyFrame = view.findViewById(R.id.vid_body_fl) + this.contentLinear = view.findViewById(R.id.vid_content_ll) + this.stateLinear = view.findViewById(R.id.vid_state_ll) + this.floatFrame = view.findViewById(R.id.vid_float_fl) + } + return this + } + + fun bind( + rootLinear: LinearLayout?, + statusBarLinear: LinearLayout?, + titleLinear: LinearLayout?, + bodyFrame: FrameLayout?, + contentLinear: LinearLayout?, + stateLinear: LinearLayout?, + floatFrame: FrameLayout?, + ): DevBaseContentAssist { + this.rootLinear = rootLinear + this.statusBarLinear = statusBarLinear + this.titleLinear = titleLinear + this.bodyFrame = bodyFrame + this.contentLinear = contentLinear + this.stateLinear = stateLinear + this.floatFrame = floatFrame + return this + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否安全处理 + */ + fun isSafe(): Boolean { + return mSafe + } + + /** + * 设置是否安全处理 + * @return [DevBaseContentAssist] + */ + fun setSafe(safe: Boolean): DevBaseContentAssist { + mSafe = safe + return this + } + + // ========== + // = 显隐操作 = + // ========== + + /** + * 显示 statusBarLinear + * @return [DevBaseContentAssist] + */ + fun visibleStatusBarLinear(): DevBaseContentAssist { + return setVisibility(true, statusBarLinear) + } + + /** + * 显示 titleLinear + * @return [DevBaseContentAssist] + */ + fun visibleTitleLinear(): DevBaseContentAssist { + return setVisibility(true, titleLinear) + } + + /** + * 显示 bodyFrame + * @return [DevBaseContentAssist] + */ + fun visibleBodyFrame(): DevBaseContentAssist { + return setVisibility(true, bodyFrame) + } + + /** + * 显示 contentLinear + * @return [DevBaseContentAssist] + */ + fun visibleContentLinear(): DevBaseContentAssist { + return setVisibility(true, contentLinear) + } + + /** + * 显示 stateLinear + * @return [DevBaseContentAssist] + */ + fun visibleStateLinear(): DevBaseContentAssist { + return setVisibility(true, stateLinear) + } + + /** + * 显示 floatFrame + * @return [DevBaseContentAssist] + */ + fun visibleFloatFrame(): DevBaseContentAssist { + return setVisibility(true, floatFrame) + } + + // = + + /** + * 隐藏 statusBarLinear + * @return [DevBaseContentAssist] + */ + fun goneStatusBarLinear(): DevBaseContentAssist { + return setVisibility(false, statusBarLinear) + } + + /** + * 隐藏 titleLinear + * @return [DevBaseContentAssist] + */ + fun goneTitleLinear(): DevBaseContentAssist { + return setVisibility(false, titleLinear) + } + + /** + * 隐藏 bodyFrame + * @return [DevBaseContentAssist] + */ + fun goneBodyFrame(): DevBaseContentAssist { + return setVisibility(false, bodyFrame) + } + + /** + * 隐藏 contentLinear + * @return [DevBaseContentAssist] + */ + fun goneContentLinear(): DevBaseContentAssist { + return setVisibility(false, contentLinear) + } + + /** + * 隐藏 stateLinear + * @return [DevBaseContentAssist] + */ + fun goneStateLinear(): DevBaseContentAssist { + return setVisibility(false, stateLinear) + } + + /** + * 隐藏 floatFrame + * @return [DevBaseContentAssist] + */ + fun goneFloatFrame(): DevBaseContentAssist { + return setVisibility(false, floatFrame) + } + + // ============ + // = 添加 View = + // ============ + + /** + * rootLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addRootView(view: View?): DevBaseContentAssist { + return addView(rootLinear, view, -1) + } + + /** + * rootLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addRootView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(rootLinear, view, index) + } + + /** + * rootLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addRootView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(rootLinear, view, -1, params) + } + + /** + * rootLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addRootView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(rootLinear, view, index, params) + } + + /** + * statusBarLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStatusBarView(view: View?): DevBaseContentAssist { + return addView(statusBarLinear, view, -1) + } + + /** + * statusBarLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStatusBarView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(statusBarLinear, view, index) + } + + /** + * statusBarLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStatusBarView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(statusBarLinear, view, -1, params) + } + + /** + * statusBarLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStatusBarView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(statusBarLinear, view, index, params) + } + + /** + * titleLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addTitleView(view: View?): DevBaseContentAssist { + return addView(titleLinear, view, -1) + } + + /** + * titleLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addTitleView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(titleLinear, view, index) + } + + /** + * titleLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addTitleView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(titleLinear, view, -1, params) + } + + /** + * titleLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addTitleView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(titleLinear, view, index, params) + } + + /** + * bodyFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addBodyView(view: View?): DevBaseContentAssist { + return addView(bodyFrame, view, -1) + } + + /** + * bodyFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addBodyView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(bodyFrame, view, index) + } + + /** + * bodyFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addBodyView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(bodyFrame, view, -1, params) + } + + /** + * bodyFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addBodyView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(bodyFrame, view, index, params) + } + + /** + * contentLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addContentView(view: View?): DevBaseContentAssist { + return addView(contentLinear, view, -1) + } + + /** + * contentLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addContentView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(contentLinear, view, index) + } + + /** + * contentLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addContentView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(contentLinear, view, -1, params) + } + + /** + * contentLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addContentView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(contentLinear, view, index, params) + } + + /** + * stateLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStateView(view: View?): DevBaseContentAssist { + return addView(stateLinear, view, -1) + } + + /** + * stateLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStateView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(stateLinear, view, index) + } + + /** + * stateLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStateView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(stateLinear, view, -1, params) + } + + /** + * stateLinear 添加 View + * @return [DevBaseContentAssist] + */ + fun addStateView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(stateLinear, view, index, params) + } + + /** + * floatFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addFloatView(view: View?): DevBaseContentAssist { + return addView(floatFrame, view, -1) + } + + /** + * floatFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addFloatView( + view: View?, + index: Int + ): DevBaseContentAssist { + return addView(floatFrame, view, index) + } + + /** + * floatFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addFloatView( + view: View?, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(floatFrame, view, -1, params) + } + + /** + * floatFrame 添加 View + * @return [DevBaseContentAssist] + */ + fun addFloatView( + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + return addView(floatFrame, view, index, params) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 添加 View + * @param viewGroup 容器 Layout + * @param view 待添加 View + * @param index 添加索引 + * @return [DevBaseContentAssist] + */ + private fun addView( + viewGroup: ViewGroup?, + view: View?, + index: Int + ): DevBaseContentAssist { + // ViewUtils.removeSelfFromParent(view) + val parent = view?.parent as? ViewGroup + // 防止重复添加, 当 parent 不为 null 则表示已添加, 则进行移除 + parent?.removeView(view) + + if (mSafe) { + view?.let { viewGroup?.addView(it, index) } + } else { + viewGroup!!.addView(view, index) + } + return this + } + + /** + * 添加 View + * @param viewGroup 容器 Layout + * @param view 待添加 View + * @param index 添加索引 + * @param params LayoutParams + * @return [DevBaseContentAssist] + */ + private fun addView( + viewGroup: ViewGroup?, + view: View?, + index: Int, + params: ViewGroup.LayoutParams? + ): DevBaseContentAssist { + // ViewUtils.removeSelfFromParent(view) + val parent = view?.parent as? ViewGroup + // 防止重复添加, 当 parent 不为 null 则表示已添加, 则进行移除 + parent?.removeView(view) + + if (mSafe) { + view?.let { viewGroup?.addView(it, index, params) } + } else { + viewGroup!!.addView(view, index, params) + } + return this + } + + /** + * 设置 View 显示的状态 + * @param isVisibility `true` View.VISIBLE, `false` View.GONE + * @param view [View] + * @return [DevBaseContentAssist] + */ + private fun setVisibility( + isVisibility: Boolean, + view: View? + ): DevBaseContentAssist { + view?.visibility = if (isVisibility) View.VISIBLE else View.GONE + return this + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseViewBindingAssist.kt b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseViewBindingAssist.kt new file mode 100644 index 0000000000..e803614dfe --- /dev/null +++ b/lib/DevBase/src/main/java/dev/base/utils/assist/DevBaseViewBindingAssist.kt @@ -0,0 +1,43 @@ +package dev.base.utils.assist + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import dev.base.utils.ViewBindingUtils + +/** + * detail: DevBase ViewBinding 辅助类 + * @author Ttt + */ +class DevBaseViewBindingAssist { + + private var _binding: VB? = null + val binding: VB get() = _binding!! + + /** + * ViewBinding 初始化处理 + * @param inflater [LayoutInflater] + * @param container [ViewGroup] + * @param view 待绑定 View + * @param clazz VB Class + */ + fun viewBinding( + inflater: LayoutInflater, + container: ViewGroup?, + view: View?, + clazz: Class + ): DevBaseViewBindingAssist { + _binding = ViewBindingUtils.viewBinding( + inflater, container, view, clazz + ) + return this + } + + /** + * 销毁 binding + */ + fun destroy() { + _binding = null + } +} \ No newline at end of file diff --git a/lib/DevBase/src/main/res/layout/base_content_view.xml b/lib/DevBase/src/main/res/layout/base_content_view.xml new file mode 100644 index 0000000000..7294487b84 --- /dev/null +++ b/lib/DevBase/src/main/res/layout/base_content_view.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/DevBaseMVVM/.gitignore b/lib/DevBaseMVVM/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevBaseMVVM/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevBaseMVVM/CHANGELOG.md b/lib/DevBaseMVVM/CHANGELOG.md new file mode 100644 index 0000000000..764e2198fc --- /dev/null +++ b/lib/DevBaseMVVM/CHANGELOG.md @@ -0,0 +1,69 @@ +Change Log +========== + +Version 1.1.2 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.1 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.5 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.4 *(2021-05-19)* +---------------------------- + +* `[chore]` 更新 DevBase 依赖版本 + +Version 1.0.3 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.2 *(2021-02-27)* +---------------------------- + +* `[Add]` DevBaseViewDataBindingVH ( RecyclerView ViewDataBinding ViewHolder ) + +Version 1.0.1 *(2021-01-24)* +---------------------------- + +* `[Perf]` 进行代码检测优化 + +* `[Delete]` 移除 isTryViewBindingCatch 方法,以及相关处理代码 + +Version 1.0.0 *(2020-11-26)* +---------------------------- + +* Initial release diff --git a/lib/DevBaseMVVM/README.md b/lib/DevBaseMVVM/README.md new file mode 100644 index 0000000000..a2f6eb54e2 --- /dev/null +++ b/lib/DevBaseMVVM/README.md @@ -0,0 +1,98 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevBaseMVVM:1.1.2' +``` + +## 目录结构 + +``` +- dev.base | 根目录 + - able | 基类库接口相关 + - adapter | RecyclerView ViewDataBinding ViewHolder + - expand | 基于 Base Activity、Fragment 扩展包 + - content | Content Layout MVVM 基类 + - mvvm | MVVM 架构基类 + - viewdata | ViewDataBinding 基类 + - viewmodel | ViewModel 基类 + - utils | 依赖工具包 + - assist | 功能辅助类 ( 抽取通用代码 ) +``` + + +## 项目类结构 - [包目录][包目录] + +### 核心代码 + +* 核心依赖库 [DevBase][DevBase]:整个库最终基类都基于该库 `DevBase` 代码 + +### 其他代码 + +* 接口相关([able][able]):对外提供开放方法接口,用于基类可选配置及获取操作 + +* 库依赖工具包([utils、assist][utils、assist]):抽取通用代码工具类、封装相同逻辑代码辅助类 + +### 基于 Base Activity、Fragment 扩展包([expand][expand]) + +* Content Layout MVVM 基类([content][content]):通过内置 Layout 作为根布局,方便对全局进行增删 View 控制处理 + +* MVVM 架构基类([mvvm][mvvm]):MVVM ( ViewDataBinding + ViewModel ) 架构基类 + +* ViewDataBinding 基类([viewdata][viewdata]):使用 ViewDataBinding 实现对 View 进行 bind、数据双向绑定基类 + +* ViewModel 基类([viewmodel][viewmodel]):使用 ViewModel 进行数据管理、交互基类 + +## Google + +* [android / sunflower](https://github.com/android/sunflower) + +* [ViewModel 概览](https://developer.android.com/topic/libraries/architecture/viewmodel) + +* [LiveData 概览](https://developer.android.com/topic/libraries/architecture/livedata) + +## 其他 + +* [MVPVM in Action, 谁告诉你 MVP 和 MVVM 是互斥的](http://blog.zhaiyifan.cn/2016/03/16/android-new-project-from-0-p3) + +* [是让人耳目一新的 Jetpack MVVM 精讲啊!](https://juejin.cn/post/6844903976240939021) + +* [DataBinding 最全使用说明](https://juejin.cn/post/6844903549223059463) + +* [Android 官方 MVVM 框架实现组件化之整体结构](https://www.jianshu.com/p/c0988e7f31fd) + +* [AndroidX Jetpack Practice](https://github.com/hi-dhl/AndroidX-Jetpack-Practice) + +* [AndroidLibs / 框架](https://github.com/GuoYangGit/AndroidLibs/blob/master/框架) + +* [深入浅出 MVVM 教程](https://www.jianshu.com/p/bcdb7c2a07eb) + +* [深入浅出 MVVM 教程 Repository ( 数据仓库 ) ](https://juejin.cn/post/6844903505635835911) + +* [关于 Android 架构,你是否还在生搬硬套?](https://juejin.cn/post/6942464122273398820) + +* [引入 Jetpack 架构后,你的 App 会发生哪些变化?](https://juejin.cn/post/6955491901265051661) + +* [一个整合了大量主流开源项目高度可配置化的 Android MVP 快速集成框架](https://github.com/JessYanCoding/MVPArms/blob/master/MVPArms.md) + +## Other + +* [GitHub MVVMHabit](https://github.com/goldze/MVVMHabit) + +* [GitHub Jetpack-Mvvm](https://github.com/zskingking/Jetpack-Mvvm) + +* [GitHub Jetpack-MVVM-Best-Practice](https://github.com/KunMinX/Jetpack-MVVM-Best-Practice) + + + + + +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base +[DevBase]: https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md +[able]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/able +[utils、assist]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/utils +[expand]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/expand +[content]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/expand/content +[mvvm]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm +[viewdata]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata +[viewmodel]: https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel \ No newline at end of file diff --git a/lib/DevBaseMVVM/build.gradle b/lib/DevBaseMVVM/build.gradle new file mode 100644 index 0000000000..172aa0410d --- /dev/null +++ b/lib/DevBaseMVVM/build.gradle @@ -0,0 +1,39 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply from: rootProject.file(files.unified_use_view_data_binding_gradle) + +android.defaultConfig { + versionCode versions.dev_base_mvvm_versionCode + versionName versions.dev_base_mvvm_versionName + // DevBaseMVVM Module Version + buildConfigField "int", "DevBaseMVVM_VersionCode", "${versions.dev_base_mvvm_versionCode}" + buildConfigField "String", "DevBaseMVVM_Version", "\"${versions.dev_base_mvvm_versionName}\"" + // DevBase Module Version + buildConfigField "int", "DevBase_VersionCode", "${versions.dev_base_versionCode}" + buildConfigField "String", "DevBase_Version", "\"${versions.dev_base_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.kotlin.lifecycle_viewmodel + api deps.kotlin.lifecycle_livedata + api deps.kotlin.lifecycle_viewmodel_savedstate + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_base + } else { + // 编译时使用 + api project(':DevBase') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/proguard-rules.pro b/lib/DevBaseMVVM/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/DevBaseMVVM/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/DevBaseMVVM/project.properties b/lib/DevBaseMVVM/project.properties new file mode 100644 index 0000000000..d9dd837799 --- /dev/null +++ b/lib/DevBaseMVVM/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevBaseMVVM +project.groupId=io.github.afkt +project.artifactId=DevBaseMVVM +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevBaseMVVM \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/AndroidManifest.xml b/lib/DevBaseMVVM/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4414d43152 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/DevBaseMVVM.kt b/lib/DevBaseMVVM/src/main/java/dev/base/DevBaseMVVM.kt new file mode 100644 index 0000000000..d78c358869 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/DevBaseMVVM.kt @@ -0,0 +1,73 @@ +package dev.base + +import dev.base.mvvm.BuildConfig + +/** + * detail: DevBaseMVVM + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevBaseMVVM { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevBaseMVVM 版本号 + * @return DevBaseMVVM versionCode + */ + fun getDevBaseMVVMVersionCode(): Int { + return BuildConfig.DevBaseMVVM_VersionCode + } + + /** + * 获取 DevBaseMVVM 版本 + * @return DevBaseMVVM versionName + */ + fun getDevBaseMVVMVersion(): String { + return BuildConfig.DevBaseMVVM_Version + } + + /** + * 获取 DevBase 版本号 + * @return DevBase versionCode + */ + fun getDevBaseVersionCode(): Int { + return BuildConfig.DevBase_VersionCode + } + + /** + * 获取 DevBase 版本 + * @return DevBase versionName + */ + fun getDevBaseVersion(): String { + return BuildConfig.DevBase_Version + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewDataBinding.kt b/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewDataBinding.kt new file mode 100644 index 0000000000..52b09b28da --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewDataBinding.kt @@ -0,0 +1,30 @@ +package dev.base.able + +import android.view.View +import androidx.databinding.ViewDataBinding + +/** + * detail: 基类 ViewDataBinding 接口 + * @author Ttt + */ +interface IDevBaseViewDataBinding { + + /** + * 获取待 Bind View + */ + fun getBindingView(): View? + + /** + * 是否 Bind View + */ + fun isViewBinding(): Boolean { + return true + } + + /** + * 是否分离 ( 销毁 ) Binding + */ + fun isDetachBinding(): Boolean { + return isViewBinding() + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewModel.kt b/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewModel.kt new file mode 100644 index 0000000000..88d1a3af45 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/able/IDevBaseViewModel.kt @@ -0,0 +1,84 @@ +package dev.base.able + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +/** + * detail: 基类 ViewModel 接口 + * @author Ttt + */ +interface IDevBaseViewModel { + + /** + * 初始化 [ViewModel] + */ + fun initViewModel() + + // ===================== + // = Activity Provider = + // ===================== + + /** + * 获取 Activity ViewModel + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModel( + modelClass: Class + ): T? + + // ===================== + // = Fragment Provider = + // ===================== + + /** + * 获取 Fragment ViewModel + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModel( + modelClass: Class + ): T? + + /** + * 获取 Fragment ViewModel + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? + + // ======================== + // = Application Provider = + // ======================== + + /** + * 获取 Application ViewModel + * @param application [Application] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? + + /** + * 获取 Application ViewModelProvider + * @param application [Application] + * @return [ViewModelProvider] + */ + fun getAppViewModelProvider(application: Application?): ViewModelProvider? + + /** + * 获取 Application AndroidViewModel Factory + * @param application [Application] + * @return [ViewModelProvider.Factory] + */ + fun getAppFactory(application: Application?): ViewModelProvider.Factory? +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/adapter/DevBaseViewDataBindingVH.kt b/lib/DevBaseMVVM/src/main/java/dev/base/adapter/DevBaseViewDataBindingVH.kt new file mode 100644 index 0000000000..a8f66dcf8f --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/adapter/DevBaseViewDataBindingVH.kt @@ -0,0 +1,71 @@ +package dev.base.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView + +/** + * detail: RecyclerView ViewDataBinding ViewHolder + * @author Ttt + */ +class DevBaseViewDataBindingVH(val binding: VDB) : RecyclerView.ViewHolder( + binding.root +) { + companion object { + @JvmStatic + fun create( + parent: ViewGroup, + @LayoutRes resource: Int + ) = + DevBaseViewDataBindingVH( + DataBindingUtil.bind( + LayoutInflater.from(parent.context).inflate(resource, null) + )!! + ) + + @JvmStatic + fun create( + context: Context, + @LayoutRes resource: Int + ) = + DevBaseViewDataBindingVH( + DataBindingUtil.bind( + LayoutInflater.from(context).inflate(resource, null) + )!! + ) + + @JvmStatic + fun create( + view: View + ) = + DevBaseViewDataBindingVH( + DataBindingUtil.bind(view)!! + ) + } +} + +fun newDataBindingViewHolder( + parent: ViewGroup, + @LayoutRes resource: Int +): DevBaseViewDataBindingVH = + DevBaseViewDataBindingVH.create(parent, resource) + +fun newDataBindingViewHolder( + context: Context, + @LayoutRes resource: Int +): DevBaseViewDataBindingVH = + DevBaseViewDataBindingVH.create(context, resource) + +fun newDataBindingViewHolder( + view: View, +): DevBaseViewDataBindingVH = + DevBaseViewDataBindingVH.create(view) + +fun newDataBindingViewHolder( + binding: VDB +): DevBaseViewDataBindingVH = DevBaseViewDataBindingVH(binding) \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMActivity.kt new file mode 100644 index 0000000000..88efa28607 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMActivity.kt @@ -0,0 +1,69 @@ +package dev.base.expand.content + +import android.app.Application +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Content Activity MVVM 基类 + * @author Ttt + */ +abstract class DevBaseContentMVVMActivity : + DevBaseContentViewDataBindingActivity(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(this, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return null + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMFragment.kt new file mode 100644 index 0000000000..2401fbf982 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentMVVMFragment.kt @@ -0,0 +1,69 @@ +package dev.base.expand.content + +import android.app.Application +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Content Fragment MVVM 基类 + * @author Ttt + */ +abstract class DevBaseContentMVVMFragment : + DevBaseContentViewDataBindingFragment(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(activity, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return viewModelAssist.getFragmentViewModelCache(this, modelClass) + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingActivity.kt new file mode 100644 index 0000000000..edaf40f60d --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingActivity.kt @@ -0,0 +1,41 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.View +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import dev.base.able.IDevBaseViewDataBinding + +/** + * detail: Content Activity ViewDataBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentViewDataBindingActivity : + DevBaseContentActivity(), + IDevBaseViewDataBinding { + + lateinit var binding: VDB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (isViewBinding()) { + // ViewDataBinding 初始化处理 + binding = DataBindingUtil.bind(getBindingView()!!)!! + // 支持 LiveData 绑定 xml 数据改变 UI 自动会更新 + binding.lifecycleOwner = this + } + } + + override fun onDestroy() { + super.onDestroy() + if (isDetachBinding()) binding.unbind() + } + + // =========================== + // = IDevBaseViewDataBinding = + // =========================== + + final override fun getBindingView(): View? { + return layoutView + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingFragment.kt new file mode 100644 index 0000000000..24839515c0 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewDataBindingFragment.kt @@ -0,0 +1,52 @@ +package dev.base.expand.content + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import dev.base.able.IDevBaseViewDataBinding + +/** + * detail: Content Fragment ViewDataBinding 基类 + * @author Ttt + */ +abstract class DevBaseContentViewDataBindingFragment : + DevBaseContentFragment(), + IDevBaseViewDataBinding { + + private var _binding: VDB? = null + val binding: VDB get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + if (isViewBinding()) { + // ViewDataBinding 初始化处理 + _binding = DataBindingUtil.bind(getBindingView()!!)!! + // 支持 LiveData 绑定 xml 数据改变 UI 自动会更新 + _binding?.lifecycleOwner = this + } + return mContentView + } + + override fun onDestroyView() { + super.onDestroyView() + if (isDetachBinding()) { + _binding?.unbind() + _binding = null + } + } + + // =========================== + // = IDevBaseViewDataBinding = + // =========================== + + final override fun getBindingView(): View? { + return layoutView + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelActivity.kt new file mode 100644 index 0000000000..e9fa71c2fc --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelActivity.kt @@ -0,0 +1,68 @@ +package dev.base.expand.content + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Content Activity ViewModel 基类 + * @author Ttt + */ +abstract class DevBaseContentViewModelActivity : + DevBaseContentActivity(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(this, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return null + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelFragment.kt new file mode 100644 index 0000000000..6099cbe54f --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/content/DevBaseContentViewModelFragment.kt @@ -0,0 +1,68 @@ +package dev.base.expand.content + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Content Fragment ViewModel 基类 + * @author Ttt + */ +abstract class DevBaseContentViewModelFragment : + DevBaseContentFragment(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(activity, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return viewModelAssist.getFragmentViewModelCache(this, modelClass) + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMActivity.kt new file mode 100644 index 0000000000..57e5c48752 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMActivity.kt @@ -0,0 +1,70 @@ +package dev.base.expand.mvvm + +import android.app.Application +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.expand.viewdata.DevBaseViewDataBindingActivity +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Activity MVVM 基类 + * @author Ttt + */ +abstract class DevBaseMVVMActivity : + DevBaseViewDataBindingActivity(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(this, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return null + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMFragment.kt new file mode 100644 index 0000000000..fd4d4b3104 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/DevBaseMVVMFragment.kt @@ -0,0 +1,70 @@ +package dev.base.expand.mvvm + +import android.app.Application +import androidx.databinding.ViewDataBinding +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.expand.viewdata.DevBaseViewDataBindingFragment +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Fragment MVVM 基类 + * @author Ttt + */ +abstract class DevBaseMVVMFragment : + DevBaseViewDataBindingFragment(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(activity, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return viewModelAssist.getFragmentViewModelCache(this, modelClass) + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/MVVM.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/MVVM.kt new file mode 100644 index 0000000000..679ca70cf2 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/mvvm/MVVM.kt @@ -0,0 +1,16 @@ +package dev.base.expand.mvvm + +import androidx.lifecycle.ViewModel + +/** + * detail: MVVM 类 + * @author Ttt + */ +class MVVM private constructor() { + + /** + * detail: 空实现 ViewModel + * @author Ttt + */ + class VMImpl : ViewModel() +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingActivity.kt new file mode 100644 index 0000000000..1902420bbb --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingActivity.kt @@ -0,0 +1,41 @@ +package dev.base.expand.viewdata + +import android.os.Bundle +import android.view.View +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import dev.base.able.IDevBaseViewDataBinding +import dev.base.activity.DevBaseActivity + +/** + * detail: Activity ViewDataBinding 基类 + * @author Ttt + */ +abstract class DevBaseViewDataBindingActivity : DevBaseActivity(), + IDevBaseViewDataBinding { + + lateinit var binding: VDB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (isViewBinding()) { + // ViewDataBinding 初始化处理 + binding = DataBindingUtil.bind(getBindingView()!!)!! + // 支持 LiveData 绑定 xml 数据改变 UI 自动会更新 + binding.lifecycleOwner = this + } + } + + override fun onDestroy() { + super.onDestroy() + if (isDetachBinding()) binding.unbind() + } + + // =========================== + // = IDevBaseViewDataBinding = + // =========================== + + final override fun getBindingView(): View? { + return mContentView + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingFragment.kt new file mode 100644 index 0000000000..eb586bb3d5 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewdata/DevBaseViewDataBindingFragment.kt @@ -0,0 +1,52 @@ +package dev.base.expand.viewdata + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import dev.base.able.IDevBaseViewDataBinding +import dev.base.fragment.DevBaseFragment + +/** + * detail: Fragment ViewDataBinding 基类 + * @author Ttt + */ +abstract class DevBaseViewDataBindingFragment : DevBaseFragment(), + IDevBaseViewDataBinding { + + private var _binding: VDB? = null + val binding: VDB get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + super.onCreateView(inflater, container, savedInstanceState) + if (isViewBinding()) { + // ViewDataBinding 初始化处理 + _binding = DataBindingUtil.bind(getBindingView()!!)!! + // 支持 LiveData 绑定 xml 数据改变 UI 自动会更新 + _binding?.lifecycleOwner = this + } + return mContentView + } + + override fun onDestroyView() { + super.onDestroyView() + if (isDetachBinding()) { + _binding?.unbind() + _binding = null + } + } + + // =========================== + // = IDevBaseViewDataBinding = + // =========================== + + final override fun getBindingView(): View? { + return mContentView + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelActivity.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelActivity.kt new file mode 100644 index 0000000000..b611628e54 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelActivity.kt @@ -0,0 +1,68 @@ +package dev.base.expand.viewmodel + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.activity.DevBaseActivity +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Activity ViewModel 基类 + * @author Ttt + */ +abstract class DevBaseViewModelActivity : DevBaseActivity(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(this, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return null + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelFragment.kt b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelFragment.kt new file mode 100644 index 0000000000..770c9072bf --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/expand/viewmodel/DevBaseViewModelFragment.kt @@ -0,0 +1,68 @@ +package dev.base.expand.viewmodel + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dev.base.able.IDevBaseViewModel +import dev.base.fragment.DevBaseFragment +import dev.base.utils.assist.DevBaseViewModelAssist + +/** + * detail: Fragment ViewModel 基类 + * @author Ttt + */ +abstract class DevBaseViewModelFragment : DevBaseFragment(), + IDevBaseViewModel { + + lateinit var viewModel: VM + + @JvmField // DevBase ViewModel 辅助类 + protected var viewModelAssist = DevBaseViewModelAssist() + + // ===================== + // = IDevBaseViewModel = + // ===================== + + // ===================== + // = Activity Provider = + // ===================== + + override fun getActivityViewModel(modelClass: Class): T? { + return viewModelAssist.getActivityViewModelCache(activity, modelClass) + } + + // ===================== + // = Fragment Provider = + // ===================== + + override fun getFragmentViewModel(modelClass: Class): T? { + return viewModelAssist.getFragmentViewModelCache(this, modelClass) + } + + override fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } + + // ======================== + // = Application Provider = + // ======================== + + override fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + return viewModelAssist.getAppViewModel(application, modelClass) + } + + override fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + return viewModelAssist.getAppViewModelProvider(application) + } + + override fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + return viewModelAssist.getAppFactory(application) + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/utils/ViewModelUtils.kt b/lib/DevBaseMVVM/src/main/java/dev/base/utils/ViewModelUtils.kt new file mode 100644 index 0000000000..2938101e2e --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/utils/ViewModelUtils.kt @@ -0,0 +1,231 @@ +package dev.base.utils + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner +import dev.base.utils.assist.DevBaseViewModelAssist +import dev.utils.LogPrintUtils + +/** + * detail: ViewModel 工具类 + * @author Ttt + */ +object ViewModelUtils { + + // 日志 TAG + private val TAG = ViewModelUtils::class.java.simpleName + + // ===================== + // = Activity Provider = + // ===================== + + /** + * 获取 Activity ViewModel + * @param activity [FragmentActivity] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModel( + activity: FragmentActivity?, + modelClass: Class + ): T? { + if (activity == null) return null + try { + return ViewModelProvider(activity)[modelClass] + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "getActivityViewModel") + } + return null + } + + // ===================== + // = Fragment Provider = + // ===================== + + /** + * 获取 Fragment ViewModel + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + if (fragment == null) return null + try { + return ViewModelProvider(fragment)[modelClass] + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "getFragmentViewModel") + } + return null + } + + // ======================== + // = Application Provider = + // ======================== + + /** + * 获取 Application ViewModel + * @param application [Application] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + if (application == null) return null + try { + return getAppViewModelProvider(application)?.get(modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "getAppViewModel") + } + return null + } + + /** + * 获取 Application ViewModelProvider + * @param application [Application] + * @return [ViewModelProvider] + */ + fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + if (application is ViewModelStoreOwner) { + return ViewModelProvider( + application as ViewModelStoreOwner, getAppFactory(application)!! + ) + } + return null + } + + /** + * 获取 Application AndroidViewModel Factory + * @param application [Application] + * @return [ViewModelProvider.Factory] + */ + fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + if (application == null) return null + return ViewModelProvider.AndroidViewModelFactory.getInstance(application) + } + + // ========================== + // = DevBaseViewModelAssist = + // ========================== + + // ===================== + // = Activity Provider = + // ===================== + + /** + * 获取 Activity ViewModel + * @param viewModelAssist [DevBaseViewModelAssist] + * @param activity [FragmentActivity] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModel( + viewModelAssist: DevBaseViewModelAssist, + activity: FragmentActivity?, + modelClass: Class + ): T? { + if (activity == null) return null + try { + return viewModelAssist.getActivityViewModel(activity, modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewModelAssist - getActivityViewModel") + } + return null + } + + /** + * 获取 Activity ViewModel + * @param viewModelAssist [DevBaseViewModelAssist] + * @param activity [FragmentActivity] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModelCache( + viewModelAssist: DevBaseViewModelAssist, + activity: FragmentActivity?, + modelClass: Class + ): T? { + if (activity == null) return null + try { + return viewModelAssist.getActivityViewModelCache(activity, modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewModelAssist - getActivityViewModelCache") + } + return null + } + + // ===================== + // = Fragment Provider = + // ===================== + + /** + * 获取 Fragment ViewModel + * @param viewModelAssist [DevBaseViewModelAssist] + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModel( + viewModelAssist: DevBaseViewModelAssist, + fragment: Fragment?, + modelClass: Class + ): T? { + try { + return viewModelAssist.getFragmentViewModel(fragment, modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewModelAssist - getFragmentViewModel") + } + return null + } + + /** + * 获取 Fragment ViewModel + * @param viewModelAssist [DevBaseViewModelAssist] + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModelCache( + viewModelAssist: DevBaseViewModelAssist, + fragment: Fragment?, + modelClass: Class + ): T? { + try { + return viewModelAssist.getFragmentViewModelCache(fragment, modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewModelAssist - getFragmentViewModelCache") + } + return null + } + + // ======================== + // = Application Provider = + // ======================== + + /** + * 获取 Application ViewModel + * @param viewModelAssist [DevBaseViewModelAssist] + * @param application [Application] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getAppViewModel( + viewModelAssist: DevBaseViewModelAssist, + application: Application?, + modelClass: Class + ): T? { + try { + return viewModelAssist.getAppViewModel(application, modelClass) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "viewModelAssist - getAppViewModel") + } + return null + } +} \ No newline at end of file diff --git a/lib/DevBaseMVVM/src/main/java/dev/base/utils/assist/DevBaseViewModelAssist.kt b/lib/DevBaseMVVM/src/main/java/dev/base/utils/assist/DevBaseViewModelAssist.kt new file mode 100644 index 0000000000..d4ea10f343 --- /dev/null +++ b/lib/DevBaseMVVM/src/main/java/dev/base/utils/assist/DevBaseViewModelAssist.kt @@ -0,0 +1,138 @@ +package dev.base.utils.assist + +import android.app.Application +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStoreOwner + +/** + * detail: DevBase ViewModel 辅助类 + * @author Ttt + *

+ * 使用全局 ViewModel 则通过 Application implements ViewModelStoreOwner + * 并通过 [getAppViewModel] 获取全局 ViewModel + */ +class DevBaseViewModelAssist { + + private var mActivityProvider: ViewModelProvider? = null + private var mFragmentProvider: ViewModelProvider? = null + private var mFactory: ViewModelProvider.Factory? = null + + // ===================== + // = Activity Provider = + // ===================== + + /** + * 获取 Activity ViewModel + * @param activity [FragmentActivity] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModel( + activity: FragmentActivity?, + modelClass: Class + ): T? { + if (activity == null) return null + return ViewModelProvider(activity)[modelClass] + } + + /** + * 获取 Activity ViewModel + * @param activity [FragmentActivity] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getActivityViewModelCache( + activity: FragmentActivity?, + modelClass: Class + ): T? { + if (activity == null) return null + if (mActivityProvider == null) { + mActivityProvider = ViewModelProvider(activity) + } + return mActivityProvider!![modelClass] + } + + // ===================== + // = Fragment Provider = + // ===================== + + /** + * 获取 Fragment ViewModel + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModel( + fragment: Fragment?, + modelClass: Class + ): T? { + if (fragment == null) return null + return ViewModelProvider(fragment)[modelClass] + } + + /** + * 获取 Fragment ViewModel + * @param fragment [Fragment] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getFragmentViewModelCache( + fragment: Fragment?, + modelClass: Class + ): T? { + if (fragment == null) return null + if (mFragmentProvider == null) { + mFragmentProvider = ViewModelProvider(fragment) + } + return mFragmentProvider!![modelClass] + } + + // ======================== + // = Application Provider = + // ======================== + + /** + * 获取 Application ViewModel + * @param application [Application] + * @param modelClass [ViewModel] + * @return [T] + */ + fun getAppViewModel( + application: Application?, + modelClass: Class + ): T? { + if (application == null) return null + return getAppViewModelProvider(application)?.get(modelClass) + } + + /** + * 获取 Application ViewModelProvider + * @param application [Application] + * @return [ViewModelProvider] + */ + fun getAppViewModelProvider(application: Application?): ViewModelProvider? { + if (application is ViewModelStoreOwner) { + return ViewModelProvider( + application as ViewModelStoreOwner, getAppFactory(application)!! + ) + } + return null + } + + /** + * 获取 Application AndroidViewModel Factory + * @param application [Application] + * @return [ViewModelProvider.Factory] + */ + fun getAppFactory(application: Application?): ViewModelProvider.Factory? { + if (mFactory != null) return mFactory + if (application == null) return null + if (mFactory == null) { + mFactory = ViewModelProvider.AndroidViewModelFactory.getInstance(application) + } + return mFactory + } +} \ No newline at end of file diff --git a/lib/DevEngine/.gitignore b/lib/DevEngine/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevEngine/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevEngine/CHANGELOG.md b/lib/DevEngine/CHANGELOG.md new file mode 100644 index 0000000000..71c7822d16 --- /dev/null +++ b/lib/DevEngine/CHANGELOG.md @@ -0,0 +1,59 @@ +Change Log +========== + +Version 1.1.0 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.9 *(2022-08-07)* +---------------------------- + +* `[Add]` 新增 engine kotlin 扩展函数实现代码 + +Version 1.0.8 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +* `[Upgrade]` 升级第三方库以及对应 API 变动更新 + +Version 1.0.7 *(2022-06-02)* +---------------------------- + +* `[Chore]` 更新 Fastjson 为 Fastjson2 及其实现代码,并使用 fastjson2.android 版本 + +Version 1.0.6 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.5 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.4 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.3 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.2 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.1 *(2021-12-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.0 *(2021-09-20)* +---------------------------- + +* Initial release diff --git a/lib/DevEngine/README.md b/lib/DevEngine/README.md new file mode 100644 index 0000000000..0c6e247098 --- /dev/null +++ b/lib/DevEngine/README.md @@ -0,0 +1,900 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevEngine:1.1.0' +``` + +## 目录结构 + +``` +- dev.engine | 根目录 + - barcode | BarCode Engine 条形码、二维码处理 + - cache | Cache Engine 有效期键值对缓存 + - compress | Image Compress Engine 图片压缩 + - image | Image Engine 图片加载、下载、转格式等 + - json | JSON Engine 映射 + - keyvalue | KeyValue Engine 键值对存储 + - log | Log Engine 日志打印 + - media | Media Selector Engine 多媒体资源选择 + - permission | Permission Engine 权限申请 + - storage | Storage Engine 外部、内部文件存储 +``` + + +## About + +DevEngine(基于 [DevAssist Engine 模块](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist)) +主要为了解决项目代码中**对第三方框架强依赖使用、以及部分功能版本适配。** + +通过实现对应功能模块 Engine 接口,实现对应的方法功能, +**对外无需关注实现代码,直接通过 DevXXXEngine 进行调用,实现对第三方框架解耦、一键替换第三方库、同类库多 Engine 混合使用、以及部分功能适配 ( 如外部文件存储 MediaStore 全局适配 ) 等** + +> 可通过 Key-Engine 实现对组件化、模块化各个 Module 使用同类型 Engine 不同库实现使用 + +## 依赖实现信息 + +> 该库为 Kotlin 实现,另有 [Java 实现代码](https://github.com/afkT/DevUtils-repo/blob/main/lib/LocalModules/DevOther/src/main/java/java/dev/engine) +> ,**该库会依赖第三方库导致项目体积变大等情况**,可自行 copy 所需已实现代码进行使用 + +已实现模块有(依赖第三方库实现列表如下展示): + +* **BarCode 条形码** +* **Cache 有效期键值对缓存** +* **Image Compress 图片压缩** +* **Image 图片加载、下载、转格式** +* **JSON 映射** +* **KeyValue 键值对存储** +* **Log 日志打印** +* **Media Selector 多媒体资源选择** +* **Permission 权限申请** +* **Storage Engine 外部、内部文件存储** + +> 如已封装代码无法直接使用于需求实现,可自行 copy 代码进行修改,或自定义对应模块 Engine 实现类进行设置并通过 DevXxxEngine.getEngine(key) 进行调用获取 + +## 项目类结构 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine) + + +### BarCode 条形码 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/barcode) + +可选实现方案: + +* 依赖 [ZXing](https://github.com/zxing/zxing) 实现 [ZXingEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/barcode/ZXingEngineImpl.kt) + + +### Cache 有效期键值对缓存 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/cache) + +可选实现方案: + +* 依赖 [DevApp DevCache](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/cache) + 实现 [DevCacheEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/cache/engine_dev_cache.kt) + + +### Image Compress 图片压缩 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/compress) + +可选实现方案: + +* 依赖 [Luban 鲁班图片压缩](https://github.com/Curzibn/Luban) + 实现 [LubanEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/compress/engine_luban.kt) + + +### Image 图片加载、下载、转格式 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/image) + +可选实现方案: + +* 依赖 [Glide 图片加载框架](https://github.com/bumptech/glide) + 实现 [GlideEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/image/engine_glide.kt) + + +### JSON 映射 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/json) + +可选实现方案: + +* 依赖 [Gson](https://github.com/google/gson) + 实现 [GsonEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/json/engine_gson.kt) + +* 依赖 [Fastjson2](https://github.com/alibaba/fastjson2) + 实现 [FastjsonEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/json/engine_fastjson.kt) + + +### KeyValue 键值对存储 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/keyvalue) + +可选实现方案: + +* 依赖 [基于 mmap 的高性能通用 key-value 组件 MMKV](https://github.com/Tencent/MMKV/blob/master/README_CN.md) + 实现 [MMKVKeyValueEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_mmkv.kt) + +* 依赖 [DevApp SharedPreferences 封装](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/share) + 实现 [SPKeyValueEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_sp.kt) + + +### Log 日志打印 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/log) + +可选实现方案: + +* 依赖 [DevApp DevLogger](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/logger) + 实现 [DevLoggerEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/log/engine_dev_logger.kt) + + +### Media Selector 多媒体资源选择 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/media) + +可选实现方案: + +* 依赖 [Android 平台下的图片选择器 PictureSelector](https://github.com/LuckSiege/PictureSelector) + 实现 [PictureSelectorEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/media/engine_picture_selector.kt) + + +### Permission 权限申请 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/permission) + +可选实现方案: + +* 依赖 [DevApp PermissionUtils](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/permission/PermissionUtils.java) + 实现 [DevPermissionEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/permission/engine_dev_permission.kt) + + +### Storage Engine 外部、内部文件存储 - [包目录](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/storage) + +可选实现方案: + +* 依赖 [DevApp MediaStoreUtils](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/MediaStoreUtils.java) + 、[UriUtils](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/src/main/java/dev/utils/app/UriUtils.java) + 实现 [DevMediaStoreEngineImpl](https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/src/main/java/dev/engine/storage/engine_dev_media_store.kt) + + +## 使用示例 + +```kotlin +/** + * 初始化 Engine + */ +private fun initEngine(appContext: Application) { + + // ========= + // = 初始化 = + // ========= + + // ============= + // = 完整版初始化 = + // ============= + + // 完整初始化 ( 全面使用该库调用该方法初始化即可 ) + DevEngine.completeInitialize(appContext) + + // ============ + // = 部分初始化 = + // ============ + + // 如不需使用 MMKV Key-Value 则直接调用以下方法即可 + DevEngine.defaultEngine() + + // 如果需要使用 MMKV Key-Value 则需要如下初始化 + + // 使用内部默认实现 Engine ( 使用 MMKV 必须调用 defaultMMKVInitialize() ) + DevEngine.defaultMMKVInitialize(appContext) + .defaultEngine(MMKVConfig(cipher, mmkv)) + + // 如不想内部默认初始化全部 Engine 也可单独调用进行初始化覆盖 ( 参考下方【设置】 ) + + // ======= + // = 使用 = + // ======= + + // ===================== + // = 通过 DevEngine 使用 = + // ===================== + + // Analytics Engine 数据统计 ( 埋点 ) + DevEngine.getAnalytics().xxx + + // Cache Engine 有效期键值对缓存 + DevEngine.getCache().xxx + + // Image Compress Engine 图片压缩 + DevEngine.getCompress().xxx + + // Image Engine 图片加载、下载、转格式等 + DevEngine.getImage().xxx + + // JSON Engine 映射 + DevEngine.getJSON().xxx + + // KeyValue Engine 键值对存储 + DevEngine.getKeyValue().xxx + + // Log Engine 日志打印 + DevEngine.getLog().xxx + + // Media Selector Engine 多媒体资源选择 + DevEngine.getMedia().xxx + + // Permission Engine 权限申请 + DevEngine.getPermission().xxx + + // Push Engine 推送平台处理 + DevEngine.getPush().xxx + + // Share Engine 分享平台处理 + DevEngine.getShare().xxx + + // Storage Engine 外部、内部文件存储 + DevEngine.getStorage().xxx + + // ===================== + // = 通过 DevAssist 使用 = + // ===================== + + // Analytics Engine 数据统计 ( 埋点 ) + DevAnalyticsEngine.getEngine().xxx + + // Cache Engine 有效期键值对缓存 + DevCacheEngine.getEngine().xxx + + // Image Compress Engine 图片压缩 + DevCompressEngine.getEngine().xxx + + // Image Engine 图片加载、下载、转格式等 + DevImageEngine.getEngine().xxx + + // JSON Engine 映射 + DevJSONEngine.getEngine().xxx + + // KeyValue Engine 键值对存储 + DevKeyValueEngine.getEngine().xxx + + // Log Engine 日志打印 + DevLogEngine.getEngine().xxx + + // Media Selector Engine 多媒体资源选择 + DevMediaEngine.getEngine().xxx + + // Permission Engine 权限申请 + DevPermissionEngine.getEngine().xxx + + // Push Engine 推送平台处理 + DevPushEngine.getEngine().xxx + + // Share Engine 分享平台处理 + DevShareEngine.getEngine().xxx + + // Storage Engine 外部、内部文件存储 + DevStorageEngine.getEngine().xxx + + // ======= + // = 设置 = + // ======= + + // 设置 Engine 实现, 组件化、模块化可通过设置各个模块 Key 进行区分 + DevEngine.getJSONAssist().setEngine(EngineImpl) + DevEngine.getJSONAssist().setEngine(Key, EngineImpl) + + DevJSONEngine.setEngine(EngineImpl) + DevJSONEngine.setEngine(Key, EngineImpl) + + // 使用 GSON + DevJSONEngine.setEngine(GsonEngineImpl()) + // 使用 Fastjson + DevJSONEngine.setEngine(Key, FastjsonEngineImpl()) + + // GsonEngineImpl + DevJSONEngine.getEngine() + // FastjsonEngineImpl + DevJSONEngine.getEngine(Key) +} +``` + + +## 目录结构 + +``` +- dev | 根目录 + - engine | 兼容 Engine + - analytics | Analytics Engine 数据统计 ( 埋点 ) + - barcode | BarCode Engine 条形码、二维码处理 + - listener | 条形码、二维码操作回调事件 + - cache | Cache Engine 有效期键值对缓存 + - compress | Image Compress Engine 图片压缩 + - listener | 图片压缩回调事件 + - image | Image Engine 图片加载、下载、转格式等 + - listener | 图片加载监听事件 + - json | JSON Engine 映射 + - keyvalue | KeyValue Engine 键值对存储 + - log | Log Engine 日志打印 + - media | Media Selector Engine 多媒体资源选择 + - permission | Permission Engine 权限申请 + - push | Push Engine 推送平台处理 + - share | Share Engine 分享平台处理 + - listener | 分享回调事件 + - storage | Storage Engine 外部、内部文件存储 + - listener | Storage 存储结果事件 +``` + +## API + +- dev | 根目录 + - [engine](#devengine) | 兼容 Engine + - [analytics](#devengineanalytics) | Analytics Engine 数据统计 ( 埋点 ) + - [barcode](#devenginebarcode) | BarCode Engine 条形码、二维码处理 + - [listener](#devenginebarcodelistener) | 条形码、二维码操作回调事件 + - [cache](#devenginecache) | Cache Engine 有效期键值对缓存 + - [compress](#devenginecompress) | Image Compress Engine 图片压缩 + - [listener](#devenginecompresslistener) | 图片压缩回调事件 + - [image](#devengineimage) | Image Engine 图片加载、下载、转格式等 + - [listener](#devengineimagelistener) | 图片加载监听事件 + - [json](#devenginejson) | JSON Engine 映射 + - [keyvalue](#devenginekeyvalue) | KeyValue Engine 键值对存储 + - [log](#devenginelog) | Log Engine 日志打印 + - [media](#devenginemedia) | Media Selector Engine 多媒体资源选择 + - [permission](#devenginepermission) | Permission Engine 权限申请 + - [push](#devenginepush) | Push Engine 推送平台处理 + - [share](#devengineshare) | Share Engine 分享平台处理 + - [listener](#devenginesharelistener) | 分享回调事件 + - [storage](#devenginestorage) | Storage Engine 外部、内部文件存储 + - [listener](#devenginestoragelistener) | Storage 存储结果事件 + + + +## **`dev.engine`** + + +* **DevEngine Generic Assist ->** [DevEngineAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/DevEngineAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +## **`dev.engine.analytics`** + + +* **Analytics Engine ->** [DevAnalyticsEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/analytics/DevAnalyticsEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Analytics Engine 接口 ->** [IAnalyticsEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/analytics/IAnalyticsEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| register | 绑定 | +| unregister | 解绑 | +| track | 数据统计 ( 埋点 ) 方法 | + + +## **`dev.engine.barcode`** + + +* **BarCode Engine ->** [DevBarCodeEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/DevBarCodeEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **BarCode Engine 接口 ->** [IBarCodeEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/IBarCodeEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| getConfig | 获取 BarCode Engine Config | +| encodeBarCode | 编码 ( 生成 ) 条码图片 | +| encodeBarCodeSync | 编码 ( 生成 ) 条码图片 | +| decodeBarCode | 解码 ( 解析 ) 条码图片 | +| decodeBarCodeSync | 解码 ( 解析 ) 条码图片 | +| addIconToBarCode | 添加 Icon 到条码图片上 | + + +## **`dev.engine.barcode.listener`** + + +* **条码解码 ( 解析 ) 回调 ->** [BarCodeDecodeCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeDecodeCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 条码解码 ( 解析 ) 回调 | + + +* **条码编码 ( 生成 ) 回调 ->** [BarCodeEncodeCallback.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/barcode/listener/BarCodeEncodeCallback.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 条码编码 ( 生成 ) 回调 | + + +## **`dev.engine.cache`** + + +* **Cache Engine ->** [DevCacheEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/cache/DevCacheEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Cache Engine 接口 ->** [ICacheEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/cache/ICacheEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取 Cache Engine Config | +| remove | 移除数据 | +| removeForKeys | 移除数组的数据 | +| contains | 是否存在 key | +| isDue | 判断某个 key 是否过期 | +| clear | 清除全部数据 | +| clearDue | 清除过期数据 | +| clearType | 清除某个类型的全部数据 | +| getItemByKey | 通过 Key 获取 Item | +| getKeys | 获取有效 Key 集合 | +| getPermanentKeys | 获取永久有效 Key 集合 | +| getCount | 获取有效 Key 数量 | +| getSize | 获取有效 Key 占用总大小 | +| put | 保存 int 类型的数据 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getBytes | 获取 byte[] 类型的数据 | +| getBitmap | 获取 Bitmap 类型的数据 | +| getDrawable | 获取 Drawable 类型的数据 | +| getSerializable | 获取 Serializable 类型的数据 | +| getParcelable | 获取 Parcelable 类型的数据 | +| getJSONObject | 获取 JSONObject 类型的数据 | +| getJSONArray | 获取 JSONArray 类型的数据 | +| getEntity | 获取指定类型对象 | + + +## **`dev.engine.compress`** + + +* **Image Compress Engine ->** [DevCompressEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/DevCompressEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Image Compress Engine 接口 ->** [ICompressEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/ICompressEngine.java) + +| 方法 | 注释 | +| :- | :- | +| compress | 压缩方法 | + + +## **`dev.engine.compress.listener`** + + +* **压缩过滤接口 ->** [CompressFilter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/CompressFilter.java) + +| 方法 | 注释 | +| :- | :- | +| apply | 根据路径判断是否进行压缩 | + + +* **压缩回调接口 ->** [OnCompressListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnCompressListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始压缩前调用 | +| onSuccess | 压缩成功后调用 | +| onError | 当压缩过程出现问题时触发 | +| onComplete | 压缩完成 ( 压缩结束 ) | + + +* **修改压缩图片文件名接口 ->** [OnRenameListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/compress/listener/OnRenameListener.java) + +| 方法 | 注释 | +| :- | :- | +| rename | 压缩前调用该方法用于修改压缩后文件名 | + + +## **`dev.engine.image`** + + +* **Image Engine ->** [DevImageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/DevImageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Image Engine 接口 ->** [IImageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/IImageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| pause | pause | +| resume | resume | +| preload | preload | +| clear | clear | +| clearDiskCache | clearDiskCache | +| clearMemoryCache | clearMemoryCache | +| clearAllCache | clearAllCache | +| lowMemory | lowMemory | +| display | display | +| loadImage | loadImage | +| loadImageThrows | loadImageThrows | +| loadBitmap | loadBitmap | +| loadBitmapThrows | loadBitmapThrows | +| loadDrawable | loadDrawable | +| loadDrawableThrows | loadDrawableThrows | +| convertImageFormat | convertImageFormat | + + +## **`dev.engine.image.listener`** + + +* **Bitmap 加载事件 ->** [BitmapListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/BitmapListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | getTranscodeType | + + +* **转换图片格式存储接口 ->** [ConvertStorage.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/ConvertStorage.java) + +| 方法 | 注释 | +| :- | :- | +| convert | 转换图片格式并存储 | + + +* **Drawable 加载事件 ->** [DrawableListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/DrawableListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | getTranscodeType | + + +* **图片加载事件 ->** [LoadListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/LoadListener.java) + +| 方法 | 注释 | +| :- | :- | +| getTranscodeType | 获取转码类型 | +| onStart | 开始加载 | +| onResponse | 响应回调 | +| onFailure | 失败回调 | + + +* **转换图片格式回调接口 ->** [OnConvertListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/image/listener/OnConvertListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始转换前调用 | +| onSuccess | 转换成功后调用 | +| onError | 当转换过程出现问题时触发 | +| onComplete | 转换完成 ( 转换结束 ) | + + +## **`dev.engine.json`** + + +* **JSON Engine ->** [DevJSONEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/json/DevJSONEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **JSON Engine 接口 ->** [IJSONEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/json/IJSONEngine.java) + +| 方法 | 注释 | +| :- | :- | +| toJson | 将对象转换为 JSON String | +| fromJson | 将 JSON String 映射为指定类型对象 | +| isJSON | 判断字符串是否 JSON 格式 | +| isJSONObject | 判断字符串是否 JSON Object 格式 | +| isJSONArray | 判断字符串是否 JSON Array 格式 | +| toJsonIndent | JSON String 缩进处理 | + + +## **`dev.engine.keyvalue`** + + +* **KeyValue Engine ->** [DevKeyValueEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/keyvalue/DevKeyValueEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Key-Value Engine 接口 ->** [IKeyValueEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/keyvalue/IKeyValueEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取 Key-Value Engine Config | +| remove | 移除数据 | +| removeForKeys | 移除数组的数据 | +| contains | 是否存在 key | +| clear | 清除全部数据 | +| putInt | 保存 int 类型的数据 | +| putLong | 保存 long 类型的数据 | +| putFloat | 保存 float 类型的数据 | +| putDouble | 保存 double 类型的数据 | +| putBoolean | 保存 boolean 类型的数据 | +| putString | 保存 String 类型的数据 | +| putEntity | 保存指定类型对象 | +| getInt | 获取 int 类型的数据 | +| getLong | 获取 long 类型的数据 | +| getFloat | 获取 float 类型的数据 | +| getDouble | 获取 double 类型的数据 | +| getBoolean | 获取 boolean 类型的数据 | +| getString | 获取 String 类型的数据 | +| getEntity | 获取指定类型对象 | + + +## **`dev.engine.log`** + + +* **Log Engine ->** [DevLogEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/log/DevLogEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Log Engine 接口 ->** [ILogEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/log/ILogEngine.java) + +| 方法 | 注释 | +| :- | :- | +| isPrintLog | 判断是否打印日志 | +| d | 打印 Log.DEBUG | +| e | 打印 Log.ERROR | +| w | 打印 Log.WARN | +| i | 打印 Log.INFO | +| v | 打印 Log.VERBOSE | +| wtf | 打印 Log.ASSERT | +| json | 格式化 JSON 格式数据, 并打印 | +| xml | 格式化 XML 格式数据, 并打印 | +| dTag | 打印 Log.DEBUG | +| eTag | 打印 Log.ERROR | +| wTag | 打印 Log.WARN | +| iTag | 打印 Log.INFO | +| vTag | 打印 Log.VERBOSE | +| wtfTag | 打印 Log.ASSERT | +| jsonTag | 格式化 JSON 格式数据, 并打印 | +| xmlTag | 格式化 XML 格式数据, 并打印 | + + +## **`dev.engine.media`** + + +* **Media Selector Engine ->** [DevMediaEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/media/DevMediaEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Media Selector Engine 接口 ->** [IMediaEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/media/IMediaEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getConfig | 获取全局配置 | +| setConfig | 设置全局配置 | +| openCamera | 打开相册拍照 | +| openGallery | 打开相册选择 | +| openPreview | 打开相册预览 | +| deleteCacheDirFile | 删除缓存文件 | +| deleteAllCacheDirFile | 删除全部缓存文件 | +| isMediaSelectorResult | 是否图片选择 ( onActivityResult ) | +| getSelectors | 获取 Media Selector Data List | +| getSelectorUris | 获取 Media Selector Uri List | +| getSingleSelector | 获取 Single Media Selector Data | +| getSingleSelectorUri | 获取 Single Media Selector Uri | + + +## **`dev.engine.permission`** + + +* **Permission Engine ->** [DevPermissionEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/permission/DevPermissionEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Permission Engine 接口 ->** [IPermissionEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/permission/IPermissionEngine.java) + +| 方法 | 注释 | +| :- | :- | +| isGranted | 判断是否授予了权限 | +| shouldShowRequestPermissionRationale | 获取拒绝权限询问勾选状态 | +| getDeniedPermissionStatus | 获取拒绝权限询问状态集合 | +| againRequest | 再次请求处理操作 | +| request | 请求权限 | +| onGranted | 授权通过权限回调 | +| onDenied | 授权未通过权限回调 | + + +## **`dev.engine.push`** + + +* **Push Engine ->** [DevPushEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/push/DevPushEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Push Engine 接口 ->** [IPushEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/push/IPushEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| register | 绑定 | +| unregister | 解绑 | +| onReceiveServicePid | 推送进程启动通知 | +| onReceiveClientId | 初始化 Client Id 成功通知 | +| onReceiveDeviceToken | 设备 ( 厂商 ) Token 通知 | +| onReceiveOnlineState | 在线状态变化通知 | +| onReceiveCommandResult | 命令回执通知 | +| onNotificationMessageArrived | 推送消息送达通知 | +| onNotificationMessageClicked | 推送消息点击通知 | +| onReceiveMessageData | 透传消息送达通知 | +| convertMessage | 传入 Object 转换 Engine Message | + + +## **`dev.engine.share`** + + +* **Share Engine ->** [DevShareEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/DevShareEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Share Engine 接口 ->** [IShareEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/IShareEngine.java) + +| 方法 | 注释 | +| :- | :- | +| initialize | 初始化方法 | +| openMinApp | 打开小程序 | +| shareMinApp | 分享小程序 | +| shareUrl | 分享链接 | +| shareImage | 分享图片 | +| shareImageList | 分享多张图片 | +| shareText | 分享文本 | +| shareVideo | 分享视频 | +| shareMusic | 分享音乐 | +| shareEmoji | 分享表情 | +| shareFile | 分享文件 | +| share | 分享操作 ( 通用扩展 ) | +| onActivityResult | 部分平台 Activity onActivityResult 额外调用处理 | + + +## **`dev.engine.share.listener`** + + +* **分享回调 ->** [ShareListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/share/listener/ShareListener.java) + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始分享 | +| onResult | 分享成功 | +| onError | 分享失败 | +| onCancel | 取消分享 | + + +## **`dev.engine.storage`** + + +* **Storage Engine ->** [DevStorageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/DevStorageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| getEngine | 获取 Engine | +| setEngine | 设置 Engine | +| removeEngine | 移除 Engine | +| getAssist | 获取 DevEngine Generic Assist | +| getEngineMaps | 获取 Engine Map | +| contains | 是否存在 Engine | +| isEmpty | 判断 Engine 是否为 null | + + +* **Storage Engine 接口 ->** [IStorageEngine.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/IStorageEngine.java) + +| 方法 | 注释 | +| :- | :- | +| insertImageToExternal | 插入一张图片到外部存储空间 ( SDCard ) | +| insertVideoToExternal | 插入一条视频到外部存储空间 ( SDCard ) | +| insertAudioToExternal | 插入一条音频到外部存储空间 ( SDCard ) | +| insertDownloadToExternal | 插入一条文件资源到外部存储空间 ( SDCard ) | +| insertMediaToExternal | 插入一条多媒体资源到外部存储空间 ( SDCard ) | +| insertImageToInternal | 插入一张图片到内部存储空间 | +| insertVideoToInternal | 插入一条视频到内部存储空间 | +| insertAudioToInternal | 插入一条音频到内部存储空间 | +| insertDownloadToInternal | 插入一条文件资源到内部存储空间 | +| insertMediaToInternal | 插入一条多媒体资源到内部存储空间 | + + +## **`dev.engine.storage.listener`** + + +* **插入多媒体资源事件 ->** [OnInsertListener.java](https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/src/main/java/dev/engine/storage/listener/OnInsertListener.java) + +| 方法 | 注释 | +| :- | :- | +| onResult | 插入多媒体资源结果方法 | + + + diff --git a/lib/DevEngine/build.gradle b/lib/DevEngine/build.gradle new file mode 100644 index 0000000000..964c97a037 --- /dev/null +++ b/lib/DevEngine/build.gradle @@ -0,0 +1,60 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply plugin: 'kotlin-kapt' + +android.defaultConfig { + versionCode versions.dev_engine_versionCode + versionName versions.dev_engine_versionName + // DevEngine Module Version + buildConfigField "int", "DevEngine_VersionCode", "${versions.dev_engine_versionCode}" + buildConfigField "String", "DevEngine_Version", "\"${versions.dev_engine_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + + // =================== + // = Engine 具体实现库 = + // =================== + + // Glide 图片加载框架 https://github.com/bumptech/glide + api deps.lib.glide + kapt deps.lib.glide_compiler + // Gson https://github.com/google/gson + api deps.lib.gson + // Fastjson2 https://github.com/alibaba/fastjson2 + api deps.lib.fastjson2_android + // 基于 mmap 的高性能通用 key-value 组件 https://github.com/Tencent/MMKV/blob/master/README_CN.md + api deps.lib.mmkv + // Luban 鲁班图片压缩 https://github.com/Curzibn/Luban + api deps_split.lib.luban + // 二维码 ZXing https://github.com/zxing/zxing + api deps_split.lib.zxing_code + // Android 平台下的图片选择器 https://github.com/LuckSiege/PictureSelector + api deps_split.lib.pictureSelector + + // ============= + // = DevEngine = + // ============= + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_assist + } else { + // 编译时使用 + api project(':DevAssist') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevEngine/proguard-rules.pro b/lib/DevEngine/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/DevEngine/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/DevEngine/project.properties b/lib/DevEngine/project.properties new file mode 100644 index 0000000000..fa31141ec8 --- /dev/null +++ b/lib/DevEngine/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevEngine +project.groupId=io.github.afkt +project.artifactId=DevEngine +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevEngine \ No newline at end of file diff --git a/lib/DevEngine/src/main/AndroidManifest.xml b/lib/DevEngine/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..0e738dd193 --- /dev/null +++ b/lib/DevEngine/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/DevEngine.kt b/lib/DevEngine/src/main/java/dev/engine/DevEngine.kt new file mode 100644 index 0000000000..769b5340ed --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/DevEngine.kt @@ -0,0 +1,515 @@ +package dev.engine + +import android.content.Context +import com.tencent.mmkv.MMKV +import dev.DevUtils +import dev.engine.analytics.DevAnalyticsEngine +import dev.engine.barcode.DevBarCodeEngine +import dev.engine.barcode.ZXingEngineImpl +import dev.engine.cache.CacheConfig +import dev.engine.cache.DevCacheEngine +import dev.engine.cache.DevCacheEngineImpl +import dev.engine.compress.DevCompressEngine +import dev.engine.compress.LubanEngineImpl +import dev.engine.image.DevImageEngine +import dev.engine.image.GlideEngineImpl +import dev.engine.json.DevJSONEngine +import dev.engine.json.GsonEngineImpl +import dev.engine.keyvalue.* +import dev.engine.log.DevLogEngine +import dev.engine.log.DevLoggerEngineImpl +import dev.engine.media.DevMediaEngine +import dev.engine.media.PictureSelectorEngineImpl +import dev.engine.permission.DevPermissionEngine +import dev.engine.permission.DevPermissionEngineImpl +import dev.engine.push.DevPushEngine +import dev.engine.share.DevShareEngine +import dev.engine.storage.DevMediaStoreEngineImpl +import dev.engine.storage.DevStorageEngine +import dev.utils.app.cache.DevCache +import dev.utils.app.logger.LogConfig +import dev.utils.common.cipher.Cipher + +/** + * detail: DevEngine + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevEngine { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevEngine 版本号 + * @return DevEngine versionCode + */ + fun getDevEngineVersionCode(): Int { + return BuildConfig.DevEngine_VersionCode + } + + /** + * 获取 DevEngine 版本 + * @return DevEngine versionName + */ + fun getDevEngineVersion(): String { + return BuildConfig.DevEngine_Version + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + fun getDevAppVersionCode(): Int { + return BuildConfig.DevApp_VersionCode + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + fun getDevAppVersion(): String { + return BuildConfig.DevApp_Version + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 创建 MMKV Config + * @param cipher 加解密中间层 + * @param mmkv [MMKV] + * @return [MMKVConfig] + *

+ * 需先调用 [defaultMMKVInitialize] + */ + fun createMMKVConfig( + cipher: Cipher? = null, + mmkv: MMKV + ): MMKVConfig { + return MMKVConfig(cipher, mmkv) + } + + // ============ + // = 初始化方法 = + // ============ + + /** + * 完整初始化 ( 全面使用该库调用该方法初始化即可 ) + * @param context Context + */ + fun completeInitialize(context: Context) { + defaultMMKVInitialize(context) + // 如果 MMKV 不为 null 则进行初始化 + MMKVUtils.defaultHolder().mmkv?.let { mmkv -> + defaultEngine(createMMKVConfig(cipher = null, mmkv = mmkv)) + return + } + defaultEngine() + } + + // = + + /** + * 使用 DevEngine 库内部默认实现 MMKV 初始化 + * @param context Context? + * @return DevEngine + */ + fun defaultMMKVInitialize(context: Context?): DevEngine { + DevUtils.getContext(context)?.let { + MMKVUtils.initialize(it.applicationContext) + } + return this + } + + /** + * 使用 DevEngine 库内部默认实现 Engine + * @param keyValueConfig Key-Value Engine Config + * @param logConfig Log Config + * 如果使用 MMKV 必须先调用 [defaultMMKVInitialize] + * 接着调用该方法 [defaultEngine] 传入 [MMKVConfig] or [createMMKVConfig] + */ + fun defaultEngine( + keyValueConfig: IKeyValueEngine.EngineConfig? = null + ) { + defaultEngine( + CacheConfig(null, DevCache.newCache()), + keyValueConfig + ) + } + + /** + * 使用 DevEngine 库内部默认实现 Engine + * @param cacheConfig Cache Engine Config + * @param keyValueConfig Key-Value Engine Config + * @param logConfig Log Config + *

+ * 如果 Key-Value 是 MMKV Engine 则需要先调用 MMKV.initialize() + */ + fun defaultEngine( + cacheConfig: CacheConfig?, + keyValueConfig: IKeyValueEngine.EngineConfig?, + logConfig: LogConfig? = null + ) { + + // ======================== + // = BarCode Engine 条形码 = + // ======================== + + // 初始化 ZXing BarCode Engine 实现 + DevBarCodeEngine.setEngine(ZXingEngineImpl()) + + // =============== + // = JSON Engine = + // =============== + + // 初始化 Gson JSON Engine 实现 + DevJSONEngine.setEngine(GsonEngineImpl()) +// // 初始化 Fastjson JSON Engine 实现 +// DevJSONEngine.setEngine(FastjsonEngineImpl()) + + // ============================== + // = Cache Engine 有效期键值对缓存 = + // ============================== + + cacheConfig?.apply { + // 初始化 DevCache ( DevUtils ) Cache Engine 实现 + DevCacheEngine.setEngine(DevCacheEngineImpl(this)) + } + + // ================================ + // = Image Compress Engine 图片压缩 = + // ================================ + + // 初始化 Luban Image Compress Engine 实现 + DevCompressEngine.setEngine(LubanEngineImpl()) + + // ==================================== + // = Image Engine 图片加载、下载、转格式等 = + // ==================================== + + // 初始化 Glide Image Engine 实现 + DevImageEngine.setEngine(GlideEngineImpl()) + + // ============================ + // = KeyValue Engine 键值对存储 = + // ============================ + + keyValueConfig?.apply { + if (this is MMKVConfig) { + // 初始化 MMKV Key-Value Engine 实现 + DevKeyValueEngine.setEngine(MMKVKeyValueEngineImpl(this)) + } else if (this is SPConfig) { + // 初始化 SharedPreferences Key-Value Engine 实现 + DevKeyValueEngine.setEngine(SPKeyValueEngineImpl(this)) + } + } + + // ===================== + // = Log Engine 日志打印 = + // ===================== + + // 初始化 DevLogger Log Engine 实现 + DevLogEngine.setEngine(object : DevLoggerEngineImpl(logConfig) { + override fun isPrintLog(): Boolean { + // 属于 Debug 模式才打印日志 + return DevUtils.isDebug() + } + }) + + // ===================================== + // = Media Selector Engine 多媒体资源选择 = + // ===================================== + + // 初始化 PictureSelector Media Selector Engine 实现 + DevMediaEngine.setEngine(PictureSelectorEngineImpl()) + + // ============================ + // = Permission Engine 权限申请 = + // ============================ + + // 初始化 DevUtils Permission Engine 实现 + DevPermissionEngine.setEngine(DevPermissionEngineImpl()) + + // ================================= + // = Storage Engine 外部、内部文件存储 = + // ================================= + + // 初始化 DevUtils MediaStore Engine 实现 + DevStorageEngine.setEngine(DevMediaStoreEngineImpl()) + } + + // ============== + // = Engine get = + // ============== + + /** + * 获取 Analytics Engine + * @return Analytics Engine + */ + fun getAnalytics() = DevAnalyticsEngine.getEngine() + + /** + * 获取 BarCode Engine + * @return BarCode Engine + */ + fun getBarCode() = DevBarCodeEngine.getEngine() + + /** + * 获取 Cache Engine + * @return Cache Engine + */ + fun getCache() = DevCacheEngine.getEngine() + + /** + * 获取 Compress Engine + * @return Compress Engine + */ + fun getCompress() = DevCompressEngine.getEngine() + + /** + * 获取 Image Engine + * @return Image Engine + */ + fun getImage() = DevImageEngine.getEngine() + + /** + * 获取 JSON Engine + * @return JSON Engine + */ + fun getJSON() = DevJSONEngine.getEngine() + + /** + * 获取 KeyValue Engine + * @return KeyValue Engine + */ + fun getKeyValue() = DevKeyValueEngine.getEngine() + + /** + * 获取 Log Engine + * @return Log Engine + */ + fun getLog() = DevLogEngine.getEngine() + + /** + * 获取 Media Engine + * @return Media Engine + */ + fun getMedia() = DevMediaEngine.getEngine() + + /** + * 获取 Permission Engine + * @return Permission Engine + */ + fun getPermission() = DevPermissionEngine.getEngine() + + /** + * 获取 Push Engine + * @return Push Engine + */ + fun getPush() = DevPushEngine.getEngine() + + /** + * 获取 Share Engine + * @return Share Engine + */ + fun getShare() = DevShareEngine.getEngine() + + /** + * 获取 Storage Engine + * @return Storage Engine + */ + fun getStorage() = DevStorageEngine.getEngine() + + // ================== + // = Engine Key get = + // ================== + + /** + * 获取 Analytics Engine + * @return Analytics Engine + */ + fun getAnalytics(key: String?) = DevAnalyticsEngine.getEngine(key) + + /** + * 获取 BarCode Engine + * @return BarCode Engine + */ + fun getBarCode(key: String?) = DevBarCodeEngine.getEngine(key) + + /** + * 获取 Cache Engine + * @return Cache Engine + */ + fun getCache(key: String?) = DevCacheEngine.getEngine(key) + + /** + * 获取 Compress Engine + * @return Compress Engine + */ + fun getCompress(key: String?) = DevCompressEngine.getEngine(key) + + /** + * 获取 Image Engine + * @return Image Engine + */ + fun getImage(key: String?) = DevImageEngine.getEngine(key) + + /** + * 获取 JSON Engine + * @return JSON Engine + */ + fun getJSON(key: String?) = DevJSONEngine.getEngine(key) + + /** + * 获取 KeyValue Engine + * @return KeyValue Engine + */ + fun getKeyValue(key: String?) = DevKeyValueEngine.getEngine(key) + + /** + * 获取 Log Engine + * @return Log Engine + */ + fun getLog(key: String?) = DevLogEngine.getEngine(key) + + /** + * 获取 Media Engine + * @return Media Engine + */ + fun getMedia(key: String?) = DevMediaEngine.getEngine(key) + + /** + * 获取 Permission Engine + * @return Permission Engine + */ + fun getPermission(key: String?) = DevPermissionEngine.getEngine(key) + + /** + * 获取 Push Engine + * @return Push Engine + */ + fun getPush(key: String?) = DevPushEngine.getEngine(key) + + /** + * 获取 Share Engine + * @return Share Engine + */ + fun getShare(key: String?) = DevShareEngine.getEngine(key) + + /** + * 获取 Storage Engine + * @return Storage Engine + */ + fun getStorage(key: String?) = DevStorageEngine.getEngine(key) + + // ================= + // = Engine Assist = + // ================= + + /** + * 获取 Analytics Engine Generic Assist + * @return Analytics Engine Generic Assist + */ + fun getAnalyticsAssist() = DevAnalyticsEngine.getAssist() + + /** + * 获取 BarCode Engine Generic Assist + * @return BarCode Engine Generic Assist + */ + fun getBarCodeAssist() = DevBarCodeEngine.getAssist() + + /** + * 获取 Cache Engine Generic Assist + * @return Cache Engine Generic Assist + */ + fun getCacheAssist() = DevCacheEngine.getAssist() + + /** + * 获取 Compress Engine Generic Assist + * @return Compress Engine Generic Assist + */ + fun getCompressAssist() = DevCompressEngine.getAssist() + + /** + * 获取 Image Engine Generic Assist + * @return Image Engine Generic Assist + */ + fun getImageAssist() = DevImageEngine.getAssist() + + /** + * 获取 JSON Engine Generic Assist + * @return JSON Engine Generic Assist + */ + fun getJSONAssist() = DevJSONEngine.getAssist() + + /** + * 获取 KeyValue Engine Generic Assist + * @return KeyValue Engine Generic Assist + */ + fun getKeyValueAssist() = DevKeyValueEngine.getAssist() + + /** + * 获取 Log Engine Generic Assist + * @return Log Engine Generic Assist + */ + fun getLogAssist() = DevLogEngine.getAssist() + + /** + * 获取 Media Engine Generic Assist + * @return Media Engine Generic Assist + */ + fun getMediaAssist() = DevMediaEngine.getAssist() + + /** + * 获取 Permission Engine Generic Assist + * @return Permission Engine Generic Assist + */ + fun getPermissionAssist() = DevPermissionEngine.getAssist() + + /** + * 获取 Push Engine Generic Assist + * @return Push Engine Generic Assist + */ + fun getPushAssist() = DevPushEngine.getAssist() + + /** + * 获取 Share Engine Generic Assist + * @return Share Engine Generic Assist + */ + fun getShareAssist() = DevShareEngine.getAssist() + + /** + * 获取 Storage Engine Generic Assist + * @return Storage Engine Generic Assist + */ + fun getStorageAssist() = DevStorageEngine.getAssist() +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeConfig.kt b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeConfig.kt new file mode 100644 index 0000000000..114e0237f3 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeConfig.kt @@ -0,0 +1,80 @@ +package dev.engine.barcode + +import com.google.zxing.DecodeHintType +import com.google.zxing.EncodeHintType +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import dev.utils.DevFinal +import java.util.* + +/** + * detail: BarCode Config + * @author Ttt + */ +open class BarCodeConfig : IBarCodeEngine.EngineConfig() { + + // 编码 ( 生成 ) 配置 + private val encodeHints: MutableMap = EnumMap( + EncodeHintType::class.java + ) + + // 解码 ( 解析 ) 配置 + private val decodeHints: MutableMap = EnumMap( + DecodeHintType::class.java + ) + + // =========== + // = get/set = + // =========== + + /** + * 获取编码 ( 生成 ) 配置 + * @return 编码 ( 生成 ) 配置 + */ + fun getEncodeHints(): MutableMap { + return encodeHints + } + + /** + * 获取解码 ( 解析 ) 配置 + * @return 解码 ( 解析 ) 配置 + */ + fun getDecodeHints(): MutableMap { + return decodeHints + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 设置默认编码 ( 生成 ) 配置 + * @return BarCode Config + */ + fun defaultEncode(): BarCodeConfig { + // 编码类型 + encodeHints[EncodeHintType.CHARACTER_SET] = DevFinal.ENCODE.UTF_8 + // 指定纠错等级, 纠错级别 ( L 7%、M 15%、Q 25%、H 30% ) + encodeHints[EncodeHintType.ERROR_CORRECTION] = ErrorCorrectionLevel.H + // 设置二维码边的空度, 非负数 + encodeHints[EncodeHintType.MARGIN] = 0 + return this + } + + /** + * 设置编码 ( 生成 ) 配置 + * @return BarCode Config + */ + fun putEncodeHints(hints: Map?): BarCodeConfig { + hints?.let { encodeHints.putAll(it) } + return this + } + + /** + * 设置解码 ( 解析 ) 配置 + * @return BarCode Config + */ + fun putDecodeHints(hints: Map?): BarCodeConfig { + hints?.let { decodeHints.putAll(it) } + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeData.kt b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeData.kt new file mode 100644 index 0000000000..d1c621b8a2 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeData.kt @@ -0,0 +1,198 @@ +package dev.engine.barcode + +import android.graphics.Bitmap +import android.graphics.Color +import com.google.zxing.BarcodeFormat + +/** + * detail: BarCode ( Data、Params ) Item + * @author Ttt + */ +open class BarCodeData private constructor( + // 条码内容 + private val mContent: String?, + // 条码宽度 + private val mWidth: Int, + // 条码高度 + private val mHeight: Int +) : IBarCodeEngine.EngineItem() { + + companion object { + + /** + * 快捷创建 BarCode ( Data、Params ) Item + * @param content 条码内容 + * @param size 条码大小 + * @return BarCode Item + */ + operator fun get( + content: String, + size: Int + ): BarCodeData { + return BarCodeData(content, size, size) + } + + /** + * 快捷创建 BarCode ( Data、Params ) Item + * @param content 条码内容 + * @param width 条码宽度 + * @param height 条码高度 + * @return BarCode Item + */ + operator fun get( + content: String, + width: Int, + height: Int + ): BarCodeData { + return BarCodeData(content, width, height) + } + } + + // 条码类型 ( 默认二维码 ) + private var mFormat = BarcodeFormat.QR_CODE + + // 条码前景色 ( 默认黑色方块 ) + private var mForegroundColor = Color.BLACK + + // 条码背景色 ( 默认白色背景 ) + private var mBackgroundColor = Color.WHITE + + // 条码嵌入 icon、logo + private var mIcon: Bitmap? = null + + // icon 占比 + private var mIconScale = 4.0F + + // =========== + // = get/set = + // =========== + + /** + * 获取条码内容 + * @return 条码内容 + */ + fun getContent(): String? { + return mContent + } + + /** + * 获取条码宽度 + * @return 条码宽度 + */ + fun getWidth(): Int { + return mWidth + } + + /** + * 获取条码高度 + * @return 条码高度 + */ + fun getHeight(): Int { + return mHeight + } + + /** + * 获取条码类型 + * @return 条码类型 + */ + fun getFormat(): BarcodeFormat { + return mFormat + } + + /** + * 设置条码类型 + * @param format 条码类型 + * @return BarCode Item + */ + fun setFormat(format: BarcodeFormat): BarCodeData { + mFormat = format + return this + } + + /** + * 获取条码前景色 + * @return 条码前景色 + */ + fun getForegroundColor(): Int { + return mForegroundColor + } + + /** + * 设置条码前景色 + * @param foregroundColor 条码前景色 + * @return BarCode Item + */ + fun setForegroundColor(foregroundColor: Int): BarCodeData { + mForegroundColor = foregroundColor + return this + } + + /** + * 获取条码背景色 + * @return 条码背景色 + */ + fun getBackgroundColor(): Int { + return mBackgroundColor + } + + /** + * 设置条码背景色 + * @param backgroundColor 条码背景色 + * @return BarCode Item + */ + fun setBackgroundColor(backgroundColor: Int): BarCodeData { + mBackgroundColor = backgroundColor + return this + } + + /** + * 设置条码前景、背景色 + * @param foregroundColor 条码前景色 + * @param backgroundColor 条码背景色 + * @return BarCode Item + */ + fun setBackgroundColor( + foregroundColor: Int, + backgroundColor: Int + ): BarCodeData { + mForegroundColor = foregroundColor + mBackgroundColor = backgroundColor + return this + } + + /** + * 获取条码嵌入 icon、logo + * @return 嵌入 icon、logo + */ + fun getIcon(): Bitmap? { + return mIcon + } + + /** + * 设置条码嵌入 icon、logo + * @param icon icon、logov + * @return BarCode Item + */ + fun setIcon(icon: Bitmap?): BarCodeData { + mIcon = icon + return this + } + + /** + * 获取 icon 占比 + * @return icon 占比 + */ + fun getIconScale(): Float { + return mIconScale + } + + /** + * 设置 icon 占比 + * @param iconScale icon 占比 + * @return BarCode Item + */ + fun setIconScale(iconScale: Float): BarCodeData { + mIconScale = iconScale + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeResult.kt b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeResult.kt new file mode 100644 index 0000000000..4ec3d5c109 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/barcode/BarCodeResult.kt @@ -0,0 +1,51 @@ +package dev.engine.barcode + +import com.google.zxing.BarcodeFormat +import com.google.zxing.Result + +/** + * detail: BarCode Result + * @author Ttt + */ +open class BarCodeResult( + private val mResult: Result? +) : IBarCodeEngine.EngineResult() { + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否解析成功 + * @return `true` success, `false` fail + */ + fun isSuccess(): Boolean { + return mResult != null + } + + /** + * 获取解析结果 + * @return 解析结果数据 + */ + fun getResult(): Result? { + return mResult + } + + // = + + /** + * 获取扫描结果数据 + * @return 扫描结果数据 + */ + fun getResultData(): String? { + return mResult?.text + } + + /** + * 获取条码类型 + * @return 条码类型 + */ + fun getBarcodeFormat(): BarcodeFormat? { + return mResult?.barcodeFormat + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/barcode/ZXingEngineImpl.kt b/lib/DevEngine/src/main/java/dev/engine/barcode/ZXingEngineImpl.kt new file mode 100644 index 0000000000..44e058a18b --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/barcode/ZXingEngineImpl.kt @@ -0,0 +1,300 @@ +package dev.engine.barcode + +import android.graphics.Bitmap +import android.graphics.Canvas +import com.google.zxing.BinaryBitmap +import com.google.zxing.MultiFormatReader +import com.google.zxing.MultiFormatWriter +import com.google.zxing.RGBLuminanceSource +import com.google.zxing.common.HybridBinarizer +import dev.engine.barcode.listener.BarCodeDecodeCallback +import dev.engine.barcode.listener.BarCodeEncodeCallback +import dev.utils.LogPrintUtils +import dev.utils.common.StringUtils +import dev.utils.common.thread.DevThreadPool + +/** + * detail: ZXing BarCode Engine 实现 + * @author Ttt + */ +open class ZXingEngineImpl(threadNumber: Int = 6) : IBarCodeEngine { + + // 日志 TAG + private val TAG = ZXingEngineImpl::class.java.simpleName + + // 线程池 ( 构建类 ) + private val DEV_THREAD_POOL = DevThreadPool(threadNumber) + + // 默认条码配置 + private val DEFAULT_CONFIG = BarCodeConfig().defaultEncode() + + // 当前条码配置 + private var mBarCodeConfig: BarCodeConfig? = null + + // ============= + // = 对外公开方法 = + // ============= + + override fun initialize(config: BarCodeConfig?) { + mBarCodeConfig = config + } + + override fun getConfig(): BarCodeConfig? { + return mBarCodeConfig + } + + // ========== + // = 生成条码 = + // ========== + + override fun encodeBarCode( + params: BarCodeData?, + callback: BarCodeEncodeCallback? + ) { + val error = isValidData(params) + if (error != null) { + encodeFailureCallback(callback, error) + return + } + DEV_THREAD_POOL.execute { + try { + var bitmap = encodeBarCodeSync(params) + // 条码嵌入 icon、logo + if (params?.getIcon() != null) { + bitmap = addIconToBarCode(params, bitmap, params.getIcon()) + } + encodeCallback(callback, bitmap) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "encodeBarCode") + // 触发回调 + encodeFailureCallback(callback, e) + } + } + } + + override fun encodeBarCodeSync(params: BarCodeData?): Bitmap { + val error = isValidData(params) + if (error == null && params != null) { + val config = getInnerConfig() + // 条码宽高 + val width = params.getWidth() + val height = params.getHeight() + // 创建条码矩阵 + val matrix = MultiFormatWriter().encode( + params.getContent(), params.getFormat(), + width, height, config.getEncodeHints() + ) + val pixels = IntArray(width * height) + for (y in 0 until height) { + for (x in 0 until width) { + if (matrix[x, y]) { + pixels[y * width + x] = params.getForegroundColor() + } else { + pixels[y * width + x] = params.getBackgroundColor() + } + } + } + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + bitmap.setPixels(pixels, 0, width, 0, 0, width, height) + return bitmap + } + throw error!! + } + + // ========== + // = 解析条码 = + // ========== + + override fun decodeBarCode( + bitmap: Bitmap?, + callback: BarCodeDecodeCallback? + ) { + if (bitmap == null) { + decodeFailureCallback(callback, Exception("BarCode decode Bitmap is null")) + return + } + DEV_THREAD_POOL.execute { + try { + val result = decodeBarCodeSync(bitmap) + // 触发回调 + decodeCallback(callback, result) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "decodeBarCode") + // 触发回调 + decodeFailureCallback(callback, e) + } + } + } + + override fun decodeBarCodeSync(bitmap: Bitmap?): BarCodeResult { + if (bitmap != null) { + val config = getInnerConfig() + // 解码处理 + val width = bitmap.width + val height = bitmap.height + val pixels = IntArray(width * height) + bitmap.getPixels(pixels, 0, width, 0, 0, width, height) + val source = RGBLuminanceSource(width, height, pixels) + val result = MultiFormatReader().decode( + BinaryBitmap(HybridBinarizer(source)), + config.getDecodeHints() + ) + return BarCodeResult(result) + } + throw Exception("BarCode decode Bitmap is null") + } + + // ========== + // = 其他功能 = + // ========== + + /** + * 添加 Icon 到条码图片上 + * @param params BarCode ( Data、Params ) Item + * @param src 条码图片 + * @param icon icon + * @return 含 icon 条码图片 + * 目前就处理了 二维码图片 其他需要根据需求自行添加 + */ + override fun addIconToBarCode( + params: BarCodeData?, + src: Bitmap?, + icon: Bitmap? + ): Bitmap { + if (params == null) { + throw Exception("BarCode ( Data、Params ) Item is null") + } + if (params.getIconScale() <= 0) { + throw Exception("BarCode iconScale Less than or equal to 0") + } + if (src == null) { + throw Exception("addIconToBarCode src Bitmap is null") + } + if (icon == null) { + throw Exception("addIconToBarCode icon Bitmap is null") + } + // 获取图片宽度高度 + val srcWidth = src.width.toFloat() + val srcHeight = src.height.toFloat() + val iconWidth = icon.width.toFloat() + val iconHeight = icon.height.toFloat() + + // 这里决定 icon 在图片的比例 ( 可自行判断 BarCodeData Format 决定绘制大小比例 ) + val scaleFactor = srcWidth / params.getIconScale() / iconWidth + val bitmap = Bitmap.createBitmap( + srcWidth.toInt(), srcHeight.toInt(), + Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + canvas.drawBitmap(src, 0F, 0F, null) + canvas.scale(scaleFactor, scaleFactor, srcWidth / 2, srcHeight / 2) + canvas.drawBitmap( + icon, (srcWidth - iconWidth) / 2, + (srcHeight - iconHeight) / 2, null + ) + canvas.save() + canvas.restore() + if (bitmap != null) return bitmap + throw Exception("addIconToBarCode failure") + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 BarCode Config + * @return BarCode Config + */ + private fun getInnerConfig(): BarCodeConfig { + return mBarCodeConfig ?: DEFAULT_CONFIG + } + + /** + * 判断是否有效数据 + * @param params BarCode ( Data、Params ) Item + * @return 如果属于有效数据则返回 null + */ + private fun isValidData(params: BarCodeData?): Exception? { + if (params == null) { + return Exception("BarCode ( Data、Params ) Item is null") + } + if (StringUtils.isEmpty(params.getContent())) { + return Exception("BarCode content is null") + } + if (params.getWidth() <= 0 || params.getHeight() <= 0) { + return Exception("BarCode width、height Less than or equal to 0") + } + return null + } + + // = + + /** + * 编码 ( 生成 ) 回调 + * @param callback 生成结果回调 + * @param bitmap 条码图片 + */ + private fun encodeCallback( + callback: BarCodeEncodeCallback?, + bitmap: Bitmap? + ) { + if (callback != null) { + if (bitmap != null) { + callback.onResult(true, bitmap, null) + } else { + callback.onResult( + false, null, + Exception("BarCode encode Bitmap is null") + ) + } + } + } + + /** + * 编码 ( 生成 ) 失败回调 + * @param callback 生成结果回调 + * @param error 异常信息 + */ + private fun encodeFailureCallback( + callback: BarCodeEncodeCallback?, + error: Throwable + ) { + callback?.onResult(false, null, error) + } + + // = + + /** + * 解码 ( 解析 ) 回调 + * @param callback 生成结果回调 + * @param result 识别结果 + */ + private fun decodeCallback( + callback: BarCodeDecodeCallback?, + result: BarCodeResult? + ) { + if (callback != null) { + if (result != null) { + callback.onResult(true, result, null) + } else { + callback.onResult( + false, null, + Exception("BarCode decode Result is null") + ) + } + } + } + + /** + * 解码 ( 解析 ) 失败回调 + * @param callback 生成结果回调 + * @param error 异常信息 + */ + private fun decodeFailureCallback( + callback: BarCodeDecodeCallback?, + error: Throwable + ) { + callback?.onResult(false, null, error) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/cache/CacheConfig.kt b/lib/DevEngine/src/main/java/dev/engine/cache/CacheConfig.kt new file mode 100644 index 0000000000..687034615e --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/cache/CacheConfig.kt @@ -0,0 +1,13 @@ +package dev.engine.cache + +import dev.utils.app.cache.DevCache +import dev.utils.common.cipher.Cipher + +/** + * detail: Cache Config + * @author Ttt + */ +open class CacheConfig( + cipher: Cipher?, + val mDevCache: DevCache +) : ICacheEngine.EngineConfig(cipher) \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/cache/DataItem.kt b/lib/DevEngine/src/main/java/dev/engine/cache/DataItem.kt new file mode 100644 index 0000000000..c0ea5ff9a2 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/cache/DataItem.kt @@ -0,0 +1,15 @@ +package dev.engine.cache + +/** + * detail: Cache ( Data、Params ) Item + * @author Ttt + */ +open class DataItem( + key: String?, + type: Int, + size: Long, + saveTime: Long, + validTime: Long, + isPermanent: Boolean, + isDue: Boolean +) : ICacheEngine.EngineItem(key, type, size, saveTime, validTime, isPermanent, isDue) \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/cache/engine_dev_cache.kt b/lib/DevEngine/src/main/java/dev/engine/cache/engine_dev_cache.kt new file mode 100644 index 0000000000..394b4bd17e --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/cache/engine_dev_cache.kt @@ -0,0 +1,399 @@ +package dev.engine.cache + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.os.Parcelable +import dev.engine.json.DevJSONEngine +import dev.engine.json.IJSONEngine +import org.json.JSONArray +import org.json.JSONObject +import java.io.Serializable +import java.lang.reflect.Type + +/** + * detail: DevCache ( DevUtils ) Cache Engine 实现 + * @author Ttt + */ +open class DevCacheEngineImpl( + private val mConfig: CacheConfig +) : ICacheEngine { + + // JSON Engine + private var mJSONEngine: IJSONEngine? = DevJSONEngine.getEngine() + + fun setJSONEngine(engine: IJSONEngine) { + this.mJSONEngine = engine + } + + // ============= + // = 对外公开方法 = + // ============= + + override fun getConfig(): CacheConfig { + return mConfig + } + + override fun remove(key: String?) { + mConfig.mDevCache.remove(key) + } + + override fun removeForKeys(keys: Array?) { + mConfig.mDevCache.removeForKeys(keys) + } + + override fun contains(key: String?): Boolean { + return mConfig.mDevCache.contains(key) + } + + override fun isDue(key: String?): Boolean { + return mConfig.mDevCache.isDue(key) + } + + override fun clear() { + mConfig.mDevCache.clear() + } + + override fun clearDue() { + mConfig.mDevCache.clearDue() + } + + override fun clearType(type: Int) { + mConfig.mDevCache.clearType(type) + } + + override fun getItemByKey(key: String?): DataItem? { + val data = mConfig.mDevCache.getItemByKey(key) ?: return null + return DataItem( + data.key, data.type, data.size, + data.saveTime, data.validTime, + data.isPermanent, data.isDue + ) + } + + override fun getKeys(): MutableList { + val datas = mConfig.mDevCache.keys + val lists = mutableListOf() + for (data in datas) { + if (data != null) { + val item = DataItem( + data.key, data.type, data.size, + data.saveTime, data.validTime, + data.isPermanent, data.isDue + ) + lists.add(item) + } + } + return lists + } + + override fun getPermanentKeys(): MutableList { + val datas = mConfig.mDevCache.permanentKeys + val lists = mutableListOf() + for (data in datas) { + if (data != null) { + val item = DataItem( + data.key, data.type, data.size, + data.saveTime, data.validTime, + data.isPermanent, data.isDue + ) + lists.add(item) + } + } + return lists + } + + override fun getCount(): Int { + return mConfig.mDevCache.count + } + + override fun getSize(): Long { + return mConfig.mDevCache.size + } + + // ======= + // = 存储 = + // ======= + + override fun put( + key: String?, + value: Int, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Long, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Float, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Double, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Boolean, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: String?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: ByteArray?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Bitmap?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Drawable?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Serializable?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: Parcelable?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: JSONObject?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: JSONArray?, + validTime: Long + ): Boolean { + return mConfig.mDevCache.put(key, value, validTime) + } + + override fun put( + key: String?, + value: T, + validTime: Long + ): Boolean { + val json = mJSONEngine?.toJson(value) + return mConfig.mDevCache.put(key, json, validTime) + } + + // ======= + // = 获取 = + // ======= + + override fun getInt(key: String?): Int { + return mConfig.mDevCache.getInt(key) + } + + override fun getLong(key: String?): Long { + return mConfig.mDevCache.getLong(key) + } + + override fun getFloat(key: String?): Float { + return mConfig.mDevCache.getFloat(key) + } + + override fun getDouble(key: String?): Double { + return mConfig.mDevCache.getDouble(key) + } + + override fun getBoolean(key: String?): Boolean { + return mConfig.mDevCache.getBoolean(key) + } + + override fun getString(key: String?): String { + return mConfig.mDevCache.getString(key) + } + + override fun getBytes(key: String?): ByteArray { + return mConfig.mDevCache.getBytes(key) + } + + override fun getBitmap(key: String?): Bitmap { + return mConfig.mDevCache.getBitmap(key) + } + + override fun getDrawable(key: String?): Drawable { + return mConfig.mDevCache.getDrawable(key) + } + + override fun getSerializable(key: String?): Any { + return mConfig.mDevCache.getSerializable(key) + } + + override fun getParcelable( + key: String?, + creator: Parcelable.Creator? + ): T { + return mConfig.mDevCache.getParcelable(key, creator) + } + + override fun getJSONObject(key: String?): JSONObject { + return mConfig.mDevCache.getJSONObject(key) + } + + override fun getJSONArray(key: String?): JSONArray { + return mConfig.mDevCache.getJSONArray(key) + } + + override fun getEntity( + key: String?, + typeOfT: Type? + ): T? { + return getEntity(key, typeOfT, null) + } + + // = + + override fun getInt( + key: String?, + defaultValue: Int + ): Int { + return mConfig.mDevCache.getInt(key, defaultValue) + } + + override fun getLong( + key: String?, + defaultValue: Long + ): Long { + return mConfig.mDevCache.getLong(key, defaultValue) + } + + override fun getFloat( + key: String?, + defaultValue: Float + ): Float { + return mConfig.mDevCache.getFloat(key, defaultValue) + } + + override fun getDouble( + key: String?, + defaultValue: Double + ): Double { + return mConfig.mDevCache.getDouble(key, defaultValue) + } + + override fun getBoolean( + key: String?, + defaultValue: Boolean + ): Boolean { + return mConfig.mDevCache.getBoolean(key, defaultValue) + } + + override fun getString( + key: String?, + defaultValue: String? + ): String { + return mConfig.mDevCache.getString(key, defaultValue) + } + + override fun getBytes( + key: String?, + defaultValue: ByteArray? + ): ByteArray { + return mConfig.mDevCache.getBytes(key, defaultValue) + } + + override fun getBitmap( + key: String?, + defaultValue: Bitmap? + ): Bitmap { + return mConfig.mDevCache.getBitmap(key, defaultValue) + } + + override fun getDrawable( + key: String?, + defaultValue: Drawable? + ): Drawable { + return mConfig.mDevCache.getDrawable(key, defaultValue) + } + + override fun getSerializable( + key: String?, + defaultValue: Any? + ): Any { + return mConfig.mDevCache.getSerializable(key, defaultValue) + } + + override fun getParcelable( + key: String?, + creator: Parcelable.Creator?, + defaultValue: T + ): T { + return mConfig.mDevCache.getParcelable(key, creator, defaultValue) + } + + override fun getJSONObject( + key: String?, + defaultValue: JSONObject? + ): JSONObject { + return mConfig.mDevCache.getJSONObject(key, defaultValue) + } + + override fun getJSONArray( + key: String?, + defaultValue: JSONArray? + ): JSONArray { + return mConfig.mDevCache.getJSONArray(key, defaultValue) + } + + override fun getEntity( + key: String?, + typeOfT: Type?, + defaultValue: T? + ): T? { + return mJSONEngine?.fromJson( + getString(key, null), typeOfT + ) ?: return defaultValue + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/compress/CompressConfig.kt b/lib/DevEngine/src/main/java/dev/engine/compress/CompressConfig.kt new file mode 100644 index 0000000000..d6424ddc2e --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/compress/CompressConfig.kt @@ -0,0 +1,36 @@ +package dev.engine.compress + +/** + * detail: Image Compress Config + * @author Ttt + */ +open class CompressConfig @JvmOverloads constructor( + // 单位 KB 默认 100 kb 以下不压缩 + val ignoreSize: Int, + // 是否保留透明通道 + val focusAlpha: Boolean = true, + // 压缩图片存储路径 + val targetDir: String? = null +) : ICompressEngine.EngineConfig() { + + // 压缩失败、异常是否结束压缩 + private var mFailFinish = false + + constructor(targetDir: String?) : this(100, true, targetDir) + + constructor( + ignoreSize: Int, + targetDir: String? + ) : this(ignoreSize, true, targetDir) + + // = + + fun isFailFinish(): Boolean { + return mFailFinish + } + + fun setFailFinish(failFinish: Boolean): CompressConfig { + mFailFinish = failFinish + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/compress/LubanUtils.kt b/lib/DevEngine/src/main/java/dev/engine/compress/LubanUtils.kt new file mode 100644 index 0000000000..90978f272e --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/compress/LubanUtils.kt @@ -0,0 +1,277 @@ +package dev.engine.compress + +import android.net.Uri +import dev.DevUtils +import dev.utils.common.ConvertUtils +import dev.utils.common.StringUtils +import top.zibin.luban.CompressionPredicate +import top.zibin.luban.InputStreamProvider +import top.zibin.luban.Luban +import top.zibin.luban.OnRenameListener +import java.io.File +import java.io.FileNotFoundException +import java.util.* + +/** + * detail: Luban 工具类 + * @author Ttt + * Luban 鲁班图片压缩 + * @see https://github.com/Curzibn/Luban + */ +internal object LubanUtils { + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 压缩方法 + * @param data 待压缩图片 + * @param config 配置信息 + * @param compressListener 压缩回调接口 + * @return `true` success, `false` fail + */ + fun compress( + data: Any?, + config: Config?, + compressListener: OnCompressListener? + ): Boolean { + return compress(data, config, null, null, compressListener) + } + + /** + * 压缩方法 + * @param data 待压缩图片 + * @param config 配置信息 + * @param predicate 开启压缩条件 + * @param renameListener 压缩前重命名接口 + * @param compressListener 压缩回调接口 + * @return `true` success, `false` fail + */ + fun compress( + data: Any?, + config: Config?, + predicate: CompressionPredicate?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? + ): Boolean { + if (data == null || config == null || compressListener == null) return false + return compress( + mutableListOf(data), config, + predicate, renameListener, compressListener + ) + } + + // = + + /** + * 压缩方法 + * @param lists 待压缩图片集合 + * @param config 配置信息 + * @param compressListener 压缩回调接口 + * @return `true` success, `false` fail + */ + fun compress( + lists: List<*>?, + config: Config?, + compressListener: OnCompressListener? + ): Boolean { + return compress(lists, config, null, null, compressListener) + } + + /** + * 压缩方法 + * @param lists 待压缩图片集合 + * @param config 配置信息 + * @param predicate 开启压缩条件 + * @param renameListener 压缩前重命名接口 + * @param compressListener 压缩回调接口 + * @return `true` success, `false` fail + */ + fun compress( + lists: List<*>?, + config: Config?, + predicate: CompressionPredicate?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? + ): Boolean { + if (lists == null || config == null || compressListener == null) return false + var number = 0 + val builder = Luban.with(DevUtils.getContext()) + for (src in lists) { + when (src) { + is File -> { + builder.load(src) + number++ + } + is Uri -> { + builder.load(src) + number++ + } + is InputStreamProvider -> { + builder.load(src) + number++ + } + else -> { + ConvertUtils.newStringNotArrayDecode(src)?.let { srcIt -> + builder.load(srcIt) + number++ + } + } + } + } + if (number == 0) return false + val count = number + val fileMaps = linkedMapOf() + // 配置信息 + builder.ignoreBy(config.ignoreSize) + .setFocusAlpha(config.focusAlpha) + .setTargetDir(config.targetDir) + .filter { path -> + if (predicate != null) return@filter predicate.apply(path) + return@filter !(StringUtils.isEmpty(path) || path.lowercase().endsWith(".gif")) + } + .setRenameListener(renameListener) + .setCompressListener(object : top.zibin.luban.OnCompressListener { + override fun onStart() { + val size = fileMaps.size + fileMaps[size] = null + compressListener.onStart(size, count) + } + + override fun onSuccess(file: File?) { + if (file == null) { + onError(NullPointerException("file is null")) + return + } + if (!file.exists()) { // 文件不存在则触发异常回调 + onError(FileNotFoundException(file.absolutePath)) + return + } + val size = fileMaps.size + val index = size - 1 + fileMaps[index] = file + compressListener.onSuccess(file, index, count) + if (size >= count) { + compressListener.onComplete(getLists(), fileMaps, count) + } + } + + override fun onError(e: Throwable) { + val size = fileMaps.size + compressListener.onError(e, size - 1, count) + if (size >= count) { + compressListener.onComplete(getLists(), fileMaps, count) + } + } + + private fun getLists(): List { + val files = mutableListOf() + val iterator = fileMaps.values.iterator() + while (iterator.hasNext()) { + val file = iterator.next() + if (file != null && file.exists()) { + files.add(file) + } + } + return files + } + }).launch() + return true + } + + // ======= + // = 配置 = + // ======= + + /** + * detail: Image Compress Config + * @author Ttt + */ + class Config @JvmOverloads constructor( + // 单位 KB 默认 100 kb 以下不压缩 + val ignoreSize: Int, + // 是否保留透明通道 + val focusAlpha: Boolean = true, + // 压缩图片存储路径 + val targetDir: String? = null + ) { + // 压缩失败、异常是否结束压缩 + private var mFailFinish = false + + constructor(targetDir: String?) : this(100, true, targetDir) + + constructor( + ignoreSize: Int, + targetDir: String? + ) : this(ignoreSize, true, targetDir) + + // = + + fun isFailFinish(): Boolean { + return mFailFinish + } + + fun setFailFinish(failFinish: Boolean): Config { + mFailFinish = failFinish + return this + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 压缩回调接口 + * @author Ttt + */ + interface OnCompressListener { + + /** + * 开始压缩前调用 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + fun onStart( + index: Int, + count: Int + ) + + /** + * 压缩成功后调用 + * @param file 压缩成功文件 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + fun onSuccess( + file: File?, + index: Int, + count: Int + ) + + /** + * 当压缩过程出现问题时触发 + * @param error 异常信息 + * @param index 当前压缩索引 + * @param count 压缩总数 + */ + fun onError( + error: Throwable, + index: Int, + count: Int + ) + + /** + * 压缩完成 ( 压缩结束 ) + * @param lists 压缩成功存储 List + * @param maps 每个索引对应压缩存储地址 + * @param count 压缩总数 + */ + fun onComplete( + lists: List, + maps: Map, + count: Int + ) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/compress/engine_luban.kt b/lib/DevEngine/src/main/java/dev/engine/compress/engine_luban.kt new file mode 100644 index 0000000000..53dabad618 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/compress/engine_luban.kt @@ -0,0 +1,101 @@ +package dev.engine.compress + +import dev.engine.compress.listener.CompressFilter +import dev.engine.compress.listener.OnCompressListener +import dev.engine.compress.listener.OnRenameListener +import top.zibin.luban.CompressionPredicate +import java.io.File + +/** + * detail: Luban Image Compress Engine 实现 + * @author Ttt + */ +open class LubanEngineImpl : ICompressEngine { + + override fun compress( + data: Any?, + config: CompressConfig?, + compressListener: OnCompressListener? + ): Boolean { + return compress(data, config, null, null, compressListener) + } + + override fun compress( + data: Any?, + config: CompressConfig?, + filter: CompressFilter?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? + ): Boolean { + if (data == null || config == null || compressListener == null) return false + return compress( + mutableListOf(data), config, + filter, renameListener, compressListener + ) + } + + override fun compress( + lists: MutableList<*>?, + config: CompressConfig?, + compressListener: OnCompressListener? + ): Boolean { + return compress(lists, config, null, null, compressListener) + } + + override fun compress( + lists: MutableList<*>?, + config: CompressConfig?, + filter: CompressFilter?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? + ): Boolean { + if (lists == null || config == null || compressListener == null) return false + var predicate: CompressionPredicate? = null + if (filter != null) { + predicate = CompressionPredicate { path: String? -> filter.apply(path) } + } + var rename: top.zibin.luban.OnRenameListener? = null + if (renameListener != null) { + rename = top.zibin.luban.OnRenameListener { filePath: String? -> + renameListener.rename(filePath) + } + } + return LubanUtils.compress( + lists, LubanUtils.Config( + config.ignoreSize, config.focusAlpha, config.targetDir + ).setFailFinish(config.isFailFinish()), predicate, rename, + object : LubanUtils.OnCompressListener { + override fun onStart( + index: Int, + count: Int + ) { + compressListener.onStart(index, count) + } + + override fun onSuccess( + file: File?, + index: Int, + count: Int + ) { + compressListener.onSuccess(file, index, count) + } + + override fun onError( + error: Throwable, + index: Int, + count: Int + ) { + compressListener.onError(error, index, count) + } + + override fun onComplete( + lists: List, + maps: Map, + count: Int + ) { + compressListener.onComplete(lists, maps, count) + } + } + ) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/image/ImageConfig.kt b/lib/DevEngine/src/main/java/dev/engine/image/ImageConfig.kt new file mode 100644 index 0000000000..fe22a6d9d1 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/image/ImageConfig.kt @@ -0,0 +1,286 @@ +package dev.engine.image + +import android.graphics.drawable.Drawable + +/** + * detail: Image Config + * @author Ttt + */ +open class ImageConfig private constructor( + config: ImageConfig? +) : IImageEngine.EngineConfig() { + + // 是否缓存到 SDCard + private var mCacheDisk = true + + // 是否缓存到内存 + private var mCacheMemory = true + + private var mScaleType = 0 + private var mTransform = 0 + private var mRoundedCornersRadius = 0 + + // placeholder + private var mErrorPlaceholder = NO_PLACE_HOLDER + private var mLoadingPlaceholder = NO_PLACE_HOLDER + private var mErrorDrawable: Drawable? = null + private var mLoadingDrawable: Drawable? = null + + // 加载图片宽 + private var mWidth = 0 + + // 加载图片高 + private var mHeight = 0 + + // 加载缩略图时应用尺寸的乘数 + private var mThumbnail = 0F + + // 图片保存质量 + private var mQuality = QUALITY + + // 转换符合格式文件是否原图返回 + private var mOriginalPathReturn = false + + // 是否不显示动画 + private var mDontAnimate = false + + // 是否移除所有 Transformation 效果 + private var mDontTransform = false + + // 额外扩展对象 + private var mOptions: Any? = null + + companion object { + + // scale type + const val SCALE_NONE = 0 + const val SCALE_CENTER_CROP = 1 + const val SCALE_FIT_CENTER = 2 + + // transform + const val TRANSFORM_NONE = 1 + const val TRANSFORM_CIRCLE = 2 + const val TRANSFORM_ROUNDED_CORNERS = 3 + + // placeholder + const val NO_PLACE_HOLDER = -1 + + // 默认图片保存质量值 + const val QUALITY = 80 + + fun create(): ImageConfig { + return ImageConfig(null) + } + + fun create(config: ImageConfig?): ImageConfig { + return ImageConfig(config) + } + } + + init { + config?.let { + // 是否缓存到 SDCard + this.mCacheDisk = it.mCacheDisk + // 是否缓存到内存 + this.mCacheMemory = it.mCacheMemory + + this.mScaleType = it.mScaleType + this.mTransform = it.mTransform + this.mRoundedCornersRadius = it.mRoundedCornersRadius + // placeholder + this.mErrorPlaceholder = it.mErrorPlaceholder + this.mLoadingPlaceholder = it.mLoadingPlaceholder + this.mErrorDrawable = it.mErrorDrawable + this.mLoadingDrawable = it.mLoadingDrawable + // 加载图片宽 + this.mWidth = it.mWidth + // 加载图片高 + this.mHeight = it.mHeight + // 加载缩略图时应用尺寸的乘数 + this.mThumbnail = it.mThumbnail + // 图片保存质量 + this.mQuality = it.mQuality + // 转换符合格式文件是否原图返回 + this.mOriginalPathReturn = it.mOriginalPathReturn + // 是否不显示动画 + this.mDontAnimate = it.mDontAnimate + // 是否移除所有 Transformation 效果 + this.mDontTransform = it.mDontTransform + // 额外扩展对象 + this.mOptions = it.mOptions + } + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 克隆配置信息 + * @return [ImageConfig] + */ + fun clone(): ImageConfig { + return ImageConfig(this) + } + + // =========== + // = get/set = + // =========== + + fun isCacheDisk(): Boolean { + return mCacheDisk + } + + fun setCacheDisk(cacheDisk: Boolean): ImageConfig { + mCacheDisk = cacheDisk + return this + } + + fun isCacheMemory(): Boolean { + return mCacheMemory + } + + fun setCacheMemory(cacheMemory: Boolean): ImageConfig { + mCacheMemory = cacheMemory + return this + } + + fun getScaleType(): Int { + return mScaleType + } + + /** + * @param scaleType [SCALE_CENTER_CROP]、[SCALE_FIT_CENTER] + */ + fun setScaleType(scaleType: Int): ImageConfig { + mScaleType = scaleType + return this + } + + fun getTransform(): Int { + return mTransform + } + + /** + * @param transform [TRANSFORM_ROUNDED_CORNERS]、[TRANSFORM_CIRCLE] + */ + fun setTransform(transform: Int): ImageConfig { + mTransform = transform + return this + } + + fun getRoundedCornersRadius(): Int { + return mRoundedCornersRadius + } + + fun setRoundedCornersRadius(roundedCornersRadius: Int): ImageConfig { + mRoundedCornersRadius = roundedCornersRadius + return this + } + + fun getErrorPlaceholder(): Int { + return mErrorPlaceholder + } + + fun setErrorPlaceholder(errorPlaceholder: Int): ImageConfig { + mErrorPlaceholder = errorPlaceholder + mErrorDrawable = null + return this + } + + fun getLoadingPlaceholder(): Int { + return mLoadingPlaceholder + } + + fun setLoadingPlaceholder(loadingPlaceholder: Int): ImageConfig { + mLoadingPlaceholder = loadingPlaceholder + mLoadingDrawable = null + return this + } + + fun getErrorDrawable(): Drawable? { + return mErrorDrawable + } + + fun setErrorDrawable(errorDrawable: Drawable?): ImageConfig { + mErrorPlaceholder = NO_PLACE_HOLDER + mErrorDrawable = errorDrawable + return this + } + + fun getLoadingDrawable(): Drawable? { + return mLoadingDrawable + } + + fun setLoadingDrawable(loadingDrawable: Drawable?): ImageConfig { + mLoadingPlaceholder = NO_PLACE_HOLDER + mLoadingDrawable = loadingDrawable + return this + } + + fun getWidth(): Int { + return mWidth + } + + fun getHeight(): Int { + return mHeight + } + + fun setSize( + width: Int, + height: Int + ): ImageConfig { + mWidth = width + mHeight = height + return this + } + + fun getThumbnail(): Float { + return mThumbnail + } + + fun setThumbnail(thumbnail: Float): ImageConfig { + mThumbnail = thumbnail + return this + } + + fun getQuality(): Int { + return mQuality + } + + fun setQuality(quality: Int) { + mQuality = quality + } + + fun isOriginalPathReturn(): Boolean { + return mOriginalPathReturn + } + + fun setOriginalPathReturn(originalPathReturn: Boolean) { + mOriginalPathReturn = originalPathReturn + } + + fun isDontAnimate(): Boolean { + return mDontAnimate + } + + fun setDontAnimate(dontAnimate: Boolean) { + mDontAnimate = dontAnimate + } + + fun isDontTransform(): Boolean { + return mDontTransform + } + + fun setDontTransform(dontTransform: Boolean) { + mDontTransform = dontTransform + } + + fun getOptions(): Any? { + return mOptions + } + + fun setOptions(options: Any?) { + mOptions = options + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/image/engine_glide.kt b/lib/DevEngine/src/main/java/dev/engine/image/engine_glide.kt new file mode 100644 index 0000000000..fa155650ac --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/image/engine_glide.kt @@ -0,0 +1,1084 @@ +package dev.engine.image + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import androidx.fragment.app.Fragment +import com.bumptech.glide.Glide +import com.bumptech.glide.RequestBuilder +import com.bumptech.glide.RequestManager +import com.bumptech.glide.load.MultiTransformation +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.FitCenter +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.target.ImageViewTarget +import com.bumptech.glide.request.transition.Transition +import dev.base.DevSource +import dev.engine.image.listener.ConvertStorage +import dev.engine.image.listener.LoadListener +import dev.engine.image.listener.OnConvertListener +import dev.utils.LogPrintUtils +import dev.utils.app.PathUtils +import dev.utils.app.image.ImageUtils +import dev.utils.common.FileUtils +import dev.utils.common.RandomUtils +import dev.utils.common.StreamUtils +import dev.utils.common.encrypt.MD5Utils +import java.io.File +import java.util.* + +/** + * detail: Glide Image Engine 实现 + * @author Ttt + * Glide 详细使用 + * @see https://www.jianshu.com/p/7cfe2653a1fb + * Glide 文档 + * @see https://muyangmin.github.io/glide-docs-cn + * 解决 Glide 加载图片闪烁的问题 ( 圆角处理 ) - transform(new RoundedCorners(xx)) + * @see https://blog.csdn.net/andcisco/article/details/96487800 + * 解决圆角 + centerCrop 效果叠加处理 + * transform(new MultiTransformation(new CenterCrop(), new RoundedCorners(xx))) + */ +open class GlideEngineImpl : IImageEngine { + + // 日志 TAG + private val TAG = GlideEngineImpl::class.java.simpleName + + // ==================== + // = pause and resume = + // ==================== + + override fun pause(fragment: Fragment?) { + fragment?.let { + Glide.with(it).pauseRequests() + } + } + + override fun resume(fragment: Fragment?) { + fragment?.let { + Glide.with(it).resumeRequests() + } + } + + override fun pause(context: Context?) { + context?.let { + Glide.with(it).pauseRequests() + } + } + + override fun resume(context: Context?) { + context?.let { + Glide.with(it).resumeRequests() + } + } + + // =========== + // = preload = + // =========== + + override fun preload( + context: Context?, + source: DevSource? + ) { + if (context != null && source != null) { + val requestManager = Glide.with(context) + setToRequest(requestManager, source)?.preload() + } + } + + override fun preload( + context: Context?, + source: DevSource?, + config: ImageConfig? + ) { + if (context != null && source != null && config != null) { + val requestManager = Glide.with(context) + setToRequest(requestManager, source)?.preload( + config.getWidth(), config.getHeight() + ) + } + } + + // ========= + // = clear = + // ========= + + override fun clear(view: View?) { + view?.context?.let { + Glide.with(view.context).clear(view) + } + } + + override fun clear( + fragment: Fragment?, + view: View? + ) { + if (fragment != null && view != null) { + Glide.with(fragment).clear(view) + } + } + + override fun clearDiskCache(context: Context?) { + if (context != null) { + Thread { + try { + // This method must be called on a background thread. + Glide.get(context).clearDiskCache() + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "clearDiskCache") + } + }.start() + } + } + + override fun clearMemoryCache(context: Context?) { + if (context != null) { + try { + // This method must be called on the main thread. + Glide.get(context).clearMemory() // 必须在主线程上调用该方法 + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "clearMemoryCache") + } + } + } + + override fun clearAllCache(context: Context?) { + clearDiskCache(context) + clearMemoryCache(context) + } + + // ========= + // = other = + // ========= + + override fun lowMemory(context: Context?) { + if (context != null) { + try { + Glide.get(context).onLowMemory() + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "lowMemory") + } + } + } + + // =========== + // = display = + // =========== + + override fun display( + imageView: ImageView?, + url: String? + ) { + display(imageView, DevSource.create(url), null) + } + + override fun display( + imageView: ImageView?, + source: DevSource? + ) { + display(imageView, source, null) + } + + override fun display( + imageView: ImageView?, + url: String?, + config: ImageConfig? + ) { + display(imageView, DevSource.create(url), config) + } + + override fun display( + imageView: ImageView?, + source: DevSource?, + config: ImageConfig? + ) { + if (imageView != null && imageView.context != null) { + val requestManager = Glide.with(imageView.context) + innerDisplayToRequestBuilder( + imageView, + setToRequest(requestManager, source), + config + ) + } + } + + // = + + override fun display( + imageView: ImageView?, + url: String?, + listener: LoadListener? + ) { + display(imageView, DevSource.create(url), null, listener) + } + + override fun display( + imageView: ImageView?, + source: DevSource?, + listener: LoadListener? + ) { + display(imageView, source, null, listener) + } + + override fun display( + imageView: ImageView?, + url: String?, + config: ImageConfig?, + listener: LoadListener? + ) { + display(imageView, DevSource.create(url), config, listener) + } + + override fun display( + imageView: ImageView?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + if (imageView != null && imageView.context != null) { + val requestManager = Glide.with(imageView.context) + innerDisplayToRequestBuilder( + imageView, + setToRequest(requestManager, source), + config, + source, + listener + ) + } + } + + // = + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + url: String? + ) { + display(fragment, imageView, DevSource.create(url), null) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + source: DevSource? + ) { + display(fragment, imageView, source, null) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + url: String?, + config: ImageConfig? + ) { + display(fragment, imageView, DevSource.create(url), config) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + source: DevSource?, + config: ImageConfig? + ) { + if (fragment != null && imageView != null) { + if (canFragmentLoadImage(fragment)) { + val requestManager = Glide.with(fragment) + innerDisplayToRequestBuilder( + imageView, + setToRequest(requestManager, source), + config + ) + } + } + } + + // = + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + url: String?, + listener: LoadListener? + ) { + display(fragment, imageView, DevSource.create(url), null, listener) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + source: DevSource?, + listener: LoadListener? + ) { + display(fragment, imageView, source, null, listener) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + url: String?, + config: ImageConfig?, + listener: LoadListener? + ) { + display(fragment, imageView, DevSource.create(url), config, listener) + } + + override fun display( + fragment: Fragment?, + imageView: ImageView?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + if (fragment != null && imageView != null) { + if (canFragmentLoadImage(fragment)) { + val requestManager = Glide.with(fragment) + innerDisplayToRequestBuilder( + imageView, + setToRequest(requestManager, source), + config, + source, + listener + ) + } + } + } + + // ======== + // = load = + // ======== + + override fun loadImage( + context: Context?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + if (context != null && source != null && listener != null && listener.transcodeType != null) { + val requestManager = Glide.with(context) + val type = listener.transcodeType + if (type == Drawable::class.java) { + val request = setToRequest( + requestManager.asDrawable(), source + ) + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerDrawableTarget( + source, listener as LoadListener + ) + ) + } + } else if (type == Bitmap::class.java) { + val request = setToRequest( + requestManager.asBitmap(), source + ) + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerBitmapTarget( + source, listener as LoadListener + ) + ) + } + } + } + } + + override fun loadImage( + fragment: Fragment?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + if (fragment != null && source != null && listener != null && listener.transcodeType != null) { + val requestManager = Glide.with(fragment) + val type = listener.transcodeType + if (type == Drawable::class.java) { + val request = setToRequest( + requestManager.asDrawable(), source + ) + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerDrawableTarget( + source, listener as LoadListener + ) + ) + } + } else if (type == Bitmap::class.java) { + val request = setToRequest( + requestManager.asBitmap(), source + ) + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerBitmapTarget( + source, listener as LoadListener + ) + ) + } + } + } + } + + override fun loadImage( + context: Context?, + source: DevSource?, + config: ImageConfig?, + type: Class<*>? + ): T? { + try { + return loadImageThrows(context, source, config, type) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "loadImage") + } + return null + } + + override fun loadImageThrows( + context: Context?, + source: DevSource?, + config: ImageConfig?, + type: Class<*>? + ): T? { + if (context != null && source != null && type != null) { + val requestManager = Glide.with(context) + if (type == Drawable::class.java) { + val request = setToRequest( + requestManager.asDrawable(), source + ) + buildRequest(request, config) + return if (config != null && config.getWidth() > 0 && config.getHeight() > 0) { + try { + request!!.submit( + config.getWidth(), config.getHeight() + ).get() as T + } catch (e: Exception) { + throw e + } + } else { + try { + request!!.submit().get() as T + } catch (e: Exception) { + throw e + } + } + } else if (type == Bitmap::class.java) { + val request = setToRequest( + requestManager.asBitmap(), source + ) + buildRequest(request, config) + return if (config != null && config.getWidth() > 0 && config.getHeight() > 0) { + try { + request!!.submit( + config.getWidth(), config.getHeight() + ).get() as T + } catch (e: Exception) { + throw e + } + } else { + try { + request!!.submit().get() as T + } catch (e: Exception) { + throw e + } + } + } + } + return null + } + + // = + + override fun loadBitmap( + context: Context?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + loadImage(context, source, config, listener) + } + + override fun loadBitmap( + fragment: Fragment?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + loadImage(fragment, source, config, listener) + } + + override fun loadBitmap( + context: Context?, + source: DevSource?, + config: ImageConfig? + ): Bitmap? { + return loadImage(context, source, config, Bitmap::class.java) + } + + override fun loadBitmapThrows( + context: Context?, + source: DevSource?, + config: ImageConfig? + ): Bitmap? { + return loadImageThrows(context, source, config, Bitmap::class.java) + } + + // = + + override fun loadDrawable( + context: Context?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + loadImage(context, source, config, listener) + } + + override fun loadDrawable( + fragment: Fragment?, + source: DevSource?, + config: ImageConfig?, + listener: LoadListener? + ) { + loadImage(fragment, source, config, listener) + } + + override fun loadDrawable( + context: Context?, + source: DevSource?, + config: ImageConfig? + ): Drawable? { + return loadImage(context, source, config, Drawable::class.java) + } + + override fun loadDrawableThrows( + context: Context?, + source: DevSource?, + config: ImageConfig? + ): Drawable? { + return loadImageThrows(context, source, config, Drawable::class.java) + } + + // =========== + // = convert = + // =========== + + override fun convertImageFormat( + context: Context?, + sources: MutableList?, + listener: OnConvertListener? + ): Boolean { + return convertImageFormat(context, sources, null, listener) + } + + override fun convertImageFormat( + context: Context?, + sources: MutableList?, + config: ImageConfig?, + listener: OnConvertListener? + ): Boolean { + return innerConvertImageFormat(context, sources, config, listener) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * Fragment 是否能够用于加载图片 + * @param fragment [Fragment] + * @return `true` yes, `false` no + */ + private fun canFragmentLoadImage(fragment: Fragment): Boolean { + return fragment.isResumed || fragment.isAdded || fragment.isVisible + } + + /** + * 通过 [DevSource] 设置 [RequestBuilder] 加载 source + * @param manager [RequestManager] + * @param source [DevSource] + * @return [RequestBuilder] + */ + private fun setToRequest( + manager: RequestManager?, + source: DevSource? + ): RequestBuilder<*>? { + return if (manager != null && source != null) { + when { + source.mFile != null -> { + manager.load(source.mFile) + } + source.mUrl != null -> { + manager.load(source.mUrl) + } + source.mResource != 0 -> { + manager.load(source.mResource) + } + source.mUri != null -> { + manager.load(source.mUri) + } + source.mBytes != null -> { + manager.load(source.mBytes) + } + source.mInputStream != null -> { + val bytes = StreamUtils.inputStreamToBytes(source.mInputStream) + if (bytes != null) { + manager.load(bytes) + } else { + null + } + } + source.mDrawable != null -> { + manager.load(source.mDrawable) + } + source.mBitmap != null -> { + manager.load(source.mBitmap) + } + else -> { + throw IllegalArgumentException("UnSupport source") + } + } + } else null + } + + /** + * 通过 [DevSource] 设置 [RequestBuilder] 加载 source + * @param request [RequestBuilder] + * @param source [DevSource] + * @param 泛型 ( 如 Drawable、Bitmap ) + * @return [RequestBuilder] + */ + private fun setToRequest( + request: RequestBuilder?, + source: DevSource? + ): RequestBuilder? { + return if (request != null && source != null) { + when { + source.mFile != null -> { + request.load(source.mFile) + } + source.mUrl != null -> { + request.load(source.mUrl) + } + source.mResource != 0 -> { + request.load(source.mResource) + } + source.mUri != null -> { + request.load(source.mUri) + } + source.mBytes != null -> { + request.load(source.mBytes) + } + source.mInputStream != null -> { + val bytes = StreamUtils.inputStreamToBytes(source.mInputStream) + if (bytes != null) { + request.load(bytes) + } else { + null + } + } + source.mDrawable != null -> { + request.load(source.mDrawable) + } + source.mBitmap != null -> { + request.load(source.mBitmap) + } + else -> { + throw IllegalArgumentException("UnSupport source") + } + } + } else null + } + + /** + * 通过 [ImageConfig] 构建 [RequestOptions] + * @param config [ImageConfig] + * @return [RequestOptions] + */ + private fun buildRequestOptions(config: ImageConfig?): RequestOptions { + if (config?.getOptions() is RequestOptions) { + return config.getOptions() as RequestOptions + } + var options = RequestOptions() + config?.let { config -> + + // ============ + // = 初始化配置 = + // ============ + + // DiskCache + options = if (config.isCacheDisk()) { + options.diskCacheStrategy(DiskCacheStrategy.ALL) + } else { + options.diskCacheStrategy(DiskCacheStrategy.NONE) + } + + // MemoryCache + options = if (config.isCacheMemory()) { + options.skipMemoryCache(false) + } else { + options.skipMemoryCache(true) + } + + // scale type + if (config.getScaleType() == ImageConfig.SCALE_CENTER_CROP) { + options = options.centerCrop() + } else if (config.getScaleType() == ImageConfig.SCALE_FIT_CENTER) { + options = options.fitCenter() + } + + // transform + if (config.getTransform() == ImageConfig.TRANSFORM_CIRCLE) { + options = options.circleCrop() + } else if (config.getTransform() == ImageConfig.TRANSFORM_ROUNDED_CORNERS) { + if (config.getScaleType() == ImageConfig.SCALE_NONE) { + options = options.transform(RoundedCorners(config.getRoundedCornersRadius())) + } else if (config.getScaleType() == ImageConfig.SCALE_CENTER_CROP) { + options = options.transform( + MultiTransformation( + CenterCrop(), + RoundedCorners(config.getRoundedCornersRadius()) + ) + ) + } else if (config.getScaleType() == ImageConfig.SCALE_FIT_CENTER) { + options = options.transform( + MultiTransformation( + FitCenter(), + RoundedCorners(config.getRoundedCornersRadius()) + ) + ) + } + } else if (config.getTransform() == ImageConfig.TRANSFORM_NONE) { + options = options.dontTransform() // 不做渐入渐出转换 + } + + // placeholder + if (config.getErrorPlaceholder() != ImageConfig.NO_PLACE_HOLDER) { + options = options.error(config.getErrorPlaceholder()) + } + + if (config.getErrorDrawable() != null) { + options = options.error(config.getErrorDrawable()) + } + + if (config.getLoadingPlaceholder() != ImageConfig.NO_PLACE_HOLDER) { + options = options.placeholder(config.getLoadingPlaceholder()) + } + + if (config.getLoadingDrawable() != null) { + options = options.placeholder(config.getLoadingDrawable()) + } + + // width、height + if (config.getWidth() > 0 && config.getHeight() > 0) { + options = options.override(config.getWidth(), config.getHeight()) + } + + if (config.isDontAnimate()) { + options = options.dontAnimate() + } + + if (config.isDontTransform()) { + options = options.dontTransform() + } + } + return options + } + + /** + * 通过 [ImageConfig] 构建 [RequestBuilder] + * @param request [RequestBuilder] + * @param config [ImageConfig] + * @return [RequestBuilder] + */ + private fun buildRequest( + request: RequestBuilder?, + config: ImageConfig? + ): RequestBuilder<*>? { + request?.let { + val options = buildRequestOptions(config) + val req = it.apply(options) + config?.let { config -> + if (config.getThumbnail() > 0F) { + return req.thumbnail(config.getThumbnail()) + } + } + return req + } + return request + } + + // =================== + // = 内部 Display 方法 = + // =================== + + /** + * 通过 [RequestBuilder] 与 [ImageConfig] 快捷显示方法 + * @param imageView [ImageView] + * @param request [RequestBuilder] + * @param config [ImageConfig] + */ + private fun innerDisplayToRequestBuilder( + imageView: ImageView?, + request: RequestBuilder<*>?, + config: ImageConfig? + ) { + if (imageView != null && request != null) { + buildRequest(request, config)?.into(imageView) + } + } + + /** + * 通过 [RequestBuilder] 与 [ImageConfig] 快捷显示方法 + * @param imageView [ImageView] + * @param request [RequestBuilder] + * @param config [ImageConfig] + * @param source [DevSource] + * @param listener [LoadListener] + */ + private fun innerDisplayToRequestBuilder( + imageView: ImageView?, + request: RequestBuilder<*>?, + config: ImageConfig?, + source: DevSource?, + listener: LoadListener? + ) { + if (imageView != null && request != null && listener != null && listener.transcodeType != null) { + val type = listener.transcodeType + if (type == Drawable::class.java) { + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerDrawableViewTarget( + imageView, source, listener as LoadListener + ) + ) + } + } else if (type == Bitmap::class.java) { + buildRequest(request, config)?.let { + (it as RequestBuilder).into( + InnerBitmapViewTarget( + imageView, source, listener as LoadListener + ) + ) + } + } + } + } + + // ============= + // = 内部加载事件 = + // ============= + + private class InnerDrawableViewTarget( + view: ImageView?, + private val mSource: DevSource?, + private val mListener: LoadListener + ) : ImageViewTarget(view) { + + override fun setResource(resource: Drawable?) { + getView().setImageDrawable(resource) + } + + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + super.onResourceReady(resource, transition) + mListener.onResponse(mSource, resource) + } + + override fun onLoadStarted(placeholder: Drawable?) { + mListener.onStart(mSource) + super.onLoadStarted(placeholder) + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + mListener.onFailure(mSource, Exception("Load Failed")) + } + } + + private class InnerBitmapViewTarget( + view: ImageView?, + private val mSource: DevSource?, + private val mListener: LoadListener + ) : ImageViewTarget(view) { + + override fun setResource(resource: Drawable?) { + getView().setImageDrawable(resource) + } + + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + super.onResourceReady(resource, transition) + mListener.onResponse(mSource, ImageUtils.drawableToBitmap(resource)) + } + + override fun onLoadStarted(placeholder: Drawable?) { + mListener.onStart(mSource) + super.onLoadStarted(placeholder) + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + mListener.onFailure(mSource, Exception("Load Failed")) + } + } + + // = + + private class InnerDrawableTarget( + private val mSource: DevSource, + private val mListener: LoadListener + ) : CustomTarget() { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + mListener.onResponse(mSource, resource) + } + + override fun onLoadStarted(placeholder: Drawable?) { + mListener.onStart(mSource) + super.onLoadStarted(placeholder) + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + mListener.onFailure(mSource, Exception("Load Failed")) + } + + override fun onLoadCleared(placeholder: Drawable?) {} + } + + private class InnerBitmapTarget( + private val mSource: DevSource, + private val mListener: LoadListener + ) : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + mListener.onResponse(mSource, resource) + } + + override fun onLoadStarted(placeholder: Drawable?) { + mListener.onStart(mSource) + super.onLoadStarted(placeholder) + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + mListener.onFailure(mSource, Exception("Load Failed")) + } + + override fun onLoadCleared(placeholder: Drawable?) {} + } + + // ================== + // = 转换图片格式并存储 = + // ================== + + /** + * 私有转换图片格式处理方法 + * @param context [Context] + * @param sources 待转换资源集合 + * @param config 配置信息 + * @param listener 回调事件 + * @return `true` success, `false` fail + */ + private fun innerConvertImageFormat( + context: Context?, + sources: List?, + config: ImageConfig?, + listener: OnConvertListener? + ): Boolean { + if (context != null && sources != null && listener != null && sources.isNotEmpty()) { + val fileLists = mutableListOf() + val fileMaps = linkedMapOf() + // 转换器 + val convertStorage = InnerConvertStorage(this) + // 随机创建任务 id + val randomTask = RandomUtils.getRandom(1000000, 10000000) + val task = randomTask.toString() + // 循环转存 + val len = sources.size + for (i in 0 until len) { + var result: File? = null + try { + listener.onStart(i, len) + result = convertStorage.convert(context, sources[i], config, i, len, task) + if (result == null || !result.exists()) { + throw Exception("result file is null or not exists") + } + } catch (e: Exception) { + listener.onError(e, i, len) + } + if (result != null && result.exists()) { + listener.onSuccess(result, i, len) + fileLists.add(result) + } + fileMaps[i] = result + } + listener.onComplete(fileLists, fileMaps, len) + } + return false + } + + private class InnerConvertStorage( + private val engineImpl: GlideEngineImpl + ) : ConvertStorage { + override fun convert( + context: Context?, + source: DevSource?, + config: ImageConfig?, + index: Int, + count: Int, + task: String? + ): File? { + if (source == null) throw Exception("source is null") + // 属于文件, 判断是否符合指定格式 + if (source.isFile) { + // 符合条件直接返回 + if (FileUtils.isImageFormats( + source.mFile.absolutePath, arrayOf(".PNG", ".JPG", ".JPEG") + ) + ) { + // 配置为 null 或要求原路径返回 + if (config == null || config.isOriginalPathReturn()) { + return source.mFile + } + } + } + val readBitmap = engineImpl.loadBitmapThrows(context, source, config) + // 创建随机名 ( 一定程度上唯一, 防止出现重复情况 ) + val randomName = String.format( + "%s_%s_%s_%s_%s", task, UUID.randomUUID().hashCode(), + System.currentTimeMillis(), index, count + ) + // convert_task_index_md5.png + val md5FileName = String.format("c_%s_%s_%s.png", task, index, MD5Utils.md5(randomName)) + // 存储到外部存储私有目录 ( /storage/emulated/0/Android/data/package/ ) + val dirPath = PathUtils.getAppExternal().getAppCachePath("convertStorage") + // 图片保存质量 + var quality = ImageConfig.QUALITY + if (config != null) { + if (config.getQuality() in 1..100) { + quality = config.getQuality() + } + } + // 创建文件夹 + FileUtils.createFolder(dirPath) + val resultFile = File(dirPath, md5FileName) + // 保存图片 + val result = ImageUtils.saveBitmapToSDCard( + readBitmap, resultFile, Bitmap.CompressFormat.PNG, quality + ) + return if (result) resultFile else null + } + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/json/FastjsonUtils.kt b/lib/DevEngine/src/main/java/dev/engine/json/FastjsonUtils.kt new file mode 100644 index 0000000000..ff34129898 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/json/FastjsonUtils.kt @@ -0,0 +1,234 @@ +package dev.engine.json + +import com.alibaba.fastjson2.JSON +import com.alibaba.fastjson2.JSONArray +import com.alibaba.fastjson2.JSONObject +import com.alibaba.fastjson2.JSONWriter +import com.alibaba.fastjson2.util.ParameterizedTypeImpl +import dev.utils.JCLogUtils +import java.lang.reflect.GenericArrayType +import java.lang.reflect.Type + +/** + * detail: Fastjson 工具类 + * @author Ttt + */ +internal object FastjsonUtils { + + // 日志 TAG + private val TAG = FastjsonUtils::class.java.simpleName + + // ========== + // = 转换方法 = + // ========== + + /** + * 将对象转换为 JSON String + * @param obj [Object] + * @return JSON String + */ + fun toJson(obj: Any?): String? { + if (obj != null) { + try { + return JSON.toJSONString(obj) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJson") + } + } + return null + } + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT [Class] T + * @return instance of type + */ + fun fromJson( + json: String?, + classOfT: Class? + ): T? { + if (json != null) { + try { + return JSON.parseObject(json, classOfT) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "fromJson") + } + } + return null + } + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param typeOfT [Type] T + * @return instance of type + */ + fun fromJson( + json: String?, + typeOfT: Type? + ): T? { + if (json != null) { + try { + return JSON.parseObject(json, typeOfT) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "fromJson") + } + } + return null + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 判断字符串是否 JSON 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSON(json: String?): Boolean { + try { + val obj = JSON.parse(json) + return obj != null + } catch (e: Exception) { + return false + } + } + + /** + * 判断字符串是否 JSON Object 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSONObject(json: String?): Boolean { + try { + val obj = JSON.parse(json) + return obj is JSONObject + } catch (ignored: Exception) { + } + return false + } + + /** + * 判断字符串是否 JSON Array 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSONArray(json: String?): Boolean { + try { + val obj = JSON.parse(json) + return obj is JSONArray + } catch (ignored: Exception) { + } + return false + } + + // = + + /** + * JSON String 缩进处理 + * @param json JSON String + * @return JSON String + */ + fun toJsonIndent(json: String?): String? { + if (json != null) { + try { + // 保持 JSON 字符串次序 + val obj = JSON.parse(json) + return JSON.toJSONString( + obj, JSONWriter.Feature.PrettyFormat + ) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJsonIndent") + } + } + return null + } + + /** + * Object 转 JSON String 并进行缩进处理 + * @param obj [Object] + * @return JSON String + */ + fun toJsonIndent(obj: Any?): String? { + if (obj != null) { + try { + return toJsonIndent(toJson(obj)) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJsonIndent") + } + } + return null + } + + // ======== + // = Type = + // ======== + + /** + * 获取 Array Type + * @param type Bean.class + * @return Bean[] Type + */ + fun getArrayType(type: Type): Type { + return GenericArrayType { type } + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + fun getListType(type: Type): Type { + return ParameterizedTypeImpl( + arrayOf(type), null, + List::class.java + ) + } + + /** + * 获取 Set Type + * @param type Bean.class + * @return Set Type + */ + fun getSetType(type: Type): Type { + return ParameterizedTypeImpl( + arrayOf(type), null, + Set::class.java + ) + } + + /** + * 获取 Map Type + * @param keyType Key.class + * @param valueType Value.class + * @return Map Type + */ + fun getMapType( + keyType: Type?, + valueType: Type? + ): Type { + return ParameterizedTypeImpl( + arrayOf(keyType, valueType), null, + Map::class.java + ) + } + + /** + * 获取 Type + * @param rawType raw type + * @param typeArguments type arguments + * @return Type + */ + fun getType( + rawType: Type?, + vararg typeArguments: Type? + ): Type { + return ParameterizedTypeImpl(typeArguments, null, rawType) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/json/GsonUtils.kt b/lib/DevEngine/src/main/java/dev/engine/json/GsonUtils.kt new file mode 100644 index 0000000000..e329ca64ca --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/json/GsonUtils.kt @@ -0,0 +1,333 @@ +package dev.engine.json + +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.JsonParser +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import dev.utils.JCLogUtils +import java.io.StringReader +import java.lang.reflect.Modifier +import java.lang.reflect.Type + +/** + * detail: Gson 工具类 + * @author Ttt + * Gson 详细使用 + * @see https://www.jianshu.com/p/d62c2be60617 + */ +internal object GsonUtils { + + // 日志 TAG + private val TAG = GsonUtils::class.java.simpleName + + // Object 转 JSON 字符串 + private val TO_GSON = createGson(true).create() + + // JSON 字符串转 T Object + private val FROM_GSON = createGson(true).create() + + // JSON 缩进 + private val INDENT_GSON = createGson(true).setPrettyPrinting().create() + + // ========== + // = 转换方法 = + // ========== + + /** + * 将对象转换为 JSON String + * @param obj [Object] + * @return JSON String + */ + fun toJson(obj: Any?): String? { + return toJson(obj, TO_GSON) + } + + /** + * 将对象转换为 JSON String + * @param obj [Object] + * @param gson [Gson] + * @return JSON String + */ + fun toJson( + obj: Any?, + gson: Gson? + ): String? { + if (gson != null) { + try { + return gson.toJson(obj) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJson") + } + } + return null + } + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT [Class] T + * @return instance of type + */ + fun fromJson( + json: String?, + classOfT: Class? + ): T? { + return fromJson(json, classOfT, FROM_GSON) + } + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT [Class] T + * @param gson [Gson] + * @return instance of type + */ + fun fromJson( + json: String?, + classOfT: Class?, + gson: Gson? + ): T? { + if (gson != null) { + try { + return gson.fromJson(json, classOfT) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "fromJson") + } + } + return null + } + + // = + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param typeOfT [Type] T + * @return instance of type + */ + fun fromJson( + json: String?, + typeOfT: Type? + ): T? { + return fromJson(json, typeOfT, FROM_GSON) + } + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param typeOfT [Type] T + * @param gson [Gson] + * @return instance of type + */ + fun fromJson( + json: String?, + typeOfT: Type?, + gson: Gson? + ): T? { + if (gson != null) { + try { + return gson.fromJson(json, typeOfT) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "fromJson") + } + } + return null + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 判断字符串是否 JSON 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSON(json: String?): Boolean { + try { + val jsonElement = JsonParser.parseString(json) ?: return false + return jsonElement.isJsonObject || jsonElement.isJsonArray + } catch (ignored: Exception) { + } + return false + } + + /** + * 判断字符串是否 JSON Object 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSONObject(json: String?): Boolean { + try { + val jsonElement = JsonParser.parseString(json) ?: return false + return jsonElement.isJsonObject + } catch (ignored: Exception) { + } + return false + } + + /** + * 判断字符串是否 JSON Array 格式 + * @param json 待校验 JSON String + * @return `true` yes, `false` no + */ + fun isJSONArray(json: String?): Boolean { + try { + val jsonElement = JsonParser.parseString(json) ?: return false + return jsonElement.isJsonArray + } catch (ignored: Exception) { + } + return false + } + + /** + * JSON String 缩进处理 + * @param json JSON String + * @return JSON String + */ + fun toJsonIndent(json: String?): String? { + return toJsonIndent(json, INDENT_GSON) + } + + /** + * JSON String 缩进处理 + * @param json JSON String + * @param gson [Gson] + * @return JSON String + */ + fun toJsonIndent( + json: String?, + gson: Gson? + ): String? { + if (gson != null) { + try { + val reader = JsonReader(StringReader(json)) + reader.isLenient = true + val jsonElement = JsonParser.parseReader(reader) + return gson.toJson(jsonElement) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJsonIndent") + } + } + return null + } + + // = + + /** + * Object 转 JSON String 并进行缩进处理 + * @param obj [Object] + * @return JSON String + */ + fun toJsonIndent(obj: Any?): String? { + return toJsonIndent(obj, INDENT_GSON) + } + + /** + * Object 转 JSON String 并进行缩进处理 + * @param obj [Object] + * @param gson [Gson] + * @return JSON String + */ + fun toJsonIndent( + obj: Any?, + gson: Gson? + ): String? { + if (gson != null) { + try { + return gson.toJson(obj) + } catch (e: Exception) { + JCLogUtils.eTag(TAG, e, "toJsonIndent") + } + } + return null + } + + // ======== + // = Gson = + // ======== + + /** + * 创建 GsonBuilder + * @param serializeNulls 是否序列化 null 值 + * @return [GsonBuilder] + */ + fun createGson(serializeNulls: Boolean): GsonBuilder { + val builder = GsonBuilder() + if (serializeNulls) builder.serializeNulls() + return builder + } + + /** + * 创建过滤指定修饰符字段 GsonBuilder + * @param builder [GsonBuilder] + * @param modifiers 需过滤不处理的字段修饰符 [Modifier] + * @return [GsonBuilder] + */ + fun createGsonExcludeFields( + builder: GsonBuilder?, + vararg modifiers: Int + ): GsonBuilder? { + return builder?.excludeFieldsWithModifiers(*modifiers) + } + + // ======== + // = Type = + // ======== + + /** + * 获取 Array Type + * @param type Bean.class + * @return Bean[] Type + */ + fun getArrayType(type: Type?): Type? { + return TypeToken.getArray(type).type + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + fun getListType(type: Type?): Type? { + return TypeToken.getParameterized(List::class.java, type).type + } + + /** + * 获取 Set Type + * @param type Bean.class + * @return Set Type + */ + fun getSetType(type: Type?): Type? { + return TypeToken.getParameterized(Set::class.java, type).type + } + + /** + * 获取 Map Type + * @param keyType Key.class + * @param valueType Value.class + * @return Map, Value> Type + */ + fun getMapType( + keyType: Type?, + valueType: Type? + ): Type? { + return TypeToken.getParameterized(Map::class.java, keyType, valueType).type + } + + /** + * 获取 Type + * @param rawType raw type + * @param typeArguments type arguments + * @return Type + */ + fun getType( + rawType: Type?, + vararg typeArguments: Type? + ): Type? { + return TypeToken.getParameterized(rawType, *typeArguments).type + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/json/JSONConfig.kt b/lib/DevEngine/src/main/java/dev/engine/json/JSONConfig.kt new file mode 100644 index 0000000000..d864f8f099 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/json/JSONConfig.kt @@ -0,0 +1,12 @@ +package dev.engine.json + +import com.google.gson.Gson + +/** + * detail: JSON Config + * @author Ttt + */ +open class JSONConfig : IJSONEngine.EngineConfig() { + @JvmField + var gson: Gson? = null +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/json/engine_fastjson.kt b/lib/DevEngine/src/main/java/dev/engine/json/engine_fastjson.kt new file mode 100644 index 0000000000..4e69283157 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/json/engine_fastjson.kt @@ -0,0 +1,93 @@ +package dev.engine.json + +import java.lang.reflect.Type + +/** + * detail: Fastjson JSON Engine 实现 + * @author Ttt + */ +open class FastjsonEngineImpl : IJSONEngine { + + // ========== + // = 转换方法 = + // ========== + + override fun toJson(obj: Any?): String? { + return FastjsonUtils.toJson(obj) + } + + override fun toJson( + obj: Any?, + config: JSONConfig? + ): String? { + return FastjsonUtils.toJson(obj) + } + + override fun fromJson( + json: String?, + classOfT: Class? + ): T? { + return FastjsonUtils.fromJson(json, classOfT) + } + + override fun fromJson( + json: String?, + classOfT: Class?, + config: JSONConfig? + ): T? { + return FastjsonUtils.fromJson(json, classOfT) + } + + override fun fromJson( + json: String?, + typeOfT: Type? + ): T? { + return FastjsonUtils.fromJson(json, typeOfT) + } + + override fun fromJson( + json: String?, + typeOfT: Type?, + config: JSONConfig? + ): T? { + return FastjsonUtils.fromJson(json, typeOfT) + } + + // ========== + // = 其他方法 = + // ========== + + override fun isJSON(json: String?): Boolean { + return FastjsonUtils.isJSON(json) + } + + override fun isJSONObject(json: String?): Boolean { + return FastjsonUtils.isJSONObject(json) + } + + override fun isJSONArray(json: String?): Boolean { + return FastjsonUtils.isJSONArray(json) + } + + override fun toJsonIndent(json: String?): String? { + return FastjsonUtils.toJsonIndent(json) + } + + override fun toJsonIndent( + json: String?, + config: JSONConfig? + ): String? { + return FastjsonUtils.toJsonIndent(json) + } + + override fun toJsonIndent(obj: Any?): String? { + return FastjsonUtils.toJsonIndent(obj) + } + + override fun toJsonIndent( + obj: Any?, + config: JSONConfig? + ): String? { + return FastjsonUtils.toJsonIndent(obj) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/json/engine_gson.kt b/lib/DevEngine/src/main/java/dev/engine/json/engine_gson.kt new file mode 100644 index 0000000000..ca131824c4 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/json/engine_gson.kt @@ -0,0 +1,93 @@ +package dev.engine.json + +import java.lang.reflect.Type + +/** + * detail: Gson JSON Engine 实现 + * @author Ttt + */ +open class GsonEngineImpl : IJSONEngine { + + // ========== + // = 转换方法 = + // ========== + + override fun toJson(obj: Any?): String? { + return GsonUtils.toJson(obj) + } + + override fun toJson( + obj: Any?, + config: JSONConfig? + ): String? { + return GsonUtils.toJson(obj, config?.gson) + } + + override fun fromJson( + json: String?, + classOfT: Class? + ): T? { + return GsonUtils.fromJson(json, classOfT) + } + + override fun fromJson( + json: String?, + classOfT: Class?, + config: JSONConfig? + ): T? { + return GsonUtils.fromJson(json, classOfT, config?.gson) + } + + override fun fromJson( + json: String?, + typeOfT: Type? + ): T? { + return GsonUtils.fromJson(json, typeOfT) + } + + override fun fromJson( + json: String?, + typeOfT: Type?, + config: JSONConfig? + ): T? { + return GsonUtils.fromJson(json, typeOfT, config?.gson) + } + + // ========== + // = 其他方法 = + // ========== + + override fun isJSON(json: String?): Boolean { + return GsonUtils.isJSON(json) + } + + override fun isJSONObject(json: String?): Boolean { + return GsonUtils.isJSONObject(json) + } + + override fun isJSONArray(json: String?): Boolean { + return GsonUtils.isJSONArray(json) + } + + override fun toJsonIndent(json: String?): String? { + return GsonUtils.toJsonIndent(json) + } + + override fun toJsonIndent( + json: String?, + config: JSONConfig? + ): String? { + return GsonUtils.toJsonIndent(json, config?.gson) + } + + override fun toJsonIndent(obj: Any?): String? { + return GsonUtils.toJsonIndent(obj) + } + + override fun toJsonIndent( + obj: Any?, + config: JSONConfig? + ): String? { + return GsonUtils.toJsonIndent(obj, config?.gson) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/keyvalue/MMKVUtils.kt b/lib/DevEngine/src/main/java/dev/engine/keyvalue/MMKVUtils.kt new file mode 100644 index 0000000000..0a631d4927 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/keyvalue/MMKVUtils.kt @@ -0,0 +1,431 @@ +package dev.engine.keyvalue + +import android.content.Context +import android.os.Parcelable +import com.tencent.mmkv.MMKV +import com.tencent.mmkv.MMKVLogLevel +import dev.engine.keyvalue.MMKVUtils.defaultHolder +import dev.engine.keyvalue.MMKVUtils.get +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.StringUtils +import java.util.* +import kotlin.collections.set + +/** + * detail: MMKV 工具类 + * @author Ttt + * 支持组件化 module 存储、以及默认通用 mmkv 对象 + * 基于 mmap 的高性能通用 key-value 组件 + * @see https://github.com/Tencent/MMKV/blob/master/README_CN.md + * Google 再见 SharedPreferences 拥抱 Jetpack DataStore + * @see https://juejin.im/post/6881442312560803853 + * Google 再见 SharedPreferences 拥抱 Jetpack DataStore + * @see https://juejin.im/post/6888847647802097672 + * 使用: + * [defaultHolder].encode/decodeXxx + * [get].encode/decodeXxx + */ +internal object MMKVUtils { + + // 日志 TAG + private val TAG = MMKVUtils::class.java.simpleName + + // 持有类存储 Key-Holder + private val HOLDER_MAP: MutableMap = HashMap() + + // Default MMKV Holder + private val DEFAULT_HOLDER: Holder by lazy { + val mmkv = MMKV.mmkvWithID(TAG, MMKV.MULTI_PROCESS_MODE) + Holder(mmkv) + } + + /** + * 初始化方法 ( 必须调用 ) + * @param context [Context] + */ + fun initialize(context: Context?) { + // 初始化 MMKV 并设置日志级别 + val rootDir = MMKV.initialize(context, MMKVLogLevel.LevelNone) + LogPrintUtils.dTag(TAG, "MMKV rootDir: %s", rootDir) + +// // 设置打印日志级别 +// MMKV.setLogLevel(MMKVLogLevel.LevelNone) + +// https://github.com/Tencent/MMKV/wiki/android_advance_cn +// // 视项目需求进行注册监听、数据恢复等 +// MMKV.registerHandler() + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否存在指定 Key 的 MMKV Holder + * @param key Key + * @return `true` yes, `false` no + */ + fun containsMMKV(key: String): Boolean { + return HOLDER_MAP.containsKey(key) + } + + /** + * 通过 Key 获取 MMKV Holder + * @param key Key + * @return [Holder] + */ + operator fun get(key: String): Holder { + return if (containsMMKV(key)) HOLDER_MAP[key]!! else putHolder(key) + } + + /** + * 保存自定义 MMKV Holder + * @param key Key + * @return [Holder] + */ + fun putHolder(key: String): Holder { + return putHolder(key, MMKV.mmkvWithID(key, MMKV.MULTI_PROCESS_MODE)) + } + + /** + * 保存自定义 MMKV Holder + * @param key Key + * @param mmkv [MMKV] + * @return [Holder] + */ + fun putHolder( + key: String, + mmkv: MMKV? + ): Holder { + val holder = Holder(mmkv) + HOLDER_MAP[key] = holder + return holder + } + + // = + + /** + * 获取 Default MMKV Holder + * @return [Holder] + */ + fun defaultHolder(): Holder { + return DEFAULT_HOLDER + } + + // ============ + // = 内部封装类 = + // ============ + + /** + * detail: MMKV 持有类 + * @author Ttt + * 提供常用方法, 可根据需求自行添加修改或通过 [Holder].mmkv 进行操作 + */ + class Holder( + // MMKV + val mmkv: MMKV? + ) { + + /** + * 获取 MMKV mmap id + * @return mmap id + */ + fun mmapID(): String? { + return mmkv?.mmapID() + } + + // ========== + // = 其他操作 = + // ========== + + /** + * 判断 MMKV 是否为 null + * @return `true` yes, `false` no + */ + fun isMMKVEmpty(): Boolean { + return mmkv == null + } + + /** + * 判断 MMKV 是否不为 null + * @return `true` yes, `false` no + */ + fun isMMKVNotEmpty(): Boolean { + return mmkv != null + } + + /** + * 是否存在指定 Key value + * @param key Key + * @return `true` yes, `false` no + */ + fun containsKey(key: String?): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.containsKey(key) ?: false + } + + /** + * 通过 key 移除 value + * @param key Key + * @return `true` success, `false` fail + */ + fun removeValueForKey(key: String?): Boolean { + if (isMMKVEmpty()) return false + if (StringUtils.isEmpty(key)) return false + mmkv?.removeValueForKey(key) + return true + } + + /** + * 通过 key 数组移除 value + * @param keys key 数组 + * @return `true` success, `false` fail + */ + fun removeValuesForKeys(keys: Array?): Boolean { + if (isMMKVEmpty()) return false + if (keys == null) return false + mmkv?.removeValuesForKeys(keys) + return true + } + + /** + * 同步操作 + * @return `true` success, `false` fail + */ + fun sync(): Boolean { + mmkv?.let { + it.sync() + return true + } + return false + } + + /** + * 异步操作 + * @return `true` success, `false` fail + */ + fun async(): Boolean { + mmkv?.let { + it.async() + return true + } + return false + } + + /** + * 清除全部数据 + * @return `true` success, `false` fail + */ + fun clear(): Boolean { + mmkv?.let { + it.clear() + return true + } + return false + } + + // ======= + // = 存储 = + // ======= + + fun encode( + key: String?, + value: Boolean + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Int + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Long + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Float + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Double + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: String? + ): Boolean { + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Set? + ): Boolean { + if (value == null) return false + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: ByteArray? + ): Boolean { + if (value == null) return false + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + fun encode( + key: String?, + value: Parcelable? + ): Boolean { + if (value == null) return false + if (StringUtils.isEmpty(key)) return false + return mmkv?.encode(key, value) ?: false + } + + // ======= + // = 读取 = + // ======= + + fun decodeBool(key: String?): Boolean { + return decodeBool(key, DevFinal.DEFAULT.BOOLEAN) + } + + fun decodeBool( + key: String?, + defaultValue: Boolean + ): Boolean { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeBool(key, defaultValue) ?: defaultValue + } + + fun decodeInt(key: String?): Int { + return decodeInt(key, DevFinal.DEFAULT.INT) + } + + fun decodeInt( + key: String?, + defaultValue: Int + ): Int { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeInt(key, defaultValue) ?: defaultValue + } + + fun decodeLong(key: String?): Long { + return decodeLong(key, DevFinal.DEFAULT.LONG) + } + + fun decodeLong( + key: String?, + defaultValue: Long + ): Long { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeLong(key, defaultValue) ?: defaultValue + } + + fun decodeFloat(key: String?): Float { + return decodeFloat(key, DevFinal.DEFAULT.FLOAT) + } + + fun decodeFloat( + key: String?, + defaultValue: Float + ): Float { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeFloat(key, defaultValue) ?: defaultValue + } + + fun decodeDouble(key: String?): Double { + return decodeDouble(key, DevFinal.DEFAULT.DOUBLE) + } + + fun decodeDouble( + key: String?, + defaultValue: Double + ): Double { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeDouble(key, defaultValue) ?: defaultValue + } + + fun decodeString(key: String?): String? { + return decodeString(key, null) + } + + fun decodeString( + key: String?, + defaultValue: String? + ): String? { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeString(key, defaultValue) ?: defaultValue + } + + fun decodeStringSet(key: String?): Set? { + return decodeStringSet(key, null) + } + + fun decodeStringSet( + key: String?, + defaultValue: Set? + ): Set? { + return decodeStringSet(key, defaultValue, HashSet::class.java) + } + + fun decodeStringSet( + key: String?, + defaultValue: Set?, + cls: Class?>? + ): Set? { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeStringSet(key, defaultValue, cls) ?: defaultValue + } + + fun decodeBytes(key: String?): ByteArray? { + return decodeBytes(key, null) + } + + fun decodeBytes( + key: String?, + defaultValue: ByteArray? + ): ByteArray? { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeBytes(key, defaultValue) ?: defaultValue + } + + fun decodeParcelable( + key: String?, + tClass: Class? + ): T? { + return decodeParcelable(key, tClass, null) + } + + fun decodeParcelable( + key: String?, + tClass: Class?, + defaultValue: T? + ): T? { + if (StringUtils.isEmpty(key)) return defaultValue + return mmkv?.decodeParcelable(key, tClass, defaultValue) ?: defaultValue + } + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_mmkv.kt b/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_mmkv.kt new file mode 100644 index 0000000000..8337a593cb --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_mmkv.kt @@ -0,0 +1,217 @@ +package dev.engine.keyvalue + +import com.tencent.mmkv.MMKV +import dev.engine.json.DevJSONEngine +import dev.engine.json.IJSONEngine +import dev.utils.common.ConvertUtils +import dev.utils.common.cipher.Cipher +import java.lang.reflect.Type + +/** + * detail: MMKV Key-Value Config + * @author Ttt + */ +open class MMKVConfig( + cipher: Cipher?, + val mmkv: MMKV +) : IKeyValueEngine.EngineConfig(cipher) + +/** + * detail: MMKV Key-Value Engine 实现 + * @author Ttt + */ +open class MMKVKeyValueEngineImpl( + private val mConfig: MMKVConfig +) : IKeyValueEngine { + + // MMKV + private val mHolder: MMKVUtils.Holder + + init { + // MMKV Holder + mHolder = MMKVUtils.putHolder(config.mmkv.mmapID(), config.mmkv) + } + + // JSON Engine + private var mJSONEngine: IJSONEngine? = DevJSONEngine.getEngine() + + fun setJSONEngine(engine: IJSONEngine) { + this.mJSONEngine = engine + } + + // ============= + // = 对外公开方法 = + // ============= + + override fun getConfig(): MMKVConfig { + return mConfig + } + + override fun remove(key: String?) { + mHolder.removeValueForKey(key) + } + + override fun removeForKeys(keys: Array?) { + mHolder.removeValuesForKeys(keys) + } + + override fun contains(key: String?): Boolean { + return mHolder.containsKey(key) + } + + override fun clear() { + mHolder.clear() + } + + // ======= + // = 存储 = + // ======= + + override fun putInt( + key: String?, + value: Int + ): Boolean { + return mHolder.encode(key, value) + } + + override fun putLong( + key: String?, + value: Long + ): Boolean { + return mHolder.encode(key, value) + } + + override fun putFloat( + key: String?, + value: Float + ): Boolean { + return mHolder.encode(key, value) + } + + override fun putDouble( + key: String?, + value: Double + ): Boolean { + return mHolder.encode(key, value) + } + + override fun putBoolean( + key: String?, + value: Boolean + ): Boolean { + return mHolder.encode(key, value) + } + + override fun putString( + key: String?, + value: String? + ): Boolean { + var content = value + if (value != null && mConfig.cipher != null) { + val bytes = mConfig.cipher.encrypt(ConvertUtils.toBytes(value)) + content = ConvertUtils.newString(bytes) + } + return mHolder.encode(key, content) + } + + override fun putEntity( + key: String?, + value: T + ): Boolean { + return putString(key, mJSONEngine?.toJson(value)) + } + + // ======= + // = 获取 = + // ======= + + override fun getInt(key: String?): Int { + return mHolder.decodeInt(key) + } + + override fun getLong(key: String?): Long { + return mHolder.decodeLong(key) + } + + override fun getFloat(key: String?): Float { + return mHolder.decodeFloat(key) + } + + override fun getDouble(key: String?): Double { + return mHolder.decodeDouble(key) + } + + override fun getBoolean(key: String?): Boolean { + return mHolder.decodeBool(key) + } + + override fun getString(key: String?): String? { + return mHolder.decodeString(key) + } + + override fun getEntity( + key: String?, + typeOfT: Type? + ): T? { + return getEntity(key, typeOfT, null) + } + + // = + + override fun getInt( + key: String?, + defaultValue: Int + ): Int { + return mHolder.decodeInt(key, defaultValue) + } + + override fun getLong( + key: String?, + defaultValue: Long + ): Long { + return mHolder.decodeLong(key, defaultValue) + } + + override fun getFloat( + key: String?, + defaultValue: Float + ): Float { + return mHolder.decodeFloat(key, defaultValue) + } + + override fun getDouble( + key: String?, + defaultValue: Double + ): Double { + return mHolder.decodeDouble(key, defaultValue) + } + + override fun getBoolean( + key: String?, + defaultValue: Boolean + ): Boolean { + return mHolder.decodeBool(key, defaultValue) + } + + override fun getString( + key: String?, + defaultValue: String? + ): String? { + var content = mHolder.decodeString(key, null) ?: return defaultValue + if (mConfig.cipher != null) { + val bytes = mConfig.cipher.decrypt(ConvertUtils.toBytes(content)) + content = ConvertUtils.newString(bytes) + } + return content + } + + override fun getEntity( + key: String?, + typeOfT: Type?, + defaultValue: T? + ): T? { + return mJSONEngine?.fromJson( + getString(key, null), typeOfT + ) ?: return defaultValue + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_sp.kt b/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_sp.kt new file mode 100644 index 0000000000..ff4badd444 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/keyvalue/engine_sp.kt @@ -0,0 +1,223 @@ +package dev.engine.keyvalue + +import dev.engine.json.DevJSONEngine +import dev.engine.json.IJSONEngine +import dev.utils.app.share.IPreference +import dev.utils.common.ConvertUtils +import dev.utils.common.cipher.Cipher +import java.lang.reflect.Type + +/** + * detail: SharedPreferences Key-Value Config + * @author Ttt + */ +open class SPConfig( + cipher: Cipher?, + // SharedPreferences + val preference: IPreference +) : IKeyValueEngine.EngineConfig(cipher) + +/** + * detail: SharedPreferences Key-Value Engine 实现 + * @author Ttt + */ +open class SPKeyValueEngineImpl( + private val mConfig: SPConfig +) : IKeyValueEngine { + + // SharedPreferences + private val mPreference = mConfig.preference + + init { + // SharedPreferences + } + + // JSON Engine + private var mJSONEngine: IJSONEngine? = DevJSONEngine.getEngine() + + fun setJSONEngine(engine: IJSONEngine) { + this.mJSONEngine = engine + } + + // ============= + // = 对外公开方法 = + // ============= + + override fun getConfig(): SPConfig { + return mConfig + } + + override fun remove(key: String?) { + mPreference.remove(key) + } + + override fun removeForKeys(keys: Array?) { + mPreference.removeAll(keys) + } + + override fun contains(key: String?): Boolean { + return mPreference.contains(key) + } + + override fun clear() { + mPreference.clear() + } + + // ======= + // = 存储 = + // ======= + + override fun putInt( + key: String?, + value: Int + ): Boolean { + mPreference.put(key, value) + return true + } + + override fun putLong( + key: String?, + value: Long + ): Boolean { + mPreference.put(key, value) + return true + } + + override fun putFloat( + key: String?, + value: Float + ): Boolean { + mPreference.put(key, value) + return true + } + + override fun putDouble( + key: String?, + value: Double + ): Boolean { + mPreference.put(key, value) + return true + } + + override fun putBoolean( + key: String?, + value: Boolean + ): Boolean { + mPreference.put(key, value) + return true + } + + override fun putString( + key: String?, + value: String? + ): Boolean { + var content = value + if (value != null && mConfig.cipher != null) { + val bytes = mConfig.cipher.encrypt(ConvertUtils.toBytes(value)) + content = ConvertUtils.newString(bytes) + } + mPreference.put(key, content) + return true + } + + override fun putEntity( + key: String?, + value: T + ): Boolean { + return putString(key, mJSONEngine?.toJson(value)) + } + + // ======= + // = 获取 = + // ======= + + override fun getInt(key: String?): Int { + return mPreference.getInt(key) + } + + override fun getLong(key: String?): Long { + return mPreference.getLong(key) + } + + override fun getFloat(key: String?): Float { + return mPreference.getFloat(key) + } + + override fun getDouble(key: String?): Double { + return mPreference.getDouble(key) + } + + override fun getBoolean(key: String?): Boolean { + return mPreference.getBoolean(key) + } + + override fun getString(key: String?): String? { + return mPreference.getString(key) + } + + override fun getEntity( + key: String?, + typeOfT: Type? + ): T? { + return getEntity(key, typeOfT, null) + } + + // = + + override fun getInt( + key: String?, + defaultValue: Int + ): Int { + return mPreference.getInt(key, defaultValue) + } + + override fun getLong( + key: String?, + defaultValue: Long + ): Long { + return mPreference.getLong(key, defaultValue) + } + + override fun getFloat( + key: String?, + defaultValue: Float + ): Float { + return mPreference.getFloat(key, defaultValue) + } + + override fun getDouble( + key: String?, + defaultValue: Double + ): Double { + return mPreference.getDouble(key, defaultValue) + } + + override fun getBoolean( + key: String?, + defaultValue: Boolean + ): Boolean { + return mPreference.getBoolean(key, defaultValue) + } + + override fun getString( + key: String?, + defaultValue: String? + ): String? { + var content = mPreference.getString(key, null) ?: return defaultValue + if (mConfig.cipher != null) { + val bytes = mConfig.cipher.decrypt(ConvertUtils.toBytes(content)) + content = ConvertUtils.newString(bytes) + } + return content + } + + override fun getEntity( + key: String?, + typeOfT: Type?, + defaultValue: T? + ): T? { + return mJSONEngine?.fromJson( + getString(key, null), typeOfT + ) ?: return defaultValue + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/log/engine_dev_logger.kt b/lib/DevEngine/src/main/java/dev/engine/log/engine_dev_logger.kt new file mode 100644 index 0000000000..61c744d6d1 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/log/engine_dev_logger.kt @@ -0,0 +1,161 @@ +package dev.engine.log + +import dev.utils.app.logger.DevLogger +import dev.utils.app.logger.LogConfig + +/** + * detail: DevLogger Log Engine 实现 + * @author Ttt + */ +abstract class DevLoggerEngineImpl( + open val logConfig: LogConfig? = null +) : ILogEngine { + + // ============================= + // = 使用默认 TAG ( 日志打印方法 ) = + // ============================= + + override fun d( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).d(message, *args) + } + + override fun e( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).e(message, *args) + } + + override fun e(throwable: Throwable?) { + DevLogger.other(logConfig).e(throwable) + } + + override fun e( + throwable: Throwable?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).e(throwable, message, *args) + } + + override fun w( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).w(message, *args) + } + + override fun i( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).i(message, *args) + } + + override fun v( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).v(message, *args) + } + + override fun wtf( + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).wtf(message, *args) + } + + override fun json(json: String?) { + DevLogger.other(logConfig).json(json) + } + + override fun xml(xml: String?) { + DevLogger.other(logConfig).xml(xml) + } + + // ============================== + // = 使用自定义 TAG ( 日志打印方法 ) = + // ============================== + + override fun dTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).dTag(tag, message, *args) + } + + override fun eTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).eTag(tag, message, *args) + } + + override fun eTag( + tag: String?, + throwable: Throwable? + ) { + DevLogger.other(logConfig).eTag(tag, throwable) + } + + override fun eTag( + tag: String?, + throwable: Throwable?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).eTag(tag, throwable, message, *args) + } + + override fun wTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).wTag(tag, message, *args) + } + + override fun iTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).iTag(tag, message, *args) + } + + override fun vTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).vTag(tag, message, *args) + } + + override fun wtfTag( + tag: String?, + message: String?, + vararg args: Any? + ) { + DevLogger.other(logConfig).wtfTag(tag, message, *args) + } + + override fun jsonTag( + tag: String?, + json: String? + ) { + DevLogger.other(logConfig).jsonTag(tag, json) + } + + override fun xmlTag( + tag: String?, + xml: String? + ) { + DevLogger.other(logConfig).xmlTag(tag, xml) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/media/MediaConfig.kt b/lib/DevEngine/src/main/java/dev/engine/media/MediaConfig.kt new file mode 100644 index 0000000000..3082e218b3 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/media/MediaConfig.kt @@ -0,0 +1,213 @@ +package dev.engine.media + +/** + * detail: Media Selector Config + * @author Ttt + * 图片选择库可配置选项过多且不一致 + * 所以新增一个 mLibCustomConfig 第三方库自定义配置 + * 要求必须设置第三方库配置、参数等自行转换进行使用 + */ +open class MediaConfig : IMediaEngine.EngineConfig() { + + // 第三方库自定义配置 ( 可自行强转 ) + private var mLibCustomConfig: Any? = null + + /** + * 强转第三方库自定义配置 + * @return 泛型类型对象 + */ + fun convertLibCustomConfig(): T? { + return mLibCustomConfig as? T + } + + /** + * 获取第三方库自定义配置 + * @return 第三方库自定义配置 + */ + fun getLibCustomConfig(): Any? { + return mLibCustomConfig + } + + /** + * 设置第三方库自定义配置 + * @param libCustomConfig 第三方库自定义配置 + * @return MediaConfig + */ + fun setLibCustomConfig(libCustomConfig: Any?): MediaConfig { + mLibCustomConfig = libCustomConfig + return this + } + + // ========== + // = 常用配置 = + // ========== + + // 相册选择类型 + private var mMimeType: Int = MimeType.ofImage() + + // 相册选择模式 + private var mSelectionMode: Int = MimeType.MULTIPLE + + // 自定义数据 + private var mCustomData: Any? = null + + // 已选择的资源 + private var mMediaDatas: MutableList? = null + + /** + * detail: 选择模式 + * @author Ttt + */ + object MimeType { + const val SINGLE = 1 + const val MULTIPLE = 2 + + const val TYPE_ALL = 0 + const val TYPE_IMAGE = 1 + const val TYPE_VIDEO = 2 + const val TYPE_AUDIO = 3 + + const val OPEN_CAMERA = 1 + const val OPEN_GALLERY = 2 + const val OPEN_PREVIEW = 3 + + fun ofAll(): Int { + return TYPE_ALL + } + + fun ofImage(): Int { + return TYPE_IMAGE + } + + fun ofVideo(): Int { + return TYPE_VIDEO + } + + fun ofAudio(): Int { + return TYPE_AUDIO + } + } + + // =========== + // = get/set = + // =========== + + /** + * 获取相册选择类型 + * @return 相册选择类型 + */ + fun getMimeType(): Int { + return mMimeType + } + + /** + * 设置相册选择类型 + * @param mimeType 相册选择类型 + * @return [MediaConfig] + * 全部 ofAll() = 0 + * 图片 ofImage() = 1 + * 视频 ofVideo() = 2 + * 音频 ofAudio() = 3 + */ + fun setMimeType(mimeType: Int): MediaConfig { + // 超过最大、最小值都默认为全部类型 + if (mimeType > MimeType.ofAudio() || mimeType < MimeType.ofAll()) { + this.mMimeType = MimeType.ofAll() + } else { + this.mMimeType = mimeType + } + return this + } + + /** + * 获取相册选择模式 + * @return 相册选择模式 + */ + fun getSelectionMode(): Int { + return mSelectionMode + } + + /** + * 设置相册选择模式 + * @param selectionMode 相册选择模式 + * @return [MediaConfig] + * 多选 [MimeType.MULTIPLE] + * 单选 [MimeType.SINGLE] + */ + fun setSelectionMode(selectionMode: Int): MediaConfig { + if (selectionMode >= MimeType.MULTIPLE) { + mSelectionMode = MimeType.MULTIPLE + } else { + mSelectionMode = MimeType.SINGLE + } + return this + } + + /** + * 获取自定义数据 + * @return 自定义数据 + */ + fun getCustomData(): Any? { + return mCustomData + } + + /** + * 设置自定义数据 + * @param customData 自定义数据 + * @return MediaConfig + */ + fun setCustomData(customData: Any?): MediaConfig { + mCustomData = customData + return this + } + + /** + * 获取已选择的资源 + * @return 已选择的资源 + */ + fun getMediaDatas(): List? { + return mMediaDatas + } + + /** + * 设置已选择的资源 + * @param mediaDatas 选择的资源 + * @return [MediaConfig] + */ + fun setMediaDatas(mediaDatas: MutableList?): MediaConfig { + mMediaDatas = mediaDatas + return this + } + + // = + + /** + * 克隆新的配置信息 + * @return [MediaConfig] + */ + fun clone(): MediaConfig { + val config = MediaConfig() + config.mLibCustomConfig = mLibCustomConfig + config.mMimeType = mMimeType + config.mSelectionMode = mSelectionMode + config.mCustomData = mCustomData + config.mMediaDatas = mMediaDatas + return config + } + + /** + * 设置新的配置信息 + * @param config 新的配置信息 + * @return [MediaConfig] + */ + fun set(config: MediaConfig?): MediaConfig { + config?.let { + mLibCustomConfig = it.mLibCustomConfig + mMimeType = it.mMimeType + mSelectionMode = it.mSelectionMode + mCustomData = it.mCustomData + mMediaDatas = it.mMediaDatas + } + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/media/MediaData.kt b/lib/DevEngine/src/main/java/dev/engine/media/MediaData.kt new file mode 100644 index 0000000000..e8b7367120 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/media/MediaData.kt @@ -0,0 +1,607 @@ +package dev.engine.media + +import android.net.Uri +import dev.utils.app.UriUtils +import java.util.* + +/** + * detail: Media Selector Data + * @author Ttt + * 统一使用 Uri 作为资源获取来源, 减少后续适配情况 + *

+ * 唯一需要考虑的是适配第三方库转换 String Path 逻辑处理 + * get 时使用 uri.toString() 还是 uri.getPath() 亦或是其他方法 + * 根据 set 时存入什么值决定 + */ +open class MediaData : IMediaEngine.EngineData { + + // 第三方库原始数据 ( 可自行强转 ) + private var mLibOriginalData: Any? = null + + /** + * 强转第三方库原始数据 + * @return 泛型类型对象 + */ + fun convertLibOriginalData(): T? { + return mLibOriginalData as? T + } + + /** + * 获取第三方库原始数据 + * @return 第三方库原始数据 + */ + fun getLibOriginalData(): Any? { + return mLibOriginalData + } + + /** + * 设置第三方库原始数据 + * @param libOriginalData 第三方库原始数据 + * @return MediaData + */ + fun setLibOriginalData(libOriginalData: Any?): MediaData { + mLibOriginalData = libOriginalData + return this + } + + // ========== + // = 构造函数 = + // ========== + + constructor() : this(UUID.randomUUID().hashCode().toLong()) + + constructor(uuid: Long) { + mUUID = uuid + } + + // ========== + // = 通用属性 = + // ========== + + // 随机 UUID randomUUID().hashCode() + private var mUUID: Long = 0 + + // 自定义数据 + private var mCustomData: Any? = null + + // ============== + // = 资源路径 Uri = + // ============== + + // 原始 Uri + private var mOriginalUri: Uri? = null + + // 沙盒转存 Uri + private var mSandboxUri: Uri? = null + + // 压缩 Uri + private var mCompressUri: Uri? = null + + // 缩略图 ( 视频、图片等 ) Uri + private var mThumbnailUri: Uri? = null + + // 水印 Uri + private var mWatermarkUri: Uri? = null + + // 裁剪 ( 编辑、剪辑等 ) Uri + private var mCropUri: Uri? = null + + // ========== + // = 资源信息 = + // ========== + + // 类型 + private var mMimeType: String? = null + + // 时长 ( 视频、音频 ) + private var mDuration: Long = 0 + + // 宽度 ( 图片、视频 ) + private var mWidth = 0 + + // 高度 ( 图片、视频 ) + private var mHeight = 0 + + // ============== + // = 资源裁剪信息 = + // ============== + + // 裁剪宽度 + private var mCropImageWidth = 0 + + // 裁剪高度 + private var mCropImageHeight = 0 + + // 裁剪 X 轴偏移值 + private var mCropOffsetX = 0 + + // 裁剪 Y 轴偏移值 + private var mCropOffsetY = 0 + + // 裁剪纵横比 X:Y + private var mCropAspectRatio = 0f + + // ========== + // = 状态信息 = + // ========== + + // 是否裁剪状态 + private var mCropState = false + + // 是否压缩状态 + private var mCompressState = false + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取可用资源 Uri + * @return 可用资源路径 Uri + * 获取有效可用 Uri 校验返回顺序为 + * 裁剪 -> 压缩 -> 水印 -> 沙盒 -> 原始 + * 如果有差异化可自行继承该类重写该方法或直接进行 Uri 获取 + */ + fun getAvailableUri(): Uri? { + if (isExistCropUri()) { + return mCropUri + } else if (isExistCompressUri()) { + return mCompressUri + } else if (isExistWatermarkUri()) { + return mWatermarkUri + } else if (isExistSandboxUri()) { + return mSandboxUri + } else if (isExistOriginalUri()) { + return mOriginalUri + } + return null + } + + // ========== + // = 快捷校验 = + // ========== + + /** + * 是否存在原始 Uri + * @return `true` yes, `false` no + */ + fun isExistOriginalUri(): Boolean { + return UriUtils.isUriExists(mOriginalUri) + } + + /** + * 是否存在沙盒转存 Uri + * @return `true` yes, `false` no + */ + fun isExistSandboxUri(): Boolean { + return UriUtils.isUriExists(mSandboxUri) + } + + /** + * 是否存在缩略图 Uri + * @return `true` yes, `false` no + */ + fun isExistThumbnailUri(): Boolean { + return UriUtils.isUriExists(mThumbnailUri) + } + + /** + * 是否存在水印 Uri + * @return `true` yes, `false` no + */ + fun isExistWatermarkUri(): Boolean { + return UriUtils.isUriExists(mWatermarkUri) + } + + /** + * 是否属于压缩状态且存在压缩 Uri + * @return `true` yes, `false` no + */ + fun isExistCompressUri(): Boolean { + return mCompressState && UriUtils.isUriExists(mCompressUri) + } + + /** + * 是否属于裁剪状态且存在裁剪 Uri + * @return `true` yes, `false` no + */ + fun isExistCropUri(): Boolean { + return mCropState && UriUtils.isUriExists(mCropUri) + } + + // ========== + // = 克隆方法 = + // ========== + + /** + * 克隆对象 + * @return MediaData + */ + fun clone(): MediaData { + val media = MediaData(mUUID) + media.mCustomData = mCustomData + media.mOriginalUri = mOriginalUri + media.mSandboxUri = mSandboxUri + media.mCompressUri = mCompressUri + media.mThumbnailUri = mThumbnailUri + media.mWatermarkUri = mWatermarkUri + media.mCropUri = mCropUri + media.mMimeType = mMimeType + media.mDuration = mDuration + media.mWidth = mWidth + media.mHeight = mHeight + media.mCropImageWidth = mCropImageWidth + media.mCropImageHeight = mCropImageHeight + media.mCropOffsetX = mCropOffsetX + media.mCropOffsetY = mCropOffsetY + media.mCropAspectRatio = mCropAspectRatio + media.mCropState = mCropState + media.mCompressState = mCompressState + return media + } + + /** + * 设置 MediaData 数据 + * @param media MediaData + * @return MediaData + */ + fun set(media: MediaData?): MediaData { + media?.let { + mCustomData = it.mCustomData + mOriginalUri = it.mOriginalUri + mSandboxUri = it.mSandboxUri + mCompressUri = it.mCompressUri + mThumbnailUri = it.mThumbnailUri + mWatermarkUri = it.mWatermarkUri + mCropUri = it.mCropUri + mMimeType = it.mMimeType + mDuration = it.mDuration + mWidth = it.mWidth + mHeight = it.mHeight + mCropImageWidth = it.mCropImageWidth + mCropImageHeight = it.mCropImageHeight + mCropOffsetX = it.mCropOffsetX + mCropOffsetY = it.mCropOffsetY + mCropAspectRatio = it.mCropAspectRatio + mCropState = it.mCropState + mCompressState = it.mCompressState + } + return this + } + + // =========== + // = get/set = + // =========== + + /** + * 获取唯一标识 UUID + * @return UUID.hashCode() + */ + fun getUUID(): Long { + return mUUID + } + + /** + * 获取自定义数据 + * @return 自定义数据 + */ + fun getCustomData(): Any? { + return mCustomData + } + + /** + * 设置自定义数据 + * @param customData 自定义数据 + * @return MediaData + */ + fun setCustomData(customData: Any?): MediaData { + mCustomData = customData + return this + } + + /** + * 获取原始 Uri + * @return 原始 Uri + */ + fun getOriginalUri(): Uri? { + return mOriginalUri + } + + /** + * 设置原始 Uri + * @param originalUri 原始 Uri + * @return MediaData + */ + fun setOriginalUri(originalUri: Uri?): MediaData { + mOriginalUri = originalUri + return this + } + + /** + * 获取沙盒转存 Uri + * @return 沙盒转存 Uri + */ + fun getSandboxUri(): Uri? { + return mSandboxUri + } + + /** + * 设置沙盒转存 Uri + * @param sandboxUri 沙盒转存 Uri + * @return MediaData + */ + fun setSandboxUri(sandboxUri: Uri?): MediaData { + mSandboxUri = sandboxUri + return this + } + + /** + * 获取压缩 Uri + * @return 压缩 Uri + */ + fun getCompressUri(): Uri? { + return mCompressUri + } + + /** + * 设置压缩 Uri + * @param compressUri 压缩 Uri + * @return MediaData + */ + fun setCompressUri(compressUri: Uri?): MediaData { + mCompressUri = compressUri + return this + } + + /** + * 获取缩略图 Uri + * @return 缩略图 Uri + */ + fun getThumbnailUri(): Uri? { + return mThumbnailUri + } + + /** + * 设置缩略图 Uri + * @param thumbnailUri 缩略图 Uri + * @return MediaData + */ + fun setThumbnailUri(thumbnailUri: Uri?): MediaData { + mThumbnailUri = thumbnailUri + return this + } + + /** + * 获取水印 Uri + * @return 水印 Uri + */ + fun getWatermarkUri(): Uri? { + return mWatermarkUri + } + + /** + * 设置水印 Uri + * @param watermarkUri 水印 Uri + * @return MediaData + */ + fun setWatermarkUri(watermarkUri: Uri?): MediaData { + mWatermarkUri = watermarkUri + return this + } + + /** + * 获取裁剪 Uri + * @return 裁剪 Uri + */ + fun getCropUri(): Uri? { + return mCropUri + } + + /** + * 设置裁剪 Uri + * @param cropUri 裁剪 Uri + * @return MediaData + */ + fun setCropUri(cropUri: Uri?): MediaData { + mCropUri = cropUri + return this + } + + /** + * 获取类型 + * @return 类型 + */ + fun getMimeType(): String? { + return mMimeType + } + + /** + * 设置类型 + * @param mimeType 类型 + * @return MediaData + */ + fun setMimeType(mimeType: String?): MediaData { + mMimeType = mimeType + return this + } + + /** + * 获取时长 + * @return 时长 + */ + fun getDuration(): Long { + return mDuration + } + + /** + * 设置时长 + * @param duration 时长 + * @return MediaData + */ + fun setDuration(duration: Long): MediaData { + mDuration = duration + return this + } + + /** + * 获取宽度 + * @return 宽度 + */ + fun getWidth(): Int { + return mWidth + } + + /** + * 设置宽度 + * @param width 宽度 + * @return MediaData + */ + fun setWidth(width: Int): MediaData { + mWidth = width + return this + } + + /** + * 获取高度 + * @return 高度 + */ + fun getHeight(): Int { + return mHeight + } + + /** + * 设置高度 + * @param height 高度 + * @return MediaData + */ + fun setHeight(height: Int): MediaData { + mHeight = height + return this + } + + /** + * 获取裁剪宽度 + * @return 裁剪宽度 + */ + fun getCropImageWidth(): Int { + return mCropImageWidth + } + + /** + * 设置裁剪宽度 + * @param cropImageWidth 裁剪宽度 + * @return MediaData + */ + fun setCropImageWidth(cropImageWidth: Int): MediaData { + mCropImageWidth = cropImageWidth + return this + } + + /** + * 获取裁剪高度 + * @return 裁剪高度 + */ + fun getCropImageHeight(): Int { + return mCropImageHeight + } + + /** + * 设置裁剪高度 + * @param cropImageHeight 裁剪高度 + * @return MediaData + */ + fun setCropImageHeight(cropImageHeight: Int): MediaData { + mCropImageHeight = cropImageHeight + return this + } + + /** + * 获取裁剪 X 轴偏移值 + * @return 裁剪 X 轴偏移值 + */ + fun getCropOffsetX(): Int { + return mCropOffsetX + } + + /** + * 设置裁剪 X 轴偏移值 + * @param cropOffsetX X 轴偏移值 + * @return MediaData + */ + fun setCropOffsetX(cropOffsetX: Int): MediaData { + mCropOffsetX = cropOffsetX + return this + } + + /** + * 获取裁剪 Y 轴偏移值 + * @return 裁剪 Y 轴偏移值 + */ + fun getCropOffsetY(): Int { + return mCropOffsetY + } + + /** + * 设置裁剪 Y 轴偏移值 + * @param cropOffsetY Y 轴偏移值 + * @return MediaData + */ + fun setCropOffsetY(cropOffsetY: Int): MediaData { + mCropOffsetY = cropOffsetY + return this + } + + /** + * 获取裁剪纵横比 X:Y + * @return 裁剪纵横比 X:Y + */ + fun getCropAspectRatio(): Float { + return mCropAspectRatio + } + + /** + * 设置裁剪纵横比 X:Y + * @param cropAspectRatio 裁剪纵横比 X:Y + * @return MediaData + */ + fun setCropAspectRatio(cropAspectRatio: Float): MediaData { + mCropAspectRatio = cropAspectRatio + return this + } + + /** + * 获取裁剪状态 + * @return `true` yes, `false` no + */ + fun isCropState(): Boolean { + return mCropState + } + + /** + * 设置裁剪状态 + * @param cropState `true` yes, `false` no + * @return MediaData + */ + fun setCropState(cropState: Boolean): MediaData { + mCropState = cropState + return this + } + + /** + * 获取压缩状态 + * @return `true` yes, `false` no + */ + fun isCompressState(): Boolean { + return mCompressState + } + + /** + * 设置压缩状态 + * @param compressState `true` yes, `false` no + * @return MediaData + */ + fun setCompressState(compressState: Boolean): MediaData { + mCompressState = compressState + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/media/engine_picture_selector.kt b/lib/DevEngine/src/main/java/dev/engine/media/engine_picture_selector.kt new file mode 100644 index 0000000000..03200e3afc --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/media/engine_picture_selector.kt @@ -0,0 +1,365 @@ +package dev.engine.media + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.fragment.app.Fragment +import com.luck.picture.lib.basic.* +import com.luck.picture.lib.entity.LocalMedia +import com.luck.picture.lib.manager.PictureCacheManager +import dev.utils.LogPrintUtils +import dev.utils.app.UriUtils +import dev.utils.common.ConvertUtils + +// =================== +// = PictureSelector = +// =================== + +/** + * detail: PictureSelector Media Selector Engine 实现 + * @author Ttt + * 功能配置文档 + * @see https://github.com/LuckSiege/PictureSelector + * 尽量不使用 isCompressed 压缩, 通过获取选中的路径后自行进行压缩 + * 防止需要适配 Android 11 ( R ) 进行转存文件需判断文件路径 + */ +open class PictureSelectorEngineImpl : IMediaEngine { + + // 日志 TAG + private val TAG = PictureSelectorEngineImpl::class.java.simpleName + + // 全局请求跳转回传 code + private val PIC_REQUEST_CODE = 159857 + + // 全局配置信息 + private val PIC_CONFIG = MediaConfig() + + // ========== + // = 配置方法 = + // ========== + + override fun getConfig(): MediaConfig { + return PIC_CONFIG + } + + override fun setConfig(config: MediaConfig?) { + PIC_CONFIG.set(config) + } + + // ============= + // = 对外公开方法 = + // ============= + + override fun openCamera(activity: Activity?): Boolean { + return openCamera(activity, PIC_CONFIG) + } + + override fun openCamera( + activity: Activity?, + config: MediaConfig? + ): Boolean { + return startCameraModel(config) + } + + override fun openCamera(fragment: Fragment?): Boolean { + return openCamera(fragment, PIC_CONFIG) + } + + override fun openCamera( + fragment: Fragment?, + config: MediaConfig? + ): Boolean { + return startCameraModel(config) + } + + // = + + override fun openGallery(activity: Activity?): Boolean { + return openGallery(activity, PIC_CONFIG) + } + + override fun openGallery( + activity: Activity?, + config: MediaConfig? + ): Boolean { + return startGalleryModel(config) + } + + override fun openGallery(fragment: Fragment?): Boolean { + return openGallery(fragment, PIC_CONFIG) + } + + override fun openGallery( + fragment: Fragment?, + config: MediaConfig? + ): Boolean { + return startGalleryModel(config) + } + + // = + + override fun openPreview(activity: Activity?): Boolean { + return openPreview(activity, PIC_CONFIG) + } + + override fun openPreview( + activity: Activity?, + config: MediaConfig? + ): Boolean { + return startPreviewModel(config) + } + + override fun openPreview(fragment: Fragment?): Boolean { + return openPreview(fragment, PIC_CONFIG) + } + + override fun openPreview( + fragment: Fragment?, + config: MediaConfig? + ): Boolean { + return startPreviewModel(config) + } + + // ========== + // = 其他方法 = + // ========== + + override fun deleteCacheDirFile( + context: Context?, + type: Int + ) { + try { + PictureCacheManager.deleteCacheDirFile(context, type) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "deleteCacheDirFile") + } + } + + override fun deleteAllCacheDirFile(context: Context?) { + try { + PictureCacheManager.deleteAllCacheDirFile(context) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "deleteAllCacheDirFile") + } + } + + override fun isMediaSelectorResult( + requestCode: Int, + resultCode: Int + ): Boolean { + return requestCode == PIC_REQUEST_CODE && resultCode == Activity.RESULT_OK + } + + // = + + override fun getSelectors(intent: Intent?): MutableList { + val lists = mutableListOf() + val result = PictureSelector.obtainSelectorList(intent) + result.forEach { + it?.let { libData -> + lists.add(createMediaData(libData)) + } + } + return lists + } + + override fun getSelectorUris( + intent: Intent?, + original: Boolean + ): MutableList { + val lists = mutableListOf() + val result = getSelectors(intent) + result.forEach { media -> + val uri = if (original) { + media.getOriginalUri() + } else { + val availableUri = media.getAvailableUri() + if (UriUtils.isUriExists(availableUri)) { + availableUri + } else { + media.getOriginalUri() + } + } + uri?.let { lists.add(it) } + } + return lists + } + + override fun getSingleSelector(intent: Intent?): MediaData? { + val lists = getSelectors(intent) + return if (lists.size > 0) lists[0] else null + } + + override fun getSingleSelectorUri( + intent: Intent?, + original: Boolean + ): Uri? { + val lists = getSelectorUris(intent, original) + return if (lists.size > 0) lists[0] else null + } + + // ========== + // = 内部方法 = + // ========== + + // ========== + // = 转换对象 = + // ========== + + /** + * 转换 List + * @param lists [MediaData] list + * @return [LocalMedia] list + */ + private fun convertList(lists: List?): ArrayList { + val medias = ArrayList() + if (lists != null) { + for (media in lists) { + val libData = media.getLibOriginalData() + if (libData is LocalMedia) { + medias.add(libData) + } else { + media.getOriginalUri()?.let { uri -> + val localMedia = LocalMedia() + localMedia.path = uri.toString() + localMedia.originalPath = uri.toString() + medias.add(localMedia) + } + } + } + } + return medias + } + + /** + * 创建 MediaData 并填充数据 + * @param libData 第三方库选择数据实体类 + * @return MediaData + */ + private fun createMediaData(libData: LocalMedia): MediaData { + val media = MediaData() + media.setLibOriginalData(libData) + + // ============ + // = 初始化数据 = + // ============ + + // 资源路径 Uri + media.setOriginalUri(UriUtils.ofUri(libData.originalPath)) + .setSandboxUri(UriUtils.ofUri(libData.sandboxPath)) + .setCompressUri(UriUtils.ofUri(libData.compressPath)) + .setThumbnailUri(UriUtils.ofUri(libData.videoThumbnailPath)) + .setWatermarkUri(UriUtils.ofUri(libData.watermarkPath)) + .setCropUri(UriUtils.ofUri(libData.cutPath)) + + // 资源信息 + media.setMimeType(libData.mimeType) + .setDuration(libData.duration) + .setWidth(libData.width) + .setHeight(libData.height) + + // 资源裁剪信息 + media.setCropImageWidth(libData.cropImageWidth) + .setCropImageHeight(libData.cropImageHeight) + .setCropOffsetX(libData.cropOffsetX) + .setCropOffsetY(libData.cropOffsetY) + .setCropAspectRatio(libData.cropResultAspectRatio) + + // 状态信息 + media.setCropState(libData.isCut) + .setCompressState(libData.isCompressed) + + // ================= + // = 资源路径最后校验 = + // ================= + + // 判断是否存在有效 Uri + val availableUri = media.getAvailableUri() + if (!UriUtils.isUriExists(availableUri)) { + // 上述已设置路径都不存在则设置原图路径 + media.setOriginalUri(UriUtils.ofUri(libData.path)) + } + return media + } + + // ========================== + // = PictureSelection Model = + // ========================== + + /** + * 跳转 Camera PictureSelection Model + * @param config [MediaConfig] + * @return `true` success, `false` fail + */ + private fun startCameraModel(config: MediaConfig?): Boolean { + if (config != null) { + try { + val libConfig = config.getLibCustomConfig() + if (libConfig is PictureSelectionCameraModel) { + libConfig.forResultActivity( + PIC_REQUEST_CODE + ) + return true + } + throw Exception("MediaConfig libCustomConfig is null") + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "startCameraModel") + } + } + return false + } + + /** + * 跳转 Gallery PictureSelection Model + * @param config [MediaConfig] + * @return `true` success, `false` fail + */ + private fun startGalleryModel(config: MediaConfig?): Boolean { + if (config != null) { + try { + val libConfig = config.getLibCustomConfig() + if (libConfig is PictureSelectionModel) { + libConfig.forResult( + PIC_REQUEST_CODE + ) + return true + } + if (libConfig is PictureSelectionSystemModel) { + libConfig.forSystemResultActivity( + PIC_REQUEST_CODE + ) + return true + } + throw Exception("MediaConfig libCustomConfig is null") + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "startGalleryModel") + } + } + return false + } + + /** + * 跳转 Preview PictureSelection Model + * @param config [MediaConfig] + * @return `true` success, `false` fail + */ + private fun startPreviewModel(config: MediaConfig?): Boolean { + if (config != null) { + try { + val libConfig = config.getLibCustomConfig() + if (libConfig is PictureSelectionPreviewModel) { + libConfig.startActivityPreview( + ConvertUtils.toInt(config.getCustomData(), 0), + false, convertList(config.getMediaDatas()) + ) + return true + } + throw Exception("MediaConfig libCustomConfig is null") + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "startPreviewModel") + } + } + return false + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/permission/engine_dev_permission.kt b/lib/DevEngine/src/main/java/dev/engine/permission/engine_dev_permission.kt new file mode 100644 index 0000000000..1b7ca2003d --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/permission/engine_dev_permission.kt @@ -0,0 +1,109 @@ +package dev.engine.permission + +import android.app.Activity +import android.content.Context +import dev.utils.app.permission.PermissionUtils + +/** + * detail: DevUtils Permission Engine 实现 + * @author Ttt + */ +open class DevPermissionEngineImpl : IPermissionEngine { + + // ============= + // = 对外公开方法 = + // ============= + + override fun isGranted( + context: Context?, + vararg permissions: String? + ): Boolean { + return PermissionUtils.isGranted(*permissions) + } + + override fun shouldShowRequestPermissionRationale( + activity: Activity?, + vararg permissions: String? + ): Boolean { + return PermissionUtils.shouldShowRequestPermissionRationale( + activity, *permissions + ) + } + + override fun getDeniedPermissionStatus( + activity: Activity?, + shouldShow: Boolean, + vararg permissions: String? + ): MutableList { + return PermissionUtils.getDeniedPermissionStatus( + activity, shouldShow, *permissions + ) + } + + override fun againRequest( + activity: Activity?, + callback: IPermissionEngine.Callback?, + deniedList: List? + ): Int { + return PermissionUtils.againRequest(activity, object : PermissionUtils.PermissionCallback { + override fun onGranted() { + callback?.onGranted() + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + callback?.onDenied(grantedList, deniedList, notFoundList) + } + }, deniedList) + } + + // ============= + // = 权限请求方法 = + // ============= + + override fun request( + activity: Activity?, + permissions: Array? + ) { + request(activity, permissions, null, false) + } + + override fun request( + activity: Activity?, + permissions: Array?, + callback: IPermissionEngine.Callback? + ) { + request(activity, permissions, callback, false) + } + + override fun request( + activity: Activity?, + permissions: Array?, + callback: IPermissionEngine.Callback?, + againRequest: Boolean + ) { + permissions?.let { + PermissionUtils.permission(*it) + .callback(object : PermissionUtils.PermissionCallback { + override fun onGranted() { + callback?.onGranted() + } + + override fun onDenied( + grantedList: List, + deniedList: List, + notFoundList: List + ) { + callback?.onDenied(grantedList, deniedList, notFoundList) + // 再次请求处理操作 + if (againRequest && deniedList.isNotEmpty()) { + againRequest(activity, callback, deniedList) + } + } + }).request(activity) + } + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/storage/StorageItem.kt b/lib/DevEngine/src/main/java/dev/engine/storage/StorageItem.kt new file mode 100644 index 0000000000..d328cfe4ee --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/storage/StorageItem.kt @@ -0,0 +1,300 @@ +package dev.engine.storage + +import android.graphics.Bitmap.CompressFormat +import android.net.Uri +import dev.engine.storage.StorageItem.Companion.createExternalItem +import dev.engine.storage.StorageItem.Companion.createInternalItem +import dev.utils.app.MediaStoreUtils +import dev.utils.app.PathUtils +import dev.utils.common.FileUtils +import dev.utils.common.StringUtils +import java.io.File + +/** + * detail: Storage Item Params + * @author Ttt + * 可传入输出 Uri 或通过拼接路径创建 Uri 二选一 + * 内部存储时: + * 必须传入 [mFilePath]、[mFileName] + * [mFileName] 需携带后缀 + * 可以使用快捷创建方法 [createInternalItem] + * 外部存储时: + * 必须传入 [mFileName]、[mMimeType]、[mFolder] + * [mFileName] 存储文件名是否需要后缀视 [mMimeType] 情况而定 + * ( 正常无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/ * 则需指定后缀 ) + * 可以使用快捷创建方法 [createExternalItem] + */ +open class StorageItem private constructor() : IStorageEngine.EngineItem() { + + // 输出 Uri ( 可以自行指定输出 Uri 优先使用该值 ) + @Transient + private var mOutputUri: Uri? = null + + // ============= + // = 内外存储通用 = + // ============= + + // 存储文件名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/* 则需指定后缀 ) + private var mFileName: String? = null + + // ============= + // = 内部存储独有 = + // ============= + + // 存储路径 ( 不包含文件名, 纯路径 ) 只会在内部存储时使用 + private var mFilePath: String? = null + + // ============= + // = 外部存储独有 = + // ============= + + // 存储文件夹 ( 不包含完整路径, 只需要文件夹名 ) RELATIVE_PATH + private var mFolder: String? = null // 例: /Pictures + + // 资源类型 + private var mMimeType: String? = null + + // ============================================== + // = Storage DevSource 属于 Bitmap、Drawable 使用 = + // ============================================== + + // 图片格式 + private var mFormat = CompressFormat.PNG + + // 图片质量 + private var mQuality = 100 + + // ============= + // = 对外公开方法 = + // ============= + + fun getOutputUri(): Uri? { + return mOutputUri + } + + fun getFileName(): String? { + return mFileName + } + + fun getFilePath(): String? { + return mFilePath + } + + fun getFolder(): String? { + return mFolder + } + + fun getMimeType(): String? { + return mMimeType + } + + // = + + fun getFormat(): CompressFormat { + return mFormat + } + + fun setFormat(format: CompressFormat): StorageItem { + mFormat = format + return this + } + + fun getQuality(): Int { + return mQuality + } + + fun setQuality(quality: Int): StorageItem { + mQuality = quality + return this + } + + // = + + private fun setOutputUri(outputUri: Uri?): StorageItem { + mOutputUri = outputUri + return this + } + + private fun setFileName(fileName: String?): StorageItem { + mFileName = fileName + return this + } + + private fun setFilePath(filePath: String?): StorageItem { + mFilePath = filePath + return this + } + + private fun setFolder(folder: String?): StorageItem { + mFolder = folder + return this + } + + private fun setMimeType(mimeType: String?): StorageItem { + mMimeType = mimeType + return this + } + + // ========== + // = 快捷方法 = + // ========== + + // ========== + // = 路径获取 = + // ========== + + /** + * 获取内部存储完整路径 + * @return 内部存储完整路径 + */ + fun getInternalFile(): File? { + // 创建文件夹 + FileUtils.createFolder(mFilePath) + // filePath + fileName + return FileUtils.getFile(mFilePath, mFileName) + } + + // = + + /** + * 获取外部存储完整路径 + * @return 外部存储完整路径 + */ + fun getExternalFile(): File? { + return getExternalFile(mFileName) + } + + /** + * 获取外部存储完整路径 + * @param fileName 文件名 + * @return 外部存储完整路径 + */ + fun getExternalFile(fileName: String?): File? { + val path = PathUtils.getSDCard().getSDCardPath(mFolder) + // 创建文件夹 + FileUtils.createFolder(path) + // SDCard/folder/fileName + return FileUtils.getFile(path, fileName) + } + + /** + * 获取外部存储文件夹路径 + * @return 外部存储文件夹路径 + */ + fun getExternalFolder(): File? { + val path = PathUtils.getSDCard().getSDCardPath(mFolder) + // 创建文件夹 + FileUtils.createFolder(path) + // SDCard/folder + return FileUtils.getFile(path) + } + + // ========== + // = 快捷方法 = + // ========== + + companion object { + + /** + * 创建指定输出 Uri Item + * @param outputUri 输出 Uri + * @return [StorageItem] + */ + fun createUriItem(outputUri: Uri?): StorageItem { + return StorageItem().setOutputUri(outputUri) + } + + // ========== + // = 内部存储 = + // ========== + + /** + * 创建内部存储路径信息 Item + * @param filePath 存储路径 ( 不包含文件名, 纯路径 ) 只会在内部存储时使用 + * @param fileName 存储文件名 ( 可不携带后缀 ) + * @return [StorageItem] + */ + fun createInternalItem( + filePath: String?, + fileName: String? + ): StorageItem { + return StorageItem().setFilePath(filePath) + .setFileName(fileName) + } + + // ========== + // = 外部存储 = + // ========== + + /** + * 创建外部存储路径信息 Item + * @param fileName 存储文件名 ( 必须携带后缀 ) + * @return [StorageItem] + * 根据 fileName 获取后缀推导出 mimeType + * 如果系统不支持的格式、文件名不含后缀则可能获取失败 ( 将直接返回 null Item ) + */ + fun createExternalItem(fileName: String?): StorageItem? { + val fileExtension = FileUtils.getFileExtension(fileName) + val mimeType = MediaStoreUtils.getMimeTypeFromExtension(fileExtension) ?: return null + val name = FileUtils.getFileNameNoExtension(fileName) + return createExternalItem(name, mimeType) + } + + /** + * 创建外部存储路径信息 Item + * @param fileName 存储文件名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/ * 则需指定后缀 ) + * @param mimeType 资源类型 + * @return [StorageItem] + */ + fun createExternalItem( + fileName: String?, + mimeType: String? + ): StorageItem? { + return createExternalItem( + fileName, mimeType, + StorageType.getTypeRelativePath(mimeType) + ) + } + + /** + * 创建外部存储路径信息 Item + * @param fileName 存储文件名 ( 无需后缀, 根据 mimeType 决定, 如果 mimeType 用了 xxx/ * 则需指定后缀 ) + * @param mimeType 资源类型 + * @param folder 存储文件夹 ( 不包含完整路径, 只需要文件夹名 ) + * @return [StorageItem] + */ + fun createExternalItem( + fileName: String?, + mimeType: String?, + folder: String? + ): StorageItem? { + if (StringUtils.isEmpty(fileName)) return null + if (StringUtils.isEmpty(mimeType)) return null + if (StringUtils.isEmpty(folder)) return null + return StorageItem().setFileName(fileName) + .setMimeType(mimeType).setFolder(folder) + } + + // ========== + // = 额外方法 = + // ========== + + /** + * 创建外部存储路径信息 Item + * @param fileName 存储文件名 ( 必须携带后缀 ) + * @param folder 存储文件夹 ( 不包含完整路径, 只需要文件夹名 ) + * @return [StorageItem] + * 根据 fileName 获取后缀推导出 mimeType + * 如果系统不支持的格式、文件名不含后缀则可能获取失败 ( 将直接返回 null Item ) + */ + fun createExternalItemFolder( + fileName: String?, + folder: String? + ): StorageItem? { + val fileExtension = FileUtils.getFileExtension(fileName) + val mimeType = MediaStoreUtils.getMimeTypeFromExtension(fileExtension) ?: return null + val name = FileUtils.getFileNameNoExtension(fileName) + return createExternalItem(name, mimeType, folder) + } + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/storage/StorageResult.kt b/lib/DevEngine/src/main/java/dev/engine/storage/StorageResult.kt new file mode 100644 index 0000000000..9689869de8 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/storage/StorageResult.kt @@ -0,0 +1,112 @@ +package dev.engine.storage + +import android.net.Uri +import java.io.File + +/** + * detail: Storage Result + * @author Ttt + * 外部存储时以 Uri 为准, 可在存储成功通过 [dev.utils.app.UriUtils.getFilePathByUri] + * 获取真实 File 存储地址 ( 部分 ROM 传入 RELATIVE_PATH 无效 ) 只会存储在对应 MimeType 根目录下 + */ +open class StorageResult( + // 存储结果 + private val mInsertResult: Boolean +) : IStorageEngine.EngineResult() { + + // 存储文件 Uri + @Transient + private var mUri: Uri? = null + + // 存储文件地址 + private var mFile: File? = null + + // 异常信息 + private var mError: Exception? = null + + // 存储类型 + private var mType: StorageType? = null + + // 是否外部存储 + private var mExternal = false + + // ========== + // = 静态方法 = + // ========== + + companion object { + + /** + * 存储成功 + * @return [StorageResult] + */ + fun success(): StorageResult { + return StorageResult(true) + } + + /** + * 存储失败 + * @return [StorageResult] + */ + fun failure(): StorageResult { + return StorageResult(false) + } + } + + // ============= + // = 对外公开方法 = + // ============= + + fun isSuccess(): Boolean { + return mInsertResult + } + + // = + + fun getUri(): Uri? { + return mUri + } + + fun getFile(): File? { + return mFile + } + + fun getError(): Exception? { + return mError + } + + fun getType(): StorageType? { + return mType + } + + fun isExternal(): Boolean { + return mExternal + } + + // = + + fun setUri(uri: Uri?): StorageResult { + mUri = uri + return this + } + + fun setFile(file: File?): StorageResult { + mFile = file + return this + } + + fun setError(error: Exception?): StorageResult { + mError = error + return this + } + + fun setType(type: StorageType?): StorageResult { + mType = type + return this + } + + fun setExternal(external: Boolean): StorageResult { + mExternal = external + return this + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/storage/StorageType.kt b/lib/DevEngine/src/main/java/dev/engine/storage/StorageType.kt new file mode 100644 index 0000000000..3fcb8c1c2b --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/storage/StorageType.kt @@ -0,0 +1,135 @@ +package dev.engine.storage + +import dev.utils.app.MediaStoreUtils +import dev.utils.common.FileUtils + +/** + * detail: Storage Type + * @author Ttt + */ +enum class StorageType { + + IMAGE, + + VIDEO, + + AUDIO, + + DOWNLOAD, + + NONE + + ; + + // ========== + // = 快捷方法 = + // ========== + + open fun isImage(): Boolean { + return this == IMAGE + } + + open fun isVideo(): Boolean { + return this == VIDEO + } + + open fun isAudio(): Boolean { + return this == AUDIO + } + + open fun isDownload(): Boolean { + return this == DOWNLOAD + } + + open fun isNone(): Boolean { + return this == NONE + } + + // ======== + // = Type = + // ======== + + companion object { + + /** + * 通过文件后缀判断存储类型 + * @param extension 文件后缀 + * @return 存储类型 + */ + fun convertType(extension: String?): StorageType { + extension?.let { + val temp = if (it.startsWith(".")) { + it + } else { // 不存在 . 则补上 + ".$it" + } + if (FileUtils.isImageFormats(temp)) { + return IMAGE + } + if (FileUtils.isVideoFormats(temp)) { + return VIDEO + } + if (FileUtils.isAudioFormats(temp)) { + return AUDIO + } + } + return NONE + } + + /** + * 通过 mimeType 判断存储类型 + * @param mimeType 资源类型 + * @return 存储类型 + */ + fun convertTypeByMimeType(mimeType: String?): StorageType { + // 获取 mimeType 后缀 + val mimeTypeExtension = MediaStoreUtils.getExtensionFromMimeType( + mimeType + ) + return convertType(mimeTypeExtension) + } + + /** + * 通过 fileName 判断存储类型 + * @param fileName 文件名 + * @return 存储类型 + */ + fun convertTypeByFileName(fileName: String?): StorageType { + // 获取文件名内的文件后缀 + val fileNameExtension = FileUtils.getFileExtension( + fileName + ) + return convertType(fileNameExtension) + } + + // = + + /** + * 通过 mimeType 获取对应存储文件夹 + * @param mimeType 资源类型 + * @return 存储文件夹 + */ + fun getTypeRelativePath(mimeType: String?): String { + return getTypeRelativePath( + convertTypeByMimeType(mimeType) + ) + } + + /** + * 通过存储类型获取对应存储文件夹 + * @param type 存储类型 + * @return 存储文件夹 + */ + fun getTypeRelativePath(type: StorageType?): String { + type?.let { + when (it) { + IMAGE -> return MediaStoreUtils.RELATIVE_IMAGE_PATH + VIDEO -> return MediaStoreUtils.RELATIVE_VIDEO_PATH + AUDIO -> return MediaStoreUtils.RELATIVE_AUDIO_PATH + else -> MediaStoreUtils.RELATIVE_DOWNLOAD_PATH + } + } + return MediaStoreUtils.RELATIVE_DOWNLOAD_PATH + } + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/engine/storage/engine_dev_media_store.kt b/lib/DevEngine/src/main/java/dev/engine/storage/engine_dev_media_store.kt new file mode 100644 index 0000000000..528a4f9a40 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/engine/storage/engine_dev_media_store.kt @@ -0,0 +1,610 @@ +package dev.engine.storage + +import android.net.Uri +import android.os.Build +import dev.base.DevSource +import dev.engine.storage.listener.OnInsertListener +import dev.utils.app.MediaStoreUtils +import dev.utils.app.UriUtils +import dev.utils.app.image.ImageUtils +import dev.utils.common.FileIOUtils +import dev.utils.common.FileUtils +import dev.utils.common.StreamUtils +import dev.utils.common.StringUtils +import java.io.File + +/** + * detail: 插入多媒体资源事件 封装接口 + * @author Ttt + * 减少需要 OnInsertListener 声明使用 + * 内部直接封装具体泛型类 + */ +interface OnDevInsertListener : OnInsertListener + +/** + * detail: DevUtils MediaStore Engine 实现 + * @author Ttt + * 如果需要设置全局结果监听, 可以新增构造函数传入 [OnDevInsertListener] + * 并在 [finalCallback] 方法中设置触发事件回调 + */ +open class DevMediaStoreEngineImpl : IStorageEngine { + + // ========== + // = 外部存储 = + // ========== + + /** + * 插入一张图片到外部存储空间 ( SDCard ) + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertImageToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, true, StorageType.IMAGE)) { + innerInsertToExternal(params, source, listener, StorageType.IMAGE) + } + } + + /** + * 插入一条视频到外部存储空间 ( SDCard ) + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertVideoToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, true, StorageType.VIDEO)) { + innerInsertToExternal(params, source, listener, StorageType.VIDEO) + } + } + + /** + * 插入一条音频到外部存储空间 ( SDCard ) + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertAudioToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, true, StorageType.AUDIO)) { + innerInsertToExternal(params, source, listener, StorageType.AUDIO) + } + } + + /** + * 插入一条文件资源到外部存储空间 ( SDCard ) + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertDownloadToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, true, StorageType.DOWNLOAD)) { + innerInsertToExternal(params, source, listener, StorageType.DOWNLOAD) + } + } + + /** + * 插入一条多媒体资源到外部存储空间 ( SDCard ) + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + * 并不局限于多媒体, 如文本存储、其他文件写入等 + */ + override fun insertMediaToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, true, StorageType.NONE)) { + innerInsertToExternal(params, source, listener, StorageType.NONE) + } + } + + // ========== + // = 内部存储 = + // ========== + + /** + * 插入一张图片到内部存储空间 + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertImageToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, false, StorageType.IMAGE)) { + innerInsertToInternal(params, source, listener, StorageType.IMAGE) + } + } + + /** + * 插入一条视频到内部存储空间 + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertVideoToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, false, StorageType.VIDEO)) { + innerInsertToInternal(params, source, listener, StorageType.VIDEO) + } + } + + /** + * 插入一条音频到内部存储空间 + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertAudioToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, false, StorageType.AUDIO)) { + innerInsertToInternal(params, source, listener, StorageType.AUDIO) + } + } + + /** + * 插入一条文件资源到内部存储空间 + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + */ + override fun insertDownloadToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, false, StorageType.DOWNLOAD)) { + innerInsertToInternal(params, source, listener, StorageType.DOWNLOAD) + } + } + + /** + * 插入一条多媒体资源到内部存储空间 + * @param params EngineItem + * @param source DevSource + * @param listener OnInsertListener + * 并不局限于多媒体, 如文本存储、其他文件写入等 + */ + override fun insertMediaToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + if (innerPreCheck(params, source, listener, false, StorageType.NONE)) { + innerInsertToInternal(params, source, listener, StorageType.NONE) + } + } + + // ========== + // = 内部方法 = + // ========== + + // ======== + // = Type = + // ======== + + /** + * 当属于 NONE 类型则进行只能校验 + * @param params 原始参数 + * @param source 原始数据 + * @param type 存储类型 + * @return 输出 Uri + */ + private fun convertType( + params: StorageItem?, + source: DevSource?, + type: StorageType + ): StorageType { + if (type == StorageType.NONE) { + params?.let { + var typeResult = StorageType.convertTypeByMimeType( + it.getMimeType() + ) + if (!typeResult.isNone()) return typeResult + + typeResult = StorageType.convertTypeByFileName( + it.getFileName() + ) + if (!typeResult.isNone()) return typeResult + } + // 其他未知都放到 Download 文件夹下 + return StorageType.DOWNLOAD + } + return type + } + + /** + * 通用内部预校验 + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + * @param external 是否外部存储 + * @param type 存储类型 + * @return `true` 校验通过, `false` 校验失败 + */ + private fun innerPreCheck( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener?, + external: Boolean, + type: StorageType + ): Boolean { + // 判断参数是否有效 + if (params != null && source != null && source.isSource) { + // url: 暂不处理 Url 文件 ( 涉及下载 ) 自行下载后传入 File + // resource Id: 无法确认属于 drawable、raw、assets 等 id ( 自行传入 Bitmap、InputStream、byte[] ) + if (!source.isUrl && !source.isResource) { + return true + } + } + // 无效数据触发回调 + if (listener != null) { + val result = StorageResult.failure() + .setExternal(external) + .setType(convertType(params, source, type)) + if (params == null) { + result.setError(Exception("params is null")) + } + if (source == null || !source.isSource) { + if (params == null) { + result.setError(Exception("params、source is null")) + } else { + result.setError(Exception("source is null")) + } + } else { + if (params == null) { + result.setError(Exception("params is null and UnSupport url、resourceId source")) + } else { + result.setError(Exception("UnSupport url、resourceId source")) + } + } + // 统一触发事件回调 + finalCallback(result, params, source, listener) + } + return false + } + + // ============ + // = Uri、File = + // ============ + + /** + * 获取输出 Uri ( 存储文件 Uri ) + * @param params 原始参数 + * @param source 原始数据 + * @param external 是否外部存储 + * @param type 存储类型 + * @return 输出 Uri + */ + private fun getOutputUri( + params: StorageItem, + source: DevSource, + external: Boolean, + type: StorageType + ): Uri? { + if (params.getOutputUri() != null) { + return params.getOutputUri() + } + // 外部存储才需要进行适配 + if (external) { + // Android 9 ( Pie ) 及以下版本则直接使用 File 读写 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { +// if (type == StorageType.DOWNLOAD) { +// // 低版本直接创建 SDCard/Download File +// val file = getOutputFile(params, source, external, type) +// return MediaStoreUtils.createUriByFile(file) +// } + // 目前属于 IMAGE、VIDEO、AUDIO、DOWNLOAD 低版本都通过 File 读写 + // 如果需要 IMAGE、VIDEO、AUDIO 使用 MediaStore 则放开上面注释 + // 低版本直接创建 SDCard/Pictures、DCIM、Music、Download File + val file = getOutputFile(params, source, external, type) + return MediaStoreUtils.createUriByFile(file) + } + when (type) { + // 创建 Image Uri + StorageType.IMAGE -> { + return MediaStoreUtils.createImageUri( + params.getFileName(), + params.getMimeType(), + params.getFolder() + ) + } + // 创建 Video Uri + StorageType.VIDEO -> { + return MediaStoreUtils.createVideoUri( + params.getFileName(), + params.getMimeType(), + params.getFolder() + ) + } + // 创建 Audio Uri + StorageType.AUDIO -> { + return MediaStoreUtils.createAudioUri( + params.getFileName(), + params.getMimeType(), + params.getFolder() + ) + } + // 创建 Download Uri + StorageType.DOWNLOAD -> { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return MediaStoreUtils.createDownloadUri( + params.getFileName(), + params.getMimeType(), + params.getFolder() + ) + } + } + else -> return null + } + } else { // 内部存储路径 + val file = getOutputFile(params, source, external, type) + // FileProvider File Path Uri + return UriUtils.getUriForFile(file) + } + return null + } + + /** + * 获取输出文件路径 + * @param params 原始参数 + * @param source 原始数据 + * @param external 是否外部存储 + * @param type 存储类型 + * @return 输出文件路径 + */ + private fun getOutputFile( + params: StorageItem, + source: DevSource, + external: Boolean, + type: StorageType + ): File? { + if (params.getOutputUri() != null) { + val uriPath = UriUtils.getFilePathByUri( + params.getOutputUri() + ) + if (uriPath != null) { + return FileUtils.getFile(uriPath) + } + } + if (external) { // 外部存储路径 + when (type) { + StorageType.IMAGE, StorageType.VIDEO, StorageType.AUDIO, StorageType.DOWNLOAD -> { + // 需考虑 fileName 是否存在后缀情况 ( 如果 mimeType 用了 xxx/* 则需指定后缀 ) + params.getMimeType()?.let { mimeType -> + // 属于 * 自动识别, 则 fileName 要求指定后缀 ( 直接拼接即可 ) + if (mimeType.contains("*")) { + // SDCard/folder/fileName + return params.getExternalFile() + } else { + // 进行获取文件后缀 ( 不含 . ) + val extension = MediaStoreUtils.getExtensionFromMimeType( + mimeType + ) + // 存在后缀才进行拼接 + if (StringUtils.isNotEmpty(extension)) { + // fileName.extension ( 小写后缀 ) + val fileName = + params.getFileName() + "." + extension.lowercase() + // SDCard/folder/fileName.extension + return params.getExternalFile(fileName) + } + // 无法获取到后缀, 可考虑进行拼接返回 + // SDCard/folder/fileName + return params.getExternalFile() + } + } + } + else -> return null + } + } else { // 内部存储路径 + return params.getInternalFile() + } + return null + } + + // ================= + // = 内部插入数据方法 = + // ================= + + /** + * 内部插入数据方法 ( 外部存储空间 ) + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + * @param type 存储类型 + */ + private fun innerInsertToExternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener?, + type: StorageType + ) { + innerInsertThread( + params!!, source!!, listener, true, + convertType(params, source, type) + ) + } + + /** + * 内部插入数据方法 ( 内部存储空间 ) + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + * @param type 存储类型 + */ + private fun innerInsertToInternal( + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener?, + type: StorageType + ) { + innerInsertThread( + params!!, source!!, listener, false, + convertType(params, source, type) + ) + } + + /** + * 内部插入数据方法 ( 后台运行 ) + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + * @param external 是否外部存储 + * @param type 存储类型 + */ + private fun innerInsertThread( + params: StorageItem, + source: DevSource, + listener: OnInsertListener?, + external: Boolean, + type: StorageType + ) { + Thread { + try { + innerInsertFinal(params, source, listener, external, type) + } catch (e: Exception) { + // 统一触发事件回调 + finalCallback( + StorageResult.failure().setError(e), + params, source, listener + ) + } + }.start() + } + + /** + * 内部插入数据方法 ( 最终方法 ) + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + * @param external 是否外部存储 + * @param type 存储类型 + */ + private fun innerInsertFinal( + params: StorageItem, + source: DevSource, + listener: OnInsertListener?, + external: Boolean, + type: StorageType + ) { + // 获取输出 Uri + val outputUri = getOutputUri(params, source, external, type) + // 获取输出文件路径 + val outputFile = getOutputFile(params, source, external, type) + + // ============= + // = 数据类型判断 = + // ============= + + // 存储结果 + var insertResult = false + + if (outputUri != null) { + // 属于 Bitmap、Drawable + if (source.isBitmap || source.isDrawable) { + var bitmap = source.mBitmap + if (source.isDrawable) { + bitmap = ImageUtils.drawableToBitmap(source.mDrawable) + } + insertResult = MediaStoreUtils.insertMedia( + outputUri, bitmap, params.getFormat(), params.getQuality() + ) + } + + // 属于 byte[]、File、InputStream + if (source.isBytes || source.isFile || source.isInputStream) { + var inputStream = source.mInputStream + if (source.isBytes) { + inputStream = StreamUtils.bytesToInputStream(source.mBytes) + } else if (source.isFile) { + inputStream = FileIOUtils.getFileInputStream(source.mFile) + } + insertResult = MediaStoreUtils.insertMedia( + outputUri, inputStream + ) + } + + // 属于 Uri + if (source.isUri) { + insertResult = MediaStoreUtils.insertMedia( + outputUri, source.mUri + ) + } + + // url: 暂不处理 Url 文件 ( 涉及下载 ) 自行下载后传入 File + // resource Id: 无法确认属于 drawable、raw、assets 等 id ( 自行传入 Bitmap、InputStream、byte[] ) + if (source.isUrl || source.isResource) { + } + } + + // ========== + // = 结果回调 = + // ========== + + val result = + if (insertResult) StorageResult.success() else StorageResult.failure() + // 保存结果信息 + result.setUri(outputUri).setFile(outputFile) + .setExternal(external).setType(type) + // 输出 Uri 为 null, 则设置 Error + if (outputUri == null) { + result.setError(Exception("outputUri is null")) + } + // 属于外部存储并且存储成功 + if (external && insertResult) { + val uriPath = UriUtils.getFilePathByUri(outputUri) + if (StringUtils.isNotEmpty(uriPath)) { + result.setFile(FileUtils.getFile(uriPath)) + } + // 通知相册 + MediaStoreUtils.notifyMediaStore(outputUri) + } + // 统一触发事件回调 + finalCallback(result, params, source, listener) + } + + // ========== + // = 回调方法 = + // ========== + + /** + * 最终回调方法 + * @param result 存储结果 + * @param params 原始参数 + * @param source 原始数据 + * @param listener 回调接口 + */ + private fun finalCallback( + result: StorageResult, + params: StorageItem?, + source: DevSource?, + listener: OnInsertListener? + ) { + listener?.onResult(result, params, source) + } +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/analytics/analytics.kt b/lib/DevEngine/src/main/java/dev/expand/engine/analytics/analytics.kt new file mode 100644 index 0000000000..53cf7eddd9 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/analytics/analytics.kt @@ -0,0 +1,60 @@ +package dev.expand.engine.analytics + +import android.app.Application +import android.content.Context +import dev.engine.DevEngine +import dev.engine.analytics.IAnalyticsEngine + +// ============================================== +// = IAnalyticsEngine = +// ============================================== + +/** + * 通过 Key 获取 Analytics Engine + * @param engine String? + * @return IAnalyticsEngine + * 内部做了处理如果匹配不到则返回默认 Analytics Engine + */ +fun String?.getAnalyticsEngine(): IAnalyticsEngine< + in IAnalyticsEngine.EngineConfig, + in IAnalyticsEngine.EngineItem>? { + DevEngine.getAnalytics(this)?.let { value -> + return value + } + return DevEngine.getAnalytics() +} + +// ======================== +// = Key Analytics Engine = +// ======================== + +// ============= +// = 对外公开方法 = +// ============= + +fun Application.analytics_initialize( + engine: String? = null, + config: Config? +) { + engine.getAnalyticsEngine()?.initialize(this, config) +} + +fun Context.analytics_register( + engine: String? = null, + config: Config? +) { + engine.getAnalyticsEngine()?.register(this, config) +} + +fun Context.analytics_unregister( + engine: String? = null, + config: Config? +) { + engine.getAnalyticsEngine()?.unregister(this, config) +} + +fun Item.analytics_track( + engine: String? = null +): Boolean { + return engine.getAnalyticsEngine()?.track(this) ?: false +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/barcode/barcode.kt b/lib/DevEngine/src/main/java/dev/expand/engine/barcode/barcode.kt new file mode 100644 index 0000000000..c0e878f0f5 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/barcode/barcode.kt @@ -0,0 +1,101 @@ +package dev.expand.engine.barcode + +import android.graphics.Bitmap +import dev.engine.DevEngine +import dev.engine.barcode.IBarCodeEngine +import dev.engine.barcode.listener.BarCodeDecodeCallback +import dev.engine.barcode.listener.BarCodeEncodeCallback + +// ========================================================== +// = IBarCodeEngine = +// ========================================================== + +/** + * 通过 Key 获取 BarCode Engine + * @param engine String? + * @return IBarCodeEngine + * 内部做了处理如果匹配不到则返回默认 BarCode Engine + */ +fun String?.getBarCodeEngine(): IBarCodeEngine< + in IBarCodeEngine.EngineConfig, + in IBarCodeEngine.EngineItem, + in IBarCodeEngine.EngineResult>? { + DevEngine.getBarCode(this)?.let { value -> + return value + } + return DevEngine.getBarCode() +} + +// ====================== +// = Key BarCode Engine = +// ====================== + +// ============= +// = 对外公开方法 = +// ============= + +fun barcode_initialize( + engine: String? = null, + config: Config? +) { + engine.getBarCodeEngine()?.initialize(config) +} + +fun barcode_getConfig( + engine: String? = null +): Config? { + return engine.getBarCodeEngine()?.config as? Config +} + +// ========== +// = 生成条码 = +// ========== + +fun Item.barcode_encodeBarCode( + engine: String? = null, + callback: BarCodeEncodeCallback? +) { + engine.getBarCodeEngine()?.encodeBarCode(this, callback) +} + +@Throws(Exception::class) +fun Item.barcode_encodeBarCodeSync( + engine: String? = null +): Bitmap? { + return engine.getBarCodeEngine()?.encodeBarCodeSync(this) +} + +// ========== +// = 解析条码 = +// ========== + +fun Bitmap.barcode_decodeBarCode( + engine: String? = null, + callback: BarCodeDecodeCallback? +) { + engine.getBarCodeEngine()?.let { + (it as? IBarCodeEngine<*, *, Result>)?.decodeBarCode( + this, callback + ) + } +} + +@Throws(Exception::class) +fun Bitmap.barcode_decodeBarCodeSync( + engine: String? = null +): Result? { + return engine.getBarCodeEngine()?.decodeBarCodeSync(this) as? Result +} + +// ========== +// = 其他功能 = +// ========== + +@Throws(Exception::class) +fun Item.barcode_addIconToBarCode( + engine: String? = null, + src: Bitmap?, + icon: Bitmap? +): Bitmap? { + return engine.getBarCodeEngine()?.addIconToBarCode(this, src, icon) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/cache/cache.kt b/lib/DevEngine/src/main/java/dev/expand/engine/cache/cache.kt new file mode 100644 index 0000000000..020ec043e3 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/cache/cache.kt @@ -0,0 +1,438 @@ +package dev.expand.engine.cache + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.os.Parcelable +import dev.engine.DevEngine +import dev.engine.cache.ICacheEngine +import dev.utils.DevFinal +import org.json.JSONArray +import org.json.JSONObject +import java.io.Serializable +import java.lang.reflect.Type + +// ========================================== +// = ICacheEngine = +// ========================================== + +/** + * 通过 Key 获取 Cache Engine + * @param engine String? + * @return ICacheEngine + * 内部做了处理如果匹配不到则返回默认 Cache Engine + */ +fun String?.getCacheEngine(): ICacheEngine? { + DevEngine.getCache(this)?.let { value -> + return value + } + return DevEngine.getCache() +} + +// ======== +// = 默认值 = +// ======== + +private const val INTEGER_DEFAULT: Int = DevFinal.DEFAULT.INT +private const val LONG_DEFAULT: Long = DevFinal.DEFAULT.LONG +private const val FLOAT_DEFAULT: Float = DevFinal.DEFAULT.FLOAT +private const val DOUBLE_DEFAULT: Double = DevFinal.DEFAULT.DOUBLE +private const val BOOLEAN_DEFAULT: Boolean = DevFinal.DEFAULT.BOOLEAN + +// ==================== +// = Key Cache Engine = +// ==================== + +// ============= +// = 对外公开方法 = +// ============= + +fun cache_getConfig( + engine: String? = null +): Config? { + return engine.getCacheEngine()?.config as? Config +} + +// = + +fun String.cache_remove( + engine: String? = null +) { + engine.getCacheEngine()?.remove(this) +} + +fun Array.cache_removeForKeys( + engine: String? = null +) { + engine.getCacheEngine()?.removeForKeys(this) +} + +fun String.cache_contains( + engine: String? = null +): Boolean { + return engine.getCacheEngine()?.contains(this) ?: false +} + +fun String.cache_isDue( + engine: String? = null +): Boolean { + return engine.getCacheEngine()?.isDue(this) ?: false +} + +fun cache_clear( + engine: String? = null +) { + engine.getCacheEngine()?.clear() +} + +fun cache_clearDue( + engine: String? = null +) { + engine.getCacheEngine()?.clearDue() +} + +fun cache_clearType( + engine: String? = null, + type: Int +) { + engine.getCacheEngine()?.clearType(type) +} + +fun String.cache_getItemByKey( + engine: String? = null +): Item? { + return engine.getCacheEngine()?.getItemByKey(this) as? Item +} + +fun cache_getKeys( + engine: String? = null +): List? { + return engine.getCacheEngine()?.keys as? List +} + +fun cache_getPermanentKeys( + engine: String? = null +): List? { + return engine.getCacheEngine()?.permanentKeys as? List +} + +fun cache_getCount( + engine: String? = null +): Int { + return engine.getCacheEngine()?.count ?: 0 +} + +fun cache_getSize( + engine: String? = null +): Long { + return engine.getCacheEngine()?.size ?: 0L +} + +// ======= +// = 存储 = +// ======= + +fun String.cache_put( + engine: String? = null, + value: Int, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Long, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Float, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Double, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Boolean, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: String?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: ByteArray?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Bitmap?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Drawable?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Serializable?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: Parcelable?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: JSONObject?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: JSONArray?, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +fun String.cache_put( + engine: String? = null, + value: T, + validTime: Long +): Boolean { + return engine.getCacheEngine()?.put(this, value, validTime) ?: false +} + +// ======= +// = 获取 = +// ======= + +fun String.cache_getInt( + engine: String? = null +): Int { + return engine.getCacheEngine()?.getInt(this) ?: INTEGER_DEFAULT +} + +fun String.cache_getLong( + engine: String? = null +): Long { + return engine.getCacheEngine()?.getLong(this) ?: LONG_DEFAULT +} + +fun String.cache_getFloat( + engine: String? = null +): Float { + return engine.getCacheEngine()?.getFloat(this) ?: FLOAT_DEFAULT +} + +fun String.cache_getDouble( + engine: String? = null +): Double { + return engine.getCacheEngine()?.getDouble(this) ?: DOUBLE_DEFAULT +} + +fun String.cache_getBoolean( + engine: String? = null +): Boolean { + return engine.getCacheEngine()?.getBoolean(this) ?: BOOLEAN_DEFAULT +} + +fun String.cache_getString( + engine: String? = null +): String? { + return engine.getCacheEngine()?.getString(this) +} + +fun String.cache_getBytes( + engine: String? = null +): ByteArray? { + return engine.getCacheEngine()?.getBytes(this) +} + +fun String.cache_getBitmap( + engine: String? = null +): Bitmap? { + return engine.getCacheEngine()?.getBitmap(this) +} + +fun String.cache_getDrawable( + engine: String? = null +): Drawable? { + return engine.getCacheEngine()?.getDrawable(this) +} + +fun String.cache_getSerializable( + engine: String? = null +): Any? { + return engine.getCacheEngine()?.getSerializable(this) +} + +fun String.cache_getParcelable( + engine: String? = null, + creator: Parcelable.Creator? +): T? { + return engine.getCacheEngine()?.getParcelable(this, creator) +} + +fun String.cache_getJSONObject( + engine: String? = null +): JSONObject? { + return engine.getCacheEngine()?.getJSONObject(this) +} + +fun String.cache_getJSONArray( + engine: String? = null +): JSONArray? { + return engine.getCacheEngine()?.getJSONArray(this) +} + +fun String.cache_getEntity( + engine: String? = null, + typeOfT: Type? +): T? { + return engine.getCacheEngine()?.getEntity(this, typeOfT) +} + +// ================= +// = 获取 ( 默认值 ) = +// ================= + +fun String.cache_getInt( + engine: String? = null, + defaultValue: Int +): Int { + return engine.getCacheEngine()?.getInt(this, defaultValue) ?: defaultValue +} + +fun String.cache_getLong( + engine: String? = null, + defaultValue: Long +): Long { + return engine.getCacheEngine()?.getLong(this, defaultValue) ?: defaultValue +} + +fun String.cache_getFloat( + engine: String? = null, + defaultValue: Float +): Float { + return engine.getCacheEngine()?.getFloat(this, defaultValue) ?: defaultValue +} + +fun String.cache_getDouble( + engine: String? = null, + defaultValue: Double +): Double { + return engine.getCacheEngine()?.getDouble(this, defaultValue) ?: defaultValue +} + +fun String.cache_getBoolean( + engine: String? = null, + defaultValue: Boolean +): Boolean { + return engine.getCacheEngine()?.getBoolean(this, defaultValue) ?: defaultValue +} + +fun String.cache_getString( + engine: String? = null, + defaultValue: String? +): String? { + return engine.getCacheEngine()?.getString(this, defaultValue) +} + +fun String.cache_getBytes( + engine: String? = null, + defaultValue: ByteArray? +): ByteArray? { + return engine.getCacheEngine()?.getBytes(this, defaultValue) +} + +fun String.cache_getBitmap( + engine: String? = null, + defaultValue: Bitmap? +): Bitmap? { + return engine.getCacheEngine()?.getBitmap(this, defaultValue) +} + +fun String.cache_getDrawable( + engine: String? = null, + defaultValue: Drawable? +): Drawable? { + return engine.getCacheEngine()?.getDrawable(this, defaultValue) +} + +fun String.cache_getSerializable( + engine: String? = null, + defaultValue: Any? +): Any? { + return engine.getCacheEngine()?.getSerializable(this, defaultValue) +} + +fun String.cache_getParcelable( + engine: String? = null, + creator: Parcelable.Creator?, + defaultValue: T? +): T? { + return engine.getCacheEngine()?.getParcelable(this, creator, defaultValue) +} + +fun String.cache_getJSONObject( + engine: String? = null, + defaultValue: JSONObject? +): JSONObject? { + return engine.getCacheEngine()?.getJSONObject(this, defaultValue) +} + +fun String.cache_getJSONArray( + engine: String? = null, + defaultValue: JSONArray? +): JSONArray? { + return engine.getCacheEngine()?.getJSONArray(this, defaultValue) +} + +fun String.cache_getEntity( + engine: String? = null, + typeOfT: Type?, + defaultValue: T? +): T? { + return engine.getCacheEngine()?.getEntity(this, typeOfT, defaultValue) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/compress/compress.kt b/lib/DevEngine/src/main/java/dev/expand/engine/compress/compress.kt new file mode 100644 index 0000000000..9abd5c3df0 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/compress/compress.kt @@ -0,0 +1,80 @@ +package dev.expand.engine.compress + +import dev.engine.DevEngine +import dev.engine.compress.ICompressEngine +import dev.engine.compress.listener.CompressFilter +import dev.engine.compress.listener.OnCompressListener +import dev.engine.compress.listener.OnRenameListener + +// ================================= +// = ICompressEngine = +// ================================= + +/** + * 通过 Key 获取 Compress Engine + * @param engine String? + * @return ICompressEngine + * 内部做了处理如果匹配不到则返回默认 Compress Engine + */ +fun String?.getCompressEngine(): ICompressEngine? { + DevEngine.getCompress(this)?.let { value -> + return value + } + return DevEngine.getCompress() +} + +// ======================= +// = Key Compress Engine = +// ======================= + +// ============= +// = 对外公开方法 = +// ============= + +fun compress_any( + engine: String? = null, + data: Any?, + config: Config?, + compressListener: OnCompressListener? +): Boolean { + return engine.getCompressEngine()?.compress(data, config, compressListener) ?: false +} + +fun compress_any( + engine: String? = null, + data: Any?, + config: Config?, + filter: CompressFilter?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? +): Boolean { + return engine.getCompressEngine()?.compress( + data, config, filter, renameListener, compressListener + ) ?: false +} + +// = + +fun compress_list( + engine: String? = null, + lists: List<*>?, + config: Config?, + compressListener: OnCompressListener? +): Boolean { + return engine.getCompressEngine()?.compress( + lists, config, compressListener + ) ?: false +} + +fun compress_list( + engine: String? = null, + lists: List<*>?, + config: Config?, + filter: CompressFilter?, + renameListener: OnRenameListener?, + compressListener: OnCompressListener? +): Boolean { + return engine.getCompressEngine()?.compress( + lists, config, filter, renameListener, compressListener + ) ?: false +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/image/image.kt b/lib/DevEngine/src/main/java/dev/expand/engine/image/image.kt new file mode 100644 index 0000000000..eeb9d9ff68 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/image/image.kt @@ -0,0 +1,370 @@ +package dev.expand.engine.image + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.view.View +import android.widget.ImageView +import androidx.fragment.app.Fragment +import dev.base.DevSource +import dev.engine.DevEngine +import dev.engine.image.IImageEngine +import dev.engine.image.listener.LoadListener +import dev.engine.image.listener.OnConvertListener + +// ============================== +// = IImageEngine = +// ============================== + +/** + * 通过 Key 获取 Image Engine + * @param engine String? + * @return IImageEngine + * 内部做了处理如果匹配不到则返回默认 Image Engine + */ +fun String?.getImageEngine(): IImageEngine? { + DevEngine.getImage(this)?.let { value -> + return value + } + return DevEngine.getImage() +} + +// ========== +// = 快捷方法 = +// ========== + +/** + * 请求校验 Source + * @return `true` 直接 return, `false` 表示可以接着操作 + */ +fun DevSource?.requireSource(): Boolean { + if (this?.isSource == true) { + return false + } + return true +} + +// ==================== +// = Key Image Engine = +// ==================== + +// ==================== +// = pause and resume = +// ==================== + +fun Fragment.pause( + engine: String? = null +) { + engine.getImageEngine()?.pause(this) +} + +fun Fragment.resume( + engine: String? = null +) { + engine.getImageEngine()?.resume(this) +} + +fun Context.pause( + engine: String? = null +) { + engine.getImageEngine()?.pause(this) +} + +fun Context.resume( + engine: String? = null +) { + engine.getImageEngine()?.resume(this) +} + +// =========== +// = preload = +// =========== + +fun Context.preload( + engine: String? = null, + source: DevSource? +) { + if (source.requireSource()) return + engine.getImageEngine()?.preload(this, source) +} + +fun Context.preload( + engine: String? = null, + source: DevSource?, + config: Config? +) { + if (source.requireSource()) return + engine.getImageEngine()?.preload(this, source, config) +} + +// ========= +// = clear = +// ========= + +fun View.clear( + engine: String? = null +) { + engine.getImageEngine()?.clear(this) +} + +fun Fragment.clear( + engine: String? = null, + view: View? +) { + engine.getImageEngine()?.clear(this, view) +} + +fun Context.clearDiskCache( + engine: String? = null +) { + engine.getImageEngine()?.clearDiskCache(this) +} + +fun Context.clearMemoryCache( + engine: String? = null +) { + engine.getImageEngine()?.clearMemoryCache(this) +} + +fun Context.clearAllCache( + engine: String? = null +) { + engine.getImageEngine()?.clearAllCache(this) +} + +// ========= +// = other = +// ========= + +fun Context.lowMemory( + engine: String? = null +) { + engine.getImageEngine()?.lowMemory(this) +} + +// =========== +// = display = +// =========== + +fun ImageView.display( + engine: String? = null, + source: DevSource? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(this, source) +} + +fun ImageView.display( + engine: String? = null, + source: DevSource?, + config: Config? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(this, source, config) +} + +fun ImageView.display( + engine: String? = null, + source: DevSource?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(this, source, listener) +} + +fun ImageView.display( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(this, source, config, listener) +} + +// = + +fun ImageView.display( + engine: String? = null, + fragment: Fragment?, + source: DevSource? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(fragment, this, source) +} + +fun ImageView.display( + engine: String? = null, + fragment: Fragment?, + source: DevSource?, + config: Config? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(fragment, this, source, config) +} + +fun ImageView.display( + engine: String? = null, + fragment: Fragment?, + source: DevSource?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(fragment, this, source, listener) +} + +fun ImageView.display( + engine: String? = null, + fragment: Fragment?, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.display(fragment, this, source, config, listener) +} + +// ======== +// = load = +// ======== + +fun Context.loadImage( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadImage(this, source, config, listener) +} + +fun Fragment.loadImage( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadImage(this, source, config, listener) +} + +fun Context.loadImage( + engine: String? = null, + source: DevSource?, + config: Config?, + type: Class<*>? +): T? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadImage(this, source, config, type) +} + +fun Context.loadImageThrows( + engine: String? = null, + source: DevSource?, + config: Config?, + type: Class<*>? +): T? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadImageThrows(this, source, config, type) +} + +// = + +fun Context.loadBitmap( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadBitmap(this, source, config, listener) +} + +fun Fragment.loadBitmap( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadBitmap(this, source, config, listener) +} + +fun Context.loadBitmap( + engine: String? = null, + source: DevSource?, + config: Config? +): Bitmap? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadBitmap(this, source, config) +} + +fun Context.loadBitmapThrows( + engine: String? = null, + source: DevSource?, + config: Config? +): Bitmap? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadBitmapThrows(this, source, config) +} + +// = + +fun Context.loadDrawable( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadDrawable(this, source, config, listener) +} + +fun Fragment.loadDrawable( + engine: String? = null, + source: DevSource?, + config: Config?, + listener: LoadListener? +) { + if (source.requireSource()) return + engine.getImageEngine()?.loadDrawable(this, source, config, listener) +} + +fun Context.loadDrawable( + engine: String? = null, + source: DevSource?, + config: Config? +): Drawable? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadDrawable(this, source, config) +} + +fun Context.loadDrawableThrows( + engine: String? = null, + source: DevSource?, + config: Config? +): Drawable? { + if (source.requireSource()) return null + return engine.getImageEngine()?.loadDrawableThrows(this, source, config) +} + +// =========== +// = convert = +// =========== + +fun Context.convertImageFormat( + engine: String? = null, + sources: MutableList?, + listener: OnConvertListener? +): Boolean { + return engine.getImageEngine()?.convertImageFormat(this, sources, listener) ?: false +} + +fun Context.convertImageFormat( + engine: String? = null, + sources: MutableList?, + config: Config?, + listener: OnConvertListener? +): Boolean { + return engine.getImageEngine()?.convertImageFormat(this, sources, config, listener) ?: false +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/json/json.kt b/lib/DevEngine/src/main/java/dev/expand/engine/json/json.kt new file mode 100644 index 0000000000..55eca3447e --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/json/json.kt @@ -0,0 +1,127 @@ +package dev.expand.engine.json + +import dev.engine.DevEngine +import dev.engine.json.IJSONEngine +import java.lang.reflect.Type + +// ============================= +// = IJSONEngine = +// ============================= + +/** + * 通过 Key 获取 JSON Engine + * @param engine String? + * @return IJSONEngine + * 内部做了处理如果匹配不到则返回默认 JSON Engine + */ +fun String?.getJSONEngine(): IJSONEngine? { + DevEngine.getJSON(this)?.let { value -> + return value + } + return DevEngine.getJSON() +} + +// =================== +// = Key JSON Engine = +// =================== + +// ========== +// = 转换方法 = +// ========== + +fun Any.toJson( + engine: String? = null +): String? { + return engine.getJSONEngine()?.toJson(this) +} + +fun Any.toJson( + engine: String? = null, + config: Config? +): String? { + return engine.getJSONEngine()?.toJson(this, config) +} + +// = + +fun String.fromJson( + engine: String? = null, + classOfT: Class? +): T? { + return engine.getJSONEngine()?.fromJson(this, classOfT) +} + +fun String.fromJson( + engine: String? = null, + classOfT: Class?, + config: Config? +): T? { + return engine.getJSONEngine()?.fromJson(this, classOfT, config) +} + +// = + +fun String.fromJson( + engine: String? = null, + typeOfT: Type? +): T? { + return engine.getJSONEngine()?.fromJson(this, typeOfT) +} + +fun String.fromJson( + engine: String? = null, + typeOfT: Type?, + config: Config? +): T? { + return engine.getJSONEngine()?.fromJson(this, typeOfT, config) +} + +// ========== +// = 其他方法 = +// ========== + +fun String.isJSON( + engine: String? = null +): Boolean { + return engine.getJSONEngine()?.isJSON(this) ?: false +} + +fun String.isJSONObject( + engine: String? = null +): Boolean { + return engine.getJSONEngine()?.isJSONObject(this) ?: false +} + +fun String.isJSONArray( + engine: String? = null +): Boolean { + return engine.getJSONEngine()?.isJSONArray(this) ?: false +} + +// = + +fun String.toJsonIndent( + engine: String? = null +): String? { + return engine.getJSONEngine()?.toJsonIndent(this) +} + +fun String.toJsonIndent( + engine: String? = null, + config: Config? +): String? { + return engine.getJSONEngine()?.toJsonIndent(this, config) +} + +fun Any.toJsonIndent( + engine: String? = null +): String? { + return engine.getJSONEngine()?.toJsonIndent(this) +} + +fun Any.toJsonIndent( + engine: String? = null, + config: Config? +): String? { + return engine.getJSONEngine()?.toJsonIndent(this, config) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/keyvalue/key_value.kt b/lib/DevEngine/src/main/java/dev/expand/engine/keyvalue/key_value.kt new file mode 100644 index 0000000000..aabf149ffc --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/keyvalue/key_value.kt @@ -0,0 +1,228 @@ +package dev.expand.engine.keyvalue + +import dev.engine.DevEngine +import dev.engine.keyvalue.IKeyValueEngine +import dev.utils.DevFinal +import java.lang.reflect.Type + +// ================================= +// = IKeyValueEngine = +// ================================= + +/** + * 通过 Key 获取 KeyValue Engine + * @param engine String? + * @return IKeyValueEngine + * 内部做了处理如果匹配不到则返回默认 KeyValue Engine + */ +fun String?.getKeyValueEngine(): IKeyValueEngine? { + DevEngine.getKeyValue(this)?.let { value -> + return value + } + return DevEngine.getKeyValue() +} + +// ======== +// = 默认值 = +// ======== + +private const val INTEGER_DEFAULT: Int = DevFinal.DEFAULT.INT +private const val LONG_DEFAULT: Long = DevFinal.DEFAULT.LONG +private const val FLOAT_DEFAULT: Float = DevFinal.DEFAULT.FLOAT +private const val DOUBLE_DEFAULT: Double = DevFinal.DEFAULT.DOUBLE +private const val BOOLEAN_DEFAULT: Boolean = DevFinal.DEFAULT.BOOLEAN + +// ======================= +// = Key KeyValue Engine = +// ======================= + +// ============= +// = 对外公开方法 = +// ============= + +fun kv_getConfig( + engine: String? = null +): Config? { + return engine.getKeyValueEngine()?.config as? Config +} + +// = + +fun String.kv_remove( + engine: String? = null +) { + engine.getKeyValueEngine()?.remove(this) +} + +fun kv_removeForKeys( + engine: String? = null, + keys: Array? +) { + engine.getKeyValueEngine()?.removeForKeys(keys) +} + +fun String.kv_contains( + engine: String? = null +): Boolean { + return engine.getKeyValueEngine()?.contains(this) ?: false +} + +fun kv_clear( + engine: String? = null +) { + engine.getKeyValueEngine()?.clear() +} + +// ======= +// = 存储 = +// ======= + +fun String.kv_putInt( + engine: String? = null, + value: Int +): Boolean { + return engine.getKeyValueEngine()?.putInt(this, value) ?: false +} + +fun String.kv_putLong( + engine: String? = null, + value: Long +): Boolean { + return engine.getKeyValueEngine()?.putLong(this, value) ?: false +} + +fun String.kv_putFloat( + engine: String? = null, + value: Float +): Boolean { + return engine.getKeyValueEngine()?.putFloat(this, value) ?: false +} + +fun String.kv_putDouble( + engine: String? = null, + value: Double +): Boolean { + return engine.getKeyValueEngine()?.putDouble(this, value) ?: false +} + +fun String.kv_putBoolean( + engine: String? = null, + value: Boolean +): Boolean { + return engine.getKeyValueEngine()?.putBoolean(this, value) ?: false +} + +fun String.kv_putString( + engine: String? = null, + value: String? +): Boolean { + return engine.getKeyValueEngine()?.putString(this, value) ?: false +} + +fun String.kv_putEntity( + engine: String? = null, + value: T +): Boolean { + return engine.getKeyValueEngine()?.putEntity(this, value) ?: false +} + +// ======= +// = 获取 = +// ======= + +fun String.kv_getInt( + engine: String? = null +): Int { + return engine.getKeyValueEngine()?.getInt(this) ?: INTEGER_DEFAULT +} + +fun String.kv_getLong( + engine: String? = null +): Long { + return engine.getKeyValueEngine()?.getLong(this) ?: LONG_DEFAULT +} + +fun String.kv_getFloat( + engine: String? = null +): Float { + return engine.getKeyValueEngine()?.getFloat(this) ?: FLOAT_DEFAULT +} + +fun String.kv_getDouble( + engine: String? = null +): Double { + return engine.getKeyValueEngine()?.getDouble(this) ?: DOUBLE_DEFAULT +} + +fun String.kv_getBoolean( + engine: String? = null +): Boolean { + return engine.getKeyValueEngine()?.getBoolean(this) ?: BOOLEAN_DEFAULT +} + +fun String.kv_getString( + engine: String? = null +): String? { + return engine.getKeyValueEngine()?.getString(this) +} + +fun String.kv_getEntity( + engine: String? = null, + typeOfT: Type? +): T? { + return engine.getKeyValueEngine()?.getEntity(this, typeOfT) as? T +} + +// ================= +// = 获取 ( 默认值 ) = +// ================= + +fun String.kv_getInt( + engine: String? = null, + defaultValue: Int +): Int { + return engine.getKeyValueEngine()?.getInt(this, defaultValue) ?: defaultValue +} + +fun String.kv_getLong( + engine: String? = null, + defaultValue: Long +): Long { + return engine.getKeyValueEngine()?.getLong(this, defaultValue) ?: defaultValue +} + +fun String.kv_getFloat( + engine: String? = null, + defaultValue: Float +): Float { + return engine.getKeyValueEngine()?.getFloat(this, defaultValue) ?: defaultValue +} + +fun String.kv_getDouble( + engine: String? = null, + defaultValue: Double +): Double { + return engine.getKeyValueEngine()?.getDouble(this, defaultValue) ?: defaultValue +} + +fun String.kv_getBoolean( + engine: String? = null, + defaultValue: Boolean +): Boolean { + return engine.getKeyValueEngine()?.getBoolean(this, defaultValue) ?: defaultValue +} + +fun String.kv_getString( + engine: String? = null, + defaultValue: String? +): String? { + return engine.getKeyValueEngine()?.getString(this, defaultValue) +} + +fun String.kv_getEntity( + engine: String? = null, + typeOfT: Type?, + defaultValue: T? +): T? { + return engine.getKeyValueEngine()?.getEntity(this, typeOfT, defaultValue) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/log/log.kt b/lib/DevEngine/src/main/java/dev/expand/engine/log/log.kt new file mode 100644 index 0000000000..a3d11768b9 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/log/log.kt @@ -0,0 +1,199 @@ +package dev.expand.engine.log + +import dev.engine.DevEngine +import dev.engine.log.ILogEngine + +// ============== +// = ILogEngine = +// ============== + +/** + * 通过 Key 获取 Log Engine + * @param engine String? + * @return ILogEngine + * 内部做了处理如果匹配不到则返回默认 Log Engine + */ +fun String?.getLogEngine(): ILogEngine? { + DevEngine.getLog(this)?.let { value -> + return value + } + return DevEngine.getLog() +} + +// ================== +// = Key Log Engine = +// ================== + +fun log_isPrintLog( + engine: String? = null +): Boolean { + return engine.getLogEngine()?.isPrintLog ?: false +} + +// ============================= +// = 使用默认 TAG ( 日志打印方法 ) = +// ============================= + +fun log_d( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.d(message, *args) +} + +fun log_e( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.e(message, *args) +} + +fun log_e( + engine: String? = null, + throwable: Throwable? +) { + engine.getLogEngine()?.e(throwable) +} + +fun log_e( + engine: String? = null, + throwable: Throwable?, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.e(throwable, message, *args) +} + +fun log_w( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.w(message, *args) +} + +fun log_i( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.i(message, *args) +} + +fun log_v( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.v(message, *args) +} + +fun log_wtf( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.wtf(message, *args) +} + +// = + +fun log_json( + engine: String? = null, + json: String? +) { + engine.getLogEngine()?.json(json) +} + +fun log_xml( + engine: String? = null, + xml: String? +) { + engine.getLogEngine()?.xml(xml) +} + +// ============================== +// = 使用自定义 TAG ( 日志打印方法 ) = +// ============================== + +fun String.log_dTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.dTag(this, message, *args) +} + +fun String.log_eTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.eTag(this, message, *args) +} + +fun String.log_eTag( + engine: String? = null, + throwable: Throwable? +) { + engine.getLogEngine()?.eTag(this, throwable) +} + +fun String.log_eTag( + engine: String? = null, + throwable: Throwable?, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.eTag(this, throwable, message, *args) +} + +fun String.log_wTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.wTag(this, message, *args) +} + +fun String.log_iTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.iTag(this, message, *args) +} + +fun String.log_vTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.vTag(this, message, *args) +} + +fun String.log_wtfTag( + engine: String? = null, + message: String?, + vararg args: Any? +) { + engine.getLogEngine()?.wtfTag(this, message, *args) +} + +// = + +fun String.log_jsonTag( + engine: String? = null, + json: String? +) { + engine.getLogEngine()?.jsonTag(this, json) +} + +fun String.log_xmlTag( + engine: String? = null, + xml: String? +) { + engine.getLogEngine()?.xmlTag(this, xml) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/media/media.kt b/lib/DevEngine/src/main/java/dev/expand/engine/media/media.kt new file mode 100644 index 0000000000..8269b982bd --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/media/media.kt @@ -0,0 +1,186 @@ +package dev.expand.engine.media + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.fragment.app.Fragment +import dev.engine.DevEngine +import dev.engine.media.IMediaEngine + +// ========================================== +// = IMediaEngine = +// ========================================== + +/** + * 通过 Key 获取 Media Engine + * @param engine String? + * @return IMediaEngine + * 内部做了处理如果匹配不到则返回默认 Media Engine + */ +fun String?.getMediaEngine(): IMediaEngine? { + DevEngine.getMedia(this)?.let { value -> + return value + } + return DevEngine.getMedia() +} + +// ==================== +// = Key Media Engine = +// ==================== + +// ========== +// = 配置方法 = +// ========== + +fun media_getConfig( + engine: String? = null +): Config? { + return engine.getMediaEngine()?.config as? Config +} + +fun media_setConfig( + engine: String? = null, + config: Config? +) { + engine.getMediaEngine()?.config = config +} + +// ============= +// = 对外公开方法 = +// ============= + +fun Activity.media_openCamera( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openCamera(this) ?: false +} + +fun Activity.media_openCamera( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openCamera(this, config) ?: false +} + +fun Fragment.media_openCamera( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openCamera(this) ?: false +} + +fun Fragment.media_openCamera( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openCamera(this, config) ?: false +} + +// = + +fun Activity.media_openGallery( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openGallery(this) ?: false +} + +fun Activity.media_openGallery( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openGallery(this, config) ?: false +} + +fun Fragment.media_openGallery( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openGallery(this) ?: false +} + +fun Fragment.media_openGallery( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openGallery(this, config) ?: false +} + +// = + +fun Activity.media_openPreview( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openPreview(this) ?: false +} + +fun Activity.media_openPreview( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openPreview(this, config) ?: false +} + +fun Fragment.media_openPreview( + engine: String? = null +): Boolean { + return engine.getMediaEngine()?.openPreview(this) ?: false +} + +fun Fragment.media_openPreview( + engine: String? = null, + config: Config? +): Boolean { + return engine.getMediaEngine()?.openPreview(this, config) ?: false +} + +// ========== +// = 其他方法 = +// ========== + +fun Context.media_deleteCacheDirFile( + engine: String? = null, + type: Int +) { + engine.getMediaEngine()?.deleteCacheDirFile(this, type) +} + +fun Context.media_deleteAllCacheDirFile( + engine: String? = null +) { + engine.getMediaEngine()?.deleteAllCacheDirFile(this) +} + +fun media_isMediaSelectorResult( + engine: String? = null, + requestCode: Int, + resultCode: Int +): Boolean { + return engine.getMediaEngine()?.isMediaSelectorResult(requestCode, resultCode) ?: false +} + +// = + +fun Intent.media_getSelectors( + engine: String? = null +): List? { + return engine.getMediaEngine()?.getSelectors(this) as? List +} + +fun Intent.media_getSelectorUris( + engine: String? = null, + original: Boolean +): List? { + return engine.getMediaEngine()?.getSelectorUris(this, original) +} + +fun Intent.media_getSingleSelector( + engine: String? = null +): Data? { + return engine.getMediaEngine()?.getSingleSelector(this) as? Data +} + +fun Intent.media_getSingleSelectorUri( + engine: String? = null, + original: Boolean +): Uri? { + return engine.getMediaEngine()?.getSingleSelectorUri(this, original) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/permission/permission.kt b/lib/DevEngine/src/main/java/dev/expand/engine/permission/permission.kt new file mode 100644 index 0000000000..be134fd80a --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/permission/permission.kt @@ -0,0 +1,93 @@ +package dev.expand.engine.permission + +import android.app.Activity +import android.content.Context +import dev.engine.DevEngine +import dev.engine.permission.IPermissionEngine + +// ===================== +// = IPermissionEngine = +// ===================== + +/** + * 通过 Key 获取 Permission Engine + * @param engine String? + * @return IPermissionEngine + * 内部做了处理如果匹配不到则返回默认 Permission Engine + */ +fun String?.getPermissionEngine(): IPermissionEngine? { + DevEngine.getPermission(this)?.let { value -> + return value + } + return DevEngine.getPermission() +} + +// ========================= +// = Key Permission Engine = +// ========================= + +// ============= +// = 对外公开方法 = +// ============= + +fun Context.permission_isGranted( + engine: String? = null, + vararg permissions: String? +): Boolean { + return engine.getPermissionEngine()?.isGranted(this, *permissions) ?: false +} + +fun Activity.permission_shouldShowRequestPermissionRationale( + engine: String? = null, + vararg permissions: String? +): Boolean { + return engine.getPermissionEngine()?.shouldShowRequestPermissionRationale( + this, *permissions + ) ?: false +} + +fun Activity.permission_getDeniedPermissionStatus( + engine: String? = null, + shouldShow: Boolean, + vararg permissions: String? +): MutableList { + return engine.getPermissionEngine()?.getDeniedPermissionStatus( + this, shouldShow, *permissions + ) ?: mutableListOf() +} + +fun Activity.permission_againRequest( + engine: String? = null, + callback: IPermissionEngine.Callback?, + deniedList: MutableList? +): Int { + return engine.getPermissionEngine()?.againRequest(this, callback, deniedList) ?: 0 +} + +// ============= +// = 权限请求方法 = +// ============= + +fun Activity.permission_request( + engine: String? = null, + permissions: Array? +) { + engine.getPermissionEngine()?.request(this, permissions) +} + +fun Activity.permission_request( + engine: String? = null, + permissions: Array?, + callback: IPermissionEngine.Callback? +) { + engine.getPermissionEngine()?.request(this, permissions, callback) +} + +fun Activity.permission_request( + engine: String? = null, + permissions: Array?, + callback: IPermissionEngine.Callback?, + againRequest: Boolean +) { + engine.getPermissionEngine()?.request(this, permissions, callback, againRequest) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/push/push.kt b/lib/DevEngine/src/main/java/dev/expand/engine/push/push.kt new file mode 100644 index 0000000000..ae26e0e871 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/push/push.kt @@ -0,0 +1,129 @@ +package dev.expand.engine.push + +import android.app.Application +import android.content.Context +import dev.engine.DevEngine +import dev.engine.push.IPushEngine + +// ========================================= +// = IPushEngine = +// ========================================= + +/** + * 通过 Key 获取 Push Engine + * @param engine String? + * @return IPushEngine + * 内部做了处理如果匹配不到则返回默认 Push Engine + */ +fun String?.getPushEngine(): IPushEngine? { + DevEngine.getPush(this)?.let { value -> + return value + } + return DevEngine.getPush() +} + +// =================== +// = Key Push Engine = +// =================== + +// ============= +// = 对外公开方法 = +// ============= + +fun Application.push_initialize( + engine: String? = null, + config: Config? +) { + engine.getPushEngine()?.initialize(this, config) +} + +fun Context.push_register( + engine: String? = null, + config: Config? +) { + engine.getPushEngine()?.register(this, config) +} + +fun Context.push_unregister( + engine: String? = null, + config: Config? +) { + engine.getPushEngine()?.unregister(this, config) +} + +// = + +fun push_onReceiveServicePid( + engine: String? = null, + context: Context?, + pid: Int +) { + engine.getPushEngine()?.onReceiveServicePid(context, pid) +} + +fun push_onReceiveClientId( + engine: String? = null, + context: Context?, + clientId: String? +) { + engine.getPushEngine()?.onReceiveClientId(context, clientId) +} + +fun push_onReceiveDeviceToken( + engine: String? = null, + context: Context?, + deviceToken: String? +) { + engine.getPushEngine()?.onReceiveDeviceToken(context, deviceToken) +} + +fun push_onReceiveOnlineState( + engine: String? = null, + context: Context?, + online: Boolean +) { + engine.getPushEngine()?.onReceiveOnlineState(context, online) +} + +fun push_onReceiveCommandResult( + engine: String? = null, + context: Context?, + message: Item? +) { + engine.getPushEngine()?.onReceiveCommandResult(context, message) +} + +fun push_onNotificationMessageArrived( + engine: String? = null, + context: Context?, + message: Item? +) { + engine.getPushEngine()?.onNotificationMessageArrived(context, message) +} + +fun push_onNotificationMessageClicked( + engine: String? = null, + context: Context?, + message: Item? +) { + engine.getPushEngine()?.onNotificationMessageClicked(context, message) +} + +fun push_onReceiveMessageData( + engine: String? = null, + context: Context?, + message: Item? +) { + engine.getPushEngine()?.onReceiveMessageData(context, message) +} + +// =============== +// = 转换 Message = +// =============== + +fun push_convertMessage( + engine: String? = null, + message: Any? +): Item? { + return engine.getPushEngine()?.convertMessage(message) as? Item +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/share/share.kt b/lib/DevEngine/src/main/java/dev/expand/engine/share/share.kt new file mode 100644 index 0000000000..662ede94c2 --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/share/share.kt @@ -0,0 +1,188 @@ +package dev.expand.engine.share + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.Intent +import dev.engine.DevEngine +import dev.engine.share.IShareEngine +import dev.engine.share.listener.ShareListener + +// ========================================== +// = IShareEngine = +// ========================================== + +/** + * 通过 Key 获取 Share Engine + * @param engine String? + * @return IShareEngine + * 内部做了处理如果匹配不到则返回默认 Share Engine + */ +fun String?.getShareEngine(): IShareEngine? { + DevEngine.getShare(this)?.let { value -> + return value + } + return DevEngine.getShare() +} + +// ==================== +// = Key Share Engine = +// ==================== + +// ============= +// = 对外公开方法 = +// ============= + +fun Application.share_initialize( + engine: String? = null, + config: Config? +) { + engine.getShareEngine()?.initialize(this, config) +} + +// ========== +// = 分享操作 = +// ========== + +fun Activity.share_openMinApp( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.openMinApp( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareMinApp( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareMinApp( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareUrl( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareUrl( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareImage( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareImage( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareImageList( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareImageList( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareText( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareText( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareVideo( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareVideo( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareMusic( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareMusic( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareEmoji( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareEmoji( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_shareFile( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.shareFile( + this, params, listener + ) ?: false + } ?: false +} + +fun Activity.share_share( + engine: String? = null, + params: Item?, + listener: ShareListener? +): Boolean { + return engine.getShareEngine()?.let { + return (it as? IShareEngine<*, Item>)?.share( + this, params, listener + ) ?: false + } ?: false +} + +// = + +fun Context.share_onActivityResult( + engine: String? = null, + requestCode: Int, + resultCode: Int, + intent: Intent? +) { + engine.getShareEngine()?.onActivityResult(this, requestCode, resultCode, intent) +} \ No newline at end of file diff --git a/lib/DevEngine/src/main/java/dev/expand/engine/storage/storage.kt b/lib/DevEngine/src/main/java/dev/expand/engine/storage/storage.kt new file mode 100644 index 0000000000..53d6abfe6a --- /dev/null +++ b/lib/DevEngine/src/main/java/dev/expand/engine/storage/storage.kt @@ -0,0 +1,159 @@ +package dev.expand.engine.storage + +import dev.base.DevSource +import dev.engine.DevEngine +import dev.engine.storage.IStorageEngine +import dev.engine.storage.listener.OnInsertListener + +// ============================================ +// = IStorageEngine = +// ============================================ + +/** + * 通过 Key 获取 Storage Engine + * @param engine String? + * @return IStorageEngine + * 内部做了处理如果匹配不到则返回默认 Storage Engine + */ +fun String?.getStorageEngine(): IStorageEngine? { + DevEngine.getStorage(this)?.let { value -> + return value + } + return DevEngine.getStorage() +} + +// ====================== +// = Key Storage Engine = +// ====================== + +// ============= +// = 对外公开方法 = +// ============= + +// ========== +// = 外部存储 = +// ========== + +fun DevSource.storage_insertImageToExternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertImageToExternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertVideoToExternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertVideoToExternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertAudioToExternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertAudioToExternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertDownloadToExternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertDownloadToExternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertMediaToExternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertMediaToExternal( + params, this, listener + ) + } +} + +// ========== +// = 内部存储 = +// ========== + +fun DevSource.storage_insertImageToInternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertImageToInternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertVideoToInternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertVideoToInternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertAudioToInternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertAudioToInternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertDownloadToInternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertDownloadToInternal( + params, this, listener + ) + } +} + +fun DevSource.storage_insertMediaToInternal( + engine: String? = null, + params: Item?, + listener: OnInsertListener? +) { + engine.getStorageEngine()?.let { + (it as? IStorageEngine)?.insertMediaToInternal( + params, this, listener + ) + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/.gitignore b/lib/DevHttpCapture/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevHttpCapture/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevHttpCapture/CHANGELOG.md b/lib/DevHttpCapture/CHANGELOG.md new file mode 100644 index 0000000000..1bfb06f143 --- /dev/null +++ b/lib/DevHttpCapture/CHANGELOG.md @@ -0,0 +1,80 @@ +Change Log +========== + +Version 1.1.3 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +* `[Refactor]` 使用 Kotlin 重新编写该库代码 + +Version 1.1.2 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.1.1 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +* `[Add]` 新增 RequestBody isOneShot() 判断 + +Version 1.1.0 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.9 *(2022-02-13)* +---------------------------- + +* `[Update]` 优化抓包数据读取、数据文件拆分处理 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.5 *(2021-12-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.4 *(2021-11-26)* +---------------------------- + +* `[Update]` 修改原本 Activity 管理代码使用 ActivityManagerAssist 进行管理控制 + +Version 1.0.3 *(2021-10-11)* +---------------------------- + +* `[Update]` 修改存储间隔以 10 分钟为单位 + +* `[Add]` UtilsPublic#getStoragePath、getModulePath、getAllModuleName、deleteModule、deleteAllModule、getModuleFileSize、getAllModuleFileSize、getModuleFileLength、getAllModuleFileLength + +Version 1.0.2 *(2021-10-06)* +---------------------------- + +* `[Add]` DevHttpCapture#getAllModule + +Version 1.0.1 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +* `[Add]` CaptureItem + +Version 1.0.0 *(2021-07-04)* +---------------------------- + +* Initial release diff --git a/lib/DevHttpCapture/README.md b/lib/DevHttpCapture/README.md new file mode 100644 index 0000000000..37888cfa88 --- /dev/null +++ b/lib/DevHttpCapture/README.md @@ -0,0 +1,163 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevHttpCapture:1.1.3' +``` + +## 目录结构 + +``` +- dev | 根目录 + - capture | Http 抓包实现代码 +``` + + +## 框架功能介绍 + +> 该库主要对使用 OkHttp 网络请求库的项目,提供 Http 抓包功能,并支持抓包数据加密存储。 +> +> **并且是以 Module ( ModuleName Key ) 为基础,支持组件化不同 Module 各自的抓包功能**,支持实时开关抓包功能、可控 Http 拦截过滤器。 +> +> 内置两个 Http 抓包拦截器,CallbackInterceptor ( 无存储逻辑,进行回调通知 )、HttpCaptureInterceptor ( 存在存储抓包数据逻辑 ) + +### 使用示例 + +```kotlin +// 添加 Http 抓包拦截处理 +DevHttpCapture.addInterceptor( + OkHttpClient.Builder, moduleName +) + +// 移除对应 Module Http 抓包拦截 +DevHttpCapture.removeInterceptor(moduleName) + +// 更新对应 Module Http 抓包拦截处理 +DevHttpCapture.updateInterceptor(moduleName, capture) +``` + +## 事项 + +- 部分 API 更新不及时或有遗漏等,`具体以对应的工具类为准` + +- [检测代码规范、注释内容排版,API 文档生成](https://github.com/afkT/JavaDoc) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/CHANGELOG.md) + +## API + + +- dev | 根目录 + - [capture](#devcapture) | Http 抓包实现代码 + + +## **`dev`** + + +* **OkHttp 抓包工具库 ->** [DevHttpCapture.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/DevHttpCapture.kt) + +| 方法 | 注释 | +| :- | :- | +| getDevHttpCaptureVersionCode | 获取 DevHttpCapture 版本号 | +| getDevHttpCaptureVersion | 获取 DevHttpCapture 版本 | +| getDevAppVersionCode | 获取 DevApp 版本号 | +| getDevAppVersion | 获取 DevApp 版本 | +| addInterceptor | 添加 Http 抓包拦截处理 | +| containsInterceptor | 是否存在对应 Module Http 抓包拦截 | +| removeInterceptor | 移除对应 Module Http 抓包拦截 | +| updateInterceptor | 更新对应 Module Http 抓包拦截处理 | +| getModulePath | 获取指定模块抓包存储路径 | +| getModuleHttpCaptures | 获取指定模块所有抓包数据 | +| utils | 对外公开快捷工具类 ( UtilsPublic ) | + + +## **`dev.capture`** + + +* **Http 抓包拦截器 ( 无存储逻辑, 进行回调通知 ) ->** [CallbackInterceptor.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/CallbackInterceptor.kt) +* **Http 抓包拦截器 ( 存在存储抓包数据逻辑 ) ->** [HttpCaptureInterceptor.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/HttpCaptureInterceptor.kt) + + +* **对外公开快捷方法 ->** [UtilsPublic.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Utils.kt) + +| 方法 | 注释 | +| :- | :- | +| getStoragePath | 获取抓包存储路径 | +| getModulePath | 获取指定模块抓包存储路径 | +| getAllModuleName | 获取全部模块名 | +| getAllModule | 获取全部模块所有抓包数据 | +| deleteModule | 删除指定模块抓包数据 | +| deleteAllModule | 删除全部模块抓包数据 | +| getModuleFileSize | 获取指定模块抓包文件大小 | +| getAllModuleFileSize | 获取全部模块抓包文件大小 | +| getModuleFileLength | 获取指定模块抓包文件大小 | +| getAllModuleFileLength | 获取全部模块抓包文件大小 | + + +* **抓包存储文件 ->** [CaptureFile.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Model.kt#L103) + +| 方法 | 注释 | +| :- | :- | +| getUrl | getUrl | +| getMethod | getMethod | +| isEncrypt | isEncrypt | +| getTime | getTime | +| getFileName | getFileName | +| getModuleName | getModuleName | +| getHttpCaptureData | 获取请求数据 ( 抓包数据 ) | +| getCaptureInfo | 获取抓包信息封装类 | +| toJson | 将对象转换为 JSON String | +| deleteFile | 删除该对象抓包存储文件 | +| getFile | 获取该对象抓包存储文件 | +| getDataFile | 获取该对象抓包数据存储文件 | + + +* **抓包信息封装类 ->** [CaptureInfo.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Model.kt#L67) + +| 方法 | 注释 | +| :- | :- | +| requestUrl | 请求链接 | +| requestMethod | 请求方法 | +| requestHeader | 请求头信息 | +| requestBody | 请求数据 | +| responseStatus | 响应状态 | +| responseHeader | 响应头信息 | +| responseBody | 响应数据 | +| toJson | 将对象转换为 JSON String | + + +* **Http 拦截过滤器 ->** [IHttpFilter.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt) + +| 方法 | 注释 | +| :- | :- | +| filter | 是否过滤该 Http 请求不进行抓包存储 | + + +* **Http 抓包接口信息获取 ->** [IHttpCapture.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt#L31) + +| 方法 | 注释 | +| :- | :- | +| getModuleName | 获取模块名 ( 要求唯一性 ) | +| getEncrypt | 获取抓包数据加密中间层 | +| getHttpFilter | 获取 Http 拦截过滤器 | +| isCapture | 是否进行 Http 抓包拦截 | +| setCapture | 设置是否进行 Http 抓包拦截 | +| captureRedact | 获取抓包信息隐藏字段 | +| getModulePath | 获取模块抓包存储路径 | +| getModuleHttpCaptures | 获取模块所有抓包数据 | + + +* **Http 抓包事件回调 ->** [IHttpCaptureEvent.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt#L94) + +| 方法 | 注释 | +| :- | :- | +| callRequestUrl | 生成请求链接字符串 | +| callRequestMethod | 生成请求方法字符串 | +| callRequestHeaders | 生成请求头信息 Map | +| callRequestBody | 生成请求体信息 Map | +| callResponseStatus | 生成响应状态 Map | +| callResponseHeaders | 生成响应头信息 Map | +| callResponseBodyFailed | 生成错误响应体信息 | +| callResponseBody | 生成响应体信息 Map | +| converterRequestBody | 转换请求体信息 Map | +| callEnd | 抓包结束 | \ No newline at end of file diff --git a/lib/DevHttpCapture/build.gradle b/lib/DevHttpCapture/build.gradle new file mode 100644 index 0000000000..01306fbedb --- /dev/null +++ b/lib/DevHttpCapture/build.gradle @@ -0,0 +1,39 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) + +android.defaultConfig { + versionCode versions.dev_http_capture_versionCode + versionName versions.dev_http_capture_versionName + // DevHttpCapture Module Version + buildConfigField "int", "DevHttpCapture_VersionCode", "${versions.dev_http_capture_versionCode}" + buildConfigField "String", "DevHttpCapture_Version", "\"${versions.dev_http_capture_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + // OkHttp3 网络请求框架 https://github.com/square/okhttp + api deps.lib.okhttp3 + // Gson https://github.com/google/gson + api deps.lib.gson + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_app + } else { + // 编译时使用 + api project(':DevApp') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevHttpCapture/proguard-rules.pro b/lib/DevHttpCapture/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/DevHttpCapture/project.properties b/lib/DevHttpCapture/project.properties new file mode 100644 index 0000000000..1a9b467f5f --- /dev/null +++ b/lib/DevHttpCapture/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevHttpCapture +project.groupId=io.github.afkt +project.artifactId=DevHttpCapture +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevHttpCapture \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/AndroidManifest.xml b/lib/DevHttpCapture/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..b6dc193018 --- /dev/null +++ b/lib/DevHttpCapture/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/DevHttpCapture.kt b/lib/DevHttpCapture/src/main/java/dev/DevHttpCapture.kt new file mode 100644 index 0000000000..512514a8ab --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/DevHttpCapture.kt @@ -0,0 +1,217 @@ +package dev + +import dev.capture.* +import dev.utils.common.StringUtils +import dev.utils.common.cipher.Encrypt +import okhttp3.OkHttpClient + +/** + * detail: OkHttp 抓包工具库 + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevHttpCapture { + + // 日志 TAG + val TAG = DevHttpCapture::class.java.simpleName + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevHttpCapture 版本号 + * @return DevHttpCapture versionCode + */ + fun getDevHttpCaptureVersionCode(): Int { + return BuildConfig.DevHttpCapture_VersionCode + } + + /** + * 获取 DevHttpCapture 版本 + * @return DevHttpCapture versionName + */ + fun getDevHttpCaptureVersion(): String { + return BuildConfig.DevHttpCapture_Version + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + fun getDevAppVersionCode(): Int { + return BuildConfig.DevApp_VersionCode + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + fun getDevAppVersion(): String { + return BuildConfig.DevApp_Version + } + + // =============== + // = Interceptor = + // =============== + + // Http 抓包接口 Map + private val sCaptureMaps = linkedMapOf() + + /** + * 添加 Http 抓包拦截处理 + * @param builder OkHttpClient Builder + * @param moduleName 模块名 ( 要求唯一性 ) + * @param encrypt 抓包数据加密中间层 + * @param httpFilter Http 拦截过滤器 + * @param capture 是否进行 Http 抓包拦截 + * @param eventIMPL Http 抓包事件回调 + * @return `true` success, `false` fail + */ + fun addInterceptor( + builder: OkHttpClient.Builder, + moduleName: String, + encrypt: Encrypt? = null, + httpFilter: IHttpFilter? = null, + capture: Boolean = true, + eventIMPL: IHttpCaptureEvent = object : HttpCaptureEventIMPL() { + override fun callEnd(info: CaptureInfo) { + } + } + ): Boolean { + if (StringUtils.isNotEmpty(moduleName)) { + if (!sCaptureMaps.containsKey(moduleName)) { + val interceptor = HttpCaptureInterceptor( + moduleName, encrypt, httpFilter, + capture, eventIMPL + ) + // 添加抓包拦截 + builder.addInterceptor(interceptor) + // 保存 IHttpCapture 接口对象 + sCaptureMaps[moduleName] = interceptor + return true + } + } + return false + } + + /** + * 是否存在对应 Module Http 抓包拦截 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return `true` yes, `false` no + */ + fun containsInterceptor(moduleName: String): Boolean { + return if (StringUtils.isNotEmpty(moduleName)) { + sCaptureMaps.containsKey(moduleName) + } else false + } + + /** + * 移除对应 Module Http 抓包拦截 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return `true` success, `false` fail + */ + fun removeInterceptor(moduleName: String): Boolean { + if (StringUtils.isNotEmpty(moduleName)) { + val httpCapture = sCaptureMaps[moduleName] + if (httpCapture != null) { + httpCapture.setCapture(false) + sCaptureMaps.remove(moduleName) + return true + } + } + return false + } + + /** + * 更新对应 Module Http 抓包拦截处理 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param capture 是否进行 Http 抓包拦截 + * @return `true` success, `false` fail + */ + fun updateInterceptor( + moduleName: String, + capture: Boolean + ): Boolean { + if (StringUtils.isNotEmpty(moduleName)) { + val httpCapture = sCaptureMaps[moduleName] + if (httpCapture != null) { + httpCapture.setCapture(capture) + return true + } + } + return false + } + + // ========== + // = 获取操作 = + // ========== + + /** + * 获取指定模块抓包存储路径 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块抓包存储路径 + */ + fun getModulePath(moduleName: String): String? { + if (StringUtils.isNotEmpty(moduleName)) { + val httpCapture = sCaptureMaps[moduleName] + if (httpCapture != null) { + return httpCapture.getModulePath() + } + } + return null + } + + /** + * 获取指定模块所有抓包数据 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块所有抓包数据 + */ + fun getModuleHttpCaptures(moduleName: String): MutableList { + if (StringUtils.isNotEmpty(moduleName)) { + val httpCapture = sCaptureMaps[moduleName] + if (httpCapture != null) { + return httpCapture.getModuleHttpCaptures() + } + } + return mutableListOf() + } + + // ================= + // = 对外公开快捷方法 = + // ================= + + /** + * 对外公开快捷工具类 ( UtilsPublic ) + * @return UtilsPublic + */ + fun utils(): UtilsPublic { + return UtilsPublic.get() + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/BaseInterceptor.kt b/lib/DevHttpCapture/src/main/java/dev/capture/BaseInterceptor.kt new file mode 100644 index 0000000000..9b3325c647 --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/BaseInterceptor.kt @@ -0,0 +1,155 @@ +package dev.capture + +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Response +import java.util.concurrent.TimeUnit + +/** + * detail: 通用 Http 抓包拦截器 + * @author Ttt + */ +abstract class BaseInterceptor( + // 是否属于存储抓包数据类型 + private val storageHttpCaptureType: Boolean, + // Http 抓包事件回调 + private val eventIMPL: IHttpCaptureEvent +) : Interceptor, + IHttpCapture { + + // 抓包数据本地存储实现 Engine + private val storageEngine = HttpCaptureStorageEngine(eventIMPL) + + /** + * 是否属于存储抓包数据类型 + * @return `true` yes, `false` no + */ + fun isStorageHttpCaptureType(): Boolean { + return storageHttpCaptureType + } + + // =============== + // = Interceptor = + // =============== + + final override fun intercept(chain: Interceptor.Chain): Response { + // 如果属于存储抓包数据类型, 但是不需要抓包则直接返回 + if (isStorageHttpCaptureType() && !isCapture()) { + return chain.proceed(chain.request()) + } + return innerResponse(chain) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 统一抓包逻辑代码 + */ + private fun innerResponse(chain: Interceptor.Chain): Response { + // 抓包信息封装类 + val captureInfo = CaptureInfo() + + val request = chain.request() + val requestUrl = request.url + val requestBody = request.body + val requestMethod = request.method + val requestHeaders = request.headers + + val connection = chain.connection() + val protocol = connection?.protocol() ?: Protocol.HTTP_1_1 + + // ================= + // = Http 拦截过滤器 = + // ================= + + val httpFilter = getHttpFilter() + if (httpFilter != null) { + var isFilter = false + try { + // 是否过滤该 Http 请求不进行抓包存储 + isFilter = httpFilter.filter( + request, requestUrl, requestMethod, + protocol, requestHeaders + ) + } catch (ignored: Exception) { + } + if (isFilter) { + // 不需要抓包直接返回 + return chain.proceed(chain.request()) + } + } + + // =========== + // = request = + // =========== + + // 请求链接 + captureInfo.requestUrl = eventIMPL.callRequestUrl( + request, requestUrl + ) + // 请求方法 + captureInfo.requestMethod = eventIMPL.callRequestMethod( + request, requestMethod, protocol, requestBody + ) + // 请求头信息 + captureInfo.requestHeader.putAll( + eventIMPL.callRequestHeaders( + request, requestHeaders, requestBody, + captureRedact() + ) + ) + // 请求体数据 + captureInfo.requestBody.putAll( + eventIMPL.callRequestBody( + request, requestBody, captureRedact() + ) + ) + // 请求时间 + val requestTime = System.currentTimeMillis() + + // ============ + // = response = + // ============ + + val startNs = System.nanoTime() + val response: Response + try { + response = chain.proceed(request) + } catch (e: Exception) { + // 响应数据 + captureInfo.responseBody = eventIMPL.callResponseBodyFailed( + request, e + ) + // 抓包数据存储 + storageEngine.captureStorage(this, captureInfo, requestTime) + throw e + } + val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) + + val responseBody = response.body!! + val responseHeaders = response.headers + + // 响应状态 + captureInfo.responseStatus.putAll( + eventIMPL.callResponseStatus( + request, response, responseBody, tookMs + ) + ) + // 响应头信息 + captureInfo.responseHeader.putAll( + eventIMPL.callResponseHeaders( + request, response, responseHeaders, + responseBody, captureRedact() + ) + ) + // 响应数据 + captureInfo.responseBody = eventIMPL.callResponseBody( + request, response, responseBody + ) + // 抓包数据存储 + storageEngine.captureStorage(this, captureInfo, requestTime) + return response + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/CallbackInterceptor.kt b/lib/DevHttpCapture/src/main/java/dev/capture/CallbackInterceptor.kt new file mode 100644 index 0000000000..2f61dd6b7f --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/CallbackInterceptor.kt @@ -0,0 +1,68 @@ +package dev.capture + +import dev.utils.common.cipher.Encrypt + +/** + * detail: Http 抓包拦截器 ( 无存储逻辑, 进行回调通知 ) + * @author Ttt + * 支持两种初始化方式 + * // 只有结束回调 + * CallbackInterceptor(endCall = xxx) + * // 可自定义解析逻辑 + * CallbackInterceptor(eventIMPL = xxx) + */ +open class CallbackInterceptor( + // Http 抓包结束回调 + endCall: IHttpCaptureEnd? = null, + // Http 抓包事件回调 + eventIMPL: IHttpCaptureEvent = object : HttpCaptureEventIMPL() { + override fun callEnd(info: CaptureInfo) { + endCall?.callEnd(info) + } + } +) : BaseInterceptor(false, eventIMPL) { + + private val TAG = CallbackInterceptor::class.java.simpleName + + // 抓包信息隐藏字段 + private val captureRedact = CaptureRedact() + + // ================ + // = IHttpCapture = + // ================ + + final override fun getModuleName(): String { + return TAG + } + + final override fun getEncrypt(): Encrypt? { + return null + } + + final override fun isCapture(): Boolean { + return true + } + + final override fun setCapture(capture: Boolean) { + } + + final override fun captureRedact(): CaptureRedact { + return captureRedact + } + + final override fun getModulePath(): String { + return "" + } + + final override fun getModuleHttpCaptures(): MutableList { + return mutableListOf() + } + + // ============ + // = override = + // ============ + + override fun getHttpFilter(): IHttpFilter? { + return null + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/HttpCaptureInterceptor.kt b/lib/DevHttpCapture/src/main/java/dev/capture/HttpCaptureInterceptor.kt new file mode 100644 index 0000000000..001a83fc44 --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/HttpCaptureInterceptor.kt @@ -0,0 +1,65 @@ +package dev.capture + +import dev.utils.common.cipher.Encrypt + +/** + * detail: Http 抓包拦截器 ( 存在存储抓包数据逻辑 ) + * @author Ttt + */ +class HttpCaptureInterceptor( + // 模块名 ( 要求唯一性 ) + private val moduleName: String, + // 抓包数据加密中间层 + private val encrypt: Encrypt? = null, + // Http 拦截过滤器 + private val httpFilter: IHttpFilter? = null, + // 是否进行 Http 抓包拦截 + private var capture: Boolean = true, + // Http 抓包事件回调 + eventIMPL: IHttpCaptureEvent = object : HttpCaptureEventIMPL() { + override fun callEnd(info: CaptureInfo) { + } + } +) : BaseInterceptor(true, eventIMPL) { + + // 抓包信息隐藏字段 + private val captureRedact = CaptureRedact() + + // ================ + // = IHttpCapture = + // ================ + + override fun getModuleName(): String { + return moduleName + } + + override fun getEncrypt(): Encrypt? { + return encrypt + } + + override fun getHttpFilter(): IHttpFilter? { + return httpFilter + } + + override fun isCapture(): Boolean { + return capture + } + + override fun setCapture(capture: Boolean) { + this.capture = capture + } + + override fun captureRedact(): CaptureRedact { + return captureRedact + } + + override fun getModulePath(): String { + return Utils.getModulePath(moduleName) + } + + override fun getModuleHttpCaptures(): MutableList { + return Utils.getModuleHttpCaptures( + moduleName, encrypt != null + ) + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt b/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt new file mode 100644 index 0000000000..2889d65abd --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/Interface.kt @@ -0,0 +1,251 @@ +package dev.capture + +import dev.utils.common.cipher.Encrypt +import okhttp3.* +import okio.Buffer + +/** + * detail: Http 拦截过滤器 + * @author Ttt + */ +interface IHttpFilter { + + /** + * 是否过滤该 Http 请求不进行抓包存储 + * @param request 请求对象 + * @param url 请求链接 + * @param method 请求方法 + * @param protocol 请求协议 + * @param headers 请求头信息 + * @return `true` yes, `false` no + * 返回 true 则不进行存储 Http 请求信息 + */ + fun filter( + request: Request, + url: HttpUrl, + method: String, + protocol: Protocol, + headers: Headers + ): Boolean +} + +/** + * detail: Http 抓包接口信息获取 + * @author Ttt + */ +interface IHttpCapture { + + // ========== + // = 基础方法 = + // ========== + + /** + * 获取模块名 ( 要求唯一性 ) + * @return 模块名 + */ + fun getModuleName(): String + + /** + * 获取抓包数据加密中间层 + * @return [Encrypt] + */ + fun getEncrypt(): Encrypt? + + /** + * 获取 Http 拦截过滤器 + * @return [IHttpFilter] + */ + fun getHttpFilter(): IHttpFilter? + + /** + * 是否进行 Http 抓包拦截 + * @return `true` yes, `false` no + */ + fun isCapture(): Boolean + + /** + * 设置是否进行 Http 抓包拦截 + * @param capture `true` yes, `false` no + */ + fun setCapture(capture: Boolean) + + /** + * 获取抓包信息隐藏字段 + * @return [CaptureRedact] + */ + fun captureRedact(): CaptureRedact + + // ========== + // = 获取操作 = + // ========== + + /** + * 获取模块抓包存储路径 + * @return 模块抓包存储路径 + */ + fun getModulePath(): String + + /** + * 获取模块所有抓包数据 + * @return 模块所有抓包数据 + */ + fun getModuleHttpCaptures(): MutableList +} + +/** + * detail: Http 抓包事件回调 + * @author Ttt + * 方便二次扩展, 允许自行解析抓包数据 + */ +interface IHttpCaptureEvent : IHttpCaptureEnd { + + // =========== + // = request = + // =========== + + /** + * 生成请求链接字符串 + * @param request 请求对象 + * @param url 请求链接 + * @return 请求链接字符串 + */ + fun callRequestUrl( + request: Request, + url: HttpUrl + ): String + + /** + * 生成请求方法字符串 + * @param request 请求对象 + * @param method 请求方法 + * @param protocol 请求协议 + * @param requestBody 请求体 + * @return 请求方法字符串 + */ + fun callRequestMethod( + request: Request, + method: String, + protocol: Protocol, + requestBody: RequestBody? + ): String + + /** + * 生成请求头信息 Map + * @param request 请求对象 + * @param headers 请求头信息 + * @param requestBody 请求体 + * @param captureRedact 抓包信息隐藏字段 + * @return 请求头信息 Map + */ + fun callRequestHeaders( + request: Request, + headers: Headers, + requestBody: RequestBody?, + captureRedact: CaptureRedact + ): LinkedHashMap + + /** + * 生成请求体信息 Map + * @param request 请求对象 + * @param requestBody 请求体 + * @param captureRedact 抓包信息隐藏字段 + * @return 请求体信息 Map + */ + fun callRequestBody( + request: Request, + requestBody: RequestBody?, + captureRedact: CaptureRedact + ): LinkedHashMap + + // ============ + // = response = + // ============ + + /** + * 生成响应状态 Map + * @param request 请求对象 + * @param response 响应对象 + * @param responseBody 响应体 + * @param tookMs 响应耗时 + * @return 响应状态 Map + */ + fun callResponseStatus( + request: Request, + response: Response, + responseBody: ResponseBody, + tookMs: Long + ): LinkedHashMap + + /** + * 生成响应头信息 Map + * @param request 请求对象 + * @param response 响应对象 + * @param headers 响应头信息 + * @param responseBody 响应体 + * @param captureRedact 抓包信息隐藏字段 + * @return 响应头信息 Map + */ + fun callResponseHeaders( + request: Request, + response: Response, + headers: Headers, + responseBody: ResponseBody, + captureRedact: CaptureRedact + ): LinkedHashMap + + /** + * 生成错误响应体信息 + * @param request 请求对象 + * @param error 异常信息 + * @return 错误响应体信息 + */ + fun callResponseBodyFailed( + request: Request, + error: Exception + ): String + + /** + * 生成响应体信息 Map + * @param request 请求对象 + * @param response 响应对象 + * @param responseBody 响应体 + * @return 响应体信息 Map + */ + fun callResponseBody( + request: Request, + response: Response, + responseBody: ResponseBody, + ): String + + // ========== + // = 转换处理 = + // ========== + + /** + * 转换请求体信息 Map + * @param request 请求对象 + * @param requestBody 请求体 + * @param captureRedact 抓包信息隐藏字段 + * @param buffer Body Buffer + * @return 请求体信息 Map + */ + fun converterRequestBody( + request: Request, + requestBody: RequestBody, + captureRedact: CaptureRedact, + buffer: Buffer + ): LinkedHashMap +} + +/** + * detail: Http 抓包结束回调 + * @author Ttt + */ +interface IHttpCaptureEnd { + + /** + * 抓包结束 + * @param info 抓包数据 + */ + fun callEnd(info: CaptureInfo) +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/InterfaceIMPL.kt b/lib/DevHttpCapture/src/main/java/dev/capture/InterfaceIMPL.kt new file mode 100644 index 0000000000..34075946e6 --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/InterfaceIMPL.kt @@ -0,0 +1,473 @@ +package dev.capture + +import dev.utils.common.ThrowableUtils +import okhttp3.* +import okhttp3.internal.http.promisesBody +import okio.Buffer +import okio.GzipSource +import java.io.EOFException +import java.nio.charset.Charset + +/** + * detail: Http 抓包事件回调实现类 + * @author Ttt + */ +abstract class HttpCaptureEventIMPL : IHttpCaptureEvent { + + val UTF_8 = Charset.forName("UTF-8") + + // ===================== + // = IHttpCaptureEvent = + // ===================== + + // =========== + // = request = + // =========== + + override fun callRequestUrl( + request: Request, + url: HttpUrl + ): String { + // https://???.xxx + return url.toString() + } + + override fun callRequestMethod( + request: Request, + method: String, + protocol: Protocol, + requestBody: RequestBody? + ): String { + // POST http/1.1 ( 6 byte body ) + val builder = StringBuilder() + .append(method).append(" ") + .append(protocol) + requestBody?.let { body -> + builder.append(" ( ") + .append(body.contentLength()) + .append(" byte body )") + } + return builder.toString() + } + + override fun callRequestHeaders( + request: Request, + headers: Headers, + requestBody: RequestBody?, + captureRedact: CaptureRedact + ): LinkedHashMap { + val map = linkedMapOf() + requestBody?.let { body -> + body.contentType()?.let { type -> + if (headers[CONTENT_TYPE] == null) { + map[CONTENT_TYPE] = type.toString() + } + } + val length = body.contentLength() + if (length != -1L) { + if (headers[CONTENT_LENGTH] == null) { + map[CONTENT_LENGTH] = length.toString() + } + } + } + map.putAll( + mapHeader( + headers, captureRedact.requestHeader, + captureRedact.replaceValue + ) + ) + return map + } + + override fun callRequestBody( + request: Request, + requestBody: RequestBody?, + captureRedact: CaptureRedact + ): LinkedHashMap { + val map = linkedMapOf() + if (requestBody == null) { + map[END] = "${request.method} requestBody is null" + } else if (bodyHasUnknownEncoding(request.headers)) { + map[END] = "${request.method} ( encoded body omitted )" + } else if (requestBody.isDuplex()) { + map[END] = "${request.method} ( duplex request body omitted )" + } else if (requestBody.isOneShot()) { + map[END] = "${request.method} ( one-shot body omitted )" + } else { + val buffer = Buffer() + requestBody.writeTo(buffer) + + if (isProbablyUtf8(buffer)) { + map.putAll( + converterRequestBody( + request, requestBody, captureRedact, buffer + ) + ) + map[END] = "${request.method} ( ${requestBody.contentLength()}-byte body )" + } else { + map[END] = + "${request.method} ( binary ${requestBody.contentLength()}-byte body omitted )" + } + } + return map + } + + // ============ + // = response = + // ============ + + override fun callResponseStatus( + request: Request, + response: Response, + responseBody: ResponseBody, + tookMs: Long + ): LinkedHashMap { + val contentLength = responseBody.contentLength() + val bodySize = if (contentLength != -1L) "$contentLength-byte" else "unknown-length" + + val map = linkedMapOf() + map[STATUS] = response.code.toString() + " " + response.message + map[TIME] = tookMs.toString() + "ms" + map[BODY_SIZE] = bodySize + return map + } + + override fun callResponseHeaders( + request: Request, + response: Response, + headers: Headers, + responseBody: ResponseBody, + captureRedact: CaptureRedact + ): LinkedHashMap { + return mapHeader( + headers, captureRedact.responseHeader, + captureRedact.replaceValue + ) + } + + override fun callResponseBodyFailed( + request: Request, + error: Exception + ): String { + return "HTTP FAILED: $error" + } + + override fun callResponseBody( + request: Request, + response: Response, + responseBody: ResponseBody + ): String { + if (!response.promisesBody()) { + return "END HTTP ( promisesBody )" + } else if (bodyHasUnknownEncoding(response.headers)) { + return "END HTTP ( encoded body omitted )" + } else { + val source = responseBody.source() + source.request(Long.MAX_VALUE) // Buffer the entire body. + var buffer = source.buffer + + val headers = response.headers + var gzippedLength: Long? = null + if (GZIP.equals(headers[CONTENT_ENCODING], ignoreCase = true)) { + gzippedLength = buffer.size + GzipSource(buffer.clone()).use { gzippedResponseBody -> + buffer = Buffer() + buffer.writeAll(gzippedResponseBody) + } + } + + val contentType = responseBody.contentType() + val charset = contentType?.charset(UTF_8) ?: UTF_8 + + if (!isProbablyUtf8(buffer)) { + return "END HTTP ( binary ${buffer.size}-byte body omitted )" + } + + val contentLength = responseBody.contentLength() + if (contentLength != 0L) { + return buffer.clone().readString(charset) + } + return if (gzippedLength != null) { + "END HTTP ( ${buffer.size}-byte, $gzippedLength-gzipped-byte body )" + } else { + "END HTTP ( ${buffer.size}-byte body )" + } + } + } + + // ========== + // = 转换处理 = + // ========== + + override fun converterRequestBody( + request: Request, + requestBody: RequestBody, + captureRedact: CaptureRedact, + buffer: Buffer + ): LinkedHashMap { + when (requestBody) { + is FormBody -> { + return mapFormBody( + requestBody, + captureRedact.requestBody, + captureRedact.replaceValue + ) + } + is MultipartBody -> { + return mapMultipartBody( + requestBody, + captureRedact.requestBody, + captureRedact.replaceValue + ) + } + else -> { + val contentType = requestBody.contentType() + val charset = contentType?.charset(UTF_8) ?: UTF_8 + + val map = linkedMapOf() + map[BODY_STRING] = buffer.readString(charset) + return map + } + } + } + + // ========== + // = 通用方法 = + // ========== + + companion object { + + const val END = "END" + const val GZIP = "gzip" + const val IDENTITY = "identity" + const val CONTENT_TYPE = "Content-Type" + const val CONTENT_LENGTH = "Content-Length" + const val CONTENT_ENCODING = "Content-Encoding" + const val CONTENT_DISPOSITION = "Content-Disposition" + const val REDACT_REPLACE_VALUE = "██" + const val DISPOSITION = "disposition" + const val DISPOSITION_END = "\"" + const val DISPOSITION_FRONT = "form-data; name=\"" + const val TIME = "time" + const val STATUS = "status" + const val BODY_SIZE = "body-size" + const val BODY_STRING = "body-string" + + /** + * 判断 body 是否未知编码 + * @param headers 头信息 + * @return `true` yes, `false` no + */ + fun bodyHasUnknownEncoding(headers: Headers): Boolean { + val contentEncoding = headers[CONTENT_ENCODING] ?: return false + return !contentEncoding.equals(IDENTITY, ignoreCase = true) && + !contentEncoding.equals(GZIP, ignoreCase = true) + } + + /** + * 判断 Buffer 内容是否 UTF-8 编码 + * @param buffer Buffer + * @return Boolean + */ + fun isProbablyUtf8(buffer: Buffer): Boolean { + try { + val prefix = Buffer() + val byteCount = buffer.size.coerceAtMost(64) + buffer.copyTo(prefix, 0, byteCount) + for (i in 0 until 16) { + if (prefix.exhausted()) { + break + } + val codePoint = prefix.readUtf8CodePoint() + if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { + return false + } + } + return true + } catch (ignored: EOFException) { + return false // Truncated UTF-8 sequence. + } + } + + /** + * 存储通用头信息到 Map 中 + * @param headers 头信息 + * @param headersToRedact 待隐藏字段信息 + * @param replaceValue 隐藏信息替换值 + * @return LinkedHashMap + */ + fun mapHeader( + headers: Headers, + headersToRedact: Set, + replaceValue: String = REDACT_REPLACE_VALUE + ): LinkedHashMap { + val map = linkedMapOf() + for (i in 0 until headers.size) { + val name = headers.name(i) + if (name in headersToRedact) { + map[name] = replaceValue + } else { + map[name] = headers.value(i) + } + } + return map + } + + /** + * 存储通用 Body 信息到 Map 中 + * @param body [FormBody] + * @param bodyToRedact 待隐藏字段信息 + * @param replaceValue 隐藏信息替换值 + * @return LinkedHashMap + */ + fun mapFormBody( + body: FormBody, + bodyToRedact: Set, + replaceValue: String = REDACT_REPLACE_VALUE + ): LinkedHashMap { + val map = linkedMapOf() + for (i in 0 until body.size) { + val name = body.name(i) + if (name in bodyToRedact) { + map[name] = replaceValue + } else { + map[name] = body.value(i) + } + } + return map + } + + /** + * 存储通用 Body 信息到 Map 中 + * @param body [MultipartBody] + * @param bodyToRedact 待隐藏字段信息 + * @param replaceValue 隐藏信息替换值 + * @return LinkedHashMap + */ + fun mapMultipartBody( + body: MultipartBody, + bodyToRedact: Set, + replaceValue: String = REDACT_REPLACE_VALUE + ): LinkedHashMap { + val UTF_8 = Charset.forName("UTF-8") + val contentType = body.contentType() + val charset = contentType.charset(UTF_8) ?: UTF_8 + + val mapKeyCount = linkedMapOf() + val mapDisposition = linkedMapOf() + + val map = linkedMapOf() + for (i in 0 until body.size) { + val part = body.part(i) + part.headers?.let { headers -> + val disposition = dispositionByHeaders(headers) + val name = dispositionNameSplit(disposition) + val key = if (mapKeyCount.contains(name)) { + val count = (mapKeyCount[name] ?: 1) + 1 + mapKeyCount[name] = count + "$name-$count" + } else { + mapKeyCount[name] = 1 + name + } + if (name in bodyToRedact) { + map[key] = replaceValue + } else { + val buffer = Buffer() + part.body.writeTo(buffer) + map[key] = buffer.readString(charset) + } + if (disposition != name) { + mapDisposition["${key}-${DISPOSITION}"] = disposition + } + } + } + map.putAll(mapDisposition) + return map + } + + /** + * 通过 Headers 获取 Content-Disposition + * @param headers 头信息 + * @return Content-Disposition + */ + fun dispositionByHeaders(headers: Headers): String { + return headers[CONTENT_DISPOSITION] ?: headers.toString() + } + + /** + * 通过 Headers disposition 裁剪获取 name + * @param headers 头信息 + * @return name + */ + fun dispositionNameSplit(headers: Headers): String { + return dispositionNameSplit(dispositionByHeaders(headers)) + } + + /** + * 通过 Headers disposition 裁剪获取 name + * @param disposition Content-Disposition + * @return name + */ + fun dispositionNameSplit(disposition: String): String { + // Content-Disposition: form-data; name=\"xxx\" => xxx + val frontIndex = disposition.indexOf(DISPOSITION_FRONT) + if (frontIndex != -1) { + val startIndex = frontIndex + DISPOSITION_FRONT.length + val endIndex = disposition.indexOf(DISPOSITION_END, startIndex) + if (endIndex != -1 && startIndex < endIndex) { + return disposition.substring(startIndex, endIndex) + } + } + return disposition + } + } +} + +/** + * detail: 抓包数据本地存储实现 Engine + * @author Ttt + */ +internal class HttpCaptureStorageEngine( + private val eventIMPL: IHttpCaptureEvent +) { + + /** + * 抓包数据存储 + * @param base 通用 Http 抓包拦截器 + * @param info 抓包信息封装类 + * @param requestTime 开始请求时间 + */ + fun captureStorage( + base: BaseInterceptor, + info: CaptureInfo, + requestTime: Long + ) { + if (base.isStorageHttpCaptureType()) { + // 创建抓包文件信息 + val captureFile = CaptureFile() + .setUrl(info.requestUrl) + .setMethod(info.requestMethod) + .setEncrypt(base.getEncrypt() != null) + .setModuleName(base.getModuleName()) + .setTime(requestTime) + + var httpCaptureData = info.toJson() + // 如果存在加密则进行处理 + base.getEncrypt()?.let { encrypt -> + httpCaptureData = try { + val bytes = encrypt.encrypt(httpCaptureData?.toByteArray()) + String(bytes) + } catch (e: Exception) { + ThrowableUtils.getThrowable(e) + } + } + // 保存抓包数据 + captureFile.httpCaptureData = httpCaptureData + // 存储文件 + Utils.saveHttpCaptureFile(captureFile) + } + // 抓包结束 + eventIMPL.callEnd(info) + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/Model.kt b/lib/DevHttpCapture/src/main/java/dev/capture/Model.kt new file mode 100644 index 0000000000..089c0a281e --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/Model.kt @@ -0,0 +1,263 @@ +package dev.capture + +import dev.capture.HttpCaptureEventIMPL.Companion.REDACT_REPLACE_VALUE +import dev.utils.common.FileUtils +import java.io.File + +/** + * detail: 抓包信息隐藏字段 + * @author Ttt + */ +class CaptureRedact( + val replaceValue: String = REDACT_REPLACE_VALUE +) { + // 请求头隐藏信息 Key + val requestHeader = mutableSetOf() + + // 请求体隐藏信息 Key + val requestBody = mutableSetOf() + + // 响应头隐藏信息 Key + val responseHeader = mutableSetOf() + + /** + * 克隆新的抓包信息隐藏字段 + * @return [CaptureRedact] + */ + fun clone(): CaptureRedact { + val redact = CaptureRedact() + redact.requestHeader.addAll(requestHeader) + redact.requestBody.addAll(requestBody) + redact.responseHeader.addAll(responseHeader) + return redact + } + + /** + * 插入抓包信息隐藏字段 + * @param source 数据源 + * @param clear 是否清空旧数据 + */ + fun insertRedact( + source: CaptureRedact, + clear: Boolean = false + ) { + if (clear) { + requestHeader.clear() + requestBody.clear() + responseHeader.clear() + } + requestHeader.addAll(source.requestHeader) + requestBody.addAll(source.requestBody) + responseHeader.addAll(source.responseHeader) + } +} + +/** + * detail: 抓包存储 Item + * @author Ttt + */ +class CaptureItem( + // 存储时间 - 年月日 ( 文件夹名 ) + val yyyyMMdd: String +) { + // 存储数据 - 时分 + val data = linkedMapOf>() +} + +/** + * detail: 抓包信息封装类 + * @author Ttt + */ +class CaptureInfo { + + // 请求链接 + var requestUrl: String = "" + + // 请求方法 + var requestMethod: String = "" + + // 请求头信息 + val requestHeader = linkedMapOf() + + // 请求数据 + val requestBody = linkedMapOf() + + // 响应状态 + val responseStatus = linkedMapOf() + + // 响应头信息 + val responseHeader = linkedMapOf() + + // 响应数据 + var responseBody: String = "" + + /** + * 将对象转换为 JSON String + * @return JSON String + */ + fun toJson(): String? { + return Utils.toJson(this) + } +} + +/** + * detail: 抓包存储文件 + * @author Ttt + * 加密情况下, 抓包数据不会进行解析展示, 只能自行导出进行解密 + * 非加密情况下 [httpCaptureData] 则会映射成 [CaptureInfo] 实体类 + */ +class CaptureFile { + + // 请求链接 + private var url: String = "" + + // 请求方法 + private var method: String = "" + + // 是否加密 + private var encrypt = false + + // 创建时间 ( 本地时间戳 ) + private var time: Long = 0 + + // 文件名 + private var fileName: String = "" + + // 模块名 + private var moduleName: String = "" + + // ======= + // = get = + // ======= + + fun getUrl(): String { + return url + } + + fun getMethod(): String { + return method + } + + fun isEncrypt(): Boolean { + return encrypt + } + + fun getTime(): Long { + return time + } + + fun getFileName(): String { + return fileName + } + + fun getModuleName(): String { + return moduleName + } + + // ======= + // = set = + // ======= + + internal fun setUrl(url: String): CaptureFile { + this.url = url + return this + } + + internal fun setMethod(method: String): CaptureFile { + this.method = method + return this + } + + internal fun setEncrypt(encrypt: Boolean): CaptureFile { + this.encrypt = encrypt + return this + } + + internal fun setTime(time: Long): CaptureFile { + this.time = time + return this + } + + internal fun setFileName(fileName: String): CaptureFile { + this.fileName = fileName + return this + } + + internal fun setModuleName(moduleName: String): CaptureFile { + this.moduleName = moduleName + return this + } + + // ========== + // = 抓包数据 = + // ========== + + // 请求数据 ( 抓包数据 ) + @Transient + internal var httpCaptureData: String? = null + + fun getHttpCaptureData(): String? { + if (httpCaptureData == null) { + httpCaptureData = FileUtils.readFile(getDataFile()) + } + return httpCaptureData + } + + // ========== + // = 其他处理 = + // ========== + + // 抓包信息封装类 + @Transient + private var captureInfo: CaptureInfo? = null + + /** + * 获取抓包信息封装类 + * @return 抓包信息封装类 + */ + fun getCaptureInfo(): CaptureInfo? { + if (captureInfo == null && !encrypt) { + captureInfo = Utils.fromJson( + getHttpCaptureData(), CaptureInfo::class.java + ) + } + return captureInfo + } + + /** + * 将对象转换为 JSON String + * @return JSON String + */ + fun toJson(): String? { + return Utils.toJson(this) + } + + // ============= + // = 文件操作相关 = + // ============= + + /** + * 删除该对象抓包存储文件 + * @return `true` success, `false` fail + */ + fun deleteFile(): Boolean { + FileUtils.deleteFile(getDataFile()) + return FileUtils.deleteFile(getFile()) + } + + /** + * 获取该对象抓包存储文件 + * @return 该对象抓包存储文件 + */ + fun getFile(): File { + return Utils.getModuleHttpCaptureFile(this) + } + + /** + * 获取该对象抓包数据存储文件 + * @return 该对象抓包数据存储文件 + */ + fun getDataFile(): File { + return Utils.getModuleHttpCaptureDataFile(this) + } +} \ No newline at end of file diff --git a/lib/DevHttpCapture/src/main/java/dev/capture/Utils.kt b/lib/DevHttpCapture/src/main/java/dev/capture/Utils.kt new file mode 100644 index 0000000000..dcd3d80ed9 --- /dev/null +++ b/lib/DevHttpCapture/src/main/java/dev/capture/Utils.kt @@ -0,0 +1,508 @@ +package dev.capture + +import com.google.gson.GsonBuilder +import dev.DevHttpCapture +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.app.PathUtils +import dev.utils.common.* +import dev.utils.common.encrypt.MD5Utils +import dev.utils.common.validator.ValidatorUtils +import java.io.File + +// ================= +// = 对外公开快捷方法 = +// ================= + +/** + * detail: 对外公开快捷方法 + * @author Ttt + * 为减少使用入口, 统一封装为通过 [DevHttpCapture.utils] 进行获取 + */ +class UtilsPublic private constructor() { + + private object Holder { + val instance = UtilsPublic() + } + + companion object { + + internal fun get(): UtilsPublic { + return Holder.instance + } + } + + /** + * 获取抓包存储路径 + * @return 抓包存储路径 + */ + fun getStoragePath(): String { + return Utils.getStoragePath() + } + + /** + * 获取指定模块抓包存储路径 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块抓包存储路径 + */ + fun getModulePath(moduleName: String): String { + return Utils.getModulePath(moduleName) + } + + /** + * 获取全部模块名 + * @return 模块名集合 + */ + fun getAllModuleName(): MutableList { + val lists = mutableListOf() + val root = FileUtils.getFile(getStoragePath()) + if (FileUtils.isFileExists(root)) { + root.listFiles()?.forEach { file -> + if (FileUtils.isDirectory(file)) { + lists.add(file.name) + } + } + } + return lists + } + + /** + * 获取全部模块所有抓包数据 + * @param isEncrypt 是否加密数据 + * @return 全部模块所有抓包数据 + */ + fun getAllModule(isEncrypt: Boolean): LinkedHashMap> { + return Utils.getAllModule(isEncrypt) + } + + // ====================== + // = 耗时操作需开启线程执行 = + // ====================== + + /** + * 删除指定模块抓包数据 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return `true` success, `false` fail + */ + fun deleteModule(moduleName: String): Boolean { + return FileUtils.deleteAllInDir(getModulePath(moduleName)) + } + + /** + * 删除全部模块抓包数据 + * @return `true` success, `false` fail + */ + fun deleteAllModule(): Boolean { + return FileUtils.deleteAllInDir(getStoragePath()) + } + + /** + * 获取指定模块抓包文件大小 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块抓包文件大小 + */ + fun getModuleFileSize(moduleName: String): String { + return FileUtils.getDirSize(getModulePath(moduleName)) + } + + /** + * 获取全部模块抓包文件大小 + * @return 全部模块抓包文件大小 + */ + fun getAllModuleFileSize(): String { + return FileUtils.getDirSize(getStoragePath()) + } + + /** + * 获取指定模块抓包文件大小 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块抓包文件大小 + */ + fun getModuleFileLength(moduleName: String): Long { + return FileUtils.getDirLength(getModulePath(moduleName)) + } + + /** + * 获取全部模块抓包文件大小 + * @return 全部模块抓包文件大小 + */ + fun getAllModuleFileLength(): Long { + return FileUtils.getDirLength(getStoragePath()) + } +} + +/** + * detail: 内部实现工具类 + * @author Ttt + */ +internal object Utils { + + // ======== + // = Gson = + // ======== + + private val GSON = GsonBuilder().create() + + /** + * 将对象转换为 JSON String + * @param obj [Object] + * @return JSON String + */ + fun toJson(obj: Any?): String? { + if (obj == null) return null + try { + return GSON.toJson(obj) + } catch (e: Exception) { + LogPrintUtils.eTag(DevHttpCapture.TAG, e, "toJson") + } + return null + } + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT [Class] T + * @param 泛型 + * @return instance of type + */ + fun fromJson( + json: String?, + classOfT: Class? + ): T? { + if (json == null) return null + try { + return GSON.fromJson(json, classOfT) + } catch (e: Exception) { + LogPrintUtils.eTag(DevHttpCapture.TAG, e, "fromJson") + } + return null + } + + // ======= + // = 排序 = + // ======= + + /** + * 通过文件名进行排序 + * @param files 待排序文件数组 + * @return 排序后的文件名 + */ + private fun sortFileByName(files: Array?): MutableList { + val lists = mutableListOf() + if (files != null) { + lists.addAll(files.toMutableList()) + lists.sortWith { file1, file2 -> + val value1 = ConvertUtils.toInt(file1.name) + val value2 = ConvertUtils.toInt(file2.name) + value2.compareTo(value1) + } + } + return lists + } + + // ============= + // = 文件操作相关 = + // ============= + + // Http Capture Storage Path + private var sStoragePath: String? = null + + // 文件后缀 + private const val FILE_EXTENSION = ".json" + private const val DATA_FILE_EXTENSION = "_data.json" + + /** + * 获取抓包存储路径 + * @return 抓包存储路径 + */ + fun getStoragePath(): String { + if (StringUtils.isEmpty(sStoragePath)) { + sStoragePath = PathUtils.getInternal().getAppDataPath( + DevHttpCapture.TAG + ) + } + return sStoragePath ?: "" + } + + /** + * 获取指定模块抓包存储路径 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 指定模块抓包存储路径 + */ + fun getModulePath(moduleName: String): String { + return FileUtils.getAbsolutePath( + FileUtils.getFile(getStoragePath(), moduleName) + ) ?: "" + } + + /** + * 获取指定模块指定抓包存储文件 + * @param modulePath 模块路径 + * @param fileName 抓包存储文件名 + * @return 指定模块指定抓包存储文件 + */ + private fun getModuleHttpCaptureFile( + modulePath: String, + fileName: String + ): File { + return FileUtils.getFile(modulePath, fileName) + } + + /** + * 获取指定模块指定抓包存储文件 + * @param captureFile 抓包存储文件 + * @return 指定模块指定抓包存储文件 + */ + fun getModuleHttpCaptureFile(captureFile: CaptureFile): File { + val modulePath = getModulePath(captureFile.getModuleName()) + val filePath = getTimeFile(modulePath, captureFile.getTime()) + return getModuleHttpCaptureFile(filePath, captureFile.getFileName()) + } + + /** + * 获取指定模块指定抓包数据存储文件 + * @param captureFile 抓包存储文件 + * @return 指定模块指定抓包数据存储文件 + */ + fun getModuleHttpCaptureDataFile( + captureFile: CaptureFile + ): File { + val filePath = StringUtils.replaceEndsWith( + FileUtils.getAbsolutePath(getModuleHttpCaptureFile(captureFile)), + FILE_EXTENSION, DATA_FILE_EXTENSION + ) + return FileUtils.getFile(filePath) + } + + // = + + /** + * 获取唯一文件名 + * @param filePath 文件路径 [getModulePath] + * @param isEncrypt 是否加密数据 + * @return 唯一文件名 + */ + private fun getUniqueFileName( + filePath: String, + isEncrypt: Boolean + ): String? { + if (StringUtils.isEmpty(filePath)) return null + while (true) { + val md5Value = MD5Utils.md5(DevCommonUtils.getRandomUUIDToString()) + // 属于加密的文件名前加前缀 + val fileName = if (isEncrypt) { + "encrypt_$md5Value$FILE_EXTENSION" + } else { + md5Value + FILE_EXTENSION + } + // 文件不存在才返回文件名 + if (!FileUtils.isFileExists(filePath, fileName)) { + return fileName + } + } + } + + /** + * 获取时间间隔文件夹路径 + * @param modulePath 模块名 + * @param millis 创建时间 ( 本地时间戳 ) + * @return 时间间隔文件夹路径 + */ + private fun getTimeFile( + modulePath: String, + millis: Long + ): String { + val yyyyMMdd = DateUtils.formatTime(millis, DevFinal.TIME.yyyyMMdd) + val HH = DateUtils.formatTime(millis, DevFinal.TIME.HH) + val mm = ConvertUtils.toInt(DateUtils.formatTime(millis, DevFinal.TIME.mm)) + // 存储间隔以 10 分钟为单位 + val mmStr = if (mm < 10) { // 00-09 + "00" + } else if (mm < 20) { // 10-19 + "10" + } else if (mm < 30) { // 20-29 + "20" + } else if (mm < 40) { // 30-39 + "30" + } else if (mm < 50) { // 40-49 + "40" + } else { // 50-59 + "50" + } + // 存储文件夹路径 + return FileUtils.getAbsolutePath( + FileUtils.getFile(modulePath, yyyyMMdd + File.separator + HH + mmStr) + ) + } + + /** + * 存储 Http 抓包数据 + * @param captureFile 抓包存储文件 + * @return `true` success, `false` fail + */ + fun saveHttpCaptureFile(captureFile: CaptureFile): Boolean { + // 获取指定模块抓包存储路径 + val modulePath = getModulePath(captureFile.getModuleName()) + // 存储文件夹路径 + val filePath = getTimeFile(modulePath, captureFile.getTime()) + // 创建文件夹 + FileUtils.createFolder(filePath) + // 获取文件名 + val fileName = getUniqueFileName(filePath, captureFile.isEncrypt()) + if (fileName != null) { + captureFile.setFileName(fileName) + // 将对象转换为 JSON String + val json = captureFile.toJson() + // 存储抓包数据文件 + FileUtils.saveFile( + captureFile.getDataFile(), + StringUtils.getBytes(captureFile.httpCaptureData) + ) + // 存储处理 + return FileUtils.saveFile( + getModuleHttpCaptureFile(filePath, fileName), + StringUtils.getBytes(json) + ) + } + return false + } + + /** + * 通过 File 读取文件映射为抓包存储文件 + * @param file 抓包存储文件地址 + * @return 抓包存储文件 + */ + private fun fromCaptureFile(file: File): CaptureFile? { + if (FileUtils.isFile(file)) { + val json = FileUtils.readFile(file) + return fromJson(json, CaptureFile::class.java) + } + return null + } + + /** + * 获取全部模块所有抓包数据 + * @param isEncrypt 是否加密数据 + * @return 全部模块所有抓包数据 + */ + fun getAllModule(isEncrypt: Boolean): LinkedHashMap> { + val maps = linkedMapOf>() + val filePath = getStoragePath() + val rootFile = FileUtils.getFile(filePath) + if (FileUtils.isFileExists(rootFile)) { + rootFile.listFiles()?.forEach { file -> + if (FileUtils.isDirectory(file)) { + val moduleName = file.name + maps[moduleName] = getModuleHttpCaptures(moduleName, isEncrypt) + } + } + } + return maps + } + + /** + * 获取指定模块所有抓包数据 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param isEncrypt 是否加密数据 + * @return 指定模块所有抓包数据 + */ + fun getModuleHttpCaptures( + moduleName: String, + isEncrypt: Boolean + ): MutableList { + val lists = mutableListOf() + // 获取指定模块抓包存储路径 + val filePath = getModulePath(moduleName) + val moduleFile = FileUtils.getFile(filePath) + if (FileUtils.isFileExists(moduleFile)) { + // 循环年月日文件夹 + val yyyyMMddFiles = sortFileByName(moduleFile.listFiles()) + yyyyMMddFiles.forEach { ymdFile -> + // 验证是否 yyyyMMdd 8 位数数字文件名 + if (validateFileName(ymdFile, 8)) { + val ymdName = ymdFile.name + // 循环时分文件夹 + val hhmmFiles = sortFileByName(ymdFile.listFiles()) + if (hhmmFiles.isNotEmpty()) { + val captureItem = CaptureItem(ymdName) + hhmmFiles.forEach { hmFile -> + // 验证是否 hhmm 4 位数数字文件名 + if (validateFileName(hmFile, 4)) { + val hmName = hmFile.name + // 循环抓包存储文件 + val files = hmFile.listFiles() + if (files != null && files.isNotEmpty()) { + val captureList = mutableListOf() + files.forEach { file -> + if (FileUtils.isFile(file)) { + val fileName = file.name + // 不属于数据文件才读取 + if (!fileName.endsWith(DATA_FILE_EXTENSION)) { + // 判断是否加密文件 + val isEncryptFile = fileName.startsWith( + "encrypt_" + ) + if (isEncrypt) { + // 要求获取加密文件并且属于加密文件才处理 + if (isEncryptFile) { + fromCaptureFile(file)?.let { + captureList.add(it) + } + } + } else { + // 要求获取非加密文件并且属于非加密文件才处理 + if (!isEncryptFile) { + fromCaptureFile(file)?.let { + captureList.add(it) + } + } + } + } + } + } + if (captureList.isNotEmpty()) { + // 最新的在最前面 + captureList.sortWith { o1, o2 -> + val diff = o1.getTime() - o2.getTime() + if (diff > 0) { + -1 + } else if (diff < 0) { + 1 + } else { + 0 + } + } + // 存储数据 - 时分 + captureItem.data[hmName] = captureList + } + } + } + } + if (captureItem.data.size != 0) { + lists.add(captureItem) + } + } + } + } + } + return lists + } + + /** + * 验证文件名是否符合要求 + * @param file 待验证文件 + * @param length 文件名长度 + * @return `true` yes, `false` no + */ + private fun validateFileName( + file: File, + length: Int + ): Boolean { + if (FileUtils.isDirectory(file)) { + val name = file.name + return ValidatorUtils.isNumber(name) && + StringUtils.isLength(name, length) + } + return false + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/.gitignore b/lib/DevHttpManager/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevHttpManager/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevHttpManager/CHANGELOG.md b/lib/DevHttpManager/CHANGELOG.md new file mode 100644 index 0000000000..ea166ccf56 --- /dev/null +++ b/lib/DevHttpManager/CHANGELOG.md @@ -0,0 +1,22 @@ +Change Log +========== + +Version 1.0.3 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.2 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.1 *(2022-05-13)* +---------------------------- + +* `[Add]` 新增 OkHttp 上传、下载 Progress 进度监听功能 + +Version 1.0.0 *(2022-03-27)* +---------------------------- + +* Initial release diff --git a/lib/DevHttpManager/README.md b/lib/DevHttpManager/README.md new file mode 100644 index 0000000000..7cff76a4ee --- /dev/null +++ b/lib/DevHttpManager/README.md @@ -0,0 +1,708 @@ + + +## 摘要 + +* [框架功能介绍](#框架功能介绍) +* [Retrofit 多 BaseUrl 管理功能](#Retrofit-多-BaseUrl-管理功能) +* [Retrofit 多 BaseUrl 管理功能使用](#Retrofit-多-BaseUrl-管理功能使用) +* [OkHttp 上传、下载进度监听](#OkHttp-上传下载进度监听) +* [OkHttp 上传、下载进度监听使用](#OkHttp-上传下载进度监听使用) + + +## Gradle + +```gradle +implementation 'io.github.afkt:DevHttpManager:1.0.3' +``` + +## 目录结构 + +``` +- dev | 根目录 + - http | 基于 OkHttp 管理实现代码 + - manager | Retrofit 多 BaseUrl 管理 + - progress | OkHttp 上传、下载进度监听 + - operation | 监听通知不同方式实现 +``` + + +## 框架功能介绍 + +* 支持 Retrofit 多 BaseUrl 管理及操作方法封装 + +* 支持 Retrofit BaseUrl Reset 事件全局监听、各个模块单独监听回调 + +* 支持全局 OkHttp Builder 创建方法,可进行全局管理 + +* 针对多 Retrofit 管理封装 Operation 对象并支持组件化使用 + +* 支持传参 Map 对多个 Retrofit 同时进行 BaseUrl Reset + +* 支持对 App 所有链接上传、下载进度监听 + +* 基于 OkHttp 原生 Api 实现,不存在兼容问题 + +* 侵入性低,使用本框架不需要更改历史上传、下载实现代码 + +* 对外公开封装 Progress RequestBody、ResponseBody 类,支持自定义使用 + +* 支持监听代码,不同实现方式切换,内部内存回收、监听通知方式不同 + +* 针对多组件模块化封装,内置默认全局通用对象,也可传 Key 创建独立 Progress 管理操作对象 + + +## API 文档 + +* **DevHttpManager 管理库方法 ->** [DevHttpManager.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt) + +| 方法 | 注释 | +| :- | :- | +| getDevHttpManagerVersionCode | 获取 DevHttpManager 版本号 | +| getDevHttpManagerVersion | 获取 DevHttpManager 版本 | +| getDevAppVersionCode | 获取 DevApp 版本号 | +| getDevAppVersion | 获取 DevApp 版本 | + + +* **RetrofitManager 方法 ->** [DevHttpManager.RM.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt#L77) + +| 方法 | 注释 | +| :- | :- | +| getOkHttpBuilder | 获取全局 OkHttp Builder 接口对象 | +| setOkHttpBuilder | 设置全局 OkHttp Builder 接口对象 | +| removeOkHttpBuilder | 移除全局 OkHttp Builder 接口对象 | +| getRetrofitResetListener | 获取全局 Retrofit 重新构建监听事件 | +| setRetrofitResetListener | 设置全局 Retrofit 重新构建监听事件 | +| removeRetrofitResetListener | 移除全局 Retrofit 重新构建监听事件 | +| getOperation | 获取 Retrofit Operation 操作对象 | +| containsOperation | 通过 Key 判断是否存在 Retrofit Operation 操作对象 | +| putRetrofitBuilder | 通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象 | +| removeRetrofitBuilder | 通过 Key 解绑移除 RetrofitBuilder 并返回 Operation 操作对象 | +| reset | 重置处理 ( 重新构建 Retrofit ) | +| resetAll | 重置处理 ( 重新构建全部 Retrofit ) | + + +* **ProgressManager 方法 ->** [DevHttpManager.PM.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt#L219) + +| 方法 | 注释 | +| :- | :- | +| getDefault | 获取全局默认 Progress Operation 操作对象 | +| getOperation | 获取 Progress Operation 操作对象 | +| containsOperation | 通过 Key 判断是否存在 Progress Operation 操作对象 | +| removeOperation | 通过 Key 解绑并返回 Operation 操作对象 | +| clearOperation | 清空所有 Progress Operation 操作对象 | +| putOperationTypeAll | 通过 Key 绑定并返回 Operation 操作对象 ( 监听上下行 ) | +| putOperationTypeRequest | 通过 Key 绑定并返回 Operation 操作对象 ( 监听上行 ) | +| putOperationTypeResponse | 通过 Key 绑定并返回 Operation 操作对象 ( 监听下行 ) | + + +## Retrofit 多 BaseUrl 管理功能 + +* **RetrofitManager 方法 ->** [DevHttpManager.RM.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt#L77) + +| 方法 | 注释 | +| :- | :- | +| getOkHttpBuilder | 获取全局 OkHttp Builder 接口对象 | +| setOkHttpBuilder | 设置全局 OkHttp Builder 接口对象 | +| removeOkHttpBuilder | 移除全局 OkHttp Builder 接口对象 | +| getRetrofitResetListener | 获取全局 Retrofit 重新构建监听事件 | +| setRetrofitResetListener | 设置全局 Retrofit 重新构建监听事件 | +| removeRetrofitResetListener | 移除全局 Retrofit 重新构建监听事件 | +| getOperation | 获取 Retrofit Operation 操作对象 | +| containsOperation | 通过 Key 判断是否存在 Retrofit Operation 操作对象 | +| putRetrofitBuilder | 通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象 | +| removeRetrofitBuilder | 通过 Key 解绑移除 RetrofitBuilder 并返回 Operation 操作对象 | +| reset | 重置处理 ( 重新构建 Retrofit ) | +| resetAll | 重置处理 ( 重新构建全部 Retrofit ) | + +### 具体实现代码 [目录](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager) + +* **全局 OkHttp Builder 接口 ->** [OkHttpBuilder.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager/OkHttpBuilder.kt) + +| 方法 | 注释 | +| :- | :- | +| createOkHttpBuilder | 创建 OkHttp Builder | + + +* **全局 Retrofit 重新构建监听事件 ->** [OnRetrofitResetListener.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager/OnRetrofitResetListener.kt) + +| 方法 | 注释 | +| :- | :- | +| onResetBefore | 重新构建前调用 | +| onReset | 重新构建后调用 | + + +* **Retrofit Builder 接口 ->** [RetrofitBuilder.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitBuilder.kt) + +| 方法 | 注释 | +| :- | :- | +| createRetrofitBuilder | 创建 Retrofit Builder | +| onResetBefore | 重新构建前调用 | +| onReset | 重新构建后调用 | + + +* **Retrofit Operation ->** [RetrofitOperation.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitOperation.kt) + +| 方法 | 注释 | +| :- | :- | +| getRetrofit | 获取 Retrofit 对象 | +| create | 通过 Retrofit 代理创建 Service | +| reset | 重置处理 ( 重新构建 Retrofit ) | +| resetAndCreate | 重置处理 ( 重新构建 Retrofit ) 并代理创建 Service | + + +# Retrofit 多 BaseUrl 管理功能使用 + +使用代码只有一步:**通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象** + +```kotlin +// 通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象 +DevHttpManager.RM.putRetrofitBuilder( + stringKey, RetrofitBuilder +) +``` + +通过返回的 [RetrofitOperation][RetrofitOperation] 对象进行获取 Retrofit 或直接 create APIService + +```kotlin +/** + * 获取 Retrofit 对象 + * @param check 是否需要判断 Retrofit 是否为 null + * @return Retrofit + */ +fun getRetrofit(check: Boolean = true): Retrofit? + +/** + * 通过 Retrofit 代理创建 Service + * @param service Class + * @return Service Class + */ +fun create(service: Class): T? + +/** + * 重置处理 ( 重新构建 Retrofit ) + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ +fun reset(httpUrl: HttpUrl? = null): RetrofitOperation + +/** + * 重置处理 ( 重新构建 Retrofit ) 并代理创建 Service + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ +fun resetAndCreate( + service: Class, + httpUrl: HttpUrl? = null +): T? +``` + + +**具体实现代码可以查看 [DevComponent lib_network][DevComponent lib_network]、[WanAndroidAPI][WanAndroidAPI]** +,以 [DevComponent][DevComponent] 组件化项目代码为例。 + +* HttpCoreLibrary initialize() 方法中的代码非必须设置,只是提供全局管理控制方法,支持设置全局 OkHttp Builder 接口对象、全局 Retrofit 重新构建监听事件。 + +* 如 [OkHttpBuilderGlobal][OkHttpBuilderGlobal] 内部实现 OkHttpBuilder 接口, + 通过创建通用的 OkHttpClient.Builder 提供给 RetrofitBuilder.createRetrofitBuilder() 方法创建 Retrofit.Builder 使用 + +* [RetrofitResetListenerGlobal][RetrofitResetListenerGlobal] 则提供全局 BaseUrl Reset 监听,例如重新构建 Retrofit 前取消历史请求操作、重新构建后等操作 + +**DevUtilsApp Demo 完整使用查看 [RetrofitManagerUse][RetrofitManagerUse]** + +```kotlin +/** + * detail: Http Core Lib + * @author Ttt + */ +object HttpCoreLibrary { + + // 全局通用 OkHttp Builder + private val mOkHttpBuilderGlobal = OkHttpBuilderGlobal() + + // 全局 Retrofit 重新构建监听事件 + private val mRetrofitResetListenerGlobal = RetrofitResetListenerGlobal() + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 初始化 OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + * @param context Context + */ + fun initialize(context: Context) { + // 设置全局 OkHttp Builder 接口对象 + DevHttpManager.RM.setOkHttpBuilder( + mOkHttpBuilderGlobal + ) + // 设置全局 Retrofit 重新构建监听事件 + DevHttpManager.RM.setRetrofitResetListener( + mRetrofitResetListenerGlobal + ) + } +} +``` + +> **以上代码为非必须实现,以下为使用该库核心方法示例** + +```kotlin +/** + * detail: 玩 Android API Service + * @author Ttt + */ +interface WanAndroidService { + + @GET("/article/list/{page}/json") + suspend fun getArticleList(@Path("page") page: Int): ArticleBean +} + +/** + * detail: 玩 Android API + * @author Ttt + */ +class WanAndroidAPI private constructor() { + + companion object { + + private val instance: WanAndroidAPI by lazy { WanAndroidAPI() } + + fun api(): WanAndroidService { + return instance.api() + } + + fun operation(): RetrofitOperation { + return instance.operation() + } + } + + // ===================== + // = WanAndroidService = + // ===================== + + @Volatile + private var mWanAndroidService: WanAndroidService? = null + + fun api(): WanAndroidService { + if (mWanAndroidService == null) { + synchronized(WanAndroidService::class.java) { + if (mWanAndroidService == null) { + createAPI() + } + } + } + return mWanAndroidService as WanAndroidService + } + + private fun createAPI() { + mWanAndroidService = operation().create( + WanAndroidService::class.java + ) + } + + // ================== + // = DevEnvironment = + // ================== + + private fun apiBaseUrl(): HttpUrl { + return DevEnvironment.getWanAndroidEnvironmentValue( + AppContext.context() + ).toHttpUrl() + } + + // ===================== + // = RetrofitOperation = + // ===================== + + /** + * 对外提供操作对象 + * @return RetrofitOperation + */ + fun operation(): RetrofitOperation { + return mOperation + } + + // Retrofit Operation + private val mOperation: RetrofitOperation by lazy { + DevHttpManager.RM.putRetrofitBuilder( + BuildConfig.MODULE_NAME, mRetrofitBuilder + ) + } + + // =================== + // = RetrofitBuilder = + // =================== + + // Retrofit Builder 接口 + private val mRetrofitBuilder: RetrofitBuilder by lazy { + object : RetrofitBuilder { + + /** + * 创建 Retrofit Builder + * @param oldRetrofit 上一次构建的 Retrofit + * @param httpUrl 构建使用指定 baseUrl + * @param okHttp OkHttpClient 构建全局复用 + * @return Retrofit.Builder + */ + override fun createRetrofitBuilder( + oldRetrofit: Retrofit?, + httpUrl: HttpUrl?, + okHttp: OkHttpClient.Builder? + ): Retrofit.Builder { + return HttpCoreUtils.createRetrofitBuilder( + httpUrl = httpUrl ?: apiBaseUrl(), + okHttp = okHttp ?: OkHttpClient.Builder() + ) + } + + // ========== + // = 通知事件 = + // ========== + + /** + * 重新构建前调用 + * @param key String + * @param oldRetrofit 上一次构建的 Retrofit + * 在 [createRetrofitBuilder] 之前调用 + */ + override fun onResetBefore( + key: String, + oldRetrofit: Retrofit? + ) { + + } + + /** + * 重新构建后调用 + * @param key String + * @param newRetrofit 重新构建的 Retrofit 对象 + * 在 [createRetrofitBuilder] 之后调用 + */ + override fun onReset( + key: String, + newRetrofit: Retrofit? + ) { + // 重新构建后创建新的代理对象 + createAPI() + } + } + } +} +``` + +**整个方法流程执行循序为:** + +1. `Global.OnRetrofitResetListener` onResetBefore +2. `RetrofitBuilder` onResetBefore +3. `Global.OkHttpBuilder` createOkHttpBuilder +4. `RetrofitBuilder` createRetrofitBuilder +5. `RetrofitBuilder` onReset +6. `Global.OnRetrofitResetListener` onReset + +```kotlin +/** + * 构建 Retrofit 方法 ( 最终调用 ) + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ +private fun buildRetrofit(httpUrl: HttpUrl? = null): RetrofitOperation { + if (mReset) { + try { + RetrofitManager.getRetrofitResetListener()?.onResetBefore( + key, mRetrofit + ) + } catch (ignored: Exception) { + } + builder.onResetBefore(key, mRetrofit) + } + + // 获取全局 OkHttp Builder + val okHttpBuilder = try { + RetrofitManager.getOkHttpBuilder()?.createOkHttpBuilder(key) + } catch (ignored: Exception) { + null + } + // 可以通过 mRetrofit?.baseUrl() 获取之前的配置 + mRetrofit = builder.createRetrofitBuilder( + mRetrofit, httpUrl, okHttpBuilder + ).build() + + if (mReset) { + builder.onReset(key, mRetrofit) + try { + RetrofitManager.getRetrofitResetListener()?.onReset( + key, mRetrofit + ) + } catch (ignored: Exception) { + } + } + // 首次为初始化, 后续操作为重置操作 + mReset = true + return this +} +``` + + +## OkHttp 上传、下载进度监听 + +* **ProgressManager 方法 ->** [DevHttpManager.PM.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt#L219) + +| 方法 | 注释 | +| :- | :- | +| getDefault | 获取全局默认 Progress Operation 操作对象 | +| getOperation | 获取 Progress Operation 操作对象 | +| containsOperation | 通过 Key 判断是否存在 Progress Operation 操作对象 | +| removeOperation | 通过 Key 解绑并返回 Operation 操作对象 | +| clearOperation | 清空所有 Progress Operation 操作对象 | +| putOperationTypeAll | 通过 Key 绑定并返回 Operation 操作对象 ( 监听上下行 ) | +| putOperationTypeRequest | 通过 Key 绑定并返回 Operation 操作对象 ( 监听上行 ) | +| putOperationTypeResponse | 通过 Key 绑定并返回 Operation 操作对象 ( 监听下行 ) | + +### 具体实现代码 [目录](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/progress) + +* **Progress Operation ->** [ProgressOperation.kt](https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressOperation.kt) + +| 方法 | 注释 | +| :- | :- | +| setPlanType | 设置 Progress Operation 实现方式类型 | +| wrap | 进行拦截器包装 ( 必须调用 ) | +| isUseWrap | 是否已调用 wrap 方法 | +| isDeprecated | 是否废弃不使用状态 | +| isDefault | 是否全局默认操作对象 | +| isTypeAll | 是否监听上下行 | +| isTypeRequest | 是否监听上行 | +| isTypeResponse | 是否监听下行 | +| getPlanType | 获取 Progress Operation 实现方式类型 | +| getRefreshTime | 获取回调刷新时间 ( 毫秒 ) | +| setRefreshTime | 设置回调刷新时间 ( 毫秒 ) | +| resetRefreshTime | 重置回调刷新时间 ( 毫秒 ) | +| getCallback | 获取全局 Progress Callback | +| setCallback | 设置全局 Progress Callback | +| removeCallback | 移除全局 Progress Callback | +| getHandler | 获取回调 UI 线程通知 Handler | +| setHandler | 设置回调 UI 线程通知 Handler | +| resetHandler | 重置回调 UI 线程通知 Handler | +| removeSelfFromManager | 移除自身在 Manager Map 中的对象值, 并且标记为废弃状态 | +| recycleListener | 释放指定监听事件 | +| addRequestListener | 添加指定 url 上行监听事件 | +| clearRequestListener | 清空指定 url 上行所有监听事件 | +| removeRequestListener | 移除指定 url 上行监听事件 | +| addResponseListener | 添加指定 url 下行监听事件 | +| clearResponseListener | 清空指定 url 下行所有监听事件 | +| removeResponseListener | 移除指定 url 下行监听事件 | + + +## OkHttp 上传、下载进度监听使用 + +使用代码只有一步:**通过 Key 绑定并返回 Operation 操作对象** + +```kotlin +// 通过 Key 绑定并返回 Operation 操作对象 ( 监听上下行 ) +DevHttpManager.PM.putOperationTypeAll(stringKey) +// 通过 Key 绑定并返回 Operation 操作对象 ( 监听上行 ) +DevHttpManager.PM.putOperationTypeRequest(stringKey) +// 通过 Key 绑定并返回 Operation 操作对象 ( 监听下行 ) +DevHttpManager.PM.putOperationTypeResponse(stringKey) +// 或者使用全局默认 Progress Operation 操作对象 +DevHttpManager.PM.getDefault() +``` + +通过返回的 [ProgressOperation][ProgressOperation] 对象进行操作,具体公开方法可以查看 [IOperation][IOperation] 接口 + +```kotlin +/** + * 设置 Progress Operation 实现方式类型 + * @param planType 实现方式类型 [ProgressOperation.PLAN_A]、[ProgressOperation.PLAN_B] + * @return IOperation + * 在没调用 IOperation 接口任何方法前, 调用该方法切换才有效 + */ +fun setPlanType(planType: Int): IOperation + +/** + * 进行拦截器包装 ( 必须调用 ) + * @param builder Builder + * @return Builder + */ +fun wrap(builder: OkHttpClient.Builder): OkHttpClient.Builder + +// ==================== +// = 操作方法 - 对外公开 = +// ==================== + +/** + * 移除自身在 Manager Map 中的对象值, 并且标记为废弃状态 + * 会释放所有数据、监听事件且不处理任何监听 + */ +fun removeSelfFromManager() + +/** + * 释放指定监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun recycleListener( + progress: Progress, + callback: Progress.Callback +): Boolean + +// ==================== +// = Request Listener = +// ==================== + +/** + * 添加指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun addRequestListener( + url: String, + callback: Progress.Callback +): Boolean + +/** + * 清空指定 url 上行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ +fun clearRequestListener(url: String): Boolean + +/** + * 清空指定 url 上行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ +fun clearRequestListener(progress: Progress?): Boolean + +/** + * 移除指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun removeRequestListener( + url: String, + callback: Progress.Callback +): Boolean + +/** + * 移除指定 url 上行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun removeRequestListener( + progress: Progress?, + callback: Progress.Callback +): Boolean + +// ===================== +// = Response Listener = +// ===================== + +/** + * 添加指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun addResponseListener( + url: String, + callback: Progress.Callback +): Boolean + +/** + * 清空指定 url 下行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ +fun clearResponseListener(url: String): Boolean + +/** + * 清空指定 url 下行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ +fun clearResponseListener(progress: Progress?): Boolean + +/** + * 移除指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun removeResponseListener( + url: String, + callback: Progress.Callback +): Boolean + +/** + * 移除指定 url 下行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ +fun removeResponseListener( + progress: Progress?, + callback: Progress.Callback +): Boolean +``` + + +**具体实现代码可以查看 [ProgressManagerUse][ProgressManagerUse]** + +* 监听指定 url 进度,也是只用一句代码 + +```kotlin +val mOperation = DevHttpManager.PM.getDefault() +// 添加指定 url 上行监听事件 +mOperation.addRequestListener(url, progressCallback) +// 添加指定 url 下行监听事件 +mOperation.addResponseListener(url, progressCallback) +``` + +* 完整使用过程模拟代码 + +```kotlin +/** + * 需要切换内部实现方式, 必须先调用该方法 + * 实现方式差异可以查看 [ProgressOperation] 类注释 + * 可不调用默认使用 PLAN_A + */ +mOperation.setPlanType(ProgressOperation.PLAN_A) +mOperation.setPlanType(ProgressOperation.PLAN_B) + +// 进行拦截器包装 ( 必须调用 ) +val okHttpClient = mOperation.wrap(OkHttpClient.Builder()).build() + +// 基于 OkHttp 库, 不同库封装使用不同, 只要使用 wrap build 后的 client 就能够实现监听 +val retrofit = Retrofit.Builder() + // Gson 解析 + .addConverterFactory(GsonConverterFactory.create()) + // OkHttpClient + .client(okHttpClient) + // 服务器地址 + .baseUrl("") + .build() + + +// 添加指定 url 上行监听事件 +mOperation.addRequestListener(url, progressCallback) +// 添加指定 url 下行监听事件 +mOperation.addResponseListener(url, progressCallback) +``` + + + + + +[DevComponent]: https://github.com/afkT/DevComponent +[DevComponent lib_network]: https://github.com/afkT/DevComponent/blob/main/component/core/libs/lib_network/src/main/java/afkt_replace/core/lib/network +[WanAndroidAPI]: https://github.com/afkT/DevComponent/blob/main/application/module/module_wanandroid/src/main/java/afkt_replace/module/wan_android/data/api/WanAndroidAPI.kt +[OkHttpBuilderGlobal]: https://github.com/afkT/DevComponent/blob/main/component/core/libs/lib_network/src/main/java/afkt_replace/core/lib/network/common/OkHttpBuilderGlobal.kt +[RetrofitResetListenerGlobal]: https://github.com/afkT/DevComponent/blob/main/component/core/libs/lib_network/src/main/java/afkt_replace/core/lib/network/common/RetrofitResetListenerGlobal.kt +[RetrofitOperation]: https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitOperation.kt +[ProgressOperation]: https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressOperation.kt +[IOperation]: https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/src/main/java/dev/http/progress/operation/IOperation.kt +[ProgressManagerUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/base/http/ProgressManagerUse.kt +[RetrofitManagerUse]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/base/http/RetrofitManagerUse.kt \ No newline at end of file diff --git a/lib/DevHttpManager/build.gradle b/lib/DevHttpManager/build.gradle new file mode 100644 index 0000000000..e20b276432 --- /dev/null +++ b/lib/DevHttpManager/build.gradle @@ -0,0 +1,40 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply plugin: 'kotlin-parcelize' + +android.defaultConfig { + versionCode versions.dev_http_manager_versionCode + versionName versions.dev_http_manager_versionName + // DevHttpManager Module Version + buildConfigField "int", "DevHttpManager_VersionCode", "${versions.dev_http_manager_versionCode}" + buildConfigField "String", "DevHttpManager_Version", "\"${versions.dev_http_manager_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + // OkHttp3 网络请求框架 https://github.com/square/okhttp + api deps.lib.okhttp3 + // Retrofit 网络请求库 https://github.com/square/retrofit + api deps.lib.retrofit + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_app + } else { + // 编译时使用 + api project(':DevApp') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevHttpManager/proguard-rules.pro b/lib/DevHttpManager/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/DevHttpManager/project.properties b/lib/DevHttpManager/project.properties new file mode 100644 index 0000000000..7d0e8d5d4d --- /dev/null +++ b/lib/DevHttpManager/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevHttpManager +project.groupId=io.github.afkt +project.artifactId=DevHttpManager +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevHttpManager \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/AndroidManifest.xml b/lib/DevHttpManager/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..e85f0a2f70 --- /dev/null +++ b/lib/DevHttpManager/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt b/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt new file mode 100644 index 0000000000..fe62ae6141 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/DevHttpManager.kt @@ -0,0 +1,309 @@ +package dev + +import dev.http.BuildConfig +import dev.http.manager.* +import dev.http.progress.ProgressManager +import dev.http.progress.ProgressOperation +import okhttp3.HttpUrl + +/** + * detail: OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevHttpManager { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevHttpManager 版本号 + * @return DevHttpManager versionCode + */ + fun getDevHttpManagerVersionCode(): Int { + return BuildConfig.DevHttpManager_VersionCode + } + + /** + * 获取 DevHttpManager 版本 + * @return DevHttpManager versionName + */ + fun getDevHttpManagerVersion(): String { + return BuildConfig.DevHttpManager_Version + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + fun getDevAppVersionCode(): Int { + return BuildConfig.DevApp_VersionCode + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + fun getDevAppVersion(): String { + return BuildConfig.DevApp_Version + } + + // ============= + // = 对外公开方法 = + // ============= + + // =================== + // = RetrofitManager = + // =================== + + object RM { + + // ================= + // = OkHttpBuilder = + // ================= + + /** + * 获取全局 OkHttp Builder 接口对象 + * @return OkHttpBuilder + */ + @JvmStatic + fun getOkHttpBuilder(): OkHttpBuilder? { + return RetrofitManager.getOkHttpBuilder() + } + + /** + * 设置全局 OkHttp Builder 接口对象 + * @param builder [OkHttpBuilder] + */ + @JvmStatic + fun setOkHttpBuilder(builder: OkHttpBuilder?) { + RetrofitManager.setOkHttpBuilder(builder) + } + + /** + * 移除全局 OkHttp Builder 接口对象 + */ + @JvmStatic + fun removeOkHttpBuilder() { + RetrofitManager.removeOkHttpBuilder() + } + + // =========================== + // = OnRetrofitResetListener = + // =========================== + + /** + * 获取全局 Retrofit 重新构建监听事件 + * @return OnRetrofitResetListener + */ + @JvmStatic + fun getRetrofitResetListener(): OnRetrofitResetListener? { + return RetrofitManager.getRetrofitResetListener() + } + + /** + * 设置全局 Retrofit 重新构建监听事件 + * @param listener [OnRetrofitResetListener] + */ + @JvmStatic + fun setRetrofitResetListener(listener: OnRetrofitResetListener?) { + RetrofitManager.setRetrofitResetListener(listener) + } + + /** + * 移除全局 Retrofit 重新构建监听事件 + */ + @JvmStatic + fun removeRetrofitResetListener() { + RetrofitManager.removeRetrofitResetListener() + } + + // =================== + // = RetrofitBuilder = + // =================== + + /** + * 获取 Retrofit Operation 操作对象 + * @param key Key + * @return Retrofit Operation + */ + @JvmStatic + fun getOperation(key: String): RetrofitOperation? { + return RetrofitManager.getOperation(key) + } + + /** + * 通过 Key 判断是否存在 Retrofit Operation 操作对象 + * @param key Key + * @return `true` yes, `false` no + */ + @JvmStatic + fun containsOperation(key: String): Boolean { + return RetrofitManager.containsOperation(key) + } + + /** + * 通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象 + * @param key Key + * @param builder [RetrofitBuilder] + * @return Retrofit Operation + */ + @JvmStatic + fun putRetrofitBuilder( + key: String, + builder: RetrofitBuilder + ): RetrofitOperation { + return RetrofitManager.putRetrofitBuilder(key, builder) + } + + /** + * 通过 Key 解绑移除 RetrofitBuilder 并返回 Operation 操作对象 + * @param key Key + * @return Retrofit Operation + */ + @JvmStatic + fun removeRetrofitBuilder(key: String): RetrofitOperation? { + return RetrofitManager.removeRetrofitBuilder(key) + } + + // ===================== + // = RetrofitOperation = + // ===================== + + /** + * 重置处理 ( 重新构建 Retrofit ) + * @param key Key + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ + @JvmStatic + fun reset( + key: String, + httpUrl: HttpUrl? = null + ): RetrofitOperation? { + return RetrofitManager.reset(key, httpUrl) + } + + /** + * 重置处理 ( 重新构建全部 Retrofit ) + * @param mapHttpUrl MutableMap + */ + @JvmStatic + fun resetAll(mapHttpUrl: MutableMap? = null) { + RetrofitManager.resetAll(mapHttpUrl) + } + } + + // =================== + // = ProgressManager = + // =================== + + object PM { + + /** + * 获取全局默认 Progress Operation 操作对象 + * @return ProgressOperation + */ + @JvmStatic + fun getDefault(): ProgressOperation { + return ProgressManager.getDefault() + } + + /** + * 获取 Progress Operation 操作对象 + * @param key Key + * @return Progress Operation + */ + @JvmStatic + fun getOperation(key: String): ProgressOperation? { + return ProgressManager.getOperation(key) + } + + /** + * 通过 Key 判断是否存在 Progress Operation 操作对象 + * @param key Key + * @return `true` yes, `false` no + */ + @JvmStatic + fun containsOperation(key: String): Boolean { + return ProgressManager.containsOperation(key) + } + + /** + * 通过 Key 解绑并返回 Operation 操作对象 + * @param key Key + * @return Progress Operation + */ + @JvmStatic + fun removeOperation(key: String): ProgressOperation? { + return ProgressManager.removeOperation(key) + } + + /** + * 清空所有 Progress Operation 操作对象 + */ + @JvmStatic + fun clearOperation() { + ProgressManager.clearOperation() + } + + // = + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听上下行 ) + * @param key Key + * @return Progress Operation + */ + @JvmStatic + fun putOperationTypeAll(key: String): ProgressOperation { + return ProgressManager.putOperationTypeAll(key) + } + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听上行 ) + * @param key Key + * @return Progress Operation + */ + @JvmStatic + fun putOperationTypeRequest(key: String): ProgressOperation { + return ProgressManager.putOperationTypeRequest(key) + } + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听下行 ) + * @param key Key + * @return Progress Operation + */ + @JvmStatic + fun putOperationTypeResponse(key: String): ProgressOperation { + return ProgressManager.putOperationTypeResponse(key) + } + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/manager/OkHttpBuilder.kt b/lib/DevHttpManager/src/main/java/dev/http/manager/OkHttpBuilder.kt new file mode 100644 index 0000000000..a9ffff2234 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/manager/OkHttpBuilder.kt @@ -0,0 +1,19 @@ +package dev.http.manager + +import okhttp3.OkHttpClient + +/** + * detail: 全局 OkHttp Builder 接口 + * @author Ttt + * 全局 ( 通过 Key 进行特殊化创建 ) + * 可用于 [RetrofitBuilder.createRetrofitBuilder] okHttp 参数传入并创建 + */ +interface OkHttpBuilder { + + /** + * 创建 OkHttp Builder + * @param key Key ( RetrofitBuilder Manager Key ) + * @return OkHttpClient.Builder + */ + fun createOkHttpBuilder(key: String): OkHttpClient.Builder? +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/manager/OnRetrofitResetListener.kt b/lib/DevHttpManager/src/main/java/dev/http/manager/OnRetrofitResetListener.kt new file mode 100644 index 0000000000..7a60c8584c --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/manager/OnRetrofitResetListener.kt @@ -0,0 +1,31 @@ +package dev.http.manager + +import retrofit2.Retrofit + +/** + * detail: 全局 Retrofit 重新构建监听事件 + * @author Ttt + */ +interface OnRetrofitResetListener { + + /** + * 重新构建前调用 + * @param key String + * @param oldRetrofit 上一次构建的 Retrofit + */ + fun onResetBefore( + key: String, + oldRetrofit: Retrofit? + ) + + /** + * 重新构建后调用 + * @param key String + * @param newRetrofit 重新构建的 Retrofit 对象 + * 在 [onResetBefore] 之后调用 + */ + fun onReset( + key: String, + newRetrofit: Retrofit? + ) +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitBuilder.kt b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitBuilder.kt new file mode 100644 index 0000000000..eb347b8b8e --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitBuilder.kt @@ -0,0 +1,51 @@ +package dev.http.manager + +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import retrofit2.Retrofit + +/** + * detail: Retrofit Builder 接口 + * @author Ttt + */ +interface RetrofitBuilder { + + /** + * 创建 Retrofit Builder + * @param oldRetrofit 上一次构建的 Retrofit + * @param httpUrl 构建使用指定 baseUrl + * @param okHttp OkHttpClient 构建全局复用 + * @return Retrofit.Builder + */ + fun createRetrofitBuilder( + oldRetrofit: Retrofit?, + httpUrl: HttpUrl?, + okHttp: OkHttpClient.Builder? + ): Retrofit.Builder + + // ========== + // = 通知事件 = + // ========== + + /** + * 重新构建前调用 + * @param key String + * @param oldRetrofit 上一次构建的 Retrofit + * 在 [createRetrofitBuilder] 之前调用 + */ + fun onResetBefore( + key: String, + oldRetrofit: Retrofit? + ) + + /** + * 重新构建后调用 + * @param key String + * @param newRetrofit 重新构建的 Retrofit 对象 + * 在 [createRetrofitBuilder] 之后调用 + */ + fun onReset( + key: String, + newRetrofit: Retrofit? + ) +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitManager.kt b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitManager.kt new file mode 100644 index 0000000000..9426e292f0 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitManager.kt @@ -0,0 +1,150 @@ +package dev.http.manager + +import okhttp3.HttpUrl + +/** + * detail: Retrofit Manager + * @author Ttt + */ +internal object RetrofitManager { + + // 全局 OkHttp Builder 接口 + private var sOkHttpBuilder: OkHttpBuilder? = null + + // 全局 Retrofit 重新构建监听事件 + private var sOnRetrofitResetListener: OnRetrofitResetListener? = null + + // 存储 Retrofit Operation 操作对象 + private val sOperationMaps = linkedMapOf() + + // ============= + // = 对外公开方法 = + // ============= + + // ================= + // = OkHttpBuilder = + // ================= + + /** + * 获取全局 OkHttp Builder 接口对象 + * @return OkHttpBuilder + */ + fun getOkHttpBuilder(): OkHttpBuilder? { + return sOkHttpBuilder + } + + /** + * 设置全局 OkHttp Builder 接口对象 + * @param builder [OkHttpBuilder] + */ + fun setOkHttpBuilder(builder: OkHttpBuilder?) { + sOkHttpBuilder = builder + } + + /** + * 移除全局 OkHttp Builder 接口对象 + */ + fun removeOkHttpBuilder() { + setOkHttpBuilder(null) + } + + // =========================== + // = OnRetrofitResetListener = + // =========================== + + /** + * 获取全局 Retrofit 重新构建监听事件 + * @return OnRetrofitResetListener + */ + fun getRetrofitResetListener(): OnRetrofitResetListener? { + return sOnRetrofitResetListener + } + + /** + * 设置全局 Retrofit 重新构建监听事件 + * @param listener [OnRetrofitResetListener] + */ + fun setRetrofitResetListener(listener: OnRetrofitResetListener?) { + sOnRetrofitResetListener = listener + } + + /** + * 移除全局 Retrofit 重新构建监听事件 + */ + fun removeRetrofitResetListener() { + setRetrofitResetListener(null) + } + + // =================== + // = RetrofitBuilder = + // =================== + + /** + * 获取 Retrofit Operation 操作对象 + * @param key Key + * @return Retrofit Operation + */ + fun getOperation(key: String): RetrofitOperation? { + return sOperationMaps[key] + } + + /** + * 通过 Key 判断是否存在 Retrofit Operation 操作对象 + * @param key Key + * @return `true` yes, `false` no + */ + fun containsOperation(key: String): Boolean { + return sOperationMaps.containsKey(key) + } + + /** + * 通过 Key 绑定存储 RetrofitBuilder 并返回 Operation 操作对象 + * @param key Key + * @param builder [RetrofitBuilder] + * @return Retrofit Operation + */ + fun putRetrofitBuilder( + key: String, + builder: RetrofitBuilder + ): RetrofitOperation { + val operation = RetrofitOperation.get(key, builder) + sOperationMaps[key] = operation + return operation + } + + /** + * 通过 Key 解绑移除 RetrofitBuilder 并返回 Operation 操作对象 + * @param key Key + * @return Retrofit Operation + */ + fun removeRetrofitBuilder(key: String): RetrofitOperation? { + return sOperationMaps.remove(key) + } + + // ===================== + // = RetrofitOperation = + // ===================== + + /** + * 重置处理 ( 重新构建 Retrofit ) + * @param key Key + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ + fun reset( + key: String, + httpUrl: HttpUrl? = null + ): RetrofitOperation? { + return getOperation(key)?.reset(httpUrl) + } + + /** + * 重置处理 ( 重新构建全部 Retrofit ) + * @param mapHttpUrl MutableMap + */ + fun resetAll(mapHttpUrl: MutableMap? = null) { + sOperationMaps.forEach { + it.value.reset(mapHttpUrl?.get(it.key)) + } + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitOperation.kt b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitOperation.kt new file mode 100644 index 0000000000..4c47f512f9 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/manager/RetrofitOperation.kt @@ -0,0 +1,145 @@ +package dev.http.manager + +import dev.utils.LogPrintUtils +import okhttp3.HttpUrl +import retrofit2.Retrofit + +/** + * detail: Retrofit Operation + * @author Ttt + */ +class RetrofitOperation private constructor( + private val key: String, + private val builder: RetrofitBuilder +) { + + companion object { + + /** + * 创建 Retrofit Operation + * @param key Key + * @param builder [RetrofitBuilder] + * @return Retrofit Operation + */ + internal fun get( + key: String, + builder: RetrofitBuilder + ): RetrofitOperation { + return RetrofitOperation(key, builder) + } + } + + // 日志 TAG + private val TAG = RetrofitOperation::class.java.simpleName + + // Retrofit + private var mRetrofit: Retrofit? = null + + // 是否重置操作 ( 首次为初始化 ) + private var mReset: Boolean = false + + // ========== + // = 内部方法 = + // ========== + + /** + * 构建 Retrofit 方法 ( 最终调用 ) + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + * 执行循序为 + * Global onResetBefore + * builder ( this ) onResetBefore + * Global createOkHttpBuilder + * builder ( this ) createRetrofitBuilder + * builder ( this ) onReset + * Global onReset + * 使用全局监听事件、构建操作是为了提供统一管理方法, 方便统一做处理 + * 并且自身也存在回调方法, 也能够单独处理 + */ + private fun buildRetrofit(httpUrl: HttpUrl? = null): RetrofitOperation { + if (mReset) { + try { + RetrofitManager.getRetrofitResetListener()?.onResetBefore( + key, mRetrofit + ) + } catch (ignored: Exception) { + } + builder.onResetBefore(key, mRetrofit) + } + + // 获取全局 OkHttp Builder + val okHttpBuilder = try { + RetrofitManager.getOkHttpBuilder()?.createOkHttpBuilder(key) + } catch (ignored: Exception) { + null + } + // 可以通过 mRetrofit?.baseUrl() 获取之前的配置 + mRetrofit = builder.createRetrofitBuilder( + mRetrofit, httpUrl, okHttpBuilder + ).build() + + if (mReset) { + builder.onReset(key, mRetrofit) + try { + RetrofitManager.getRetrofitResetListener()?.onReset( + key, mRetrofit + ) + } catch (ignored: Exception) { + } + } + // 首次为初始化, 后续操作为重置操作 + mReset = true + return this + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Retrofit 对象 + * @param check 是否需要判断 Retrofit 是否为 null + * @return Retrofit + */ + fun getRetrofit(check: Boolean = true): Retrofit? { + if (check && mRetrofit == null) { + buildRetrofit() + } + return mRetrofit + } + + /** + * 通过 Retrofit 代理创建 Service + * @param service Class + * @return Service Class + */ + fun create(service: Class): T? { + try { + return getRetrofit()?.create(service) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "create") + } + return null + } + + /** + * 重置处理 ( 重新构建 Retrofit ) + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ + fun reset(httpUrl: HttpUrl? = null): RetrofitOperation { + return buildRetrofit(httpUrl) + } + + /** + * 重置处理 ( 重新构建 Retrofit ) 并代理创建 Service + * @param httpUrl 构建使用指定 baseUrl + * @return Retrofit Operation + */ + fun resetAndCreate( + service: Class, + httpUrl: HttpUrl? = null + ): T? { + return reset(httpUrl).create(service) + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/Progress.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/Progress.kt new file mode 100644 index 0000000000..cd79e79122 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/Progress.kt @@ -0,0 +1,664 @@ +package dev.http.progress + +import android.os.Parcelable +import android.os.SystemClock +import dev.utils.DevFinal +import dev.utils.common.FileUtils +import dev.utils.common.NumberUtils +import dev.utils.common.assist.url.UrlExtras +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize +import java.util.* + +/** + * detail: 进度信息存储类 + * @author Ttt + */ +@Parcelize +class Progress private constructor( + // 是否上行进度 => `true` 上行, `false` 下行 + private val isRequest: Boolean, + // 进度 id + private val id: Long, + // 创建时间 ( 时间戳 ) + private val date: Long, + // 数据总长度 + private var totalSize: Long, + // 当前已上传、下载总长度 + private var currentSize: Long, + // 最近一次触发大小 ( 最近一次被调用的间隔时间内上传、下载的 byte 长度 ) + private var lastSize: Long, + // 最近一次刷新、触发时间 ( 毫秒 ) + private var lastRefreshTime: Long, + // 当前状态 + private var status: Int, + // 进度异常信息 + private var exception: Throwable?, + // 上传、下载网速信息 + private val speed: Speed, + // 额外携带信息 + private var extras: Extras? +) : Parcelable { + + constructor(isRequest: Boolean) : this( + isRequest, UUID.randomUUID().hashCode().toLong(), + System.currentTimeMillis(), + 0L, 0L, 0L, 0L, + NORMAL, null, Speed(), null + ) + + companion object { + + // 默认状态 ( 暂未进行操作 ) + const val NORMAL = DevFinal.INT.NORMAL + + // 开始上传、下载 + const val START = DevFinal.INT.START + + // 上传、下载中 + const val ING = DevFinal.INT.ING + + // 异常 ( 流程失败 ) + const val ERROR = DevFinal.INT.ERROR + + // 完成 ( 流程完成 ) + const val FINISH = DevFinal.INT.FINISH + + // 回调刷新时间 ( 毫秒 ) + const val REFRESH_TIME = 300L + } + + // ======================== + // = Progress - 对外公开方法 = + // ======================== + + // 拼接 UUID id-date + @IgnoredOnParcel + private val uuidString: String by lazy { + "$id-$date" + } + + /** + * 获取 UUID + * @return uuid + */ + fun getUUID(): String { + return uuidString + } + + // = + + /** + * 是否上行进度信息 + * @return `true` yes, `false` no + */ + fun isRequest(): Boolean { + return isRequest + } + + /** + * 是否下行进度信息 + * @return `true` yes, `false` no + */ + fun isResponse(): Boolean { + return !isRequest + } + + /** + * 获取进度 id + * @return id + */ + fun getId(): Long { + return id + } + + /** + * 获取创建时间 ( 时间戳 ) + * @return 创建时间 ( 时间戳 ) + */ + fun getDate(): Long { + return date + } + + /** + * 获取数据总长度 + * @return 数据总长度 + */ + fun getTotalSize(): Long { + return totalSize + } + + /** + * 获取数据总长度格式化信息 + * @param number 保留小数位数 + * @return 数据总长度格式化信息 + */ + fun getTotalSizeFormat(number: Int = 1): String { + return FileUtils.formatByteMemorySize( + number, totalSize.toDouble() + ) + } + + /** + * 获取当前已上传、下载总长度 + * @return 当前已上传、下载总长度 + */ + fun getCurrentSize(): Long { + return currentSize + } + + /** + * 获取当前已上传、下载总长度格式化信息 + * @param number 保留小数位数 + * @return 当前已上传、下载总长度格式化信息 + */ + fun getCurrentSizeFormat(number: Int = 1): String { + return FileUtils.formatByteMemorySize( + number, currentSize.toDouble() + ) + } + + /** + * 获取最近一次触发大小 ( 最近一次被调用的间隔时间内上传、下载的 byte 长度 ) + * @return 最近一次被调用的间隔时间内上传、下载的 byte 长度 + */ + fun getLastSize(): Long { + return lastSize + } + + /** + * 获取最近一次刷新、触发时间 ( 毫秒 ) + * @return 最近一次刷新、触发时间 ( 毫秒 ) + */ + fun getLastRefreshTime(): Long { + return lastRefreshTime + } + + /** + * 获取当前状态 + * @return 当前状态 + */ + fun getStatus(): Int { + return status + } + + /** + * 获取进度异常信息 + * @return 进度异常信息 + */ + fun getException(): Throwable? { + return exception + } + + /** + * 获取上传、下载网速信息 + * @return Speed + */ + fun getSpeed(): Speed { + return speed + } + + /** + * 获取额外携带信息 + * @return Extras + */ + fun getExtras(): Extras? { + return extras + } + + // = + + /** + * 是否默认状态 ( 暂未进行操作 ) + * @return `true` yes, `false` no + */ + fun isNORMAL(): Boolean { + return status == NORMAL + } + + /** + * 是否开始上传、下载状态 + * @return `true` yes, `false` no + */ + fun isSTART(): Boolean { + return status == START + } + + /** + * 是否上传、下载中状态 + * @return `true` yes, `false` no + */ + fun isING(): Boolean { + return status == ING + } + + /** + * 是否异常状态 + * @return `true` yes, `false` no + */ + fun isERROR(): Boolean { + return status == ERROR + } + + /** + * 是否完成状态 + * @return `true` yes, `false` no + */ + fun isFINISH(): Boolean { + return status == FINISH + } + + /** + * 是否结束状态 ( 用于表示整个过程已结束 ) + * @return `true` yes, `false` no + */ + fun isEND(): Boolean { + return isERROR() || isFINISH() + } + + // = + + /** + * 计算百分比值 ( 最大 100% ) + * @return 百分比值 + */ + fun getPercent(): Int { + return NumberUtils.percentI( + currentSize.toDouble(), totalSize.toDouble() + ) + } + + /** + * 计算百分比值 ( 最大 100% ) + * @return 百分比值 + */ + fun getPercentD(): Double { + return NumberUtils.percentD( + currentSize.toDouble(), totalSize.toDouble() + ) + } + + /** + * 判断数据总长度与当前已上传、下载总长度是否一样大小 + * @return `true` yes, `false` no + */ + fun isSizeSame(): Boolean { + return totalSize > 0 && totalSize == currentSize + } + + /** + * 判断数据长度是否异常 + * @return `true` yes, `false` no + */ + fun isSizeError(): Boolean { + return totalSize < 0 || currentSize > totalSize + } + + // ============ + // = 其他扩展类 = + // ============ + + /** + * detail: 上传、下载网速信息 + * @author Ttt + */ + @Parcelize + class Speed private constructor( + // 网速 byte/s + private var speedValue: Long, + // 网速做平滑的缓存, 避免抖动过大 + private val speedBuffer: MutableList, + // 网速缓存数据存储数量 + private var bufferSize: Int + ) : Parcelable { + + constructor() : this(0L, mutableListOf(), 10) + + // ===================== + // = Speed - 对外公开方法 = + // ===================== + + /** + * 获取上传、下载网速信息 ( byte/s ) + * @return 网速信息 ( byte/s ) + * 可搭配 [FileUtils.formatByteMemorySize] 使用 + */ + fun getSpeed(): Long { + return speedValue + } + + /** + * 获取上传、下载网速信息格式化信息 + * @param number 保留小数位数 + * @return 上传、下载网速信息格式化信息 + */ + fun getSpeedFormat(number: Int = 1): String { + return FileUtils.formatByteMemorySize( + number, speedValue.toDouble() + ) + } + + /** + * 获取上传、下载网速信息格式化信息 ( byte/s ) + * @param number 保留小数位数 + * @return 上传、下载网速信息格式化信息 ( byte/s ) + */ + fun getSpeedFormatSecond(number: Int = 1): String { + return "${getSpeedFormat(number)}/s" + } + + /** + * 获取网速缓存数据存储数量 + * @return 数据存储数量 + */ + fun getBufferSize(): Int { + return bufferSize + } + + /** + * 设置网速缓存数据存储数量 + * @param size 缓存数量 + * @return Speed + *

+ * 允许设置为负数 ( 小于等于 0 则会清空缓存数据并且不进行存储操作 ) + */ + fun setBufferSize(size: Int): Speed { + synchronized(this) { + bufferSize = size.coerceAtLeast(0) + if (bufferSize <= 0) { + speedValue = 0 + speedBuffer.clear() + return this + } + val diffSize = speedBuffer.size - bufferSize + if (diffSize > 0) { + for (i in 0 until diffSize) { + speedBuffer.removeAt(0) + } + } + refreshSpeed() + return this + } + } + + // ================== + // = Speed - 内部方法 = + // ================== + + /** + * 存储网速信息并刷新网速信息 + * @param speed 网速 byte/s + */ + internal fun bufferSpeed(speed: Long) { + synchronized(this) { + if (bufferSize <= 0) { + return@synchronized + } + speedBuffer.add(speed) + if (speedBuffer.size > bufferSize) { + speedBuffer.removeAt(0) + } + refreshSpeed() + } + } + + /** + * 计算并刷新网速信息 + */ + private fun refreshSpeed() { + speedValue = calculateSpeed() + } + + /** + * 计算平均网速 + * @return 平均网速 byte/s + */ + private fun calculateSpeed(): Long { + var sum = 0L + for (speedTemp in speedBuffer) { + sum += speedTemp + } + return sum / speedBuffer.size + } + } + + /** + * detail: 额外携带信息 + * @author Ttt + */ + @Parcelize + class Extras constructor( + // 请求链接 + private val url: String, + // 请求方法 + private val method: String, + // 请求头信息 + private val headers: Map + ) : Parcelable { + + // Url 携带信息解析 + @IgnoredOnParcel + private val innerUrlExtras: UrlExtras by lazy { + UrlExtras(url) + } + + // ====================== + // = Extras - 对外公开方法 = + // ====================== + + /** + * 获取请求链接 + * @return 请求链接 + */ + fun getUrl(): String { + return url + } + + /** + * 获取请求方法 + * @return 请求方法 + */ + fun getMethod(): String { + return method + } + + /** + * 获取请求头信息 + * @return 请求头信息 + */ + fun getHeaders(): Map { + return headers + } + + /** + * 获取 Url 携带信息解析 + * @return UrlExtras + */ + fun getUrlExtras(): UrlExtras { + return innerUrlExtras + } + } + + // ===================== + // = Progress - 内部方法 = + // ===================== + + /** + * 设置数据总长度 + * @param value 数据总长度 + * @return Progress + */ + internal fun setTotalSize(value: Long): Progress { + totalSize = value + return this + } + + /** + * 设置当前已上传、下载总长度 + * @param value 当前已上传、下载总长度 + * @return Progress + */ + internal fun setCurrentSize(value: Long): Progress { + currentSize = value + return this + } + + /** + * 设置最近一次触发大小 ( 最近一次被调用的间隔时间内上传、下载的 byte 长度 ) + * @param value 最近一次被调用的间隔时间内上传、下载的 byte 长度 + * @return Progress + */ + internal fun setLastSize(value: Long): Progress { + lastSize = value + return this + } + + /** + * 设置最近一次刷新、触发时间 ( 毫秒 ) + * @param value 最近一次刷新、触发时间 ( 毫秒 ) + * @return Progress + */ + internal fun setLastRefreshTime(value: Long): Progress { + lastRefreshTime = value + return this + } + + /** + * 设置进度异常信息 + * @param value 进度异常信息 + * @return Progress + */ + internal fun setException(value: Throwable): Progress { + exception = value + return this + } + + /** + * 设置额外携带信息 + * @param value 额外携带信息 + * @return Progress + */ + internal fun setExtras(value: Extras?): Progress { + extras = value + return this + } + + // ========== + // = 更新状态 = + // ========== + + /** + * 设置当前状态 + * @param value 当前状态 + * @return Progress + */ + private fun setStatus(value: Int): Progress { + status = value + return this + } + + /** + * 设置为 [Progress.START] 状态 + * @return `true` success, `false` fail + */ + internal fun toStart(): Boolean { + if (isNORMAL()) { + setStatus(START) + .setLastRefreshTime(SystemClock.elapsedRealtime()) + return true + } + return false + } + + /** + * 设置为 [Progress.ING] 状态 + * @return `true` success, `false` fail + */ + internal fun toIng(): Boolean { + if (isSTART()) { + setStatus(ING) + return true + } + return false + } + + /** + * 设置为 [Progress.ERROR] 状态 + * @param exception 进度异常信息 + * @return `true` success, `false` fail + */ + internal fun toError(exception: Throwable): Boolean { + if (isING()) { + setStatus(ERROR).setException(exception) + return true + } + return false + } + + /** + * 设置为 [Progress.FINISH] 状态 + * @return `true` success, `false` fail + */ + internal fun toFinish(): Boolean { + if (isING()) { + setStatus(FINISH) + return true + } + return false + } + + // ========== + // = 接口事件 = + // ========== + + /** + * detail: 上传、下载回调接口 + * @author Ttt + * 回调不是表示上传、下载结果, 而是表示上传、下载这个操作流程回调 + *

+ * 如何判断是否需要处理各个方法, 只需要在 [onStart] 判断 [Progress.Extras] 信息 + * 并存储当前的 [Progress.id] 其他方法都用已存储的 id 和传入的 Progress.id 对比即可 + */ + interface Callback { + + /** + * 是否自动释放监听对象 + * @param progress Progress + * @return `true` yes, `false` no + */ + fun isAutoRecycle(progress: Progress): Boolean { + return true + } + + /** + * 开始回调 + * @param progress Progress + */ + fun onStart(progress: Progress) + + /** + * 进度回调 + * @param progress Progress + */ + fun onProgress(progress: Progress) + + /** + * 流程异常回调 + * @param progress Progress + */ + fun onError(progress: Progress) + + /** + * 流程完成回调 + * @param progress Progress + */ + fun onFinish(progress: Progress) + + /** + * 流程结束回调 + * @param progress Progress + * 不管是 [onError]、[onFinish] 最终都会触发该结束方法 + */ + fun onEnd(progress: Progress) + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressManager.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressManager.kt new file mode 100644 index 0000000000..72eb2a6431 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressManager.kt @@ -0,0 +1,216 @@ +package dev.http.progress + +import android.os.Handler +import dev.DevUtils +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import okhttp3.Request +import okhttp3.Response + +// ============= +// = 对外公开方法 = +// ============= + +/** + * Request 转 Progress.Extras + * @receiver Request + * @return Progress.Extras? + */ +fun Request.toExtras(): Progress.Extras? { + try { + // 请求链接 + val rUrl = url.toUrl().toString() + // 请求头信息 + val rHeaders = mutableMapOf() + + for (i in 0 until headers.size) { + rHeaders[headers.name(i)] = headers.value(i) + } + return Progress.Extras(rUrl, method, rHeaders) + } catch (e: Exception) { + LogPrintUtils.eTag(ProgressManager.TAG, e, "toExtras") + } + return null +} + +/** + * Request 使用 ProgressRequestBody 包装构建 + * @receiver Request + * @return Request + */ +fun Request.wrapRequestBody( + // 上传、下载回调接口 + callback: Progress.Callback?, + // 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + handler: Handler? = DevUtils.getHandler(), + // 回调刷新时间 ( 毫秒 ) - 小于等于 0 则每次进度变更都进行通知 + refreshTime: Long = Progress.REFRESH_TIME, + // 额外携带信息 + extras: Progress.Extras? = this.toExtras() +): Request { + return body?.let { requestBody -> + this.newBuilder() + .method( + method, ProgressRequestBody( + requestBody, callback, handler, + refreshTime, extras + ) + ) + .build() + } ?: this +} + +/** + * Response 使用 ProgressResponseBody 包装构建 + * @receiver Response + * @return Response + */ +fun Response.wrapResponseBody( + // 上传、下载回调接口 + callback: Progress.Callback?, + // 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + handler: Handler? = DevUtils.getHandler(), + // 回调刷新时间 ( 毫秒 ) - 小于等于 0 则每次进度变更都进行通知 + refreshTime: Long = Progress.REFRESH_TIME, + // 额外携带信息 + extras: Progress.Extras? = null +): Response { + return body?.let { responseBody -> + this.newBuilder() + .body( + ProgressResponseBody( + responseBody, callback, handler, + refreshTime, extras + ) + ) + .build() + } ?: this +} + +// ============ +// = 内部封装类 = +// ============ + +/** + * detail: Progress Manager + * @author Ttt + * OkHttp API: + * @see https://square.github.io/okhttp/recipes + */ +internal object ProgressManager { + + // 日志 TAG + val TAG = ProgressManager::class.java.simpleName + + // 存储 Progress Operation 操作对象 + private val sOperationMaps = linkedMapOf() + + // 默认监听上下行操作对象 + private val mDefault: ProgressOperation by lazy { + ProgressOperation.get( + DevFinal.STR.DEFAULT, true, + ProgressOperation.TYPE_ALL + ) + } + + // ===================== + // = ProgressOperation = + // ===================== + + /** + * 获取全局默认 Progress Operation 操作对象 + * @return ProgressOperation + */ + fun getDefault(): ProgressOperation { + return mDefault + } + + /** + * 获取 Progress Operation 操作对象 + * @param key Key + * @return Progress Operation + */ + fun getOperation(key: String): ProgressOperation? { + return sOperationMaps[key] + } + + /** + * 通过 Key 判断是否存在 Progress Operation 操作对象 + * @param key Key + * @return `true` yes, `false` no + */ + fun containsOperation(key: String): Boolean { + return sOperationMaps.containsKey(key) + } + + /** + * 通过 Key 解绑并返回 Operation 操作对象 + * @param key Key + * @return Progress Operation + */ + fun removeOperation(key: String): ProgressOperation? { + return sOperationMaps.remove(key)?.recycle() + } + + /** + * 清空所有 Progress Operation 操作对象 + */ + fun clearOperation() { + val map = sOperationMaps.toMutableMap() + sOperationMaps.clear() + map.values.forEach { + it.recycle() + } + map.clear() + } + + // = + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听上下行 ) + * @param key Key + * @return Progress Operation + */ + fun putOperationTypeAll(key: String): ProgressOperation { + return putOperation(key, ProgressOperation.TYPE_ALL) + } + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听上行 ) + * @param key Key + * @return Progress Operation + */ + fun putOperationTypeRequest(key: String): ProgressOperation { + return putOperation(key, ProgressOperation.TYPE_REQUEST) + } + + /** + * 通过 Key 绑定并返回 Operation 操作对象 ( 监听下行 ) + * @param key Key + * @return Progress Operation + */ + fun putOperationTypeResponse(key: String): ProgressOperation { + return putOperation(key, ProgressOperation.TYPE_RESPONSE) + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 通过 Key 绑定并返回 Operation 操作对象 + * @param key Key + * @param type 内部拦截器监听类型 + * @return Progress Operation + */ + private fun putOperation( + key: String, + type: Int + ): ProgressOperation { + // 如果存在那么先废弃历史对象 + getOperation(key)?.recycle() + val operation = ProgressOperation.get(key, false, type) + sOperationMaps[key] = operation + return operation + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressOperation.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressOperation.kt new file mode 100644 index 0000000000..94580aeb5a --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressOperation.kt @@ -0,0 +1,453 @@ +package dev.http.progress + +import android.os.Handler +import dev.http.progress.operation.BaseOperation +import dev.http.progress.operation.IOperation +import dev.http.progress.operation.OperationPlanA +import dev.http.progress.operation.OperationPlanB +import dev.utils.common.assist.url.UrlExtras +import okhttp3.OkHttpClient + +/** + * detail: Progress Operation + * @author Ttt + * 通过 key 区分, 支持不同组件模块化网络进度监听 + * 支持上行、下行、上下行三种类型, 内部拦截器监听回调 + *

+ * 注意事项: + * 绑定监听事件是通过 url 进行绑定的 + * 例 url 为 https://developer.android.com/docs?type=1&abc=afkt + * 会统一处理成 https://developer.android.com/docs 作为 key + * 而其他信息通过 [Progress.Extras] 进行获取请求方法、请求头信息 + * 以及 url 携带参数解析 [Progress.Extras.getUrlExtras] + * 具体参数信息拆分获取可通过 [UrlExtras] 进行获取 + * 并自行根据 Request 信息进行判断处理回调事件 + *

+ * 因通知回调功能支持方式不同, 选择的技术方案不同, 各有利弊 + * 已提供切换实现方式方法 [setPlanType] + * + * 方式二: [OperationPlanB] + * 在创建 [wrapRequestBody]、[wrapResponseBody] 时, 创建一个新的 Callback + * 并把 listener map 对应 url 监听的 Callback List toArray 传入进行通知使用 + * 优点: 可以对 List 进行弱引用处理, 会自动进行释放资源 + * 缺点: 对 listener map 新增 url key add listener 在请求之后添加 + * 则会无法触发 ( 因为在请求拦截时候就已传入 Callback List toArray ) + * + * 方式一 ( 默认 ): [OperationPlanA] + * 在创建 [wrapRequestBody]、[wrapResponseBody] 时, 使用统一回调 [OperationPlanA.innerCallback] 无需每次 new Callback + * 在统一回调内获取 listener map 对应 url 监听的 Callback List 并进行通知 + * 优点: 支持实时 add listener 并进行通知回调 + * 缺点: 实时通知可能因绑定顺序差异, 需自行根据 [Progress.Extras.getUrlExtras] 进行判断是否需要处理该通知 + * listener map 中的 Callback 需要手动进行释放 + * 针对该缺点提供了两个解决方案 + * 1.[Progress.Callback] 提供 isAutoRecycle 方法 ( 默认销毁 ) 可自行判断是否需要销毁 + * 2.提供 [recycleListener] 方法可在 Callback onEnd 中直接调用释放资源无需实现逻辑 + */ +class ProgressOperation private constructor( + private val key: String, + // 全局默认操作对象 + private val globalDefault: Boolean, + // 内部拦截器监听类型 + private val type: Int, +) : IOperation { + + companion object { + + // ================== + // = 内部拦截器监听类型 = + // ================== + + // 监听上下行类型 + internal const val TYPE_ALL = 0 + + // 监听上行 + internal const val TYPE_REQUEST = 1 + + // 监听下行 + internal const val TYPE_RESPONSE = 2 + + // ============= + // = 实现方式类型 = + // ============= + + // 实现方式一 ( 默认使用 ) + const val PLAN_A = 1 + + // 实现方式二 + const val PLAN_B = 2 + + // ========== + // = create = + // ========== + + /** + * 创建 Progress Operation + * @param key Key + * @param globalDefault 全局默认操作对象 + * @param type 内部拦截器监听类型 + * @return Progress Operation + */ + internal fun get( + key: String, + globalDefault: Boolean, + type: Int + ): ProgressOperation { + return ProgressOperation(key, globalDefault, type) + } + } + + // 实现方式类型 + private var mPlanType: Int = PLAN_A + + // Progress Operation 方案实现 + private val IMPL: BaseOperation by lazy { + when (mPlanType) { + PLAN_B -> OperationPlanB(key, globalDefault, type) + else -> OperationPlanA(key, globalDefault, type) + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 设置 Progress Operation 实现方式类型 + * @param planType 实现方式类型 [ProgressOperation.PLAN_A]、[ProgressOperation.PLAN_B] + * @return IOperation + * 在没调用 IOperation 接口任何方法前, 调用该方法切换才有效 + */ + fun setPlanType(planType: Int): IOperation { + mPlanType = when (planType) { + PLAN_B -> planType + else -> PLAN_A + } + return this + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 释放资源方法 + */ + internal fun recycle(): ProgressOperation { + IMPL.markDeprecated() + return this + } + + // ============== + // = IOperation = + // ============== + + // ============ + // = 初始化方法 = + // ============ + + /** + * 进行拦截器包装 ( 必须调用 ) + * @param builder Builder + * @return Builder + * 必须进行调用, 否则无法进行拦截监听 + * 推荐在 OkHttpClient Builder 最后一步调用 + * 防止中间有其他拦截器导致获取为旧数据 + * 例: + * val okhttpClient = wrap(builder).build() + */ + override fun wrap(builder: OkHttpClient.Builder): OkHttpClient.Builder { + return IMPL.wrap(builder) + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否已调用 wrap 方法 + * @return `true` yes, `false` no + */ + override fun isUseWrap(): Boolean { + return IMPL.isUseWrap() + } + + /** + * 是否废弃不用状态 + * @return `true` yes, `false` no + */ + override fun isDeprecated(): Boolean { + return IMPL.isDeprecated() + } + + /** + * 是否全局默认操作对象 + * @return `true` yes, `false` no + */ + override fun isDefault(): Boolean { + return IMPL.isDefault() + } + + /** + * 是否监听上下行 + * @return `true` yes, `false` no + */ + override fun isTypeAll(): Boolean { + return IMPL.isTypeAll() + } + + /** + * 是否监听上行 + * @return `true` yes, `false` no + */ + override fun isTypeRequest(): Boolean { + return IMPL.isTypeRequest() + } + + /** + * 是否监听下行 + * @return `true` yes, `false` no + */ + override fun isTypeResponse(): Boolean { + return IMPL.isTypeResponse() + } + + // =========== + // = get/set = + // =========== + + /** + * 获取 Progress Operation 实现方式类型 + * @return 实现方式类型 + */ + override fun getPlanType(): Int { + return IMPL.getPlanType() + } + + // = + + /** + * 获取回调刷新时间 ( 毫秒 ) + * @return 回调刷新时间 ( 毫秒 ) + */ + override fun getRefreshTime(): Long { + return IMPL.getRefreshTime() + } + + /** + * 设置回调刷新时间 ( 毫秒 ) + * @param refreshTime 回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + override fun setRefreshTime(refreshTime: Long): IOperation { + return IMPL.setRefreshTime(refreshTime) + } + + /** + * 重置回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + override fun resetRefreshTime(): IOperation { + return IMPL.resetRefreshTime() + } + + // = + + /** + * 获取全局 Progress Callback + * @return Progress Callback + */ + override fun getCallback(): Progress.Callback? { + return IMPL.getCallback() + } + + /** + * 设置全局 Progress Callback + * @param callback Progress Callback + * @return IOperation + */ + override fun setCallback(callback: Progress.Callback?): IOperation { + return IMPL.setCallback(callback) + } + + /** + * 移除全局 Progress Callback + * @return IOperation + */ + override fun removeCallback(): IOperation { + return IMPL.removeCallback() + } + + // = + + /** + * 获取回调 UI 线程通知 Handler + * @return 回调 UI 线程通知 Handler + */ + override fun getHandler(): Handler? { + return IMPL.getHandler() + } + + /** + * 设置回调 UI 线程通知 Handler + * @param handler 回调 UI 线程通知 Handler + * @return IOperation + */ + override fun setHandler(handler: Handler?): IOperation { + return IMPL.setHandler(handler) + } + + /** + * 重置回调 UI 线程通知 Handler + * @return IOperation + */ + override fun resetHandler(): IOperation { + return IMPL.resetHandler() + } + + // ==================== + // = 操作方法 - 对外公开 = + // ==================== + + /** + * 移除自身在 Manager Map 中的对象值, 并且标记为废弃状态 + * 会释放所有数据、监听事件且不处理任何监听 + */ + override fun removeSelfFromManager() { + return IMPL.removeSelfFromManager() + } + + /** + * 释放指定监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun recycleListener( + progress: Progress, + callback: Progress.Callback + ): Boolean { + return IMPL.recycleListener(progress, callback) + } + + // ==================== + // = Request Listener = + // ==================== + + /** + * 添加指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun addRequestListener( + url: String, + callback: Progress.Callback + ): Boolean { + return IMPL.addRequestListener(url, callback) + } + + /** + * 清空指定 url 上行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearRequestListener(url: String): Boolean { + return IMPL.clearRequestListener(url) + } + + /** + * 清空指定 url 上行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearRequestListener(progress: Progress?): Boolean { + return IMPL.clearRequestListener(progress) + } + + /** + * 移除指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeRequestListener( + url: String, + callback: Progress.Callback + ): Boolean { + return IMPL.removeRequestListener(url, callback) + } + + /** + * 移除指定 url 上行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeRequestListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean { + return IMPL.removeRequestListener(progress, callback) + } + + // ===================== + // = Response Listener = + // ===================== + + /** + * 添加指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun addResponseListener( + url: String, + callback: Progress.Callback + ): Boolean { + return IMPL.addResponseListener(url, callback) + } + + /** + * 清空指定 url 下行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearResponseListener(url: String): Boolean { + return IMPL.clearResponseListener(url) + } + + /** + * 清空指定 url 下行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearResponseListener(progress: Progress?): Boolean { + return IMPL.clearResponseListener(progress) + } + + /** + * 移除指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeResponseListener( + url: String, + callback: Progress.Callback + ): Boolean { + return IMPL.removeResponseListener(url, callback) + } + + /** + * 移除指定 url 下行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeResponseListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean { + return IMPL.removeResponseListener(progress, callback) + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressRequestBody.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressRequestBody.kt new file mode 100644 index 0000000000..4488e5082f --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressRequestBody.kt @@ -0,0 +1,218 @@ +package dev.http.progress + +import android.os.Handler +import dev.DevUtils +import dev.utils.LogPrintUtils +import okhttp3.MediaType +import okhttp3.RequestBody +import okio.* +import java.io.IOException + +/** + * detail: 上行进度监听请求体 + * @author Ttt + * 通过此类获取 OkHttp 请求体数据处理进度, 可以处理任何的 RequestBody + */ +open class ProgressRequestBody( + // 原数据请求体 + protected val delegate: RequestBody, + // 上传、下载回调接口 + protected val callback: Progress.Callback?, + // 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + protected val handler: Handler? = DevUtils.getHandler(), + // 回调刷新时间 ( 毫秒 ) - 小于等于 0 则每次进度变更都进行通知 + protected val refreshTime: Long = Progress.REFRESH_TIME, + // 额外携带信息 ( 可通过 Request.toExtras() 创建 ) + protected val extras: Progress.Extras? = null +) : RequestBody() { + + // 日志 TAG + protected val TAG = ProgressRequestBody::class.java.simpleName + + // =============== + // = RequestBody = + // =============== + + override fun isOneShot(): Boolean { + return delegate.isOneShot() + } + + override fun contentType(): MediaType? { + return delegate.contentType() + } + + override fun contentLength(): Long { + return try { + delegate.contentLength() + } catch (e: IOException) { + LogPrintUtils.eTag(TAG, e, "contentLength") + -1L + } + } + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { +// val countingSink = CountingSink(sink) +// val bufferedSink = countingSink.buffer() +// delegate.writeTo(bufferedSink) +// bufferedSink.flush() +// /** +// * 在这里调用 finish 是防止后台异常无限制上传 +// * 而不是通过 [Progress.isSizeSame] +// * 判断数据总长度与当前已上传、下载总长度是否一样大小 +// */ +// countingSink.finishCallback() + + if (innerBufferedSink == null) { + innerBufferedSink = InnerBufferedSink(sink) + } + innerBufferedSink?.writeTo(delegate) + } + + // ============ + // = 内部包装类 = + // ============ + + private var innerBufferedSink: InnerBufferedSink? = null + + /** + * detail: writeTo 注释代码二次封装 + * @author Ttt + */ + private inner class InnerBufferedSink(sink: BufferedSink) { + + val countingSink = CountingSink(sink) + + val bufferedSink: BufferedSink by lazy { + countingSink.buffer() + } + + fun writeTo(body: RequestBody) { + body.writeTo(bufferedSink) + bufferedSink.flush() + /** + * 在这里调用 finish 是防止后台异常无限制上传 + * 而不是通过 [Progress.isSizeSame] + * 判断数据总长度与当前已上传、下载总长度是否一样大小 + */ + countingSink.finishCallback() + } + } + + /** + * detail: 内部进度监听包装类 + * @author Ttt + */ + private inner class CountingSink(sink: Sink) : ForwardingSink(sink) { + + // 进度信息存储类 + private val progress = Progress(true) + + init { + progress.setExtras(extras) + .setTotalSize(contentLength()) + } + + // ================== + // = ForwardingSink = + // ================== + + @Throws(IOException::class) + override fun write( + source: Buffer, + byteCount: Long + ) { + if (progress.getTotalSize() <= 0) { + progress.setTotalSize(contentLength()) + } + if (progress.getTotalSize() > 0) { + progress.toStartAndCallback(callback, handler) + } + + try { + super.write(source, byteCount) + } catch (e: Exception) { + progress.flowIng().toErrorAndCallback(e, callback, handler) + throw e + } + if (progress.getTotalSize() > 0) { + progress.flowIng() + // 更新进度信息并返回是否允许通知 + val allowCallback = changeProgress( + progress, refreshTime, byteCount.coerceAtLeast(0) + ) + if (allowCallback) { + progress.toIngAndCallback(callback, handler) + } + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 流程完成回调 + */ + fun finishCallback() { + if (progress.getTotalSize() > 0) { + progress.toFinishAndCallback(callback, handler) + } + } + } + +// /** +// * detail: 内部进度监听包装类 +// * @author Ttt +// * 历史实现代码, 待优化回调顺序 +// */ +// private inner class CountingSink(sink: Sink) : ForwardingSink(sink) { +// +// // 进度信息存储类 +// private val progress = Progress(true) +// +// init { +// progress.setExtras(extras) +// .setTotalSize(contentLength()) +// .toStartAndCallback(callback, handler) +// } +// +// // ================== +// // = ForwardingSink = +// // ================== +// +// @Throws(IOException::class) +// override fun write( +// source: Buffer, +// byteCount: Long +// ) { +// progress.toIng() +// try { +// super.write(source, byteCount) +// } catch (e: Exception) { +// progress.toErrorAndCallback(e, callback, handler) +// throw e +// } +// if (progress.getTotalSize() <= 0) { +// progress.setTotalSize(contentLength()) +// } +// val allowCallback = changeProgress( +// progress, refreshTime, byteCount.coerceAtLeast(0) +// ) +// if (allowCallback) { +// progress.toIngAndCallback(callback, handler) +// } +// } +// +// // ============= +// // = 对外公开方法 = +// // ============= +// +// /** +// * 流程完成回调 +// */ +// fun finishCallback() { +// progress.toFinishAndCallback(callback, handler) +// } +// } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressResponseBody.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressResponseBody.kt new file mode 100644 index 0000000000..dbad4e1c1e --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressResponseBody.kt @@ -0,0 +1,153 @@ +package dev.http.progress + +import android.os.Handler +import dev.DevUtils +import okhttp3.MediaType +import okhttp3.ResponseBody +import okio.* + +/** + * detail: 下行进度监听响应体 + * @author Ttt + */ +open class ProgressResponseBody( + // 原数据响应体 + protected val delegate: ResponseBody, + // 上传、下载回调接口 + protected val callback: Progress.Callback?, + // 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + protected val handler: Handler? = DevUtils.getHandler(), + // 回调刷新时间 ( 毫秒 ) - 小于等于 0 则每次进度变更都进行通知 + protected val refreshTime: Long = Progress.REFRESH_TIME, + // 额外携带信息 ( 可通过 Request.toExtras() 创建 ) + protected val extras: Progress.Extras? = null +) : ResponseBody() { + + // =============== + // = ResponseBody = + // =============== + + override fun contentType(): MediaType? { + return delegate.contentType() + } + + override fun contentLength(): Long { + return delegate.contentLength() + } + + override fun source(): BufferedSource { +// return CountingSource(delegate.source()).buffer() + return bufferedSource + } + + // ============ + // = 内部包装类 = + // ============ + + private val bufferedSource: BufferedSource by lazy { + CountingSource(delegate.source()).buffer() + } + + /** + * detail: 内部进度监听包装类 + * @author Ttt + */ + private inner class CountingSource(source: Source) : ForwardingSource(source) { + + // 进度信息存储类 + private val progress = Progress(false) + + init { + progress.setExtras(extras) + .setTotalSize(contentLength()) + } + + // ==================== + // = ForwardingSource = + // ==================== + + override fun read( + sink: Buffer, + byteCount: Long + ): Long { + if (progress.getTotalSize() <= 0) { + progress.setTotalSize(contentLength()) + } + if (progress.getTotalSize() > 0) { + progress.toStartAndCallback(callback, handler) + } + + val byteRead: Long + try { + byteRead = super.read(sink, byteCount) + } catch (e: Exception) { + progress.flowIng().toErrorAndCallback(e, callback, handler) + throw e + } + if (progress.getTotalSize() > 0) { + progress.flowIng() + // 更新进度信息并返回是否允许通知 + val allowCallback = changeProgress( + progress, refreshTime, byteRead.coerceAtLeast(0) + ) + if (allowCallback) { + progress.toIngAndCallback(callback, handler) + } + if (progress.isSizeSame()) { + progress.toFinishAndCallback(callback, handler) + } + } + return byteRead + } + } + +// /** +// * detail: 内部进度监听包装类 +// * @author Ttt +// * 历史实现代码, 当 contentLength() 为 -1 时 +// * 只会触发一次 startCallback 且不会触发其他回调 +// */ +// private inner class CountingSource(source: Source) : ForwardingSource(source) { +// +// // 进度信息存储类 +// private val progress = Progress(false) +// +// init { +// progress.setExtras(extras) +// .setTotalSize(contentLength()) +// .toStartAndCallback(callback, handler) +// } +// +// // ==================== +// // = ForwardingSource = +// // ==================== +// +// override fun read( +// sink: Buffer, +// byteCount: Long +// ): Long { +// progress.toIng() +// +// val byteRead: Long +// try { +// byteRead = super.read(sink, byteCount) +// } catch (e: Exception) { +// progress.toErrorAndCallback(e, callback, handler) +// throw e +// } +// if (progress.getTotalSize() <= 0) { +// progress.setTotalSize(contentLength()) +// } +// val allowCallback = changeProgress( +// progress, refreshTime, byteRead.coerceAtLeast(0) +// ) +// if (allowCallback) { +// progress.toIngAndCallback(callback, handler) +// } +// if (progress.isSizeSame()) { +// progress.toFinishAndCallback(callback, handler) +// } +// return byteRead +// } +// } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressUtils.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressUtils.kt new file mode 100644 index 0000000000..ebb2d523a4 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/ProgressUtils.kt @@ -0,0 +1,182 @@ +package dev.http.progress + +import android.os.Handler +import android.os.SystemClock + +// ========== +// = 更新进度 = +// ========== + +/** + * 更新进度信息并返回是否允许通知 + * @param progress 进度信息存储类 + * @param refreshTime 回调刷新时间 ( 毫秒 ) + * @param writeSize 写入数据大小 + * @return `true` yes, `false` no + */ +internal fun changeProgress( + progress: Progress, + refreshTime: Long, + writeSize: Long +): Boolean { + return changeProgress(progress, refreshTime, writeSize, progress.getTotalSize()) +} + +/** + * 更新进度信息并返回是否允许通知 + * @param progress 进度信息存储类 + * @param refreshTime 回调刷新时间 ( 毫秒 ) + * @param writeSize 写入数据大小 + * @param totalSize 数据总长度 + * @return `true` yes, `false` no + */ +internal fun changeProgress( + progress: Progress, + refreshTime: Long, + writeSize: Long, + totalSize: Long +): Boolean { + progress.setTotalSize(totalSize) + .setCurrentSize(progress.getCurrentSize() + writeSize) + .setLastSize(progress.getLastSize() + writeSize) + + val currentTime = SystemClock.elapsedRealtime() + var diffTime = currentTime - progress.getLastRefreshTime() + // 判断当前时间 - 最近一次刷新时间是否超过回调间隔时间 + val isNotify = (diffTime >= refreshTime) + val isFinish = (progress.getCurrentSize() == totalSize) + if (isNotify || isFinish) { + if (diffTime == 0L) diffTime = 1L + val lastSize = progress.getLastSize() + // 存储网速信息并刷新网速信息 byte/s + progress.getSpeed().bufferSpeed( + lastSize * 1000L / diffTime + ) + progress.setLastSize(0L) + .setLastRefreshTime(currentTime) + return true + } + return false +} + +// ========== +// = 事件通知 = +// ========== + +/** + * 回调方法 + * @param notifyStatus 通知状态 + * @param callback 上传、下载回调接口 + * @param handler 回调 UI 线程通知 + */ +private fun Progress.callback( + notifyStatus: Int, + callback: Progress.Callback?, + handler: Handler? +) { + // 准备通知状态与当前状态相同才触发回调, 防止多次触发当前状态回调 + if (notifyStatus == getStatus()) { + callback?.let { itCallback -> + handler?.post { + innerCallback(notifyStatus, itCallback) + } ?: innerCallback(notifyStatus, itCallback) + } + } +} + +/** + * 回调方法 + * @param status Progress 当前状态 + * @param callback 上传、下载回调接口 + * status 不通过 [Progress.getStatus] 获取, 而是通过传参判断 + * 是防止线程触发回调中进行更新状态, 导致跳过 START 回调 + */ +private fun Progress.innerCallback( + status: Int, + callback: Progress.Callback +) { + when (status) { + Progress.START -> { + callback.onStart(this) + } + Progress.ING -> { + callback.onProgress(this) + } + Progress.ERROR -> { + callback.onError(this) + callback.onEnd(this) + } + Progress.FINISH -> { + callback.onFinish(this) + callback.onEnd(this) + } + } +} + +// =============== +// = 更新状态并通知 = +// =============== + +/** + * 设置为 [Progress.START] 状态并且进行通知 + * @param callback 上传、下载回调接口 + * @param handler 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + */ +internal fun Progress.toStartAndCallback( + callback: Progress.Callback?, + handler: Handler? +) { + if (toStart()) callback(Progress.START, callback, handler) +} + +/** + * 设置为 [Progress.ING] 状态并且进行通知 + * @param callback 上传、下载回调接口 + * @param handler 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + */ +internal fun Progress.toIngAndCallback( + callback: Progress.Callback?, + handler: Handler? +) { + toIng() + callback(Progress.ING, callback, handler) +} + +/** + * 设置为 [Progress.ERROR] 状态并且进行通知 + * @param exception 进度异常信息 + * @param callback 上传、下载回调接口 + * @param handler 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + */ +internal fun Progress.toErrorAndCallback( + exception: Throwable, + callback: Progress.Callback?, + handler: Handler? +) { + if (toError(exception)) callback(Progress.ERROR, callback, handler) +} + +/** + * 设置为 [Progress.FINISH] 状态并且进行通知 + * @param callback 上传、下载回调接口 + * @param handler 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + */ +internal fun Progress.toFinishAndCallback( + callback: Progress.Callback?, + handler: Handler? +) { + if (toFinish()) callback(Progress.FINISH, callback, handler) +} + +// ============= +// = 流式更新状态 = +// ============= + +/** + * 设置为 [Progress.ING] 状态 + * @return Progress + */ +internal fun Progress.flowIng(): Progress { + toIng() + return this +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/operation/BaseOperation.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/BaseOperation.kt new file mode 100644 index 0000000000..689620fc9f --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/BaseOperation.kt @@ -0,0 +1,579 @@ +package dev.http.progress.operation + +import android.os.Handler +import dev.DevUtils +import dev.http.progress.* +import okhttp3.Interceptor +import okhttp3.OkHttpClient + +/** + * detail: 基础 Progress Operation 通用实现类 + * @author Ttt + */ +internal abstract class BaseOperation constructor( + private val key: String, + // 全局默认操作对象 + private val globalDefault: Boolean, + // 内部拦截器监听类型 + private val type: Int, + // 实现方式类型 + private val planType: Int, +) : IOperation { + + // 是否已调用 wrap 方法 + private var mUseWrap = false + + // 是否废弃不用 ( true 的情况下不会拦截网络且任何操作都不会赋值 ) + private var mDeprecated = false + + // 回调刷新时间 ( 毫秒 ) - 小于等于 0 则每次进度变更都进行通知 + private var mRefreshTime: Long = Progress.REFRESH_TIME + + // 全局 Progress.Callback + private var mCallback: Progress.Callback? = null + + // 回调 UI 线程通知 ( 如果为 null 则会非 UI 线程通知 ) + private var mHandler: Handler? = null + + // ============== + // = IOperation = + // ============== + + // ============ + // = 初始化方法 = + // ============ + + /** + * 进行拦截器包装 ( 必须调用 ) + * @param builder Builder + * @return Builder + */ + override fun wrap(builder: OkHttpClient.Builder): OkHttpClient.Builder { + if (mDeprecated) return builder + mUseWrap = true + // 防止多次添加 + if (builder.interceptors().contains(innerProgressInterceptor)) { + return builder + } + builder.addInterceptor(innerProgressInterceptor) + return builder + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否已调用 wrap 方法 + * @return `true` yes, `false` no + */ + override fun isUseWrap(): Boolean { + return mUseWrap + } + + /** + * 是否废弃不用状态 + * @return `true` yes, `false` no + */ + override fun isDeprecated(): Boolean { + return mDeprecated + } + + /** + * 是否全局默认操作对象 + * @return `true` yes, `false` no + */ + override fun isDefault(): Boolean { + return globalDefault + } + + /** + * 是否监听上下行 + * @return `true` yes, `false` no + */ + override fun isTypeAll(): Boolean { + return type == ProgressOperation.TYPE_ALL + } + + /** + * 是否监听上行 + * @return `true` yes, `false` no + */ + override fun isTypeRequest(): Boolean { + return type == ProgressOperation.TYPE_REQUEST + } + + /** + * 是否监听下行 + * @return `true` yes, `false` no + */ + override fun isTypeResponse(): Boolean { + return type == ProgressOperation.TYPE_RESPONSE + } + + // =========== + // = get/set = + // =========== + + /** + * 获取 Progress Operation 实现方式类型 + * @return 实现方式类型 + */ + override fun getPlanType(): Int { + return planType + } + + // = + + /** + * 获取回调刷新时间 ( 毫秒 ) + * @return 回调刷新时间 ( 毫秒 ) + */ + override fun getRefreshTime(): Long { + return mRefreshTime + } + + /** + * 设置回调刷新时间 ( 毫秒 ) + * @param refreshTime 回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + override fun setRefreshTime(refreshTime: Long): IOperation { + if (mDeprecated) return this + mRefreshTime = refreshTime.coerceAtLeast(0) + return this + } + + /** + * 重置回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + override fun resetRefreshTime(): IOperation { + return setRefreshTime(Progress.REFRESH_TIME) + } + + // = + + /** + * 获取全局 Progress Callback + * @return Progress Callback + */ + override fun getCallback(): Progress.Callback? { + return mCallback + } + + /** + * 设置全局 Progress Callback + * @param callback Progress Callback + * @return IOperation + */ + override fun setCallback(callback: Progress.Callback?): IOperation { + if (mDeprecated) return this + mCallback = callback + return this + } + + /** + * 移除全局 Progress Callback + * @return IOperation + */ + override fun removeCallback(): IOperation { + return setCallback(null) + } + + // = + + /** + * 获取回调 UI 线程通知 Handler + * @return 回调 UI 线程通知 Handler + */ + override fun getHandler(): Handler? { + return mHandler + } + + /** + * 设置回调 UI 线程通知 Handler + * @param handler 回调 UI 线程通知 Handler + * @return IOperation + */ + override fun setHandler(handler: Handler?): IOperation { + if (mDeprecated) return this + mHandler = handler + return this + } + + /** + * 重置回调 UI 线程通知 Handler + * @return IOperation + */ + override fun resetHandler(): IOperation { + return setHandler(DevUtils.getHandler()) + } + + // ==================== + // = 操作方法 - 对外公开 = + // ==================== + + /** + * 移除自身在 Manager Map 中的对象值, 并且标记为废弃状态 + * 会释放所有数据、监听事件且不处理任何监听 + */ + override fun removeSelfFromManager() { + // 全局默认对象是不存储在 Map 中, 属于无效调用直接 return + if (globalDefault) return + ProgressManager.removeOperation(key) + } + + /** + * 释放指定监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun recycleListener( + progress: Progress, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return removeListener(progress.isRequest(), getUrlByPrefix(progress), callback) + } + + // ==================== + // = Request Listener = + // ==================== + + /** + * 添加指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun addRequestListener( + url: String, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return addListener(true, url, callback) + } + + /** + * 清空指定 url 上行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearRequestListener(url: String): Boolean { + return clearListener(true, url) + } + + /** + * 清空指定 url 上行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearRequestListener(progress: Progress?): Boolean { + return clearListener(true, progress) + } + + /** + * 移除指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeRequestListener( + url: String, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return removeListener(true, url, callback) + } + + /** + * 移除指定 url 上行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeRequestListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return removeListener(true, progress, callback) + } + + // ===================== + // = Response Listener = + // ===================== + + /** + * 添加指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun addResponseListener( + url: String, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return addListener(false, url, callback) + } + + /** + * 清空指定 url 下行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearResponseListener(url: String): Boolean { + return clearListener(false, url) + } + + /** + * 清空指定 url 下行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearResponseListener(progress: Progress?): Boolean { + return clearListener(false, progress) + } + + /** + * 移除指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeResponseListener( + url: String, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return removeListener(false, url, callback) + } + + /** + * 移除指定 url 下行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeResponseListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean { + if (mDeprecated) return false + return removeListener(false, progress, callback) + } + + // ============ + // = abstract = + // ============ + + /** + * 获取对应方案回调实现 + * @param isRequest `true` 上行, `false` 下行 + * @param extras 额外携带信息 + * @return Progress.Callback + */ + internal abstract fun getPlanCallback( + isRequest: Boolean, + extras: Progress.Extras? + ): Progress.Callback + + /** + * 添加指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + * 会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义 + */ + internal abstract fun addListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return `true` success, `false` fail + */ + internal abstract fun clearListener( + isRequest: Boolean, + url: String + ): Boolean + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @return `true` success, `false` fail + */ + internal abstract fun clearListener( + isRequest: Boolean, + progress: Progress? + ): Boolean + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + internal abstract fun removeListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + internal abstract fun removeListener( + isRequest: Boolean, + progress: Progress?, + callback: Progress.Callback + ): Boolean + + /** + * 移除指定 url 监听事件 + * @param progress Progress + * @param recycleList 待释放回调 List + * @return `true` success, `false` fail + */ + internal abstract fun removeRecycleList( + progress: Progress, + recycleList: List + ): Boolean + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return Array + */ + internal abstract fun getCallbackList( + isRequest: Boolean, + url: String, + ): Array + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param progress Progress + * @return Array + */ + internal abstract fun getCallbackList(progress: Progress): Array + + /** + * 释放废弃资源 + */ + internal abstract fun recycleDeprecated() + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @param progress Progress + * @return Url 前缀 + */ + internal fun getUrlByPrefix(progress: Progress?): String { + return getUrlByPrefix(progress?.getExtras()) + } + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @param extras 额外携带信息 + * @return Url 前缀 + */ + internal fun getUrlByPrefix(extras: Progress.Extras?): String { + return extras?.getUrlExtras()?.urlByPrefix ?: "" + } + + /** + * 标记废弃不用状态 + * @return IOperation + */ + internal fun markDeprecated(): IOperation { + mDeprecated = true + mCallback = null + mHandler = null + recycleDeprecated() + return this + } + + /** + * detail: 内部 Progress 拦截器 + * @author Ttt + * DevHttpManager 库内部包装, 拦截监听上行、下行进度 + */ + private val innerProgressInterceptor: Interceptor by lazy { + if (isTypeRequest()) { + // 监听上行类型 + Interceptor { chain -> + if (mDeprecated) { + chain.proceed(chain.request()) + } else { + val request = chain.request() + val extras = request.toExtras() + val wrapRequest = request.wrapRequestBody( + callback = getPlanCallback(true, extras), + handler = mHandler, + refreshTime = mRefreshTime, + extras = extras + ) + chain.proceed(wrapRequest) + } + } + } else if (isTypeResponse()) { + // 监听下行类型 + Interceptor { chain -> + if (mDeprecated) { + chain.proceed(chain.request()) + } else { + val request = chain.request() + val extras = request.toExtras() + val response = chain.proceed(request) + response.wrapResponseBody( + callback = getPlanCallback(false, extras), + handler = mHandler, + refreshTime = mRefreshTime, + extras = extras + ) + } + } + } else { + // 监听上下行类型 ( 默认 ) + Interceptor { chain -> + if (mDeprecated) { + chain.proceed(chain.request()) + } else { + val request = chain.request() + val extras = request.toExtras() + val wrapRequest = request.wrapRequestBody( + callback = getPlanCallback(true, extras), + handler = mHandler, + refreshTime = mRefreshTime, + extras = extras + ) + val response = chain.proceed(wrapRequest) + response.wrapResponseBody( + callback = getPlanCallback(false, extras), + handler = mHandler, + refreshTime = mRefreshTime, + extras = extras + ) + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/operation/IOperation.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/IOperation.kt new file mode 100644 index 0000000000..c37ab30218 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/IOperation.kt @@ -0,0 +1,259 @@ +package dev.http.progress.operation + +import android.os.Handler +import dev.http.progress.Progress +import okhttp3.OkHttpClient + +/** + * detail: Progress Operation 通用方法定义接口 + * @author Ttt + */ +interface IOperation { + + // ============ + // = 初始化方法 = + // ============ + + /** + * 进行拦截器包装 ( 必须调用 ) + * @param builder Builder + * @return Builder + */ + fun wrap(builder: OkHttpClient.Builder): OkHttpClient.Builder + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 是否已调用 wrap 方法 + * @return `true` yes, `false` no + */ + fun isUseWrap(): Boolean + + /** + * 是否废弃不使用状态 + * @return `true` yes, `false` no + */ + fun isDeprecated(): Boolean + + /** + * 是否全局默认操作对象 + * @return `true` yes, `false` no + */ + fun isDefault(): Boolean + + /** + * 是否监听上下行 + * @return `true` yes, `false` no + */ + fun isTypeAll(): Boolean + + /** + * 是否监听上行 + * @return `true` yes, `false` no + */ + fun isTypeRequest(): Boolean + + /** + * 是否监听下行 + * @return `true` yes, `false` no + */ + fun isTypeResponse(): Boolean + + // =========== + // = get/set = + // =========== + + /** + * 获取 Progress Operation 实现方式类型 + * @return 实现方式类型 + */ + fun getPlanType(): Int + + // = + + /** + * 获取回调刷新时间 ( 毫秒 ) + * @return 回调刷新时间 ( 毫秒 ) + */ + fun getRefreshTime(): Long + + /** + * 设置回调刷新时间 ( 毫秒 ) + * @param refreshTime 回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + fun setRefreshTime(refreshTime: Long): IOperation + + /** + * 重置回调刷新时间 ( 毫秒 ) + * @return IOperation + */ + fun resetRefreshTime(): IOperation + + // = + + /** + * 获取全局 Progress Callback + * @return Progress Callback + */ + fun getCallback(): Progress.Callback? + + /** + * 设置全局 Progress Callback + * @param callback Progress Callback + * @return IOperation + */ + fun setCallback(callback: Progress.Callback?): IOperation + + /** + * 移除全局 Progress Callback + * @return IOperation + */ + fun removeCallback(): IOperation + + // = + + /** + * 获取回调 UI 线程通知 Handler + * @return 回调 UI 线程通知 Handler + */ + fun getHandler(): Handler? + + /** + * 设置回调 UI 线程通知 Handler + * @param handler 回调 UI 线程通知 Handler + * @return IOperation + */ + fun setHandler(handler: Handler?): IOperation + + /** + * 重置回调 UI 线程通知 Handler + * @return IOperation + */ + fun resetHandler(): IOperation + + // ==================== + // = 操作方法 - 对外公开 = + // ==================== + + /** + * 移除自身在 Manager Map 中的对象值, 并且标记为废弃状态 + * 会释放所有数据、监听事件且不处理任何监听 + */ + fun removeSelfFromManager() + + /** + * 释放指定监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun recycleListener( + progress: Progress, + callback: Progress.Callback + ): Boolean + + // ==================== + // = Request Listener = + // ==================== + + /** + * 添加指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun addRequestListener( + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 清空指定 url 上行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + fun clearRequestListener(url: String): Boolean + + /** + * 清空指定 url 上行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + fun clearRequestListener(progress: Progress?): Boolean + + /** + * 移除指定 url 上行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun removeRequestListener( + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 移除指定 url 上行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun removeRequestListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean + + // ===================== + // = Response Listener = + // ===================== + + /** + * 添加指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun addResponseListener( + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 清空指定 url 下行所有监听事件 + * @param url 请求 url + * @return `true` success, `false` fail + */ + fun clearResponseListener(url: String): Boolean + + /** + * 清空指定 url 下行所有监听事件 + * @param progress Progress + * @return `true` success, `false` fail + */ + fun clearResponseListener(progress: Progress?): Boolean + + /** + * 移除指定 url 下行监听事件 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun removeResponseListener( + url: String, + callback: Progress.Callback + ): Boolean + + /** + * 移除指定 url 下行监听事件 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + fun removeResponseListener( + progress: Progress?, + callback: Progress.Callback + ): Boolean +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanA.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanA.kt new file mode 100644 index 0000000000..a61b5daa01 --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanA.kt @@ -0,0 +1,289 @@ +package dev.http.progress.operation + +import dev.http.progress.Progress +import dev.http.progress.ProgressOperation +import dev.utils.common.StringUtils + +/** + * detail: Progress Operation 实现方式一 ( 默认使用 ) + * @author Ttt + * 实现方式差异可以查看 [ProgressOperation] 类注释 + */ +internal class OperationPlanA constructor( + key: String, + // 全局默认操作对象 + globalDefault: Boolean, + // 内部拦截器监听类型 + type: Int +) : BaseOperation(key, globalDefault, type, ProgressOperation.PLAN_A) { + + // 上行监听回调 ( key = url, value = Progress.Callback ) + private val mRequestListeners = HashMap>() + + // 下行监听回调 + private val mResponseListeners = HashMap>() + + // ================= + // = BaseOperation = + // ================= + + /** + * 获取对应方案回调实现 + * @param isRequest `true` 上行, `false` 下行 + * @param extras 额外携带信息 + * @return Progress.Callback + */ + override fun getPlanCallback( + isRequest: Boolean, + extras: Progress.Extras? + ): Progress.Callback { + return innerCallback + } + + /** + * 添加指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + * 会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义 + */ + override fun addListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map[newUrl]?.let { list -> + if (!list.contains(callback)) { + list.add(callback) + } + return true + } + map[newUrl] = mutableListOf(callback) + return true + } + return false + } + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearListener( + isRequest: Boolean, + url: String + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map.remove(newUrl)?.clear() + return true + } + return false + } + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearListener( + isRequest: Boolean, + progress: Progress? + ): Boolean { + return clearListener(isRequest, getUrlByPrefix(progress)) + } + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + return map[newUrl]?.remove(callback) ?: false + } + return false + } + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeListener( + isRequest: Boolean, + progress: Progress?, + callback: Progress.Callback + ): Boolean { + return removeListener(isRequest, getUrlByPrefix(progress), callback) + } + + /** + * 移除指定 url 监听事件 + * @param progress Progress + * @param recycleList 待释放回调 List + * @return `true` success, `false` fail + */ + override fun removeRecycleList( + progress: Progress, + recycleList: List + ): Boolean { + if (recycleList.isNotEmpty()) { + val url = getUrlByPrefix(progress) + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(progress.isRequest()) + return map[newUrl]?.removeAll(recycleList) ?: false + } + } + return false + } + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return Array + */ + override fun getCallbackList( + isRequest: Boolean, + url: String + ): Array { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map[newUrl]?.let { + return it.toTypedArray() + } + } + return arrayOf() + } + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param progress Progress + * @return Array + */ + override fun getCallbackList(progress: Progress): Array { + return getCallbackList(progress.isRequest(), getUrlByPrefix(progress)) + } + + /** + * 释放废弃资源 + */ + override fun recycleDeprecated() { + mRequestListeners.clear() + mResponseListeners.clear() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Callback Map + * @param isRequest `true` 上行, `false` 下行 + * @return HashMap> + */ + private fun listenerMap(isRequest: Boolean): HashMap> { + return if (isRequest) mRequestListeners else mResponseListeners + } + + /** + * detail: 内部 Progress 回调 + * @author Ttt + */ + private val innerCallback: Progress.Callback by lazy { + object : Progress.Callback { + override fun onStart(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onStart(progress) + + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(progress) + array.forEach { + it?.onStart(progress) + } + } + + override fun onProgress(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onProgress(progress) + + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(progress) + array.forEach { + it?.onProgress(progress) + } + } + + override fun onError(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onError(progress) + + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(progress) + array.forEach { + it?.onError(progress) + } + } + + override fun onFinish(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onFinish(progress) + + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(progress) + array.forEach { + it?.onFinish(progress) + } + } + + override fun onEnd(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onEnd(progress) + + // 需要自动销毁的 list + val recycleList = mutableListOf() + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(progress) + array.forEach { + it?.let { callback -> + callback.onEnd(progress) + if (callback.isAutoRecycle(progress)) { + recycleList.add(callback) + } + } + } + removeRecycleList(progress, recycleList) + } + } + } +} \ No newline at end of file diff --git a/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanB.kt b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanB.kt new file mode 100644 index 0000000000..dce517dfff --- /dev/null +++ b/lib/DevHttpManager/src/main/java/dev/http/progress/operation/OperationPlanB.kt @@ -0,0 +1,289 @@ +package dev.http.progress.operation + +import dev.http.progress.Progress +import dev.http.progress.ProgressOperation +import dev.utils.common.StringUtils +import java.util.* + +/** + * detail: Progress Operation 实现方式二 + * @author Ttt + * 实现方式差异可以查看 [ProgressOperation] 类注释 + * WeakHashMap 何时释放资源无法进行控制, 如果想要每一个监听都能收到回调, 请使用方式一 ( 默认使用 ) + */ +internal class OperationPlanB constructor( + key: String, + // 全局默认操作对象 + globalDefault: Boolean, + // 内部拦截器监听类型 + type: Int +) : BaseOperation(key, globalDefault, type, ProgressOperation.PLAN_B) { + + // 上行监听回调 ( key = url, value = Progress.Callback ) + private val mRequestListeners = WeakHashMap>() + + // 下行监听回调 + private val mResponseListeners = WeakHashMap>() + + // ================= + // = BaseOperation = + // ================= + + /** + * 获取对应方案回调实现 + * @param isRequest `true` 上行, `false` 下行 + * @param extras 额外携带信息 + * @return Progress.Callback + */ + override fun getPlanCallback( + isRequest: Boolean, + extras: Progress.Extras? + ): Progress.Callback { + return newCallback(isRequest, extras) + } + + /** + * 添加指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + * 会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义 + */ + override fun addListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map[newUrl]?.let { list -> + if (!list.contains(callback)) { + list.add(callback) + } + return true + } + map[newUrl] = mutableListOf(callback) + return true + } + return false + } + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return `true` success, `false` fail + */ + override fun clearListener( + isRequest: Boolean, + url: String + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map.remove(newUrl)?.clear() + return true + } + return false + } + + /** + * 清空指定 url 所有监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @return `true` success, `false` fail + */ + override fun clearListener( + isRequest: Boolean, + progress: Progress? + ): Boolean { + return clearListener(isRequest, getUrlByPrefix(progress)) + } + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeListener( + isRequest: Boolean, + url: String, + callback: Progress.Callback + ): Boolean { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + return map[newUrl]?.remove(callback) ?: false + } + return false + } + + /** + * 移除指定 url 监听事件 + * @param isRequest `true` 上行, `false` 下行 + * @param progress Progress + * @param callback 上传、下载回调接口 + * @return `true` success, `false` fail + */ + override fun removeListener( + isRequest: Boolean, + progress: Progress?, + callback: Progress.Callback + ): Boolean { + return removeListener(isRequest, getUrlByPrefix(progress), callback) + } + + /** + * 移除指定 url 监听事件 + * @param progress Progress + * @param recycleList 待释放回调 List + * @return `true` success, `false` fail + */ + override fun removeRecycleList( + progress: Progress, + recycleList: List + ): Boolean { + if (recycleList.isNotEmpty()) { + val url = getUrlByPrefix(progress) + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(progress.isRequest()) + return map[newUrl]?.removeAll(recycleList) ?: false + } + } + return false + } + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param isRequest `true` 上行, `false` 下行 + * @param url 请求 url + * @return Array + */ + override fun getCallbackList( + isRequest: Boolean, + url: String + ): Array { + val newUrl = StringUtils.clearSpaceTabLine(url) + if (StringUtils.isNotEmpty(newUrl)) { + val map = listenerMap(isRequest) + map[newUrl]?.let { + return it.toTypedArray() + } + } + return arrayOf() + } + + /** + * 根据请求 url 获取对应的监听事件集合 + * @param progress Progress + * @return Array + */ + override fun getCallbackList(progress: Progress): Array { + return getCallbackList(progress.isRequest(), getUrlByPrefix(progress)) + } + + /** + * 释放废弃资源 + */ + override fun recycleDeprecated() { + mRequestListeners.clear() + mResponseListeners.clear() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取 Callback Map + * @param isRequest `true` 上行, `false` 下行 + * @return WeakHashMap> + */ + private fun listenerMap(isRequest: Boolean): WeakHashMap> { + return if (isRequest) mRequestListeners else mResponseListeners + } + + /** + * 创建对应的回调对象 + * @param isRequest `true` 上行, `false` 下行 + * @param extras 额外携带信息 + * @return Progress.Callback + */ + private fun newCallback( + isRequest: Boolean, + extras: Progress.Extras? + ): Progress.Callback { + // 根据请求 url 获取对应的监听事件集合 + val array = getCallbackList(isRequest, getUrlByPrefix(extras)) + + return object : Progress.Callback { + override fun onStart(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onStart(progress) + + array.forEach { + it?.onStart(progress) + } + } + + override fun onProgress(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onProgress(progress) + + array.forEach { + it?.onProgress(progress) + } + } + + override fun onError(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onError(progress) + + array.forEach { + it?.onError(progress) + } + } + + override fun onFinish(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onFinish(progress) + + array.forEach { + it?.onFinish(progress) + } + } + + override fun onEnd(progress: Progress) { + if (isDeprecated()) return + + // 全局 Progress Callback + getCallback()?.onEnd(progress) + + // 需要自动销毁的 list + val recycleList = mutableListOf() + array.forEach { + it?.let { callback -> + callback.onEnd(progress) + if (callback.isAutoRecycle(progress)) { + recycleList.add(callback) + } + } + } + removeRecycleList(progress, recycleList) + } + } + } +} \ No newline at end of file diff --git a/lib/DevJava/.gitignore b/lib/DevJava/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevJava/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevJava/CHANGELOG.md b/lib/DevJava/CHANGELOG.md new file mode 100644 index 0000000000..4a2e1ba5ed --- /dev/null +++ b/lib/DevJava/CHANGELOG.md @@ -0,0 +1,353 @@ +Change Log +========== + +Version 1.4.8 *(2022-09-18)* +---------------------------- + +* `[Add]` 新增 NumberUtils#calculateUnitD、calculateUnitF + +* `[Add]` 新增 FormatUtils 工具类及 UnitSpanFormatter、ArgsFormatter 封装类 + +* `[Add]` 新增部分通用 able 接口 + +Version 1.4.7 *(2022-07-18)* +---------------------------- + +* `[Add]` 新增部分通用 able 接口 + +Version 1.4.6 *(2022-07-04)* +---------------------------- + +* `[Add]` 新增 StringUtils#equalsIgnoreCase、equalsIgnoreCaseNotNull + +* `[Add]` 新增 ValiToPhoneUtils 中国广电手机号码段、更新最新运营商号码段 + +* `[Add]` 新增 DevFinal 部分常量值 + +Version 1.4.5 *(2022-06-26)* +---------------------------- + +* `[Review]` 检查并调整使用 equals、equalsIgnoreCase 代码 + +* `[Add]` 新增 StringUtils#equalsNotNull 方法 + +Version 1.4.4 *(2022-05-13)* +---------------------------- + +* `[Add]` 新增 DevFinal#INT 部分默认值 + +* `[Add]` 新增 UrlExtras Url 携带信息解析类,并默认提供 DevJavaUrlParser 实现 + +* `[Add]` 新增 ConvertUtils#newString 方法处理 CharSequence + +* `[Add]` 新增 ConvertUtils#newStringNotArrayDecode 并修改所有工具类 instanceof String 判断转换使用该方法 + +* `[Add]` 新增 BigDecimalUtils 抛出异常相对应捕获异常快捷方法,并修改抛出方法名为 xxxThrow(param) + +Version 1.4.3 *(2022-03-20)* +---------------------------- + +* `[Add]` 新增 DevFinal#DEFAULT 默认值,并全局统一该库默认值 + +* `[Add]` 新增 BigDecimalUtils 快捷方法抛出异常便于自定义异常值 + +Version 1.4.2 *(2022-02-12)* +---------------------------- + +* `[Add]` StringUtils#join、joinArgs + +* `[Add]` CollectionUtils#clearAndAddAll、clearAndAddAllNotNull + +Version 1.4.1 *(2022-01-23)* +---------------------------- + +* `[Update]` 部分布尔值变量 is 命名移除 + +Version 1.4.0 *(2022-01-10)* +---------------------------- + +* `[Add]` ComparatorUtils 排序比较器工具类 + +* `[Add]` FileUtils#getFile、listOrEmpty、listFilesOrEmpty + +Version 1.3.9 *(2021-12-30)* +---------------------------- + +* `[Refactor]` 修改整个 DevFinal 常量类,并统一使用该常量类 + +Version 1.3.8 *(2021-12-20)* +---------------------------- + +* `[Refactor]` 修改整个 DevFinal 常量类,并统一使用该常量类 + +* `[Add]` FlagsValue 标记值计算存储 ( 位运算符 ) + +Version 1.3.7 *(2021-11-26)* +---------------------------- + +* `[Add]` WeakReferenceAssist 弱引用辅助类 + +* `[Add]` MapUtils#mapToString + +Version 1.3.6 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +* `[Add]` ForUtils + +* `[Add]` XorUtils#xorChecksum + +* `[Add]` FileUtils#createTimestampFileName + +Version 1.3.5 *(2021-06-28)* +---------------------------- + +* `[Refactor]` 重构 FileRecordUtils 并进行内部类拆分便于 DevApp 模块使用 + +Version 1.3.4 *(2021-06-21)* +---------------------------- + +* `[Add]` StringUtils#urlDecodeWhile + +* `[Add]` HttpParamsUtils#getUrlParams、getUrlParamsArray、existsParams、existsParamsByURL、joinUrlParams、getUrlParamsJoinSymbol、splitParamsByUrl + +Version 1.3.3 *(2021-06-04)* +---------------------------- + +* `[Add]` DevFinal 新增部分常量 + +Version 1.3.2 *(2021-05-19)* +---------------------------- + +* `[Add]` ColorUtils#sortHUE、sortSaturation、sortBrightness + +Version 1.3.1 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.3.0 *(2021-03-21)* +---------------------------- + +* `[Add]` ColorUtils#blendColor、transitionColor + +Version 1.2.9 *(2021-03-02)* +---------------------------- + +* `[Refactor]` BigDecimalUtils + +Version 1.2.8 *(2021-02-27)* +---------------------------- + +* `[Add]` NumberUtils#subZeroAndDot + +* `[Refactor]` BigDecimalUtils + +* `[Add]` DevFinal 新增部分常量 + +Version 1.2.7 *(2021-02-08)* +---------------------------- + +* `[Add]` ColorUtils#getRandomColorString 方法 + +* `[Add]` DevFinal 新增部分常量 + +Version 1.2.6 *(2021-01-24)* +---------------------------- + +* `[Perf]` 进行代码检测优化 + +* `[Add]` DevFinal 新增部分常量 + +Version 1.2.5 *(2021-01-01)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +Version 1.2.4 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Update]` 修改 CallBack 相关代码为 Callback + +* `[Add]` DevFinal 新增部分常量 + +Version 1.2.3 *(2020-11-15)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.2.2 *(2020-11-05)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.2.1 *(2020-10-29)* +---------------------------- + +* `[Add]` ArrayUtils#asListArgs + +* `[Update]` MapUtils#putToList、removeToList、removeToMap 参数类型 + +* `[Update]` 进行 Spelling typo Analyze 修改部分拼写错误字段 + +Version 1.2.0 *(2020-10-20)* +---------------------------- + +* `[Update]` StringUtils#convertHideMobile、convertSymbolHide Method Name + +* `[Fix]` StringUtils#replaceSEWith、clearSEWiths、clearEndsWith 索引判断问题 + +Version 1.1.9 *(2020-10-12)* +---------------------------- + +* `[Add]` FilePartUtils 文件分片工具类 + +* `[Add]` CloseUtils#flush、flushQuietly、flushCloseIO、flushCloseIOQuietly + +* `[Add]` FileUtils#convertFiles、convertPaths、flushCloseIO、flushCloseIOQuietly + +* `[Refactor]` 修改整个库 Closeable Close 代码内部调用 CloseUtils + +Version 1.1.8 *(2020-09-27)* +---------------------------- + +* `[Add]` DateUtils#getZodiac、getConstellation、getConstellationDate 获取生肖、星座方法 + +* `[Add]` CalendarUtils 日历 ( 公历、农历 ) 工具类 + +Version 1.1.7 *(2020-09-20)* +---------------------------- + +* `[Delete]` 删除 DevCommonUtils 中其他工具类快捷方法 + +* `[Update]` 更新部分代码注释 + +Version 1.1.6 *(2020-09-15)* +---------------------------- + +* `[Add]` StringUtils#clearTab、clearTabTrim、clearLine、clearLineTrim、clearSpaceTabLine、clearSpaceTabLineTrim + +* `[Add]` ScaleUtils#XY type 扩展字段 + +* `[Add]` NumberUtils#addZero + +* `[Update]` DateUtils#convertTime 为 timeAddZero + +* `[Update]` 去除部分方法名 to 前缀 + +Version 1.1.5 *(2020-09-07)* +---------------------------- + +* `[Add]` ScaleUtils#calcScale、calcScaleToMath、calcXY + +* `[Add]` StringUtils#forJoint + +Version 1.1.4 *(2020-08-29)* +---------------------------- + +* `[Add]` FileUtils#canRead、canWrite、canReadWrite + +Version 1.1.3 *(2020-08-04)* +---------------------------- + +* `[Add]` ChineseUtils 中文工具类 + +* `[Add]` StringUtils#forString + +Version 1.1.2 *(2020-05-18)* +---------------------------- + +* `[Update]` ClassUtils#getGenericSuperclass、getGenericInterfaces 返回类型 + +Version 1.1.1 *(2020-03-11)* +---------------------------- + +* `[Add]` StringUtils#split + +* `[Add]` NumberUtils#calculateUnit + +* `[Update]` StringUtils#replaceStr、replaceStrToNull 方法名为 StringUtils#replaceAll、replaceAllToNull + +Version 1.1.0 *(2020-02-21)* +---------------------------- + +* `[Add]` StringUtils#getBytes + +* `[Add]` FileIOUtils#getFileInputStream、getFileOutputStream + +* `[Update]` FileUtils#saveFile、appendFile + +Version 1.0.9 *(2020-01-26)* +---------------------------- + +* `[Add]` TypeUtils 类型工具类 + +* `[Add]` ClassUtils#getClass、isGenericParamType、getGenericParamType + +* `[Add]` ConvertUtils#toBigDecimal、toBigInteger、newString、charAt + +* `[Update]` ConvertUtils#toString、toInt、toBoolean、toFloat、toDouble、toLong、toShort、toChar、toByte、toChars、toBytes + +Version 1.0.8 *(2020-01-16)* +---------------------------- + +* `[Add]` FileUtils#listFilesInDirBean、listFilesInDirWithFilterBean 方法,获取文件目录列表集合 FileList + +Version 1.0.7 *(2019-12-25)* +---------------------------- + +* `[Add]` ColorUtils#getARGB、grayLevel、sortGray、sortHSB 并增加内部类 ColorInfo,支持颜色排序 + +* `[Add]` FileIOUtils#copyLarge、DateUtils#yyyyMMdd_HHmmss、CoordinateUtils#getDistance、getAngle、getDirection + +* `[Add]` DevCommonUtils、StringUtils#appendsIgnoreLast + +* `[Update]` 删除 DevCommonUtils、StringUtils 几个重载方法 appends + +* `[Update]` 更新部分工具类、方法注释代码、代码间距等 + +Version 1.0.6 *(2019-11-25)* +---------------------------- + +* `[Refactor]` 重构整个项目,优化代码逻辑判断、代码风格、合并工具类减少包大小等,并修改 95% 返回值 void 的方法为 boolean 明确获取调用结果 + +* `[Add]` FileRecordUtils 文件记录结果回调 + +* `[Add]` MapUtils、CollectionUtils 获取泛型数组 toArrayT + +* `[Update]` 移动 FileRecordUtils、HtmlUtils 到 Java 模块 + +Version 1.0.5 *(2019-11-05)* +---------------------------- + +* `[Add]` FileUtils#isImageFormats、isAudioFormats、isVideoFormats、isFileFormats + +Version 1.0.4 *(2019-10-31)* +---------------------------- + +* `[Add]` ArrayUtils#getMinimum、getMaximum、getMinimumIndex、getMaximumIndex、sumarray + +* `[Add]` CollectionUtils#getMinimum、getMaximum、getMinimumIndex、getMaximumIndex、sumlist + +Version 1.0.3 *(2019-10-09)* +---------------------------- + +* `[Add]` NumberUtils#getMultiple、getMultipleI、getMultipleD、getMultipleL、getMultipleF + +Version 1.0.2 *(2019-09-19)* +---------------------------- + +* `[Update]` 修改 FileBreadthFirstSearchUtils 部分方法返回值 ( 返回当前对象,方便链式调用 ) + +Version 1.0.1 *(2019-09-12)* +---------------------------- + +* `[Add]` ConvertUtils#convert + +Version 1.0.0 *(2019-08-25)* +---------------------------- + +* Initial release diff --git a/lib/DevJava/README.md b/lib/DevJava/README.md new file mode 100644 index 0000000000..dd5e27124f --- /dev/null +++ b/lib/DevJava/README.md @@ -0,0 +1,1871 @@ + +## Gradle + +```gradle +implementation 'io.github.afkt:DevJava:1.4.8' +``` + +## 目录结构 + +``` +- dev.utils | 根目录 + - common | Java 工具类, 不依赖 android api + - able | 通用接口定义 + - assist | 各种快捷辅助类 + - record | 文件记录分析类 + - search | 搜索相关 ( 文件搜索等 ) + - url | Url 携带信息解析 + - cipher | 编 / 解码工具类 + - comparator | 排序比较器 + - sort | 各种类型比较器排序实现 + - encrypt | 加密工具类 + - file | 文件分片相关 + - format | 格式化相关 + - random | 随机概率算法工具类 + - thread | 线程相关 + - validator | 数据校验工具类 +``` + + +## 事项 + +- 部分 API 更新不及时或有遗漏等,`具体以对应的工具类为准` + +- [检测代码规范、注释内容排版,API 文档生成](https://github.com/afkT/JavaDoc) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/CHANGELOG.md) + +- 该工具类不依赖 android api,属于 Java 工具类库 + +- 开启日志 +```java +// 打开 lib 内部日志 - 线上 (release) 环境,不调用方法 +JCLogUtils.setPrintLog(true); +// 控制台打印日志 +JCLogUtils.setControlPrintLog(true); +// 设置 Java 模块日志信息监听 +JCLogUtils.setPrint(new JCLogUtils.Print() {}); +``` + +## API + + +- dev.utils | 根目录 + - [common](#devutilscommon) | Java 工具类, 不依赖 android api + - [able](#devutilscommonable) | 通用接口定义 + - [assist](#devutilscommonassist) | 各种快捷辅助类 + - [record](#devutilscommonassistrecord) | 文件记录分析类 + - [search](#devutilscommonassistsearch) | 搜索相关 ( 文件搜索等 ) + - [url](#devutilscommonassisturl) | Url 携带信息解析 + - [cipher](#devutilscommoncipher) | 编 / 解码工具类 + - [comparator](#devutilscommoncomparator) | 排序比较器 + - [sort](#devutilscommoncomparatorsort) | 各种类型比较器排序实现 + - [encrypt](#devutilscommonencrypt) | 加密工具类 + - [file](#devutilscommonfile) | 文件分片相关 + - [format](#devutilscommonformat) | 格式化相关 + - [random](#devutilscommonrandom) | 随机概率算法工具类 + - [thread](#devutilscommonthread) | 线程相关 + - [validator](#devutilscommonvalidator) | 数据校验工具类 + + +## **`dev.utils.common`** + + +* **Array 数组工具类 ->** [ArrayUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ArrayUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断数组是否为 null | +| isNotEmpty | 判断数组是否不为 null | +| length | 获取数组长度 | +| isLength | 判断数组长度是否等于期望长度 | +| getCount | 获取数组长度总和 | +| getByArray | 获取数组对应索引数据 | +| get | 获取数组对应索引数据 | +| getFirst | 获取数组第一条数据 | +| getLast | 获取数组最后一条数据 | +| getPosition | 根据指定值获取 value 所在位置 + 偏移量的索引 | +| getNotNull | 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null | +| getPositionNotNull | 根据指定 value 获取索引, 不允许值为 null | +| intsToIntegers | int[] 转换 Integer[] | +| bytesToBytes | byte[] 转换 Byte[] | +| charsToCharacters | char[] 转换 Character[] | +| shortsToShorts | short[] 转换 Short[] | +| longsToLongs | long[] 转换 Long[] | +| floatsToFloats | float[] 转换 Float[] | +| doublesToDoubles | double[] 转换 Double[] | +| booleansToBooleans | boolean[] 转换 Boolean[] | +| integersToInts | Integer[] 转换 int[] | +| charactersToChars | Character[] 转换 char[] | +| asList | 转换数组为集合 | +| asListArgs | 转换数组为集合 | +| asListArgsInt | 转换数组为集合 | +| asListArgsByte | 转换数组为集合 | +| asListArgsChar | 转换数组为集合 | +| asListArgsShort | 转换数组为集合 | +| asListArgsLong | 转换数组为集合 | +| asListArgsFloat | 转换数组为集合 | +| asListArgsDouble | 转换数组为集合 | +| asListArgsBoolean | 转换数组为集合 | +| equals | 判断两个值是否一样 | +| arrayCopy | 拼接数组 | +| newArray | 创建指定长度数组 | +| subArray | 从数组上截取一段 | +| appendToString | 追加数组内容字符串 | +| getMinimumIndex | 获取数组中最小值索引 | +| getMaximumIndex | 获取数组中最大值索引 | +| getMinimum | 获取数组中最小值 | +| getMaximum | 获取数组中最大值 | +| sumArray | 计算数组总和 | + + +* **资金运算工具类 ->** [BigDecimalUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/BigDecimalUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setScale | 设置全局小数点保留位数、舍入模式 | +| getBigDecimal | 获取 BigDecimal | +| operation | 获取 Operation | +| adjustDouble | 获取自己想要的数据格式 | +| compareTo | 比较大小 | +| compareToThrow | 比较大小 ( 抛出异常 ) | +| add | 提供精确的加法运算 | +| subtract | 提供精确的减法运算 | +| multiply | 提供精确的乘法运算 | +| divide | 提供精确的除法运算 | +| remainder | 提供精确的取余运算 | +| round | 提供精确的小数位四舍五入处理 | +| addThrow | 提供精确的加法运算 ( 抛出异常 ) | +| subtractThrow | 提供精确的减法运算 ( 抛出异常 ) | +| multiplyThrow | 提供精确的乘法运算 ( 抛出异常 ) | +| divideThrow | 提供精确的除法运算 ( 抛出异常 ) | +| remainderThrow | 提供精确的取余运算 ( 抛出异常 ) | +| roundThrow | 提供精确的小数位四舍五入处理 ( 抛出异常 ) | +| getScale | 获取小数点保留位数 | +| getRoundingMode | 获取舍入模式 | +| requireNonNull | 检查 Value 是否为 null, 为 null 则抛出异常 | +| setBigDecimal | 设置 Value | +| getConfig | 获取配置信息 | +| setConfig | 设置配置信息 | +| removeConfig | 移除配置信息 | +| setScaleByConfig | 设置小数点保留位数、舍入模式 | +| isThrowError | 是否抛出异常 | +| setThrowError | 设置是否抛出异常 | +| clone | 克隆对象 | +| toString | 获取此 BigDecimal 的字符串表示形式科学记数法 | +| toPlainString | 获取此 BigDecimal 的字符串表示形式不带指数字段 | +| toEngineeringString | 获取此 BigDecimal 的字符串表示形式工程计数法 | +| intValue | 获取指定类型值 | +| floatValue | 获取指定类型值 | +| longValue | 获取指定类型值 | +| doubleValue | 获取指定类型值 | +| formatMoney | 金额分割, 四舍五入金额 | + + +* **日历工具类 ->** [CalendarUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/CalendarUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSupportLunar | 是否支持农历年份计算 | +| isSupportSolar | 是否支持公历年份计算 | +| solarToLunar | 公历转农历 | +| lunarToSolar | 农历转公历 | +| getLunarYearDays | 获取农历年份总天数 | +| getLunarLeapDays | 获取农历年份闰月天数 | +| getLunarLeapMonth | 获取农历年份哪个月是闰月 | +| getLunarMonthDays | 获取农历年份与月份总天数 | +| getLunarGanZhi | 获取干支历 | +| getLunarMonthChinese | 获取农历中文月份 | +| getLunarDayChinese | 获取农历中文天数 | +| getSolarTermsIndex | 获取二十四节气 ( 公历 ) 索引 | +| getSolarTerms | 获取二十四节气 ( 公历 ) | +| getSolarTermsDate | 获取二十四节气 ( 公历 ) 时间 | +| isFestival | 校验是否相同节日 | +| getFestival | 获取符合条件的节日信息 | +| getSolarFestival | 获取公历符合条件的节日信息 | +| getLunarFestival | 获取农历符合条件的节日信息 | +| getFestivalHook | 获取节日 Hook 接口 | +| setFestivalHook | 设置节日 Hook 接口 | + + +* **中文工具类 ->** [ChineseUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ChineseUtils.java) + +| 方法 | 注释 | +| :- | :- | +| randomWord | 随机生成汉字 | +| randomName | 随机生成名字 | +| numberToCHN | 数字转中文数值 | + + +* **类 ( Class ) 工具类 ->** [ClassUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ClassUtils.java) + +| 方法 | 注释 | +| :- | :- | +| newInstance | 根据类获取对象, 不再必须一个无参构造 | +| getDefaultPrimitiveValue | 获取 Class 原始类型值 | +| getClass | 获取 Object Class | +| isPrimitive | 判断 Class 是否为原始类型 | +| isCollection | 判断是否 Collection 类型 | +| isMap | 判断是否 Map 类型 | +| isArray | 判断是否 Array 类型 | +| isGenericParamType | 判断是否参数类型 | +| getGenericParamType | 获取参数类型 | +| getGenericSuperclass | 获取父类泛型类型 | +| getGenericInterfaces | 获取接口泛型类型 | + + +* **克隆工具类 ->** [CloneUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/CloneUtils.java) + +| 方法 | 注释 | +| :- | :- | +| deepClone | 进行克隆 | +| serializableToBytes | 通过序列化实体类, 获取对应的 byte[] 数据 | + + +* **关闭 ( IO 流 ) 工具类 ->** [CloseUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/CloseUtils.java) + +| 方法 | 注释 | +| :- | :- | +| closeIO | 关闭 IO | +| closeIOQuietly | 安静关闭 IO | +| flush | 将缓冲区数据输出 | +| flushQuietly | 安静将缓冲区数据输出 | +| flushCloseIO | 将缓冲区数据输出并关闭流 | +| flushCloseIOQuietly | 安静将缓冲区数据输出并关闭流 | + + +* **集合工具类 ( Collection - List、Set、Queue ) 等 ->** [CollectionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/CollectionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Collection 是否为 null | +| isNotEmpty | 判断 Collection 是否不为 null | +| length | 获取 Collection 长度 | +| isLength | 获取长度 Collection 是否等于期望长度 | +| greaterThan | 判断 Collection 长度是否大于指定长度 | +| greaterThanOrEqual | 判断 Collection 长度是否大于等于指定长度 | +| lessThan | 判断 Collection 长度是否小于指定长度 | +| lessThanOrEqual | 判断 Collection 长度是否小于等于指定长度 | +| getCount | 获取 Collection 数组长度总和 | +| get | 获取数据 | +| getFirst | 获取第一条数据 | +| getLast | 获取最后一条数据 | +| getPosition | 根据指定 value 获取 value 所在位置 + 偏移量的索引 | +| getPositionNotNull | 根据指定 value 获取索引, 不允许值为 null | +| getNext | 根据指定 value 获取 value 所在位置的下一个值 | +| getNextNotNull | 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null | +| getPrevious | 根据指定 value 获取 value 所在位置的上一个值 | +| getPreviousNotNull | 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null | +| add | 添加一条数据 | +| addNotNull | 添加一条数据 ( value 不允许为 null ) | +| addAll | 添加集合数据 | +| addAllNotNull | 添加集合数据 ( values 内的值不允许为 null ) | +| clearAndAddAll | 移除全部数据并添加集合数据 | +| clearAndAddAllNotNull | 移除全部数据并添加集合数据 ( values 内的值不允许为 null ) | +| remove | 移除一条数据 | +| removeAll | 移除集合数据 | +| clear | 清空集合中符合指定 value 的全部数据 | +| clearNotBelong | 保留集合中符合指定 value 的全部数据 | +| clearAll | 清空集合全部数据 | +| clearNull | 清空集合中为 null 的值 | +| isEqualCollection | 判断两个集合是否相同 | +| isEqualCollections | 判断多个集合是否相同 | +| union | 两个集合并集处理 | +| unions | 多个集合并集处理 | +| intersection | 两个集合交集处理 | +| disjunction | 两个集合交集的补集处理 | +| subtract | 两个集合差集 ( 扣除 ) 处理 | +| equals | 判断两个值是否一样 | +| toArray | 转换数组 to Object | +| toArrayT | 转换数组 to T | +| reverse | 集合翻转处理 | +| getMinimumIndexI | 获取集合中最小值索引 | +| getMinimumIndexL | 获取集合中最小值索引 | +| getMinimumIndexF | 获取集合中最小值索引 | +| getMinimumIndexD | 获取集合中最小值索引 | +| getMaximumIndexI | 获取集合中最大值索引 | +| getMaximumIndexL | 获取集合中最大值索引 | +| getMaximumIndexF | 获取集合中最大值索引 | +| getMaximumIndexD | 获取集合中最大值索引 | +| getMinimumI | 获取集合中最小值 | +| getMinimumL | 获取集合中最小值 | +| getMinimumF | 获取集合中最小值 | +| getMinimumD | 获取集合中最小值 | +| getMaximumI | 获取集合中最大值 | +| getMaximumL | 获取集合中最大值 | +| getMaximumF | 获取集合中最大值 | +| getMaximumD | 获取集合中最大值 | +| sumlistI | 计算集合总和 | +| sumlistL | 计算集合总和 | +| sumlistF | 计算集合总和 | +| sumlistD | 计算集合总和 | + + +* **颜色工具类 ( 包括常用的色值 ) ->** [ColorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ColorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| hexAlpha | 获取十六进制透明度字符串 | +| getARGB | 返回一个颜色 ARGB 色值数组 ( 返回十进制 ) | +| alpha | 返回一个颜色中的透明度值 ( 返回十进制 ) | +| alphaPercent | 返回一个颜色中的透明度百分比值 | +| red | 返回一个颜色中红色的色值 ( 返回十进制 ) | +| redPercent | 返回一个颜色中红色的百分比值 | +| green | 返回一个颜色中绿色的色值 ( 返回十进制 ) | +| greenPercent | 返回一个颜色中绿色的百分比值 | +| blue | 返回一个颜色中蓝色的色值 ( 返回十进制 ) | +| bluePercent | 返回一个颜色中蓝色的百分比值 | +| rgb | 根据对应的 red、green、blue 生成一个颜色值 | +| argb | 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) | +| isRGB | 判断颜色 RGB 是否有效 | +| isARGB | 判断颜色 ARGB 是否有效 | +| setAlpha | 设置透明度 | +| setRed | 改变颜色值中的红色色值 | +| setGreen | 改变颜色值中的绿色色值 | +| setBlue | 改变颜色值中的蓝色色值 | +| parseColor | 解析颜色字符串, 返回对应的颜色值 | +| intToRgbString | 颜色值 转换 RGB 颜色字符串 | +| intToArgbString | 颜色值 转换 ARGB 颜色字符串 | +| getRandomColor | 获取随机颜色值 | +| getRandomColorString | 获取随机颜色值字符串 | +| judgeColorString | 判断是否为 ARGB 格式的十六进制颜色, 例如: FF990587 | +| setDark | 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) | +| setLight | 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) | +| setAlphaDark | 设置透明度加深 | +| setAlphaLight | 设置透明度变浅 | +| grayLevel | 获取灰度值 | +| setParser | 设置 Color 解析器 | +| sortGray | 灰度值排序 | +| sortHUE | HSB ( HSV ) HUE 色相排序 | +| sortSaturation | HSB ( HSV ) Saturation 饱和度排序 | +| sortBrightness | HSB ( HSV ) Brightness 亮度排序 | +| blendColor | 使用给定的比例在两种 ARGB 颜色之间进行混合 | +| transitionColor | 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 | +| getKey | 获取 Key | +| getValue | 获取 Value | +| getValueParser | 获取 Value 解析后的值 ( 如: #000 => #000000 ) | +| getValueColor | 获取 ARGB/RGB color | +| getAlpha | 返回颜色中的透明度值 ( 返回十进制 ) | +| getRed | 返回颜色中红色的色值 ( 返回十进制 ) | +| getGreen | 返回颜色中绿色的色值 ( 返回十进制 ) | +| getBlue | 返回颜色中蓝色的色值 ( 返回十进制 ) | +| getGrayLevel | 获取灰度值 | +| getHue | 获取颜色色调 | +| getSaturation | 获取颜色饱和度 | +| getBrightness | 获取颜色亮度 | +| handleColor | 处理 color | + + +* **转换工具类 ( Byte、Hex 等 ) ->** [ConvertUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ConvertUtils.java) + +| 方法 | 注释 | +| :- | :- | +| convert | Object 转换所需类型对象 | +| newString | Object 转 String | +| newStringNotArrayDecode | Object 转 String ( 不进行 Array 解码转 String ) | +| toString | Object 转 String | +| toInt | Object 转 Integer | +| toBoolean | Object 转 Boolean | +| toFloat | Object 转 Float | +| toDouble | Object 转 Double | +| toLong | Object 转 Long | +| toShort | Object 转 Short | +| toChar | Object 转 Character | +| toByte | Object 转 Byte | +| toBigDecimal | Object 转 BigDecimal | +| toBigInteger | Object 转 BigInteger | +| toChars | Object 获取 char[] | +| toBytes | Object 获取 byte[] | +| toCharInt | char 转换 unicode 编码 | +| charAt | Object 获取 char ( 默认第一位 ) | +| parseInt | 字符串转换对应的进制 | +| parseLong | 字符串转换对应的进制 | +| bytesToObject | byte[] 转为 Object | +| objectToBytes | Object 转为 byte[] | +| bytesToChars | byte[] 转换 char[], 并且进行补码 | +| charsToBytes | char[] 转换 byte[] | +| intsToStrings | int[] 转换 string[] | +| doublesToStrings | double[] 转换 string[] | +| longsToStrings | long[] 转换 string[] | +| floatsToStrings | float[] 转换 string[] | +| intsToDoubles | int[] 转换 double[] | +| intsToLongs | int[] 转换 long[] | +| intsToFloats | int[] 转换 float[] | +| stringsToInts | string[] 转换 int[] | +| stringsToDoubles | string[] 转换 double[] | +| stringsToLongs | string[] 转换 long[] | +| stringsToFloats | string[] 转换 float[] | +| doublesToInts | double[] 转换 int[] | +| longsToInts | long[] 转换 int[] | +| floatsToInts | float[] 转换 int[] | +| toBinaryString | 将 字节转换 为 二进制字符串 | +| decodeBinary | 二进制字符串 转换 byte[] 解码 | +| isHex | 判断是否十六进制数据 | +| decodeHex | 将十六进制字节数组解码 | +| hexToInt | 十六进制 char 转换 int | +| toHexString | int 转换十六进制 | +| toHexChars | 将 string 转换为 十六进制 char[] | +| bytesBitwiseAND | 按位求补 byte[] 位移编解码 ( 共用同一个方法 ) | + + +* **坐标 ( GPS 纠偏 ) 相关工具类 ->** [CoordinateUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/CoordinateUtils.java) + +| 方法 | 注释 | +| :- | :- | +| bd09ToGcj02 | BD09 坐标转 GCJ02 坐标 | +| gcj02ToBd09 | GCJ02 坐标转 BD09 坐标 | +| gcj02ToWGS84 | GCJ02 坐标转 WGS84 坐标 | +| wgs84ToGcj02 | WGS84 坐标转 GCJ02 坐标 | +| bd09ToWGS84 | BD09 坐标转 WGS84 坐标 | +| wgs84ToBd09 | WGS84 坐标转 BD09 坐标 | +| outOfChina | 判断是否中国境外 | +| getDistance | 计算两个坐标相距距离 ( 单位: 米 ) | +| getAngle | 计算两个坐标的方向角度 | +| getDirection | 计算两个坐标的方向 | +| getValue | 获取中文方向值 | + + +* **日期工具类 ->** [DateUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/DateUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDefaultFormat | 获取默认 SimpleDateFormat ( yyyy-MM-dd HH:mm:ss ) | +| getSafeDateFormat | 获取对应时间格式线程安全 SimpleDateFormat | +| getCalendar | 获取 Calendar | +| getCurrentTime | 获取当前时间 Date | +| getCurrentTimeMillis | 获取当前时间毫秒 | +| getDateTime | 获取 Date Time | +| getDateNow | 获取当前时间的字符串 | +| formatDate | 将 Date 转换日期字符串 | +| formatTime | 将时间毫秒转换日期字符串 | +| parseDate | 将时间毫秒转换成 Date | +| parseLong | 解析时间字符串转换为 long 毫秒 | +| parseStringDefault | 解析时间字符串转换为指定格式字符串 | +| parseString | 解析时间字符串转换为指定格式字符串 | +| getYear | 获取年份 | +| getMonth | 获取月份 ( 0 - 11 ) + 1 | +| getDay | 获取天数 | +| getWeek | 获取星期数 ( 1 - 7、日 - 六 ) | +| get24Hour | 获取小时 ( 24 ) | +| get12Hour | 获取小时 ( 12 ) | +| getMinute | 获取分钟 | +| getSecond | 获取秒数 | +| isAM | 是否上午 | +| isPM | 是否下午 | +| isYear | 是否对应年份 | +| isMonth | 是否对应月份 | +| isDay | 是否对应天数 | +| isWeek | 是否对应星期 | +| isHour | 是否对应小时 | +| isMinute | 是否对应分钟 | +| isSecond | 是否对应秒数 | +| getSecondMultiple | 获取秒数倍数 | +| getMinuteMultiple | 获取分钟倍数 | +| getHourMultiple | 获取小时倍数 | +| getDayMultiple | 获取天数倍数 | +| getWeekMultiple | 获取周数倍数 | +| getMillisMultiple | 获取对应单位倍数 | +| getTimeDiffByCurrent | 获取时间差 ( 传入时间 - 当前时间 ) | +| getTimeDiff | 获取时间差 | +| isLeapYear | 判断是否闰年 | +| getMonthDayNumberAll | 根据年份、月份, 获取对应的天数 ( 完整天数, 无判断是否属于未来日期 ) | +| getYearMonthNumber | 根据年份, 获取对应的月份 | +| getMonthDayNumber | 根据年份、月份, 获取对应的天数 | +| timeAddZero | 时间补 0 处理 ( 小于 10, 则自动补充 0x ) | +| getArrayToHH | 生成 HH 按时间排序数组 | +| getListToHH | 生成 HH 按时间排序集合 | +| getArrayToMM | 生成 MM 按时间排序数组 | +| getListToMM | 生成 MM 按时间排序集合 | +| getArrayToHHMM | 生成 HH:mm 按间隔时间排序数组 | +| getListToHHMM | 生成 HH:mm 按间隔时间排序集合 | +| getListToHHMMPosition | 获取 HH:mm 按间隔时间排序的集合中, 指定时间所在索引 | +| millisToFitTimeSpan | 转换时间 | +| millisToTimeArrays | 转换时间为数组 | +| timeConvertByMillis | 传入时间毫秒, 获取 00:00:00 格式 ( 不处理大于一天 ) | +| timeConvertBySecond | 传入时间秒, 获取 00:00:00 格式 ( 不处理大于一天 ) | +| isInTime | 判断时间是否在 [startTime, endTime] 区间 | +| isInTimeFormat | 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) | +| isInTimeHHmm | 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) | +| isInTimeHHmmss | 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) | +| getEndTimeDiffHHmm | 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) | +| getEndTimeDiff | 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) | +| getZodiac | 获取生肖 | +| getConstellation | 获取星座 | +| getConstellationDate | 获取星座日期 | + + +* **开发常用方法工具类 ->** [DevCommonUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/DevCommonUtils.java) + +| 方法 | 注释 | +| :- | :- | +| timeRecord | 耗时时间记录 | +| getOperateTime | 获取操作时间 | +| sleepOperate | 堵塞操作 | +| isHttpRes | 判断是否网络资源 | +| whileMD5 | 循环 MD5 加密处理 | +| randomUUID | 获取随机唯一数 | +| randomUUIDToHashCode | 获取随机唯一数 HashCode | +| getRandomUUID | 获取随机规则生成 UUID | +| getRandomUUIDToString | 获取随机规则生成 UUID 字符串 | + + +* **编码工具类 ->** [EncodeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/EncodeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| base64Encode | Base64 编码 | +| base64EncodeToString | Base64 编码 | +| base64Decode | Base64 解码 | +| base64DecodeToString | Base64 解码 | +| htmlEncode | Html 字符串编码 | + + +* **变量字段工具类 ->** [FieldUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/FieldUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getField | 获取变量对象 | +| getDeclaredField | 获取变量对象 | +| getFields | 获取变量对象数组 | +| getDeclaredFields | 获取变量对象数组 | +| set | 设置字段的值 | +| get | 获取字段的值 | +| isLong | 是否 long/Long 类型 | +| isFloat | 是否 float/Float 类型 | +| isDouble | 是否 double/Double 类型 | +| isInteger | 是否 int/Integer 类型 | +| isBoolean | 是否 boolean/Boolean 类型 | +| isCharacter | 是否 char/Character 类型 | +| isByte | 是否 byte/Byte 类型 | +| isShort | 是否 short/Short 类型 | +| isString | 是否 String 类型 | +| isSerializable | 判断是否序列化 | +| isInvalid | 是否静态常量或者内部结构属性 | +| isStatic | 是否静态变量 | +| isFinal | 是否常量 | +| isStaticFinal | 是否静态变量 | +| isSynthetic | 是否内部结构属性 | +| getGenericType | 获取字段的泛型类型, 如果不带泛型返回 null | +| getComponentType | 获取数组的类型 | +| getAllDeclaredFields | 获取全部 Field, 包括父类 | + + +* **文件 ( IO 流 ) 工具类 ->** [FileIOUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/FileIOUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setBufferSize | 设置缓冲区的大小, 默认大小等于 8192 字节 | +| getFileInputStream | 获取输入流 | +| getFileOutputStream | 获取输出流 | +| writeFileFromIS | 通过输入流写入文件 | +| writeFileFromBytesByStream | 通过字节流写入文件 | +| writeFileFromBytesByChannel | 通过 FileChannel 把字节流写入文件 | +| writeFileFromBytesByMap | 通过 MappedByteBuffer 把字节流写入文件 | +| writeFileFromString | 通过字符串写入文件 | +| readFileToList | 读取文件内容, 返回换行 List | +| readFileToString | 读取文件内容, 返回字符串 | +| readFileToBytesByStream | 读取文件内容, 返回 byte[] | +| readFileToBytesByChannel | 通过 FileChannel, 读取文件内容, 返回 byte[] | +| readFileToBytesByMap | 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] | +| copyLarge | 复制 InputStream 到 OutputStream | + + +* **文件操作工具类 ->** [FileUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/FileUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getFile | 获取文件 | +| getFileByPath | 获取文件 | +| getFileCreateFolder | 获取路径, 并且进行创建目录 | +| getFilePathCreateFolder | 获取路径, 并且进行创建目录 | +| createFolder | 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) | +| createFolderByPath | 创建文件夹目录 ( 可以传入文件名 ) | +| createFolderByPaths | 创建多个文件夹, 如果不存在则创建 | +| createOrExistsDir | 判断目录是否存在, 不存在则判断是否创建成功 | +| createOrExistsFile | 判断文件是否存在, 不存在则判断是否创建成功 | +| createFileByDeleteOldFile | 判断文件是否存在, 存在则在创建之前删除 | +| createTimestampFileName | 通过文件后缀创建时间戳文件名 | +| createTimestampFileNameByName | 通过文件名创建时间戳文件名 | +| createTimestampFileNameByFile | 通过文件创建时间戳文件名 | +| createTimestampFileNameByPath | 通过文件路径创建时间戳文件名 | +| convertFiles | Path List 转 File List | +| convertPaths | File List 转 Path List | +| getPath | 获取文件路径 | +| getAbsolutePath | 获取文件绝对路径 | +| getName | 获取文件名 | +| getFileSuffix | 获取文件后缀名 ( 无 "." 单独后缀 ) | +| getFileNotSuffix | 获取文件名 ( 无后缀 ) | +| getFileNotSuffixToPath | 获取文件名 ( 无后缀 ) | +| getFileNameNoExtension | 获取路径中的不带扩展名的文件名 | +| getFileExtension | 获取路径中的文件扩展名 | +| isFileExists | 检查是否存在某个文件 | +| isFile | 判断是否文件 | +| isDirectory | 判断是否文件夹 | +| isHidden | 判断是否隐藏文件 | +| canRead | 文件是否可读 | +| canWrite | 文件是否可写 | +| canReadWrite | 文件是否可读写 | +| getFileLastModified | 获取文件最后修改的毫秒时间戳 | +| getFileCharsetSimple | 获取文件编码格式 | +| getFileLines | 获取文件行数 | +| getFileSize | 获取文件大小 | +| getDirSize | 获取目录大小 | +| getFileLength | 获取文件大小 | +| getDirLength | 获取目录全部文件大小 | +| getFileLengthNetwork | 获取文件大小 ( 网络资源 ) | +| getFileName | 获取路径中的文件名 | +| getDirName | 获取路径中的最长目录地址 | +| rename | 重命名文件 ( 同个目录下, 修改文件名 ) | +| formatFileSize | 传入文件路径, 返回对应的文件大小 | +| formatByteMemorySize | 字节数转合适内存大小 保留 3 位小数 | +| deleteFile | 删除文件 | +| deleteFiles | 删除多个文件 | +| deleteFolder | 删除文件夹 | +| saveFile | 保存文件 | +| appendFile | 追加文件 | +| readFileBytes | 读取文件 | +| readFile | 读取文件 | +| copyFile | 复制单个文件 | +| copyFolder | 复制文件夹 | +| moveFile | 移动 ( 剪切 ) 文件 | +| moveFolder | 移动 ( 剪切 ) 文件夹 | +| copyOrMoveDir | 复制或移动目录 | +| copyOrMoveFile | 复制或移动文件 | +| copyDir | 复制目录 | +| moveDir | 移动目录 | +| deleteDir | 删除目录 | +| deleteAllInDir | 删除目录下所有文件 | +| deleteFilesInDir | 删除目录下所有文件 | +| deleteFilesInDirWithFilter | 删除目录下所有过滤的文件 | +| listFilesInDir | 获取目录下所有文件 ( 不递归进子目录 ) | +| listFilesInDirWithFilter | 获取目录下所有过滤的文件 ( 不递归进子目录 ) | +| listFilesInDirBean | 获取目录下所有文件 ( 不递归进子目录 ) | +| listFilesInDirWithFilterBean | 获取目录下所有过滤的文件 ( 不递归进子目录 ) | +| listOrEmpty | 获取文件夹下的文件目录列表 ( 非全部子目录 ) | +| listFilesOrEmpty | 获取文件夹下的文件目录列表 ( 非全部子目录 ) | +| isImageFormats | 根据文件名判断文件是否为图片 | +| isAudioFormats | 根据文件名判断文件是否为音频 | +| isVideoFormats | 根据文件名判断文件是否为视频 | +| isFileFormats | 根据文件名判断文件是否为指定格式 | +| getFileMD5 | 获取文件 MD5 值 | +| getFileMD5ToHexString | 获取文件 MD5 值 | + + +* **格式化工具类 ->** [FormatUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/FormatUtils.java) + +| 方法 | 注释 | +| :- | :- | +| format | 字符串格式化 | +| unitSpanOf | 获取 UnitSpanFormatter | +| argsOf | 获取 ArgsFormatter | + + +* **循环工具类 ->** [ForUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ForUtils.java) + +| 方法 | 注释 | +| :- | :- | +| forArgs | 循环可变数组 | +| forSimpleArgs | 循环可变数组 | +| forInts | 循环可变数组 | +| forDoubles | 循环可变数组 | +| forFloats | 循环可变数组 | +| forLongs | 循环可变数组 | +| forBooleans | 循环可变数组 | +| forBytes | 循环可变数组 | +| forChars | 循环可变数组 | +| forShorts | 循环可变数组 | +| accept | 循环消费方法 | + + +* **Html 工具类 ->** [HtmlUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/HtmlUtils.java) + +| 方法 | 注释 | +| :- | :- | +| addRemovePaddingMargin | 为给定的 Html 移除 padding、margin | +| addHtmlColor | 为给定的字符串添加 HTML 颜色标记 | +| addHtmlBold | 为给定的字符串添加 HTML 加粗标记 | +| addHtmlColorAndBold | 为给定的字符串添加 HTML 颜色标记并加粗 | +| addHtmlUnderline | 为给定的字符串添加 HTML 下划线 | +| addHtmlStrikeThruLine | 为给定的字符串添加 HTML 中划线 | +| addHtmlOverLine | 为给定的字符串添加 HTML 上划线 | +| addHtmlIncline | 为给定的字符串添加 HTML 字体倾斜 | +| addHtmlSPAN | 为给定的字符串添加 HTML SPAN 标签 | +| addHtmlP | 为给定的字符串添加 HTML P 标签 | +| addHtmlIMG | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlIMGByWidth | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlIMGByHeight | 为给定的字符串添加 HTML IMG 标签 | +| addHtmlDIV | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByMargin | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByPadding | 为给定的字符串添加 HTML DIV 标签 | +| addHtmlDIVByMarginPadding | 为给定的字符串添加 HTML DIV 标签 | +| keywordReplaceHtmlColor | 将给定的字符串中所有给定的关键字标色 | + + +* **Http 参数工具类 ->** [HttpParamsUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/HttpParamsUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getUrlParams | 获取 Url 携带参数 | +| getUrlParamsArray | 获取 Url、携带参数 数组 | +| existsParams | 判断是否存在参数 | +| existsParamsByURL | 通过 Url 判断是否存在参数 | +| joinUrlParams | 拼接 Url 及携带参数 | +| getUrlParamsJoinSymbol | 获取 Url 及携带参数 拼接符号 | +| splitParamsByUrl | 通过 Url 拆分参数 | +| splitParams | 拆分参数 | +| joinParams | 拼接请求参数 | +| joinParamsObj | 拼接请求参数 | +| convertObjToMS | 进行转换对象处理 ( 请求发送对象 ) | +| convertObjToMO | 进行转换对象处理 ( 请求发送对象 ) | +| urlEncode | 进行 URL 编码, 默认 UTF-8 | + + +* **HttpURLConnection 网络工具类 ->** [HttpURLConnectionUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/HttpURLConnectionUtils.java) + +| 方法 | 注释 | +| :- | :- | +| doGetAsync | 异步的 Get 请求 | +| doPostAsync | 异步的 Post 请求 | +| request | 发送请求 | +| getNetTime | 获取网络时间 ( 默认使用百度链接 ) | + + +* **Map 工具类 ->** [MapUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/MapUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断 Map 是否为 null | +| isNotEmpty | 判断 Map 是否不为 null | +| length | 获取 Map 长度 | +| isLength | 获取长度 Map 是否等于期望长度 | +| greaterThan | 判断 Map 长度是否大于指定长度 | +| greaterThanOrEqual | 判断 Map 长度是否大于等于指定长度 | +| lessThan | 判断 Map 长度是否小于指定长度 | +| lessThanOrEqual | 判断 Map 长度是否小于等于指定长度 | +| getCount | 获取 Map 数组长度总和 | +| get | 获取 value | +| getKeyByValue | 通过 value 获取 key | +| getKeysByValue | 通过 value 获取 key 集合 ( 返回等于 value 的 key 集合 ) | +| getKeys | 通过 Map 获取 key 集合 | +| getKeysToArrays | 通过 Map 获取 key 数组 | +| getValues | 通过 Map 获取 value 集合 | +| getValuesToArrays | 通过 Map 获取 value 数组 | +| getFirst | 获取第一条数据 | +| getLast | 获取最后一条数据 | +| getNext | 根据指定 key 获取 key 所在位置的下一条数据 | +| getPrevious | 根据指定 key 获取 key 所在位置的上一条数据 | +| put | 添加一条数据 | +| putNotNull | 添加一条数据 ( 不允许 key 为 null ) | +| putAll | 添加多条数据 | +| putAllNotNull | 添加多条数据, 不允许 key 为 null | +| remove | 移除一条数据 | +| removeToKeys | 移除多条数据 | +| removeToValue | 移除等于 value 的所有数据 | +| removeToValues | 移除等于 value 的所有数据 ( Collection ) | +| equals | 判断两个值是否一样 | +| toggle | 切换保存状态 | +| isNullToValue | 判断指定 key 的 value 是否为 null | +| containsKey | 判断 Map 是否存储 key | +| containsValue | 判断 Map 是否存储 value | +| putToList | 添加一条数据 | +| removeToList | 移除一条数据 | +| removeToLists | 移除多条数据 | +| removeToMap | 移除多条数据 ( 通过 Map 进行移除 ) | +| mapToString | 键值对拼接 | + + +* **数字 ( 计算 ) 工具类 ->** [NumberUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/NumberUtils.java) + +| 方法 | 注释 | +| :- | :- | +| addZero | 补 0 处理 ( 小于 10, 则自动补充 0x ) | +| subZeroAndDot | 去掉结尾多余的 . 与 0 | +| calculateUnitD | 计算指定单位倍数 | +| calculateUnitI | 计算指定单位倍数 | +| calculateUnitL | 计算指定单位倍数 | +| calculateUnitF | 计算指定单位倍数 | +| percentD | 计算百分比值 ( 最大 100% ) | +| percentI | 计算百分比值 ( 最大 100% ) | +| percentL | 计算百分比值 ( 最大 100% ) | +| percentF | 计算百分比值 ( 最大 100% ) | +| percentD2 | 计算百分比值 ( 可超出 100% ) | +| percentI2 | 计算百分比值 ( 可超出 100% ) | +| percentL2 | 计算百分比值 ( 可超出 100% ) | +| percentF2 | 计算百分比值 ( 可超出 100% ) | +| multipleD | 获取倍数 | +| multipleI | 获取倍数 | +| multipleL | 获取倍数 | +| multipleF | 获取倍数 | +| multiple | 获取整数倍数 ( 自动补 1 ) | +| clamp | 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max | +| numberToCHN | 数字转中文数值 | +| isNumber | 检验数字 | +| isNumberDecimal | 检验数字或包含小数点 | + + +* **对象相关工具类 ->** [ObjectUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ObjectUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断对象是否为空 | +| isNotEmpty | 判断对象是否非空 | +| equals | 判断两个值是否一样 | +| requireNonNull | 检查对象是否为 null, 为 null 则抛出异常, 不为 null 则返回该对象 | +| getOrDefault | 获取非空或默认对象 | +| hashCode | 获取对象哈希值 | +| getObjectTag | 获取一个对象的独一无二的标记 | +| convert | Object 转换所需类型对象 | + + +* **随机工具类 ->** [RandomUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/RandomUtils.java) + +| 方法 | 注释 | +| :- | :- | +| nextBoolean | 获取伪随机 boolean 值 | +| nextBytes | 获取伪随机 byte[] | +| nextDouble | 获取伪随机 double 值 | +| nextGaussian | 获取伪随机高斯分布值 | +| nextFloat | 获取伪随机 float 值 | +| nextInt | 获取伪随机 int 值 | +| nextLong | 获取伪随机 long 值 | +| getRandomNumbers | 获取数字自定义长度的随机数 | +| getRandomLowerCaseLetters | 获取小写字母自定义长度的随机数 | +| getRandomCapitalLetters | 获取大写字母自定义长度的随机数 | +| getRandomLetters | 获取大小写字母自定义长度的随机数 | +| getRandomNumbersAndLetters | 获取数字、大小写字母自定义长度的随机数 | +| getRandom | 获取自定义数据自定义长度的随机数 | +| shuffle | 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) | +| shuffle2 | 洗牌算法 ( 第二种 ) 随机置换指定的数组使用的默认源的随机性 | +| nextIntRange | 获取指定范围 int 值 | +| nextLongRange | 获取指定范围 long 值 | +| nextDoubleRange | 获取指定范围 double 值 | +| ints | 获取随机 int[] | +| longs | 获取随机 long[] | +| doubles | 获取随机 double[] | + + +* **反射相关工具类 ->** [Reflect2Utils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/Reflect2Utils.java) + +| 方法 | 注释 | +| :- | :- | +| setProperty | 设置某个对象变量值 ( 可设置静态变量 ) | +| getProperty | 获取某个对象的变量 ( 可获取静态变量 ) | +| getStaticProperty | 获取某个类的静态变量 ( 只能获取静态变量 ) | +| invokeMethod | 执行某个对象方法 ( 可执行静态方法 ) | +| invokeStaticMethod | 执行某个类的静态方法 ( 只能执行静态方法 ) | +| newInstance | 新建实例 ( 构造函数创建 ) | +| isInstance | 是不是某个类的实例 | +| getArgsClass | 获取参数类型 | +| getPropertyByParent | 获取父类中的变量对象 | +| getDeclaredFieldParent | 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) | + + +* **反射相关工具类 ->** [ReflectUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ReflectUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reflect | 设置要反射的类 | +| newInstance | 实例化反射对象 | +| field | 设置反射的字段 | +| setEnumVal | 设置枚举值 | +| method | 设置反射的方法 | +| proxy | 根据类, 代理创建并返回对象 | +| type | 获取类型 | +| get | 获取反射想要获取的 | +| hashCode | 获取 HashCode | +| equals | 判断反射的两个对象是否一样 | +| toString | 获取反射获取的对象 | + + +* **计算比例工具类 ->** [ScaleUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ScaleUtils.java) + +| 方法 | 注释 | +| :- | :- | +| calcScale | 计算比例 ( 商 ) | +| calcScaleToMath | 计算比例 ( 被除数 ( 最大值 ) / 除数 ( 最小值 ) ) | +| calcScaleToWidth | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeight | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScale | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScale | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScale | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthI | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightI | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleI | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleI | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleI | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthL | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightL | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleL | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleL | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleL | 以高度为基准, 转换对应比例的宽度 | +| calcScaleToWidthF | 计算缩放比例 ( 根据宽度比例转换高度 ) | +| calcScaleToHeightF | 计算缩放比例 ( 根据高度比例转换宽度 ) | +| calcWidthHeightToScaleF | 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) | +| calcWidthToScaleF | 以宽度为基准, 转换对应比例的高度 | +| calcHeightToScaleF | 以高度为基准, 转换对应比例的宽度 | +| calcXY | 计算 XY 比 | + + +* **流操作工具类 ->** [StreamUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/StreamUtils.java) + +| 方法 | 注释 | +| :- | :- | +| inputToOutputStream | 输入流转输出流 | +| outputToInputStream | 输出流转输入流 | +| inputStreamToBytes | 输入流转 byte[] | +| bytesToInputStream | byte[] 转输出流 | +| outputStreamToBytes | 输出流转 byte[] | +| bytesToOutputStream | byte[] 转 输出流 | +| inputStreamToString | 输入流转 String | +| stringToInputStream | String 转换输入流 | +| outputStreamToString | 输出流转 String | +| stringToOutputStream | String 转 输出流 | + + +* **字符串工具类 ->** [StringUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/StringUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isEmpty | 判断字符串是否为 null | +| isEmptyClear | 判断字符串是否为 null ( 调用 clearSpaceTabLineTrim ) | +| isNotEmpty | 判断字符串是否不为 null | +| isNotEmptyClear | 判断字符串是否不为 null ( 调用 clearSpaceTabLineTrim ) | +| isNull | 判断字符串是否为 "null" | +| isNullClear | 判断字符串是否为 "null" ( 调用 clearSpaceTabLineTrim ) | +| isNotNull | 判断字符串是否不为 "null" | +| isNotNullClear | 判断字符串是否不为 "null" ( 调用 clearSpaceTabLineTrim ) | +| length | 获取字符串长度 | +| isLength | 获取字符串长度 是否等于期望长度 | +| equals | 判断两个值是否一样 | +| equalsNotNull | 判断两个值是否一样 ( 非 null 判断 ) | +| equalsIgnoreCase | 判断两个值是否一样 ( 忽略大小写 ) | +| equalsIgnoreCaseNotNull | 判断两个值是否一样 ( 忽略大小写 ) | +| isEquals | 判断多个字符串是否相等, 只有全相等才返回 true ( 对比大小写 ) | +| isOrEquals | 判断多个字符串, 只要有一个符合条件则通过 | +| isContains | 判断一堆值中, 是否存在符合该条件的 ( 包含 ) | +| isStartsWith | 判断内容, 是否属于特定字符串开头 ( 对比大小写 ) | +| isEndsWith | 判断内容, 是否属于特定字符串结尾 ( 对比大小写 ) | +| countMatches | 统计字符串匹配个数 | +| countMatches2 | 统计字符串匹配个数 | +| isSpace | 判断字符串是否为 null 或全为空白字符 | +| getBytes | 字符串 转 byte[] | +| clearSpace | 清空字符串全部空格 | +| clearTab | 清空字符串全部 Tab | +| clearLine | 清空字符串全部换行符 | +| clearLine2 | 清空字符串全部换行符 | +| clearSpaceTrim | 清空字符串前后全部空格 | +| clearTabTrim | 清空字符串前后全部 Tab | +| clearLineTrim | 清空字符串前后全部换行符 | +| clearLineTrim2 | 清空字符串前后全部换行符 | +| clearSpaceTabLine | 清空字符串全部空格、Tab、换行符 | +| clearSpaceTabLineTrim | 清空字符串前后全部空格、Tab、换行符 | +| appendSpace | 追加空格 | +| appendTab | 追加 Tab | +| appendLine | 追加换行 | +| appendLine2 | 追加换行 | +| forString | 循环指定数量字符串 | +| joinArgs | 循环拼接 | +| join | 循环拼接 | +| colonSplit | 冒号分割处理 | +| getString | 获取字符串 ( 判 null ) | +| checkValue | 检查字符串 | +| checkValues | 检查字符串 ( 多个值 ) | +| checkValuesSpace | 检查字符串 ( 多个值, 删除前后空格对比判断 ) | +| format | 字符串格式化 | +| argsFormat | 根据可变参数数量自动格式化 | +| concat | 字符串连接, 将参数列表拼接为一个字符串 | +| concatSpiltWith | 字符串连接, 将参数列表拼接为一个字符串 | +| concatSpiltWithIgnoreLast | 字符串连接, 将参数列表拼接为一个字符串 ( 最后一个不追加间隔 ) | +| appends | StringBuilder 拼接处理 | +| appendsIgnoreLast | StringBuilder 拼接处理 ( 最后一个不追加间隔 ) | +| gbkEncode | 字符串进行 GBK 编码 | +| gbk2312Encode | 字符串进行 GBK2312 编码 | +| utf8Encode | 字符串进行 UTF-8 编码 | +| strEncode | 进行字符串编码 | +| urlEncode | 进行 URL 编码, 默认 UTF-8 | +| urlDecode | 进行 URL 解码, 默认 UTF-8 | +| urlDecodeWhile | 进行 URL 解码, 默认 UTF-8 ( 循环到非 URL 编码为止 ) | +| ascii | 将字符串转移为 ASCII 码 | +| unicode | 将字符串转移为 Unicode 码 | +| unicodeString | 将字符数组转移为 Unicode 码 | +| dbc | 转化为半角字符 | +| sbc | 转化为全角字符 如: a = a, A = A | +| checkChineseToString | 检测字符串是否全是中文 | +| isChinese | 判断输入汉字 | +| upperFirstLetter | 首字母大写 | +| lowerFirstLetter | 首字母小写 | +| reverse | 反转字符串 | +| underScoreCaseToCamelCase | 下划线命名转为驼峰命名 | +| camelCaseToUnderScoreCase | 驼峰命名法转为下划线命名 | +| sqliteEscape | 字符串数据库字符转义 | +| convertHideMobile | 转换手机号 | +| convertSymbolHide | 转换符号处理 | +| subEllipsize | 裁剪超出的内容, 并且追加符号 ( 如 ... ) | +| subSymbolHide | 裁剪符号处理 | +| subSetSymbol | 裁剪内容 ( 设置符号处理 ) | +| substring | 裁剪字符串 | +| replaceSEWith | 替换特定字符串开头、结尾的字符串 | +| replaceStartsWith | 替换开头字符串 | +| replaceEndsWith | 替换结尾字符串 | +| clearSEWiths | 清空特定字符串开头、结尾的字符串 | +| clearStartsWith | 清空特定字符串开头的字符串 | +| clearEndsWith | 清空特定字符串结尾的字符串 | +| replaceAll | 替换字符串 | +| replaceAllToNull | 替换字符串 | +| replaceAlls | 替换字符串 | +| split | 拆分字符串 | + + +* **异常处理工具类 ->** [ThrowableUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ThrowableUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getThrowable | 获取异常信息 | +| getThrowableStackTrace | 获取异常栈信息 | + + +* **类型工具类 ->** [TypeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/TypeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getArrayType | 获取 Array Type | +| getListType | 获取 List Type | +| getSetType | 获取 Set Type | +| getMapType | 获取 Map Type | +| getType | 获取 Type | + + +* **压缩相关工具类 ->** [ZipUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/ZipUtils.java) + +| 方法 | 注释 | +| :- | :- | +| zipFiles | 批量压缩文件 | +| zipFile | 压缩文件 | +| unzipFile | 解压文件 | +| unzipFileByKeyword | 解压带有关键字的文件 | +| getFilesPath | 获取压缩文件中的文件路径链表 | +| getComments | 获取压缩文件中的注释链表 | + + +## **`dev.utils.common.able`** + + +## **`dev.utils.common.assist`** + + +* **均值计算 ( 用以统计平均数 ) 辅助类 ->** [Averager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/Averager.java) + +| 方法 | 注释 | +| :- | :- | +| add | 添加一个数字 | +| clear | 清除全部 | +| size | 获取参与均值计算的数字个数 | +| getAverage | 获取平均数 | +| print | 输出参与均值计算的数字 | + + +* **标记值计算存储 ( 位运算符 ) ->** [FlagsValue.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/FlagsValue.java) + +| 方法 | 注释 | +| :- | :- | +| getFlags | 获取 flags value | +| setFlags | 设置 flags value | +| addFlags | 添加 flags value | +| clearFlags | 移除 flags value | +| hasFlags | 是否存在 flags value | +| notHasFlags | 是否不存在 flags value | + + +* **时间均值计算辅助类 ->** [TimeAverager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/TimeAverager.java) + +| 方法 | 注释 | +| :- | :- | +| start | 开始计时 ( 毫秒 ) | +| end | 结束计时 ( 毫秒 ) | +| endAndRestart | 结束计时, 并重新启动新的计时 | +| average | 求全部计时均值 | +| print | 输出全部时间值 | +| clear | 清除计时数据 | + + +* **时间计时辅助类 ->** [TimeCounter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/TimeCounter.java) + +| 方法 | 注释 | +| :- | :- | +| start | 开始计时 ( 毫秒 ) | +| durationRestart | 获取持续的时间并重新启动 ( 毫秒 ) | +| duration | 获取持续的时间 ( 毫秒 ) | +| getStartTime | 获取开始时间 ( 毫秒 ) | + + +* **堵塞时间辅助类 ->** [TimeKeeper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/TimeKeeper.java) + +| 方法 | 注释 | +| :- | :- | +| waitForEndAsync | 设置等待一段时间后, 通知方法 ( 异步 ) | +| waitForEnd | 设置等待一段时间后, 通知方法 ( 同步 ) | + + +* **弱引用辅助类 ->** [WeakReferenceAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getSingleWeak | 获取单个弱引用对象 | +| getSingleWeakValue | 获取单个弱引用对象值 | +| setSingleWeakValue | 保存单个弱引用对象值 | +| removeSingleWeak | 移除单个弱引用持有对象 | +| getWeak | 获取弱引用对象 | +| getWeakValue | 获取弱引用对象值 | +| setWeakValue | 保存弱引用对象值 | +| removeWeak | 移除指定弱引用持有对象 | +| clear | 清空全部弱引用持有对象 | + + +## **`dev.utils.common.assist.record`** + + +* **文件记录分析工具类 ->** [FileRecordUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isSuccessful | 校验记录方法返回字符串是否成功 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| setCallback | 设置文件记录回调 | +| getLogContent | 获取日志内容 | +| record | 记录方法 | + + +* **日志记录配置信息 ->** [RecordConfig.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordConfig.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取配置信息 | +| getStoragePath | 获取存储路径 | +| getFileName | 获取文件名 ( 固定 ) | +| getFolderName | 获取文件夹名 ( 模块名 ) | +| getFileIntervalTime | 获取文件记录间隔时间 | +| isHandler | 是否处理记录 | +| setHandler | 设置是否处理记录 | +| isInsertHeaderData | 是否插入头数据 | +| setInsertHeaderData | 设置是否插入头数据 | +| getRecordInsert | 获取日志记录插入信息 | +| setRecordInsert | 设置日志记录插入信息 | +| getFinalPath | 获取文件地址 | + + +* **日志记录插入信息 ->** [RecordInsert.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordInsert.java) + +| 方法 | 注释 | +| :- | :- | +| getFileInfo | getFileInfo | +| setFileInfo | setFileInfo | +| getLogHeader | getLogHeader | +| setLogHeader | setLogHeader | +| getLogTail | getLogTail | +| setLogTail | setLogTail | + + +## **`dev.utils.common.assist.search`** + + +* **文件广度优先搜索算法 ( 多线程 + 队列, 搜索某个目录下的全部文件 ) ->** [FileBreadthFirstSearchUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setSearchHandler | 设置搜索处理接口 | +| getQueueSameTimeNumber | 获取任务队列同时进行数量 | +| setQueueSameTimeNumber | 任务队列同时进行数量 | +| isRunning | 是否搜索中 | +| stop | 停止搜索 | +| isStop | 是否停止搜索 | +| getStartTime | 获取开始搜索时间 ( 毫秒 ) | +| getEndTime | 获取结束搜索时间 ( 毫秒 ) | +| getDelayTime | 获取延迟校验时间 ( 毫秒 ) | +| setDelayTime | 设置延迟校验时间 ( 毫秒 ) | +| query | 搜索目录 | + + +* **文件深度优先搜索算法 ( 递归搜索某个目录下的全部文件 ) ->** [FileDepthFirstSearchUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java) + +| 方法 | 注释 | +| :- | :- | +| setSearchHandler | 设置搜索处理接口 | +| isRunning | 是否搜索中 | +| stop | 停止搜索 | +| isStop | 是否停止搜索 | +| getStartTime | 获取开始搜索时间 ( 毫秒 ) | +| getEndTime | 获取结束搜索时间 ( 毫秒 ) | +| query | 搜索目录 | + + +## **`dev.utils.common.assist.url`** + + +* **Dev 库 Java 通用 Url 解析器 ->** [DevJavaUrlParser.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java) + +| 方法 | 注释 | +| :- | :- | +| reset | reset | +| setUrl | setUrl | +| getUrl | getUrl | +| getUrlByPrefix | getUrlByPrefix | +| getUrlByParams | getUrlByParams | +| getUrlParams | getUrlParams | +| getUrlParamsDecode | getUrlParamsDecode | +| isConvertMap | isConvertMap | +| setConvertMap | setConvertMap | + + +* **Url 携带信息解析 ->** [UrlExtras.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/assist/url/UrlExtras.java) + +| 方法 | 注释 | +| :- | :- | +| getUrl | 获取完整 Url | +| getUrlByPrefix | 获取 Url 前缀 ( 去除参数部分 ) | +| getUrlByParams | 获取 Url 参数部分字符串 | +| getUrlParams | 获取 Url Params Map | +| getUrlParamsDecode | 获取 Url Params Map ( 参数值进行 UrlDecode ) | +| getParser | 获取 Url 解析器 | +| setParser | 设置 Url 解析器 | +| reset | 重置并返回一个新的解析器 | +| setUrl | 设置完整 Url | +| isConvertMap | 是否解析、转换 Param Map | +| setConvertMap | 设置是否解析、转换 Param Map | + + +## **`dev.utils.common.cipher`** + + +* **Base64 工具类 ->** [Base64.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64.java) + +| 方法 | 注释 | +| :- | :- | +| decode | Decode the Base64-encoded data in input and return the data in | +| encodeToString | Base64-encode the given data and return a newly allocated | +| encode | Base64-encode the given data and return a newly allocated | + + +* **Base64 编解码 ( 并进行 ) 加解密 ->** [Base64Cipher.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64Cipher.java) + +| 方法 | 注释 | +| :- | :- | +| decrypt | 解码 | +| encrypt | 编码 | + + +* **加密工具类 ->** [CipherUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/cipher/CipherUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encrypt | 加密方法 | +| decrypt | 解密方法 | + + +## **`dev.utils.common.comparator`** + + +* **排序比较器工具类 ->** [ComparatorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/ComparatorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| reverse | List 反转处理 | +| sort | List 排序处理 | +| sortAsc | List 升序处理 | +| sortDesc | List 降序处理 | +| sortFileLastModifiedAsc | 文件修改时间升序排序 | +| sortFileLastModifiedDesc | 文件修改时间降序排序 | +| sortFileLengthAsc | 文件大小升序排序 | +| sortFileLengthDesc | 文件大小降序排序 | +| sortFileNameAsc | 文件名升序排序 | +| sortFileNameDesc | 文件名降序排序 | +| sortFileAsc | 文件升序排序 | +| sortFileDesc | 文件降序排序 | +| sortDateAsc | Date 升序排序 | +| sortDateDesc | Date 降序排序 | +| sortDoubleAsc | Double 升序排序 | +| sortDoubleDesc | Double 降序排序 | +| sortFloatAsc | Float 升序排序 | +| sortFloatDesc | Float 降序排序 | +| sortIntAsc | Int 升序排序 | +| sortIntDesc | Int 降序排序 | +| sortLongAsc | Long 升序排序 | +| sortLongDesc | Long 降序排序 | +| sortStringAsc | String 升序排序 | +| sortStringDesc | String 降序排序 | +| sortStringWindowsSimpleAsc | String Windows 排序比较器简单实现升序排序 | +| sortStringWindowsSimpleDesc | String Windows 排序比较器简单实现降序排序 | +| sortStringWindowsSimple2Asc | String Windows 排序比较器简单实现升序排序 ( 实现方式二 ) | +| sortStringWindowsSimple2Desc | String Windows 排序比较器简单实现降序排序 ( 实现方式二 ) | +| sortWindowsExplorerFileSimpleComparatorAsc | Windows 目录资源文件升序排序 | +| sortWindowsExplorerFileSimpleComparatorDesc | Windows 目录资源文件降序排序 | +| sortWindowsExplorerFileSimpleComparator2Asc | Windows 目录资源文件升序排序 ( 实现方式二 ) | +| sortWindowsExplorerFileSimpleComparator2Desc | Windows 目录资源文件降序排序 ( 实现方式二 ) | +| sortWindowsExplorerStringSimpleComparatorAsc | Windows 目录资源文件名升序排序 | +| sortWindowsExplorerStringSimpleComparatorDesc | Windows 目录资源文件名降序排序 | +| sortWindowsExplorerStringSimpleComparator2Asc | Windows 目录资源文件名升序排序 ( 实现方式二 ) | +| sortWindowsExplorerStringSimpleComparator2Desc | Windows 目录资源文件名降序排序 ( 实现方式二 ) | + + +## **`dev.utils.common.comparator.sort`** + + +* **Date 排序值 ->** [DateSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSort.java) + +| 方法 | 注释 | +| :- | :- | +| getDateSortValue | getDateSortValue | + + +* **Date 升序排序 ->** [DateSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Date 降序排序 ->** [DateSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Double 排序值 ->** [DoubleSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java) + +| 方法 | 注释 | +| :- | :- | +| getDoubleSortValue | getDoubleSortValue | + + +* **Double 升序排序 ->** [DoubleSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Double 降序排序 ->** [DoubleSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件修改时间升序排序 ->** [FileLastModifiedSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件修改时间降序排序 ->** [FileLastModifiedSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件大小升序排序 ->** [FileLengthSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件大小降序排序 ->** [FileLengthSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件名升序排序 ->** [FileNameSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件名降序排序 ->** [FileNameSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件升序排序 ->** [FileSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **文件降序排序 ->** [FileSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Float 排序值 ->** [FloatSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSort.java) + +| 方法 | 注释 | +| :- | :- | +| getFloatSortValue | getFloatSortValue | + + +* **Float 升序排序 ->** [FloatSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Float 降序排序 ->** [FloatSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Int 排序值 ->** [IntSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSort.java) + +| 方法 | 注释 | +| :- | :- | +| getIntSortValue | getIntSortValue | + + +* **Int 升序排序 ->** [IntSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Int 降序排序 ->** [IntSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Long 排序值 ->** [LongSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSort.java) + +| 方法 | 注释 | +| :- | :- | +| getLongSortValue | getLongSortValue | + + +* **Long 升序排序 ->** [LongSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Long 降序排序 ->** [LongSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String 排序值 ->** [StringSort.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSort.java) + +| 方法 | 注释 | +| :- | :- | +| getStringSortValue | getStringSortValue | + + +* **String 升序排序 ->** [StringSortAsc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String 降序排序 ->** [StringSortDesc.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String Windows 排序比较器简单实现 ->** [StringSortWindowsSimple.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **String Windows 排序比较器简单实现 ->** [StringSortWindowsSimple2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件排序比较器 ->** [WindowsExplorerFileSimpleComparator.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件排序比较器 ->** [WindowsExplorerFileSimpleComparator2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件名排序比较器 ->** [WindowsExplorerStringSimpleComparator.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +* **Windows 目录资源文件名排序比较器 ->** [WindowsExplorerStringSimpleComparator2.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java) + +| 方法 | 注释 | +| :- | :- | +| compare | compare | + + +## **`dev.utils.common.encrypt`** + + +* **AES 对称加密工具类 ->** [AESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/AESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| initKey | 生成密钥 | +| encrypt | AES 加密 | +| decrypt | AES 解密 | + + +* **CRC 工具类 ->** [CRCUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/CRCUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getCRC32 | 获取 CRC32 值 | +| getCRC32ToHexString | 获取 CRC32 值 | +| getFileCRC32 | 获取文件 CRC32 值 | + + +* **DES 对称加密工具类 ->** [DESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/DESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getDESKey | 获取可逆算法 DES 的密钥 | +| encrypt | DES 加密 | +| decrypt | DES 解密 | + + +* **加解密通用工具类 ->** [EncryptUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/EncryptUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encryptMD2 | MD2 加密 | +| encryptMD2ToHexString | MD2 加密 | +| encryptMD5 | MD5 加密 | +| encryptMD5ToHexString | MD5 加密 | +| encryptMD5File | 获取文件 MD5 值 | +| encryptMD5FileToHexString | 获取文件 MD5 值 | +| encryptSHA1 | SHA1 加密 | +| encryptSHA1ToHexString | SHA1 加密 | +| encryptSHA224 | SHA224 加密 | +| encryptSHA224ToHexString | SHA224 加密 | +| encryptSHA256 | SHA256 加密 | +| encryptSHA256ToHexString | SHA256 加密 | +| encryptSHA384 | SHA384 加密 | +| encryptSHA384ToHexString | SHA384 加密 | +| encryptSHA512 | SHA512 加密 | +| encryptSHA512ToHexString | SHA512 加密 | +| hashTemplate | Hash 加密模版方法 | +| encryptHmacMD5 | HmacMD5 加密 | +| encryptHmacMD5ToHexString | HmacMD5 加密 | +| encryptHmacSHA1 | HmacSHA1 加密 | +| encryptHmacSHA1ToHexString | HmacSHA1 加密 | +| encryptHmacSHA224 | HmacSHA224 加密 | +| encryptHmacSHA224ToHexString | HmacSHA224 加密 | +| encryptHmacSHA256 | HmacSHA256 加密 | +| encryptHmacSHA256ToHexString | HmacSHA256 加密 | +| encryptHmacSHA384 | HmacSHA384 加密 | +| encryptHmacSHA384ToHexString | HmacSHA384 加密 | +| encryptHmacSHA512 | HmacSHA512 加密 | +| encryptHmacSHA512ToHexString | HmacSHA512 加密 | +| hmacTemplate | Hmac 加密模版方法 | +| encryptDES | DES 加密 | +| encryptDESToBase64 | DES 加密 | +| encryptDESToHexString | DES 加密 | +| decryptDES | DES 解密 | +| decryptDESToBase64 | DES 解密 | +| decryptDESToHexString | DES 解密 | +| encrypt3DES | 3DES 加密 | +| encrypt3DESToBase64 | 3DES 加密 | +| encrypt3DESToHexString | 3DES 加密 | +| decrypt3DES | 3DES 解密 | +| decrypt3DESToBase64 | 3DES 解密 | +| decrypt3DESToHexString | 3DES 解密 | +| encryptAES | AES 加密 | +| encryptAESToBase64 | AES 加密 | +| encryptAESToHexString | AES 加密 | +| decryptAES | AES 解密 | +| decryptAESToBase64 | AES 解密 | +| decryptAESToHexString | AES 解密 | +| symmetricTemplate | 对称加密模版方法 | +| encryptRSA | RSA 加密 | +| encryptRSAToBase64 | RSA 加密 | +| encryptRSAToHexString | RSA 加密 | +| decryptRSA | RSA 解密 | +| decryptRSAToBase64 | RSA 解密 | +| decryptRSAToHexString | RSA 解密 | +| rsaTemplate | RSA 加解密模版方法 | + + +* **字符串 ( 编解码 ) 工具类 ->** [EscapeUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/EscapeUtils.java) + +| 方法 | 注释 | +| :- | :- | +| escape | 编码 | +| unescape | 解码 | + + +* **MD5 加密工具类 ->** [MD5Utils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/MD5Utils.java) + +| 方法 | 注释 | +| :- | :- | +| md5 | 加密内容 ( 32 位小写 MD5 ) | +| md5Upper | 加密内容 ( 32 位大写 MD5 ) | +| getFileMD5 | 获取文件 MD5 值 | +| getFileMD5ToHexString | 获取文件 MD5 值 | + + +* **SHA 加密工具类 ->** [SHAUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/SHAUtils.java) + +| 方法 | 注释 | +| :- | :- | +| sha1 | 加密内容 SHA1 | +| sha224 | 加密内容 SHA224 | +| sha256 | 加密内容 SHA256 | +| sha384 | 加密内容 SHA384 | +| sha512 | 加密内容 SHA512 | +| getFileSHA1 | 获取文件 SHA1 值 | +| getFileSHA256 | 获取文件 SHA256 值 | +| shaHex | 加密内容 SHA 模板 | +| getFileSHA | 获取文件 SHA 值 | + + +* **3DES 对称加密工具类 ->** [TripleDESUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java) + +| 方法 | 注释 | +| :- | :- | +| initKey | 生成密钥 | +| encrypt | 3DES 加密 | +| decrypt | 3DES 解密 | + + +* **异或工具类 ->** [XorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/encrypt/XorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| encryptAsFix | 加解密 ( 固定 Key 方式 ) 这种方式 加解密 方法共用 | +| encrypt | 加密 ( 非固定 Key 方式 ) | +| decrypt | 解密 ( 非固定 Key 方式 ) | +| xorChecksum | 数据异或校验位计算 | + + +## **`dev.utils.common.file`** + + +* **文件分片辅助类 ->** [FilePartAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/file/FilePartAssist.java) + +| 方法 | 注释 | +| :- | :- | +| getFile | 获取文件 | +| getFileName | 获取文件名 | +| getFilePartItems | 获取文件分片信息集合 | +| getFilePartItem | 获取指定索引文件分片信息 | +| getPartCount | 获取分片总数 | +| existsPart | 是否存在分片 | +| isOnlyOne | 是否只有一个分片 | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | + + +* **文件分片信息 Item ->** [FilePartItem.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/file/FilePartItem.java) + +| 方法 | 注释 | +| :- | :- | +| isFirstItem | 判断是否 First Item | +| isLastItem | 判断是否 Last Item | +| existsPart | 是否存在分片 | +| isOnlyOne | 是否只有一个分片 | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | + + +* **文件分片工具类 ->** [FilePartUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/file/FilePartUtils.java) + +| 方法 | 注释 | +| :- | :- | +| getPartName | 获取分片文件名 ( 后缀索引拼接 ) | +| getFilePartAssist | 获取文件分片辅助类 | +| isFilePart | 是否符合文件分片条件 | +| fileSplit | 文件拆分 | +| fileSplitSave | 文件拆分并存储 | +| fileSplitSaves | 文件拆分并存储 | +| fileSplitDelete | 删除拆分文件 | +| fileSplitDeletes | 删除拆分文件 | +| fileSplitMergePaths | 分片合并 | +| fileSplitMergeFiles | 分片合并 | +| fileSplitMerge | 分片合并 | + + +## **`dev.utils.common.format`** + + +* **可变数组格式化 ->** [ArgsFormatter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/format/ArgsFormatter.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 ArgsFormatter | +| getStartSpecifier | 获取开始占位说明符 | +| getMiddleSpecifier | 获取中间占位说明符 | +| getEndSpecifier | 获取结尾占位说明符 | +| isThrowError | 是否抛出异常 | +| getDefaultValue | 获取格式化异常默认值 | +| format | 根据可变参数数量自动格式化 | +| formatByArray | 根据可变参数数量自动格式化 | + + +* **单位数组范围格式化 ->** [UnitSpanFormatter.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/format/UnitSpanFormatter.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 UnitSpanFormatter | +| getPrecision | 获取单位格式化精度 | +| isAppendZero | 是否自动补 0 | +| getDefaultValue | 获取格式化异常默认值 | +| format | 格式化 | +| formatBySpan | 计算指定单位倍数格式化 | + + +## **`dev.utils.common.random`** + + +* **随机概率采样算法 ->** [AliasMethod.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/random/AliasMethod.java) + +| 方法 | 注释 | +| :- | :- | +| next | 获取随机索引 ( 对应几率索引 ) | + + +## **`dev.utils.common.thread`** + + +* **线程池管理工具类 ->** [DevThreadManager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadManager.java) + +| 方法 | 注释 | +| :- | :- | +| getInstance | 获取 DevThreadManager 实例 | +| initConfig | 初始化配置信息 | +| putConfig | 添加配置信息 | +| removeConfig | 移除配置信息 | + + +* **线程池 ( 构建类 ) ->** [DevThreadPool.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadPool.java) + +| 方法 | 注释 | +| :- | :- | +| getThreads | 获取线程数 | +| getCalcThreads | 获取线程数 | +| execute | 加入到线程池任务队列 | +| shutdown | shutdown 会等待所有提交的任务执行完成, 不管是正在执行还是保存在任务队列中的已提交任务 | +| shutdownNow | shutdownNow 会尝试中断正在执行的任务 ( 其主要是中断一些指定方法如 sleep 方法 ) , 并且停止执行等待队列中提交的任务 | +| isShutdown | 判断线程池是否已关闭 ( isShutDown 当调用 shutdown() 方法后返回为 true ) | +| isTerminated | 若关闭后所有任务都已完成, 则返回 true | +| awaitTermination | 请求关闭、发生超时或者当前线程中断 | +| submit | 提交一个 Callable 任务用于执行 | +| invokeAll | 执行给定的任务 | +| invokeAny | 执行给定的任务 | +| schedule | 延迟执行 Runnable 命令 | +| scheduleWithFixedRate | 延迟并循环执行命令 | +| scheduleWithFixedDelay | 延迟并以固定休息时间循环执行命令 | + + +## **`dev.utils.common.validator`** + + +* **银行卡管理工具类 ->** [BankCheckUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/validator/BankCheckUtils.java) + +| 方法 | 注释 | +| :- | :- | +| checkBankCard | 校验银行卡卡号是否合法 | +| getBankCardCheckCode | 从不含校验位的银行卡卡号采用 Luhn 校验算法获取校验位 | +| getNameOfBank | 通过银行卡的 前六位确定 判断银行开户行及卡种 | + + +* **居民身份证工具类 ->** [IDCardUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/validator/IDCardUtils.java) + +| 方法 | 注释 | +| :- | :- | +| validateIdCard15 | 身份证校验规则, 验证 15 位身份编码是否合法 | +| validateIdCard18 | 身份证校验规则, 验证 18 位身份编码是否合法 | +| convert15CardTo18 | 将 15 位身份证号码转换为 18 位 | +| validateTWCard | 验证台湾身份证号码 | +| validateHKCard | 验证香港身份证号码 ( 部份特殊身份证无法检查 ) | +| validateIdCard10 | 判断 10 位数的身份证号, 是否合法 | +| validateCard | 验证身份证是否合法 | +| getAgeByIdCard | 根据身份编号获取年龄 | +| getBirthByIdCard | 根据身份编号获取生日 | +| getBirthdayByIdCard | 根据身份编号获取生日 | +| getYearByIdCard | 根据身份编号获取生日 ( 年份 ) | +| getMonthByIdCard | 根据身份编号获取生日 ( 月份 ) | +| getDateByIdCard | 根据身份编号获取生日 ( 天数 ) | +| getGenderByIdCard | 根据身份编号获取性别 | +| getProvinceByIdCard | 根据身份编号获取户籍省份 | +| getPowerSum | 将身份证的每位和对应位的加权因子相乘之后, 再获取和值 | +| getCheckCode18 | 将 POWER 和值与 11 取模获取余数进行校验码判断 | + + +* **校验工具类 ->** [ValidatorUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/validator/ValidatorUtils.java) + +| 方法 | 注释 | +| :- | :- | +| match | 通用匹配函数 | +| isNumber | 检验数字 | +| isNumberDecimal | 检验数字或包含小数点 | +| isLetter | 判断字符串是不是全是字母 | +| isContainNumber | 判断字符串是不是包含数字 | +| isNumberLetter | 判断字符串是不是只含字母和数字 | +| isSpec | 检验特殊符号 | +| isWx | 检验微信号 | +| isRealName | 检验真实姓名 | +| isNickName | 校验昵称 | +| isUserName | 校验用户名 | +| isPassword | 校验密码 | +| isEmail | 校验邮箱 | +| isUrl | 校验 URL | +| isIPAddress | 校验 IP 地址 | +| isChinese | 校验汉字 ( 无符号, 纯汉字 ) | +| isChineseAll | 判断字符串是不是全是中文 | +| isContainChinese | 判断字符串中包含中文、包括中文字符标点等 | + + +* **检验联系 ( 手机号码、座机 ) 工具类 ->** [ValiToPhoneUtils.java](https://github.com/afkT/DevUtils/blob/master/lib/DevJava/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java) + +| 方法 | 注释 | +| :- | :- | +| isPhoneSimple | 中国手机号码格式验证 ( 简单手机号码校验 ) | +| isPhone | 是否中国手机号码 | +| isPhoneToChinaMobile | 是否中国移动手机号码 | +| isPhoneToChinaUnicom | 是否中国联通手机号码 | +| isPhoneToChinaTelecom | 是否中国电信手机号码 | +| isPhoneToChinaBroadcast | 是否中国广电手机号码 | +| isPhoneToChinaVirtual | 是否中国虚拟运营商手机号码 | +| isPhoneToChinaHkMobile | 是否中国香港手机号码 | +| isPhoneCallNum | 验证电话号码的格式 | \ No newline at end of file diff --git a/lib/DevJava/build.gradle b/lib/DevJava/build.gradle new file mode 100644 index 0000000000..56ac5be436 --- /dev/null +++ b/lib/DevJava/build.gradle @@ -0,0 +1,20 @@ +apply from: rootProject.file(files.lib_java_gradle) + +version versions.dev_java_version + +compileJava { + sourceCompatibility JavaVersion.VERSION_1_8.toString() + targetCompatibility JavaVersion.VERSION_1_8.toString() +} + +// 是否发布版本 +def isPublishing = false + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_java) +//apply from: rootProject.file(files.sonatype_upload_java) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_java) +} \ No newline at end of file diff --git a/lib/DevJava/project.properties b/lib/DevJava/project.properties new file mode 100644 index 0000000000..a82eac77a0 --- /dev/null +++ b/lib/DevJava/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevJava +project.groupId=io.github.afkt +project.artifactId=DevJava +project.packaging=jar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevJava \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/DevFinal.java b/lib/DevJava/src/main/java/dev/utils/DevFinal.java new file mode 100644 index 0000000000..ee49580c4f --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/DevFinal.java @@ -0,0 +1,1355 @@ +package dev.utils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; + +/** + * detail: 常量类 + * @author Ttt + */ +public final class DevFinal { + + private DevFinal() { + } + + /** + * detail: 工具类内部返回值等常量定义 + * @author Ttt + */ + public static final class INNER { + + // 异常错误返回值 + public static final int ERROR_INT = -1; + } + + /** + * detail: 符号、标记字符串常量 + * @author Ttt + */ + public static final class SYMBOL { + + // 空格 字符串 + public static final String SPACE = " "; + // TAB 字符串 + public static final String TAB = "\t"; + // 回车 ( CR ) 字符串 + public static final String CR = "\r"; + // 换行 ( \n ) 字符串 ( single newline ('\n') character ) + public static final String NL = "\n"; + public static final char NL_CHAR = '\n'; + // 点 字符串 + public static final String POINT = "."; + // 横杠 字符串 + public static final String HYPHEN = "-"; + // 下划线 字符串 + public static final String UNDERSCORE = "_"; + // 冒号 字符串 + public static final String COLON = ":"; + // 逗号 字符串 + public static final String COMMA = ","; + // 顿号 字符串 + public static final String COMMA2 = "、"; + // 分号 字符串 + public static final String SEMICOLON = ";"; + // 百分号 字符串 + public static final String PERCENT = "%"; + // 反斜杠 字符串 + public static final String BACKSLASH = "\\"; + // 斜杠 字符串 + public static final String SLASH = "/"; + // 换行字符串 + public static final String NEW_LINE = System.getProperty("line.separator"); + // 换行字符串 ( 两行 ) + public static final String NEW_LINE_X2 = NEW_LINE + NEW_LINE; + // 换行字符串 ( 四行 ) + public static final String NEW_LINE_X4 = NEW_LINE_X2 + NEW_LINE_X2; + // null 对象字符串 + public static final String NULL = "null"; + // "" 对象字符串 + public static final String EMPTY = ""; + } + + /** + * detail: 编码格式字符串常量 + * @author Ttt + */ + public static final class ENCODE { + + public static final String UNICODE = "Unicode"; + public static final String US_ASCII = "US-ASCII"; + public static final String ISO_8859_1 = "ISO-8859-1"; + public static final String UTF_8 = "UTF-8"; + public static final String UTF_16BE = "UTF-16BE"; + public static final String UTF_16LE = "UTF-16LE"; + public static final String UTF_16 = "UTF-16"; + public static final String GBK = "GBK"; + public static final String GBK_2312 = "GBK-2312"; + } + + /** + * detail: 时间格式字符串常量 + * @author Ttt + */ + public static final class TIME { + + // 一分钟 60 秒 + public static final int MINUTE_S = 60; + // 一小时 60 * 60 秒 + public static final int HOUR_S = 3600; + // 一天 24 * 60 * 60 秒 + public static final int DAY_S = 86400; + // 周与秒的倍数 + public static final int WEEK_S = DAY_S * 7; + // 月与秒的倍数 + public static final int MONTH_S = DAY_S * 30; + // 年与秒的倍数 + public static final int YEAR_S = DAY_S * 365; + + // 秒与毫秒的倍数 + public static final long SECOND_MS = 1000; + // 分与毫秒的倍数 + public static final long MINUTE_MS = SECOND_MS * 60; + // 时与毫秒的倍数 + public static final long HOUR_MS = MINUTE_MS * 60; + // 天与毫秒的倍数 + public static final long DAY_MS = HOUR_MS * 24; + // 周与毫秒的倍数 + public static final long WEEK_MS = DAY_MS * 7; + // 月与毫秒的倍数 + public static final long MONTH_MS = DAY_MS * 30; + // 年与毫秒的倍数 + public static final long YEAR_MS = DAY_MS * 365; + + // ========== + // = 时间格式 = + // ========== + + public static final String yy = "yy"; + public static final String yyyy = "yyyy"; + public static final String MM = "MM"; + public static final String dd = "dd"; + public static final String HH = "HH"; + public static final String mm = "mm"; + public static final String ss = "ss"; + + public static final String yyMMdd = "yyMMdd"; + public static final String yyMMdd_POINT = "yy.MM.dd"; + public static final String yyMMdd_HYPHEN = "yy-MM-dd"; + public static final String yyMMdd_UNDERSCORE = "yy_MM_dd"; + + public static final String yyyyMMdd = "yyyyMMdd"; + public static final String yyyyMMdd_POINT = "yyyy.MM.dd"; + public static final String yyyyMMdd_HYPHEN = "yyyy-MM-dd"; + public static final String yyyyMMdd_UNDERSCORE = "yyyy_MM_dd"; + + public static final String yyyyMMddHHmm = "yyyyMMddHHmm"; + public static final String yyyyMMddHHmm_POINT = "yyyy.MM.dd HH:mm"; + public static final String yyyyMMddHHmm_HYPHEN = "yyyy-MM-dd HH:mm"; + public static final String yyyyMMddHHmm_UNDERSCORE = "yyyy_MM_dd HH:mm"; + + public static final String yyyyMMddHHmmss = "yyyyMMddHHmmss"; + public static final String yyyyMMddHHmmss_POINT = "yyyy.MM.dd HH:mm:ss"; + public static final String yyyyMMddHHmmss_HYPHEN = "yyyy-MM-dd HH:mm:ss"; + public static final String yyyyMMddHHmmss_UNDERSCORE = "yyyy_MM_dd HH:mm:ss"; + + public static final String MMdd = "MMdd"; + public static final String MMdd_POINT = "MM.dd"; + public static final String MMdd_HYPHEN = "MM-dd"; + public static final String MMdd_UNDERSCORE = "MM_dd"; + + public static final String HHmm = "HHmm"; + public static final String HHmm_COLON = "HH:mm"; + public static final String HHmm_POINT = "HH.mm"; + public static final String HHmm_HYPHEN = "HH-mm"; + public static final String HHmm_UNDERSCORE = "HH_mm"; + + public static final String HHmmss = "HHmmss"; + public static final String HHmmss_COLON = "HH:mm:ss"; + public static final String HHmmss_POINT = "HH.mm.ss"; + public static final String HHmmss_HYPHEN = "HH-mm-ss"; + public static final String HHmmss_UNDERSCORE = "HH_mm_ss"; + + public static final String SPECIAL_mmddHHmmyyyyss = "MMddHHmmyyyy.ss"; + + // ============= + // = 中文时间格式 = + // ============= + + public static final String ZH_yy = "yy年"; + public static final String ZH_yyyy = "yyyy年"; + public static final String ZH_MM = "MM月"; + public static final String ZH_dd = "dd日"; + public static final String ZH_HH = "HH时"; + public static final String ZH_mm = "mm分"; + public static final String ZH_ss = "ss秒"; + + public static final String ZH_yyMMdd = "yy年MM月dd日"; + public static final String ZH_yyyyMMdd = "yyyy年MM月dd日"; + public static final String ZH_yyyyMMddHHmm = "yyyy年MM月dd日HH时mm分"; + public static final String ZH_yyyyMMddHHmmss = "yyyy年MM月dd日HH时mm分ss秒"; + public static final String ZH_MMdd = "MM月dd日"; + public static final String ZH_HHmm = "HH时mm分"; + public static final String ZH_HHmmss = "HH时mm分ss秒"; + } + + /** + * detail: 时间格式字符串常量 + * @author Ttt + *
+     *     存储历史时间格式常量, 方便更新库过渡使用
+     *     并标记已弃用, 提醒使用 {@link TIME} 类
+     *     随时删除请及时替换
+     * 
+ */ + @Deprecated + public static final class TIME_DEPRECATED { + + // 一分钟 60 秒 + public static final int MINUTE_S = TIME.MINUTE_S; + // 一小时 60 * 60 秒 + public static final int HOUR_S = TIME.HOUR_S; + // 一天 24 * 60 * 60 秒 + public static final int DAY_S = TIME.DAY_S; + // 秒与毫秒的倍数 + public static final long SECOND = TIME.SECOND_MS; + // 分与毫秒的倍数 + public static final long MINUTE = TIME.MINUTE_MS; + // 时与毫秒的倍数 + public static final long HOUR = TIME.HOUR_MS; + // 天与毫秒的倍数 + public static final long DAY = TIME.DAY_MS; + // 周与毫秒的倍数 + public static final long WEEK = TIME.WEEK_MS; + // 月与毫秒的倍数 + public static final long MONTH = TIME.MONTH_MS; + // 年与毫秒的倍数 + public static final long YEAR = TIME.YEAR_MS; + + public static final String yyyy = TIME.yyyy; + public static final String yyMMdd = TIME.yyMMdd_HYPHEN; + public static final String yyMMdd2 = TIME.yyMMdd; + public static final String yyyyMMdd = TIME.yyyyMMdd_HYPHEN; + public static final String yyyyMMdd2 = TIME.yyyyMMdd; + public static final String yyyyMMdd3 = TIME.ZH_yyyyMMdd; + public static final String yyyyMMdd4 = TIME.yyyyMMdd_UNDERSCORE; + public static final String yyyyMMdd5 = TIME.yyyyMMdd_POINT; + + public static final String yyyyMMddHHmm = TIME.yyyyMMddHHmm_HYPHEN; + public static final String yyyyMMddHHmm2 = "yyyy年M月d日 HH:mm"; + public static final String yyyyMMddHHmm3 = TIME.yyyyMMddHHmm_POINT; + + public static final String yyyyMMddHHmmss = TIME.yyyyMMddHHmmss_HYPHEN; + public static final String yyyyMMddHHmmss2 = "yyyy年M月d日 HH:mm:ss"; + public static final String yyyyMMddHHmmss3 = "yyyyMMdd_HHmmss"; + public static final String yyyyMMddHHmmss4 = "yyyyMMdd.HHmmss"; + + public static final String MMdd = TIME.MMdd_HYPHEN; + public static final String MMdd2 = TIME.ZH_MMdd; + public static final String MMdd3 = TIME.MMdd; + public static final String yy = TIME.yy; + public static final String MM = TIME.MM; + public static final String dd = TIME.dd; + public static final String hh = "hh"; + public static final String HH = TIME.HH; + public static final String mm = TIME.mm; + public static final String HHmm = TIME.HHmm_COLON; + public static final String HHmm2 = TIME.HHmm; + public static final String HHmmss = TIME.HHmmss_COLON; + public static final String HHmmss2 = TIME.HHmmss; + public static final String hhmmMMDDyyyy = "hh:mm M月d日 yyyy"; + public static final String hhmmssMMDDyyyy = "hh:mm:ss M月d日 yyyy"; + public static final String mmddHHmmyyyyss = TIME.SPECIAL_mmddHHmmyyyyss; + } + + /** + * detail: String 类型常量 + * @author Ttt + */ + public static final class STR { + + // ======= + // = 通用 = + // ======= + + public static final String DEFAULT = "default"; + public static final String NONE = "none"; + public static final String OBJECT = "object"; + public static final String UNKNOWN = "unknown"; + + public static final String BUG = "bug"; + public static final String CHANNEL = "channel"; + public static final String CHARSET = "charset"; + public static final String CMD = "cmd"; + public static final String CODE = "code"; + public static final String COMPONENT = "component"; + public static final String CORE = "core"; + public static final String ENGINE = "engine"; + public static final String FETCH = "fetch"; + public static final String FLAG = "flag"; + public static final String FROM = "from"; + public static final String GROUP = "group"; + public static final String HASH = "hash"; + public static final String LIB = "lib"; + public static final String LIBS = "libs"; + public static final String LIMIT = "limit"; + public static final String MATCH = "match"; + public static final String MODEL = "model"; + public static final String MODULE = "module"; + public static final String OBTAIN = "obtain"; + public static final String OWNER = "owner"; + public static final String PLUGIN = "plugin"; + public static final String RESET = "reset"; + public static final String ROUTER = "router"; + public static final String SAFE = "safe"; + public static final String SAFETY = "safety"; + public static final String SHARE = "share"; + public static final String STANDARD = "standard"; + public static final String TARGET = "target"; + public static final String TEMPLATE = "template"; + public static final String TO = "to"; + + public static final String DECRYPT = "decrypt"; + public static final String ENCRYPT = "encrypt"; + public static final String PREFIX = "prefix"; + public static final String SUFFIX = "suffix"; + + public static final String BASE = "base"; + public static final String BEAN = "bean"; + public static final String VO = "vo"; + + public static final String HIGH = "high"; + public static final String LOW = "low"; + public static final String MAX = "max"; + public static final String MAX_LENGTH = "max_length"; + public static final String MAX_SIZE = "max_size"; + public static final String MIN = "min"; + public static final String MIN_LENGTH = "min_length"; + public static final String MIN_SIZE = "min_size"; + + public static final String EVENT = "event"; + public static final String LINK = "link"; + public static final String LISTENER = "listener"; + public static final String LOG = "log"; + public static final String MESSAGE = "message"; + public static final String REPORT = "report"; + public static final String TRACK = "track"; + + public static final String DATABASE = "database"; + public static final String DB = "db"; + + public static final String BLANK = "blank"; + public static final String GLOBAL = "global"; + public static final String HOME = "home"; + public static final String MAIN = "main"; + public static final String PRIVACY = "privacy"; + public static final String PROFILES = "profiles"; + public static final String SETTING = "setting"; + public static final String SETTINGS = "settings"; + + // ======= + // = 其他 = + // ======= + + public static final String ATTACH = "attach"; + public static final String AUTO = "auto"; + public static final String BALANCER = "balancer"; + public static final String BANK = "bank"; + public static final String BANNER = "banner"; + public static final String BLOCK = "block"; + public static final String BUCKET = "bucket"; + public static final String CONTENT = "content"; + public static final String EDIT = "edit"; + public static final String ELASTIC = "elastic"; + public static final String FACTORY = "factory"; + public static final String GOTO = "goto"; + public static final String IMPL = "impl"; + public static final String INDENT = "indent"; + public static final String INVENTORY = "inventory"; + public static final String KIND = "kind"; + public static final String LEVEL = "level"; + public static final String LOADER = "loader"; + public static final String MENU = "menu"; + public static final String MORE = "more"; + public static final String NUMBER = "number"; + public static final String OF = "of"; + public static final String ONLY = "only"; + public static final String OPERATE = "operate"; + public static final String OPTIONS = "options"; + public static final String ORIGINAL = "original"; + public static final String OTHER = "other"; + public static final String QUICK = "quick"; + public static final String RANGE = "range"; + public static final String REMARK = "remark"; + public static final String SANDBOX = "sandbox"; + public static final String SCORE = "score"; + public static final String SKIP = "skip"; + public static final String SMS = "sms"; + public static final String TIMING = "timing"; + public static final String TITLE = "title"; + public static final String TRANSFER = "transfer"; + public static final String WITH = "with"; + + // ========== + // = 信息相关 = + // ========== + + public static final String ACCOUNT = "account"; + public static final String ADDRESS = "address"; + public static final String AREA = "area"; + public static final String CITY = "city"; + public static final String EMAIL = "email"; + public static final String INFO = "info"; + public static final String LATITUDE = "latitude"; + public static final String LONGITUDE = "longitude"; + public static final String MOBILE = "mobile"; + public static final String NAME = "name"; + public static final String PASSWORD = "password"; + public static final String PHONE = "phone"; + public static final String PROVINCE = "province"; + public static final String REGION = "region"; + public static final String SPEC = "spec"; + public static final String USER = "user"; + public static final String USER_ID = "user_id"; + + public static final String ACCESS = "access"; + public static final String ID = "id"; + public static final String IDENTITY = "identity"; + public static final String TOKEN = "token"; + public static final String UNIQUE = "unique"; + public static final String UUID = "uuid"; + + // ======= + // = 媒体 = + // ======= + + public static final String AUDIO = "audio"; + public static final String IMAGE = "image"; + public static final String IMAGES = "images"; + public static final String MEDIA = "media"; + public static final String MEDIA_TYPE = "media_type"; + public static final String TEXT = "text"; + public static final String THUMBNAIL = "thumbnail"; + public static final String VIDEO = "video"; + public static final String WATERMARK = "watermark"; + + public static final String AAC = "aac"; + public static final String AVI = "avi"; + public static final String AVIF = "avif"; + public static final String BMP = "bmp"; + public static final String GIF = "gif"; + public static final String HEIF = "heif"; + public static final String ICON = "icon"; + public static final String JPEG = "jpeg"; + public static final String JPG = "jpg"; + public static final String JSON = "json"; + public static final String MP3 = "mp3"; + public static final String MP4 = "mp4"; + public static final String PNG = "png"; + public static final String TXT = "txt"; + public static final String WEBP = "webp"; + public static final String WEBP_LOSSLESS = "webp_lossless"; + public static final String WEBP_LOSSY = "webp_lossy"; + public static final String XML = "xml"; + + public static final String ALBUM = "album"; + public static final String BEGIN_TIME = "begin_time"; + public static final String COMPRESS = "compress"; + public static final String COVER = "cover"; + public static final String CROP = "crop"; + public static final String DURATION = "duration"; + public static final String END_TIME = "end_time"; + public static final String EXIF = "exif"; + public static final String EXIF_TAG = "exif_tag"; + public static final String MIME_TYPE = "mimetype"; + public static final String PLAY_TIME = "play_time"; + public static final String PREVIEW = "preview"; + public static final String QUALITY = "quality"; + public static final String RECORD = "record"; + public static final String TIME = "time"; + public static final String TIMESTAMP = "timestamp"; + public static final String VALID_TIME = "valid_time"; + + // ========== + // = 时间相关 = + // ========== + + public static final String CALENDAR = "calendar"; + + public static final String DAY = "day"; + public static final String HOUR = "hour"; + public static final String MILLI_SECOND = "milli_second"; + public static final String MINUTE = "minute"; + public static final String MONTH = "month"; + public static final String SECOND = "second"; + public static final String WEEK = "week"; + public static final String YEAR = "year"; + + // =============== + // = 状态、操作相关 = + // =============== + + public static final String BIND = "bind"; + public static final String UN_BINDER = "un_binder"; + + public static final String ACCEPT = "accept"; + public static final String ACTIVATED = "activated"; + public static final String ACTIVE = "active"; + public static final String AFTER = "after"; + public static final String ALLOW = "allow"; + public static final String ASYNC = "async"; + public static final String BEFORE = "before"; + public static final String CANCEL = "cancel"; + public static final String CHECKABLE = "checkable"; + public static final String CLOSE = "close"; + public static final String COMPLETE = "complete"; + public static final String CONFIRM = "confirm"; + public static final String CONNECT = "connect"; + public static final String CONNECTED = "connected"; + public static final String CONNECTING = "connecting"; + public static final String CREATE = "create"; + public static final String DELAY = "delay"; + public static final String DELETE = "delete"; + public static final String DELIMITER = "delimiter"; + public static final String DENIED = "denied"; + public static final String DESTROY = "destroy"; + public static final String DISABLED = "disabled"; + public static final String DISABLING = "disabling"; + public static final String DISCONNECT = "disconnect"; + public static final String DISCONNECTED = "disconnected"; + public static final String DISK = "disk"; + public static final String DOWNLOAD = "download"; + public static final String DOWNLOADS = "downloads"; + public static final String ENABLED = "enabled"; + public static final String ENABLING = "enabling"; + public static final String END = "end"; + public static final String EXECUTE = "execute"; + public static final String FAIL = "fail"; + public static final String FINISH = "finish"; + public static final String FOUND = "found"; + public static final String GRANTED = "granted"; + public static final String ING = "ing"; + public static final String INIT = "init"; + public static final String INSERT = "insert"; + public static final String INVALID = "invalid"; + public static final String LAUNCH = "launch"; + public static final String LOAD = "load"; + public static final String LOADING = "loading"; + public static final String MARKER = "marker"; + public static final String METADATA = "metadata"; + public static final String NEED = "need"; + public static final String NEXT = "next"; + public static final String NORMAL = "normal"; + public static final String NOT_FOUND = "not_found"; + public static final String NOW = "now"; + public static final String OPEN = "open"; + public static final String OVERWRITE = "overwrite"; + public static final String PAUSE = "pause"; + public static final String PERIOD = "period"; + public static final String PLAY = "play"; + public static final String POOL = "pool"; + public static final String RECYCLE = "recycle"; + public static final String REFRESH = "refresh"; + public static final String REQUEST = "request"; + public static final String REQUIRE = "require"; + public static final String RESPONSE = "response"; + public static final String RESTART = "restart"; + public static final String RESULT = "result"; + public static final String RESUME = "resume"; + public static final String SCOPE = "scope"; + public static final String SHUTDOWN = "shutdown"; + public static final String SLEEP = "sleep"; + public static final String START = "start"; + public static final String STATE = "state"; + public static final String STOP = "stop"; + public static final String SUBMIT = "submit"; + public static final String SUCCESS = "success"; + public static final String SUSPEND = "suspend"; + public static final String SUSPENDED = "suspended"; + public static final String SYNC = "sync"; + public static final String TERMINATED = "terminated"; + public static final String TRUNCATED = "truncated"; + public static final String UNCONNECT = "unconnect"; + public static final String UPLOAD = "upload"; + public static final String VALID = "valid"; + public static final String WAITING = "waiting"; + + // ========== + // = 平台相关 = + // ========== + + public static final String ANDROID = "android"; + public static final String H5 = "h5"; + public static final String IOS = "ios"; + public static final String MIN_IPROGRAM = "min_iprogram"; + public static final String PLATFORM = "platform"; + public static final String WEB = "web"; + + // =============== + // = UI、APP 相关 = + // =============== + + public static final String BOTTOM = "bottom"; + public static final String LEFT = "left"; + public static final String RIGHT = "right"; + public static final String TOP = "top"; + + public static final String BORDER_WIDTH = "border_width"; + public static final String DASH_WIDTH = "dash_width"; + public static final String HEIGHT = "height"; + public static final String MAX_WIDTH = "max_width"; + public static final String MIN_HEIGHT = "min_height"; + public static final String SCALE_HEIGHT = "scale_height"; + public static final String SCALE_WIDTH = "scale_width"; + public static final String SCREEN_HEIGHT = "screen_height"; + public static final String SCREEN_WIDTH = "screen_width"; + public static final String STROKE_WIDTH = "stroke_width"; + public static final String WIDTH = "width"; + public static final String WIDTH_HEIGHT = "width_height"; + + public static final String ANIMATION = "animation"; + public static final String BACKGROUND = "background"; + public static final String BOLD = "bold"; + public static final String CENTER = "center"; + public static final String CHECK = "check"; + public static final String CHECKBOX = "checkbox"; + public static final String CHECKED = "checked"; + public static final String CHOOSE = "choose"; + public static final String CHOOSE_MODE = "choose_mode"; + public static final String COLOR = "color"; + public static final String DOWN = "down"; + public static final String FOCUSED = "focused"; + public static final String HORIZONTAL = "horizontal"; + public static final String INFLATER = "inflater"; + public static final String LAYOUT = "layout"; + public static final String MEASURE = "measure"; + public static final String MOVE = "move"; + public static final String ORIENTATION = "orientation"; + public static final String OUTSIDE = "outside"; + public static final String PRESS = "press"; + public static final String PRESSED = "pressed"; + public static final String PROGRESS = "progress"; + public static final String SCALE = "scale"; + public static final String SCREEN = "screen"; + public static final String SCROLL = "scroll"; + public static final String SCROLLING = "scrolling"; + public static final String SELECT = "select"; + public static final String SELECTED = "selected"; + public static final String SLIDE = "slide"; + public static final String SLIDING = "sliding"; + public static final String UP = "up"; + public static final String VERTICAL = "vertical"; + public static final String WEIGHT = "weight"; + public static final String WIDGET = "widget"; + public static final String X = "x"; + public static final String Y = "y"; + + public static final String GRADIENT = "gradient"; + public static final String SHAPE = "shape"; + public static final String SOLID = "solid"; + public static final String STROKE = "stroke"; + + public static final String DEBUG = "debug"; + public static final String RELEASE = "release"; + public static final String UPGRADE = "upgrade"; + public static final String VERSION = "version"; + public static final String VERSION_CODE = "version_code"; + public static final String VERSION_NAME = "version_name"; + + public static final String ACCESSIBILITY = "accessibility"; + public static final String ACTION = "action"; + public static final String ACTIVITY = "activity"; + public static final String ADAPTER = "adapter"; + public static final String APPLICATION = "application"; + public static final String BROADCAST = "broadcast"; + public static final String BUNDLE = "bundle"; + public static final String CANVAS = "canvas"; + public static final String CATEGORY = "category"; + public static final String COMPOSE = "compose"; + public static final String CORNER = "corner"; + public static final String CURSOR = "cursor"; + public static final String DASH = "dash"; + public static final String DIALOG = "dialog"; + public static final String DIRECTION = "direction"; + public static final String DISCRETE = "discrete"; + public static final String DISTANCE = "distance"; + public static final String DRAW = "draw"; + public static final String EFFECT = "effect"; + public static final String EXTRA = "extra"; + public static final String EXTRAS = "extras"; + public static final String FILTER = "filter"; + public static final String FONT = "font"; + public static final String FONT_FAMILY = "font_family"; + public static final String FONT_STYLE = "font_style"; + public static final String FRAGMENT = "fragment"; + public static final String GALLERY = "gallery"; + public static final String GRAPHICS = "graphics"; + public static final String GRID = "grid"; + public static final String HANDLER = "handler"; + public static final String HOLDER = "holder"; + public static final String INSETS = "insets"; + public static final String INTENT = "intent"; + public static final String INTERPOLATOR = "interpolator"; + public static final String INVALIDATE = "invalidate"; + public static final String ITEM_COUNT = "item_count"; + public static final String ITEM_DECORATION = "item_decoration"; + public static final String LAUNCHER = "launcher"; + public static final String MATRIX = "matrix"; + public static final String NOTIFY = "notify"; + public static final String OFFSETS = "offsets"; + public static final String OUTLINE = "outline"; + public static final String PAINT = "paint"; + public static final String PATH_EFFECT = "path_effect"; + public static final String POINT = "point"; + public static final String POINTF = "pointf"; + public static final String POST_INVALIDATE = "post_invalidate"; + public static final String RECEIVE = "receive"; + public static final String RECT = "rect"; + public static final String RECTF = "rectf"; + public static final String RENDER = "render"; + public static final String SERVICE = "service"; + public static final String SHADER = "shader"; + public static final String SPACE = "space"; + public static final String SPAN = "span"; + public static final String SPAN_COUNT = "span_count"; + public static final String TOAST = "toast"; + public static final String TYPEFACE = "typeface"; + public static final String VIBRATE = "vibrate"; + public static final String VIEW = "view"; + + public static final String BINDING = "binding"; + public static final String LIFECYCLE = "lifecycle"; + public static final String LIVE_DATA = "live_data"; + public static final String PERMISSION = "permission"; + public static final String VIEW_MODEL = "view_model"; + + public static final String IMAGE_VIEW = "image_view"; + public static final String RECYCLE_VIEW = "recycle_view"; + public static final String SCROLL_VIEW = "scroll_view"; + public static final String TEXT_VIEW = "text_view"; + public static final String VIEW_GROUP = "view_group"; + public static final String VIEW_PAGER = "view_pager"; + + public static final String CHILD = "child"; + public static final String COMPILE = "compile"; + public static final String DEVICE = "device"; + public static final String ELEMENT = "element"; + public static final String ENVIRONMENT = "environment"; + public static final String MEMORY = "memory"; + public static final String PACKAGE = "package"; + public static final String PACKNAME = "packname"; + + public static final String ASSETS = "assets"; + public static final String ASSIST = "assist"; + public static final String CAMERA = "camera"; + public static final String CAPTURE = "capture"; + public static final String RAW = "raw"; + public static final String RES = "res"; + public static final String RICH_TEXT = "rich_text"; + public static final String SOURCE = "source"; + public static final String STYLE = "style"; + public static final String TRANSFORM = "transform"; + + public static final String BODY = "body"; + public static final String BROWSER = "browser"; + public static final String CACHE = "cache"; + public static final String CLIENT = "client"; + public static final String CONFIG = "config"; + public static final String COOKIE = "cookie"; + public static final String COPY = "copy"; + public static final String DNS = "dns"; + public static final String DOMAIN = "domain"; + public static final String EBS = "ebs"; + public static final String FOOTER = "footer"; + public static final String FOREGROUND = "foreground"; + public static final String HEAD = "head"; + public static final String HEADER = "header"; + public static final String HTTP = "http"; + public static final String HTTPS = "https"; + public static final String LANGUAGE = "language"; + public static final String OSS = "oss"; + public static final String PASTE = "paste"; + public static final String PATCH = "patch"; + public static final String POST = "post"; + public static final String SESSION = "session"; + public static final String SLB = "slb"; + public static final String SSL = "ssl"; + public static final String TIMEOUT = "timeout"; + public static final String TRACE = "trace"; + public static final String UNLINK = "unlink"; + public static final String URI = "uri"; + public static final String URL = "url"; + public static final String WRAPPED = "wrapped"; + public static final String WWW = "www"; + + // ========== + // = 数据相关 = + // ========== + + public static final String FALSE = "false"; + public static final String TRUE = "true"; + + public static final String ARRAY = "array"; + public static final String BOOLEAN = "boolean"; + public static final String BYTE = "byte"; + public static final String CHAR = "char"; + public static final String DATE = "date"; + public static final String DOUBLE = "double"; + public static final String FLOAT = "float"; + public static final String INT = "int"; + public static final String INTEGER = "integer"; + public static final String LIST = "list"; + public static final String LONG = "long"; + public static final String MAP = "map"; + public static final String STRING = "string"; + + public static final String BINARY = "binary"; + public static final String DEC = "dec"; + public static final String DECODE = "decode"; + public static final String ENCODE = "encode"; + public static final String HEX = "hex"; + public static final String OCT = "oct"; + + public static final String AES = "aes"; + public static final String BASE64 = "base64"; + public static final String CRC32 = "crc32"; + public static final String DES = "des"; + public static final String DES3 = "des3"; + public static final String ESCAPE = "escape"; + public static final String HMACMD5 = "hmacmd5"; + public static final String HMACSHA1 = "hmacsha1"; + public static final String HMACSHA224 = "hmacsha224"; + public static final String HMACSHA256 = "hmacsha256"; + public static final String HMACSHA384 = "hmacsha384"; + public static final String HMACSHA512 = "hmacsha512"; + public static final String MD2 = "md2"; + public static final String MD5 = "md5"; + public static final String RSA = "rsa"; + public static final String SHA1 = "sha1"; + public static final String SHA224 = "sha224"; + public static final String SHA256 = "sha256"; + public static final String SHA384 = "sha384"; + public static final String SHA512 = "sha512"; + public static final String TRIPLEDES = "tripledes"; + public static final String UNESCAPE = "unescape"; + public static final String XOR = "xor"; + + // ======== + // = 关键字 = + // ======== + + public static final String CATCH = "catch"; + public static final String CRASH = "crash"; + public static final String ERROR = "error"; + public static final String EXCEPTION = "exception"; + public static final String EXIT = "exit"; + public static final String THROWABLE = "throwable"; + public static final String TRY = "try"; + + public static final String ADD = "add"; + public static final String APPEND = "append"; + public static final String ARGS = "args"; + public static final String COUNT = "count"; + public static final String CURRENT = "current"; + public static final String CYCLE = "cycle"; + public static final String DIFF = "diff"; + public static final String FIND = "find"; + public static final String FIRST = "first"; + public static final String GET = "get"; + public static final String INDEX = "index"; + public static final String ITEM = "item"; + public static final String LAST = "last"; + public static final String LOOP = "loop"; + public static final String MIDDLE = "middle"; + public static final String PAGE = "page"; + public static final String PAGER = "pager"; + public static final String POSITION = "position"; + public static final String PUT = "put"; + public static final String QUERY = "query"; + public static final String REMOVE = "remove"; + public static final String SET = "set"; + public static final String SINGLE = "single"; + public static final String SIZE = "size"; + public static final String SORT = "sort"; + public static final String SUB = "sub"; + public static final String TAB = "tab"; + public static final String TAG = "tag"; + public static final String TAKE = "take"; + public static final String UPDATE = "update"; + + public static final String AGENT = "agent"; + public static final String ALIAS = "alias"; + public static final String CUSTOM = "custom"; + public static final String DATA = "data"; + public static final String DIR = "dir"; + public static final String DIRECTORY = "directory"; + public static final String FILE = "file"; + public static final String FOLD = "fold"; + public static final String IGNORE = "ignore"; + public static final String INPUT = "input"; + public static final String KEY = "key"; + public static final String KEYWORD = "keyword"; + public static final String MISSING = "missing"; + public static final String OUTPUT = "output"; + public static final String PATH = "path"; + public static final String PRINT = "print"; + public static final String READER = "reader"; + public static final String TASK = "task"; + public static final String TEMP = "temp"; + public static final String TYPE = "type"; + public static final String VALUE = "value"; + public static final String WRAPPER = "wrapper"; + public static final String WRITER = "writer"; + + public static final String CONTROL = "control"; + public static final String CONVERT = "convert"; + public static final String INSTANCE = "instance"; + public static final String MARGIN = "margin"; + public static final String PADDING = "padding"; + public static final String PARENT = "parent"; + public static final String PARSER = "parser"; + + public static final String MARGIN_BOTTOM = "margin_bottom"; + public static final String MARGIN_LEFT = "margin_left"; + public static final String MARGIN_RIGHT = "margin_right"; + public static final String MARGIN_TOP = "margin_top"; + public static final String PADDING_BOTTOM = "padding_bottom"; + public static final String PADDING_LEFT = "padding_left"; + public static final String PADDING_RIGHT = "padding_right"; + public static final String PADDING_TOP = "padding_top"; + + public static final String ATTRIBUTE = "attribute"; + public static final String BUFFER = "buffer"; + public static final String BUILD = "build"; + public static final String BUILDER = "builder"; + public static final String CLASS = "class"; + public static final String CLONE = "clone"; + public static final String CONST = "const"; + public static final String ENUM = "enum"; + public static final String FIELD = "field"; + public static final String FINAL = "final"; + public static final String FOR = "for"; + public static final String FUNCTION = "function"; + public static final String INNER = "inner"; + public static final String INTERFACE = "interface"; + public static final String INTERNAL = "internal"; + public static final String INVOKE = "invoke"; + public static final String JOB = "job"; + public static final String METHOD = "method"; + public static final String NEW = "new"; + public static final String NULL = "null"; + public static final String PARAM = "param"; + public static final String PARAMS = "params"; + public static final String PRIVATE = "private"; + public static final String PROTECTED = "protected"; + public static final String PUBLIC = "public"; + public static final String RETURN = "return"; + public static final String RUNNABLE = "runnable"; + public static final String SCHEDULE = "schedule"; + public static final String STATIC = "static"; + public static final String STREAM = "stream"; + public static final String THREAD = "thread"; + public static final String VAL = "val"; + public static final String VAR = "var"; + public static final String VOID = "void"; + public static final String WHILE = "while"; + } + + /** + * detail: Int 类型常量 + * @author Ttt + */ + public static final class INT { + + // ============ + // = 常用操作值 = + // ============ + + public static final int BASE = 102030; + // 默认状态 ( 暂未进行操作 ) + public static final int NORMAL = BASE + 1; + // 操作中 + public static final int ING = BASE + 2; + // 操作成功 + public static final int SUCCESS = BASE + 3; + // 操作失败 + public static final int FAIL = BASE + 4; + // 操作异常 + public static final int ERROR = BASE + 5; + // 开始操作 + public static final int START = BASE + 6; + // 重新开始操作 + public static final int RESTART = BASE + 7; + // 操作结束 + public static final int END = BASE + 8; + // 操作暂停 + public static final int PAUSE = BASE + 9; + // 操作恢复 ( 继续 ) + public static final int RESUME = BASE + 10; + // 操作停止 + public static final int STOP = BASE + 11; + // 操作取消 + public static final int CANCEL = BASE + 12; + // 创建 + public static final int CREATE = BASE + 13; + // 销毁 + public static final int DESTROY = BASE + 14; + // 回收 + public static final int RECYCLE = BASE + 15; + // 初始化 + public static final int INIT = BASE + 16; + // 已打开 + public static final int ENABLED = BASE + 17; + // 正在打开 + public static final int ENABLING = BASE + 18; + // 已关闭 + public static final int DISABLED = BASE + 19; + // 正在关闭 + public static final int DISABLING = BASE + 20; + // 连接成功 + public static final int CONNECTED = BASE + 21; + // 连接中 + public static final int CONNECTING = BASE + 22; + // 连接失败、断开 + public static final int DISCONNECTED = BASE + 23; + // 暂停、延迟 + public static final int SUSPENDED = BASE + 24; + // 未知 + public static final int UNKNOWN = BASE + 25; + // 新增 + public static final int INSERT = BASE + 26; + // 删除 + public static final int DELETE = BASE + 27; + // 更新 + public static final int UPDATE = BASE + 28; + // 查询 + public static final int SELECT = BASE + 29; + // 加密 + public static final int ENCRYPT = BASE + 30; + // 解密 + public static final int DECRYPT = BASE + 31; + // 重置 + public static final int RESET = BASE + 32; + // 关闭 + public static final int CLOSE = BASE + 33; + // 打开 + public static final int OPEN = BASE + 34; + // 退出 + public static final int EXIT = BASE + 35; + // 下一步 + public static final int NEXT = BASE + 36; + // 无任何 + public static final int NONE = BASE + 37; + // 结束 + public static final int FINISH = BASE + 38; + // 等待中 + public static final int WAITING = BASE + 39; + // 完成 + public static final int COMPLETE = BASE + 40; + + // =========== + // = Request = + // =========== + + // 默认状态 ( 暂未进行操作 ) + public static final int REQUEST_NORMAL = NORMAL; + // 请求中 + public static final int REQUEST_ING = ING; + // 请求成功 + public static final int REQUEST_SUCCESS = SUCCESS; + // 请求失败 + public static final int REQUEST_FAIL = FAIL; + // 请求异常 + public static final int REQUEST_ERROR = ERROR; + // 请求开始 + public static final int REQUEST_START = START; + // 重新请求 + public static final int REQUEST_RESTART = RESTART; + // 请求结束 + public static final int REQUEST_END = END; + // 请求暂停 + public static final int REQUEST_PAUSE = PAUSE; + // 请求恢复 ( 继续 ) + public static final int REQUEST_RESUME = RESUME; + // 请求停止 + public static final int REQUEST_STOP = STOP; + // 请求取消 + public static final int REQUEST_CANCEL = CANCEL; + } + + /** + * detail: 格式化字符串常量 + * @author Ttt + */ + public static final class FORMAT { + + public static final String S2 = "%s%s"; + public static final String S2_HYPHEN = "%s-%s"; + public static final String S2_UNDERSCORE = "%s_%s"; + public static final String S2_COMMA = "%s,%s"; + public static final String S2_COMMA_SPACE = "%s, %s"; + public static final String S2_COMMA2 = "%s、%s"; + public static final String S2_SPACE = "%s %s"; + public static final String S2_SPACE_SE = " %s %s "; + + public static final String S3 = "%s%s%s"; + public static final String S3_HYPHEN = "%s-%s-%s"; + public static final String S3_UNDERSCORE = "%s_%s_%s"; + public static final String S3_COMMA = "%s,%s,%s"; + public static final String S3_COMMA_SPACE = "%s, %s, %s"; + public static final String S3_COMMA2 = "%s、%s、%s"; + public static final String S3_SPACE = "%s %s %s"; + public static final String S3_SPACE_SE = " %s %s %s "; + + public static final String S4 = "%s%s%s%s"; + public static final String S4_HYPHEN = "%s-%s-%s-%s"; + public static final String S4_UNDERSCORE = "%s_%s_%s_%s"; + public static final String S4_COMMA = "%s,%s,%s,%s"; + public static final String S4_COMMA_SPACE = "%s, %s, %s, %s"; + public static final String S4_COMMA2 = "%s、%s、%s、%s"; + public static final String S4_SPACE = "%s %s %s %s"; + public static final String S4_SPACE_SE = " %s %s %s %s "; + + public static final String BRACE = "{ %s }"; + public static final String BRACE_SPACE = " { %s } "; + + public static final String BRACKET = "[ %s ]"; + public static final String BRACKET_SPACE = " [ %s ] "; + + public static final String PARENTHESES = "( %s )"; + public static final String PARENTHESES_SPACE = " ( %s ) "; + } + + /** + * detail: 正则表达式字符串常量 + * @author Ttt + */ + public static final class REGEX { + + // 正则表达式: 空格 + public static final String SPACE = "\\s"; + + // 正则表达式: 验证数字 + public static final String NUMBER = "^[0-9]*$"; + + // 正则表达式: 验证数字或包含小数点 + public static final String NUMBER_OR_DECIMAL = "^[0-9]*[.]?[0-9]*$"; + + // 正则表达式: 验证是否包含数字 + public static final String CONTAIN_NUMBER = ".*\\d+.*"; + + // 正则表达式: 验证是否数字或者字母 + public static final String NUMBER_OR_LETTER = "^[A-Za-z0-9]+$"; + + // 正则表达式: 验证是否全是字母 + public static final String LETTER = "^[A-Za-z]+$"; + + // 正则表达式: 不能输入特殊字符 ^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$ + public static final String SPECIAL = "^[\\u4E00-\\u9FA5A-Za-z0-9]+$"; + + // 正则表达式: 验证微信号 + public static final String WX = "^[a-zA-Z]{1}[-_a-zA-Z0-9]{5,19}+$"; + + // 正则表达式: 验证真实姓名 ^[\u4e00-\u9fa5]+(·[\u4e00-\u9fa5]+)*$ + public static final String REALNAME = "^[\\u4e00-\\u9fa5]+(•[\\u4e00-\\u9fa5]*)*$|^[\\u4e00-\\u9fa5]+(·[\\u4e00-\\u9fa5]*)*$"; + + // 正则表达式: 验证昵称 + public static final String NICKNAME = "^[\\u4E00-\\u9FA5A-Za-z0-9_]+$"; + + // 正则表达式: 验证用户名 ( 不包含中文和特殊字符 ) 如果用户名使用手机号码或邮箱 则结合手机号验证和邮箱验证 + public static final String USERNAME = "^[a-zA-Z]\\w{5,17}$"; + + // 正则表达式: 验证密码 ( 不包含特殊字符 ) + public static final String PASSWORD = "^[a-zA-Z0-9]{6,18}$"; + + // 正则表达式: 验证邮箱 + public static final String EMAIL = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; + + // 正则表达式: 验证 URL + public static final String URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?"; + + // 正则表达式: 验证 IP 地址 + public static final String IP_ADDRESS = "(2[5][0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"; + + // 正则表达式: 验证汉字 + public static final String CHINESE = "^[\u4e00-\u9fa5]+$"; + + // 正则表达式: 验证汉字 ( 含双角符号 ) + public static final String CHINESE_ALL = "^[\u0391-\uFFE5]+$"; + + // 正则表达式: 验证汉字 ( 含双角符号 ) + public static final String CHINESE_ALL2 = "[\u0391-\uFFE5]"; + } + + /** + * detail: 数组常量 + * @author Ttt + */ + public static final class ARRAY { + + // 用于建立十六进制字符的输出的小写字符数组 + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // 用于建立十六进制字符的输出的大写字符数组 + private static final char[] HEX_DIGITS_UPPER = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + // 0123456789 + private static final char[] NUMBERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 + }; + + // abcdefghijklmnopqrstuvwxyz + private static final char[] LOWER_CASE_LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 + }; + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] CAPITAL_LETTERS = { + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90 + }; + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] NUMBERS_AND_LETTERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // 生肖数组 + private static final String[] ZODIAC = { + "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊" + }; + + // 星座对应日期 + private static final String[] CONSTELLATION_DATE = { + "01.20-02.18", "02.19-03.20", "03.21-04.19", "04.20-05.20", "05.21-06.21", "06.22-07.22", + "07.23-08.22", "08.23-09.22", "09.23-10.23", "10.24-11.22", "11.23-12.21", "12.22-01.19" + }; + + // 星座数组 + private static final String[] CONSTELLATION = { + "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", + "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座" + }; + + // ========== + // = 对外公开 = + // ========== + + // 用于建立十六进制字符的输出的小写字符数组 + public static char[] HEX_DIGITS() { + return Arrays.copyOf(HEX_DIGITS, HEX_DIGITS.length); + } + + // 用于建立十六进制字符的输出的大写字符数组 + public static char[] HEX_DIGITS_UPPER() { + return Arrays.copyOf(HEX_DIGITS_UPPER, HEX_DIGITS_UPPER.length); + } + + // 0123456789 + public static char[] NUMBERS() { + return Arrays.copyOf(NUMBERS, NUMBERS.length); + } + + // abcdefghijklmnopqrstuvwxyz + public static char[] LOWER_CASE_LETTERS() { + return Arrays.copyOf(LOWER_CASE_LETTERS, LOWER_CASE_LETTERS.length); + } + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] CAPITAL_LETTERS() { + return Arrays.copyOf(CAPITAL_LETTERS, CAPITAL_LETTERS.length); + } + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] LETTERS() { + return Arrays.copyOf(LETTERS, LETTERS.length); + } + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + public static char[] NUMBERS_AND_LETTERS() { + return Arrays.copyOf(NUMBERS_AND_LETTERS, NUMBERS_AND_LETTERS.length); + } + + // 生肖数组 + public static String[] ZODIAC() { + return Arrays.copyOf(ZODIAC, ZODIAC.length); + } + + // 星座对应日期 + public static String[] CONSTELLATION_DATE() { + return Arrays.copyOf(CONSTELLATION_DATE, CONSTELLATION_DATE.length); + } + + // 星座数组 + public static String[] CONSTELLATION() { + return Arrays.copyOf(CONSTELLATION, CONSTELLATION.length); + } + } + + /** + * detail: 校验接受常量 + * @author Ttt + */ + public static final class ACCEPT { + + public static final String NUMBERS = "0123456789"; + + public static final String LOWER_CASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"; + + public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final String LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final String NUMBERS_AND_LETTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + } + + /** + * detail: 默认值常量 + * @author Ttt + */ + public static final class DEFAULT { + + public static final int ERROR_INT = -1; + public static final long ERROR_LONG = -1L; + public static final float ERROR_FLOAT = -1F; + public static final double ERROR_DOUBLE = -1D; + public static final boolean ERROR_BOOLEAN = false; + public static final short ERROR_SHORT = -1; + public static final char ERROR_CHAR = (char) -1; + public static final byte ERROR_BYTE = (byte) -1; + public static final BigDecimal ERROR_BIG_DECIMAL = BigDecimal.valueOf(-1L); + public static final BigInteger ERROR_BIG_INTEGER = BigInteger.valueOf(-1L); + public static final String ERROR_STRING = null; + + public static final int INT = 0; + public static final long LONG = 0L; + public static final float FLOAT = 0F; + public static final double DOUBLE = 0D; + public static final boolean BOOLEAN = false; + public static final short SHORT = 0; + public static final char CHAR = (char) 0; + public static final byte BYTE = (byte) 0; + public static final BigDecimal BIG_DECIMAL = BigDecimal.ZERO; + public static final BigInteger BIG_INTEGER = BigInteger.ZERO; + public static final String STRING = ""; + + public static final Object ENTITY = null; + public static final Object OBJECT = null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/JCLogUtils.java b/lib/DevJava/src/main/java/dev/utils/JCLogUtils.java new file mode 100644 index 0000000000..d3808c201b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/JCLogUtils.java @@ -0,0 +1,328 @@ +package dev.utils; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * detail: Java Common 日志打印工具类 ( 简化版 ) + * @author Ttt + *
+ *     项目内部使用 ( 主要打印 Java 日志 )
+ * 
+ */ +public final class JCLogUtils { + + private JCLogUtils() { + } + + // 是否打印日志 线上 (release) = false, 开发 (debug) = true + private static boolean JUDGE_PRINT_LOG = false; + // 判断是否控制台打印信息 + private static boolean JUDGE_CONTROL_PRINT_LOG = false; + // 默认 DEFAULT_TAG + private static final String DEFAULT_TAG = JCLogUtils.class.getSimpleName(); + + // ========== + // = 日志类型 = + // ========== + + // INFO 模式 + public static final int INFO = 0; + // DEBUG 模式 + public static final int DEBUG = 1; + // ERROR 模式 + public static final int ERROR = 2; + + /** + * 判断是否打印日志 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrintLog() { + return JUDGE_PRINT_LOG; + } + + /** + * 设置是否打印日志 + * @param judgePrintLog 是否允许打印日志 + */ + public static void setPrintLog(final boolean judgePrintLog) { + JUDGE_PRINT_LOG = judgePrintLog; + } + + /** + * 设置是否在控制台打印日志 + * @param judgeControlPrintLog 是否允许控制台打印日志 + */ + public static void setControlPrintLog(final boolean judgeControlPrintLog) { + JUDGE_CONTROL_PRINT_LOG = judgeControlPrintLog; + } + + /** + * 判断字符串是否为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + private static boolean isEmpty(final String str) { + return (str == null || str.length() == 0); + } + + // = + + /** + * 最终打印日志方法 ( 全部调用此方法 ) + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + private static void printLog( + final int logType, + final String tag, + final String message + ) { + if (JCLogUtils.sPrint != null) { + JCLogUtils.sPrint.printLog(logType, tag, message); + } + + if (JUDGE_CONTROL_PRINT_LOG) { + // 打印信息 + if (isEmpty(tag)) { + System.out.println(message); + } else { + System.out.println(tag + " : " + message); + } + } + } + + /** + * 处理信息 + * @param message 日志信息 + * @param args 占位符替换 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String createMessage( + final String message, + final Object... args + ) { + String result; + try { + if (message != null) { + if (args == null) { + // 动态参数为 null + result = "params is null"; + } else { + // 格式化字符串 + result = (args.length == 0 ? message : String.format(message, args)); + } + } else { + // 打印内容为 null + result = "message is null"; + } + } catch (Exception e) { + // 出现异常 + result = e.toString(); + } + return result; + } + + /** + * 拼接错误信息 + * @param throwable 异常 + * @param message 需要打印的消息 + * @param args 动态参数 + * @return 处理 ( 格式化 ) 后准备打印的日志信息 + */ + private static String concatErrorMessage( + final Throwable throwable, + final String message, + final Object... args + ) { + String result; + try { + if (throwable != null) { + if (message != null) { + result = createMessage(message, args) + " : " + throwable.toString(); + } else { + result = throwable.toString(); + } + } else { + result = createMessage(message, args); + } + } catch (Exception e) { + result = e.toString(); + } + return result; + } + + // ============================= + // = 对外公开方法 ( 使用默认 TAG ) = + // ============================= + + public static void d( + final String message, + final Object... args + ) { + dTag(DEFAULT_TAG, message, args); + } + + public static void e(final Throwable throwable) { + eTag(DEFAULT_TAG, throwable); + } + + public static void e( + final String message, + final Object... args + ) { + e(null, message, args); + } + + public static void e( + final Throwable throwable, + final String message, + final Object... args + ) { + eTag(DEFAULT_TAG, throwable, message, args); + } + + public static void i( + final String message, + final Object... args + ) { + iTag(DEFAULT_TAG, message, args); + } + + public static void xml(final String xml) { + xmlTag(DEFAULT_TAG, xml); + } + + // ============================ + // = 对外公开方法 ( 日志打印方法 ) = + // ============================ + + public static void dTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(DEBUG, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, createMessage(message, args)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, concatErrorMessage(throwable, null)); + } + } + + public static void eTag( + final String tag, + final Throwable throwable, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(ERROR, tag, concatErrorMessage(throwable, message, args)); + } + } + + public static void iTag( + final String tag, + final String message, + final Object... args + ) { + if (JUDGE_PRINT_LOG) { + printLog(INFO, tag, createMessage(message, args)); + } + } + + public static void xmlTag( + final String tag, + final String xml + ) { + if (JUDGE_PRINT_LOG) { + // 判断传入 XML 格式信息是否为 null + if (isEmpty(xml)) { + printLog(ERROR, tag, "Empty/Null xml content"); + return; + } + try { + Source xmlInput = new StreamSource(new StringReader(xml)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(xmlInput, xmlOutput); + // 获取打印消息 + String message = xmlOutput.getWriter().toString().replaceFirst(">", ">\n"); + // 打印信息 + printLog(DEBUG, tag, message); + } catch (Exception e) { + String errorInfo; + Throwable throwable = e.getCause(); + if (throwable != null) { + errorInfo = throwable.toString(); + } else { + try { + errorInfo = e.toString(); + } catch (Exception e1) { + errorInfo = e1.toString(); + } + } + printLog(ERROR, tag, errorInfo + DevFinal.SYMBOL.NEW_LINE + xml); + } + } + } + + // ========== + // = 通知输出 = + // ========== + + private static Print sPrint; + + /** + * 设置日志输出接口 + * @param print 日志输出接口 + */ + public static void setPrint(final Print print) { + JCLogUtils.sPrint = print; + } + + /** + * detail: 日志输出接口 + * @author Ttt + */ + public interface Print { + + /** + * 日志打印 + * @param logType 日志类型 + * @param tag 打印 TAG + * @param message 日志信息 + */ + void printLog( + int logType, + String tag, + String message + ); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ArrayUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ArrayUtils.java new file mode 100644 index 0000000000..79d9e19ae5 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ArrayUtils.java @@ -0,0 +1,4083 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Array 数组工具类 + * @author Ttt + *
+ *     // 升序
+ *     Arrays.sort(arrays);
+ *     // 降序 ( 只能对对象数组降序 )
+ *     Arrays.sort(arrays, Collections.reverseOrder());
+ * 
+ */ +public final class ArrayUtils { + + private ArrayUtils() { + } + + // 日志 TAG + private static final String TAG = ArrayUtils.class.getSimpleName(); + + // ========= + // = Array = + // ========= + + /** + * 判断数组是否为 null + * @param objects object[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object[] objects) { + return objects == null || objects.length == 0; + } + + /** + * 判断数组是否为 null + * @param ints int[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final int[] ints) { + return ints == null || ints.length == 0; + } + + /** + * 判断数组是否为 null + * @param bytes byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final byte[] bytes) { + return bytes == null || bytes.length == 0; + } + + /** + * 判断数组是否为 null + * @param chars char[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final char[] chars) { + return chars == null || chars.length == 0; + } + + /** + * 判断数组是否为 null + * @param shorts short[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final short[] shorts) { + return shorts == null || shorts.length == 0; + } + + /** + * 判断数组是否为 null + * @param longs long[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final long[] longs) { + return longs == null || longs.length == 0; + } + + /** + * 判断数组是否为 null + * @param floats float[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final float[] floats) { + return floats == null || floats.length == 0; + } + + /** + * 判断数组是否为 null + * @param doubles double[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final double[] doubles) { + return doubles == null || doubles.length == 0; + } + + /** + * 判断数组是否为 null + * @param booleans boolean[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final boolean[] booleans) { + return booleans == null || booleans.length == 0; + } + + /** + * 判断数组是否为 null + * @param object Array[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object object) { + return object == null || length(object) == 0; + } + + // = + + /** + * 判断数组是否不为 null + * @param objects object[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object[] objects) { + return objects != null && objects.length != 0; + } + + /** + * 判断数组是否不为 null + * @param ints int[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final int[] ints) { + return ints != null && ints.length != 0; + } + + /** + * 判断数组是否不为 null + * @param bytes byte[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final byte[] bytes) { + return bytes != null && bytes.length != 0; + } + + /** + * 判断数组是否不为 null + * @param chars char[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final char[] chars) { + return chars != null && chars.length != 0; + } + + /** + * 判断数组是否不为 null + * @param shorts short[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final short[] shorts) { + return shorts != null && shorts.length != 0; + } + + /** + * 判断数组是否不为 null + * @param longs long[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final long[] longs) { + return longs != null && longs.length != 0; + } + + /** + * 判断数组是否不为 null + * @param floats float[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final float[] floats) { + return floats != null && floats.length != 0; + } + + /** + * 判断数组是否不为 null + * @param doubles double[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final double[] doubles) { + return doubles != null && doubles.length != 0; + } + + /** + * 判断数组是否不为 null + * @param booleans boolean[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final boolean[] booleans) { + return booleans != null && booleans.length != 0; + } + + /** + * 判断数组是否不为 null + * @param object Array[] + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object object) { + return object != null && length(object) != 0; + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取数组长度 + * @param objects object[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final Object[] objects) { + return length(objects, 0); + } + + /** + * 获取数组长度 + * @param ints int[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final int[] ints) { + return length(ints, 0); + } + + /** + * 获取数组长度 + * @param bytes byte[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final byte[] bytes) { + return length(bytes, 0); + } + + /** + * 获取数组长度 + * @param chars char[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final char[] chars) { + return length(chars, 0); + } + + /** + * 获取数组长度 + * @param shorts short[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final short[] shorts) { + return length(shorts, 0); + } + + /** + * 获取数组长度 + * @param longs long[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final long[] longs) { + return length(longs, 0); + } + + /** + * 获取数组长度 + * @param floats float[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final float[] floats) { + return length(floats, 0); + } + + /** + * 获取数组长度 + * @param doubles double[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final double[] doubles) { + return length(doubles, 0); + } + + /** + * 获取数组长度 + * @param booleans boolean[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final boolean[] booleans) { + return length(booleans, 0); + } + + /** + * 获取数组长度 + * @param object Array[] + * @return 如果数据为 null, 则返回默认长度, 如果不为 null, 则返回 array[].length + */ + public static int length(final Object object) { + return length(object, 0); + } + + // = + + /** + * 获取数组长度 + * @param objects object[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final Object[] objects, + final int defaultLength + ) { + return objects != null ? objects.length : defaultLength; + } + + /** + * 获取数组长度 + * @param ints int[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final int[] ints, + final int defaultLength + ) { + return ints != null ? ints.length : defaultLength; + } + + /** + * 获取数组长度 + * @param bytes byte[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final byte[] bytes, + final int defaultLength + ) { + return bytes != null ? bytes.length : defaultLength; + } + + /** + * 获取数组长度 + * @param chars char[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final char[] chars, + final int defaultLength + ) { + return chars != null ? chars.length : defaultLength; + } + + /** + * 获取数组长度 + * @param shorts short[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final short[] shorts, + final int defaultLength + ) { + return shorts != null ? shorts.length : defaultLength; + } + + /** + * 获取数组长度 + * @param longs long[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final long[] longs, + final int defaultLength + ) { + return longs != null ? longs.length : defaultLength; + } + + /** + * 获取数组长度 + * @param floats float[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final float[] floats, + final int defaultLength + ) { + return floats != null ? floats.length : defaultLength; + } + + /** + * 获取数组长度 + * @param doubles double[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final double[] doubles, + final int defaultLength + ) { + return doubles != null ? doubles.length : defaultLength; + } + + /** + * 获取数组长度 + * @param booleans boolean[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final boolean[] booleans, + final int defaultLength + ) { + return booleans != null ? booleans.length : defaultLength; + } + + /** + * 获取数组长度 + * @param object Array[] + * @param defaultLength 数组为 null 时, 返回的长度 + * @return 如果数据为 null, 则返回 defaultLength, 如果不为 null, 则返回 array[].length + */ + public static int length( + final Object object, + final int defaultLength + ) { + if (object != null) { + Class clazz = object.getClass(); + // 判断是否数组类型 + if (clazz.isArray()) { + try { + // = 基本数据类型 = + if (clazz.isAssignableFrom(int[].class)) { + return ((int[]) object).length; + } else if (clazz.isAssignableFrom(boolean[].class)) { + return ((boolean[]) object).length; + } else if (clazz.isAssignableFrom(long[].class)) { + return ((long[]) object).length; + } else if (clazz.isAssignableFrom(double[].class)) { + return ((double[]) object).length; + } else if (clazz.isAssignableFrom(float[].class)) { + return ((float[]) object).length; + } else if (clazz.isAssignableFrom(byte[].class)) { + return ((byte[]) object).length; + } else if (clazz.isAssignableFrom(char[].class)) { + return ((char[]) object).length; + } else if (clazz.isAssignableFrom(short[].class)) { + return ((short[]) object).length; + } + return ((Object[]) object).length; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "length"); + } + } + } + return defaultLength; + } + + // = + + /** + * 判断数组长度是否等于期望长度 + * @param objects object[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Object[] objects, + final int length + ) { + return objects != null && objects.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param ints int[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final int[] ints, + final int length + ) { + return ints != null && ints.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param bytes byte[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final byte[] bytes, + final int length + ) { + return bytes != null && bytes.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param chars char[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final char[] chars, + final int length + ) { + return chars != null && chars.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param shorts short[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final short[] shorts, + final int length + ) { + return shorts != null && shorts.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param longs long[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final long[] longs, + final int length + ) { + return longs != null && longs.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param floats float[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final float[] floats, + final int length + ) { + return floats != null && floats.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param doubles double[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final double[] doubles, + final int length + ) { + return doubles != null && doubles.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param booleans boolean[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final boolean[] booleans, + final int length + ) { + return booleans != null && booleans.length == length; + } + + /** + * 判断数组长度是否等于期望长度 + * @param object Array[] + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Object object, + final int length + ) { + return object != null && length(object) == length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取数组长度总和 + * @param objects Array[] + * @return 数组长度总和 + */ + public static int getCount(final Object... objects) { + if (objects == null) return 0; + int count = 0; + for (Object object : objects) { + count += length(object); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T getByArray( + final Object array, + final int pos + ) { + return getByArray(array, pos, null); + } + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param defaultValue 默认值 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T getByArray( + final Object array, + final int pos, + final T defaultValue + ) { + if (array == null || pos < 0) return defaultValue; + try { + return (T) Array.get(array, pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getByArray"); + } + return defaultValue; + } + + // = + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param 泛型 + * @return 数组指定索引的值 + */ + public static T get( + final T[] array, + final int pos + ) { + return get(array, pos, null); + } + + /** + * 获取数组对应索引数据 + * @param ints int[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static int get( + final int[] ints, + final int pos + ) { + return get(ints, pos, DevFinal.DEFAULT.ERROR_INT); + } + + /** + * 获取数组对应索引数据 + * @param bytes byte[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static byte get( + final byte[] bytes, + final int pos + ) { + return get(bytes, pos, DevFinal.DEFAULT.ERROR_BYTE); + } + + /** + * 获取数组对应索引数据 + * @param chars char[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static char get( + final char[] chars, + final int pos + ) { + return get(chars, pos, DevFinal.DEFAULT.ERROR_CHAR); + } + + /** + * 获取数组对应索引数据 + * @param shorts short[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static short get( + final short[] shorts, + final int pos + ) { + return get(shorts, pos, DevFinal.DEFAULT.ERROR_SHORT); + } + + /** + * 获取数组对应索引数据 + * @param longs long[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static long get( + final long[] longs, + final int pos + ) { + return get(longs, pos, DevFinal.DEFAULT.ERROR_LONG); + } + + /** + * 获取数组对应索引数据 + * @param floats float[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static float get( + final float[] floats, + final int pos + ) { + return get(floats, pos, DevFinal.DEFAULT.ERROR_FLOAT); + } + + /** + * 获取数组对应索引数据 + * @param doubles double[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static double get( + final double[] doubles, + final int pos + ) { + return get(doubles, pos, DevFinal.DEFAULT.ERROR_DOUBLE); + } + + /** + * 获取数组对应索引数据 + * @param booleans boolean[] + * @param pos 索引 + * @return 数组指定索引的值 + */ + public static boolean get( + final boolean[] booleans, + final int pos + ) { + return get(booleans, pos, DevFinal.DEFAULT.ERROR_BOOLEAN); + } + + // = + + /** + * 获取数组对应索引数据 + * @param array 数组 + * @param pos 索引 + * @param defaultValue 默认值 + * @param 泛型 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static T get( + final T[] array, + final int pos, + final T defaultValue + ) { + if (array == null || pos < 0) return defaultValue; + try { + return array[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param ints int[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static int get( + final int[] ints, + final int pos, + final int defaultValue + ) { + if (ints == null || pos < 0) return defaultValue; + try { + return ints[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param bytes byte[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static byte get( + final byte[] bytes, + final int pos, + final byte defaultValue + ) { + if (bytes == null || pos < 0) return defaultValue; + try { + return bytes[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param chars char[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static char get( + final char[] chars, + final int pos, + final char defaultValue + ) { + if (chars == null || pos < 0) return defaultValue; + try { + return chars[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param shorts short[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static short get( + final short[] shorts, + final int pos, + final short defaultValue + ) { + if (shorts == null || pos < 0) return defaultValue; + try { + return shorts[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param longs long[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static long get( + final long[] longs, + final int pos, + final long defaultValue + ) { + if (longs == null || pos < 0) return defaultValue; + try { + return longs[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param floats float[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static float get( + final float[] floats, + final int pos, + final float defaultValue + ) { + if (floats == null || pos < 0) return defaultValue; + try { + return floats[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param doubles double[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static double get( + final double[] doubles, + final int pos, + final double defaultValue + ) { + if (doubles == null || pos < 0) return defaultValue; + try { + return doubles[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + /** + * 获取数组对应索引数据 + * @param booleans boolean[] + * @param pos 索引 + * @param defaultValue 默认值 + * @return 数组指定索引的值, 如果获取失败, 则返回 defaultValue + */ + public static boolean get( + final boolean[] booleans, + final int pos, + final boolean defaultValue + ) { + if (booleans == null || pos < 0) return defaultValue; + try { + return booleans[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + return defaultValue; + } + + // = + + /** + * 获取数组第一条数据 + * @param array 数组 + * @param 泛型 + * @return 数组索引为 0 的值 + */ + public static T getFirst(final T[] array) { + return get(array, 0); + } + + /** + * 获取数组第一条数据 + * @param ints int[] + * @return 数组索引为 0 的值 + */ + public static int getFirst(final int[] ints) { + return get(ints, 0); + } + + /** + * 获取数组第一条数据 + * @param bytes byte[] + * @return 数组索引为 0 的值 + */ + public static byte getFirst(final byte[] bytes) { + return get(bytes, 0); + } + + /** + * 获取数组第一条数据 + * @param chars char[] + * @return 数组索引为 0 的值 + */ + public static char getFirst(final char[] chars) { + return get(chars, 0); + } + + /** + * 获取数组第一条数据 + * @param shorts short[] + * @return 数组索引为 0 的值 + */ + public static short getFirst(final short[] shorts) { + return get(shorts, 0); + } + + /** + * 获取数组第一条数据 + * @param longs long[] + * @return 数组索引为 0 的值 + */ + public static long getFirst(final long[] longs) { + return get(longs, 0); + } + + /** + * 获取数组第一条数据 + * @param floats float[] + * @return 数组索引为 0 的值 + */ + public static float getFirst(final float[] floats) { + return get(floats, 0); + } + + /** + * 获取数组第一条数据 + * @param doubles double[] + * @return 数组索引为 0 的值 + */ + public static double getFirst(final double[] doubles) { + return get(doubles, 0); + } + + /** + * 获取数组第一条数据 + * @param booleans boolean[] + * @return 数组索引为 0 的值 + */ + public static boolean getFirst(final boolean[] booleans) { + return get(booleans, 0); + } + + // = + + /** + * 获取数组最后一条数据 + * @param array 数组 + * @param 泛型 + * @return 数组索引 length - 1 的值 + */ + public static T getLast(final T[] array) { + return get(array, length(array) - 1); + } + + /** + * 获取数组最后一条数据 + * @param ints int[] + * @return 数组索引 length - 1 的值 + */ + public static int getLast(final int[] ints) { + return get(ints, length(ints) - 1); + } + + /** + * 获取数组最后一条数据 + * @param bytes byte[] + * @return 数组索引 length - 1 的值 + */ + public static byte getLast(final byte[] bytes) { + return get(bytes, length(bytes) - 1); + } + + /** + * 获取数组最后一条数据 + * @param chars char[] + * @return 数组索引 length - 1 的值 + */ + public static char getLast(final char[] chars) { + return get(chars, length(chars) - 1); + } + + /** + * 获取数组最后一条数据 + * @param shorts short[] + * @return 数组索引 length - 1 的值 + */ + public static short getLast(final short[] shorts) { + return get(shorts, length(shorts) - 1); + } + + /** + * 获取数组最后一条数据 + * @param longs long[] + * @return 数组索引 length - 1 的值 + */ + public static long getLast(final long[] longs) { + return get(longs, length(longs) - 1); + } + + /** + * 获取数组最后一条数据 + * @param floats float[] + * @return 数组索引 length - 1 的值 + */ + public static float getLast(final float[] floats) { + return get(floats, length(floats) - 1); + } + + /** + * 获取数组最后一条数据 + * @param doubles double[] + * @return 数组索引 length - 1 的值 + */ + public static double getLast(final double[] doubles) { + return get(doubles, length(doubles) - 1); + } + + /** + * 获取数组最后一条数据 + * @param booleans boolean[] + * @return 数组索引 length - 1 的值 + */ + public static boolean getLast(final boolean[] booleans) { + return get(booleans, length(booleans) - 1); + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (array != null) { + if (notNull && value == null) { + return null; + } + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + T t = array[i]; + // 判断是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (array != null) { + if (notNull && value == null) { + return -1; + } + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + T t = array[i]; + // 判断是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value + ) { + return get(array, value, 0, false, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number + ) { + return get(array, value, number, false, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final boolean notNull + ) { + return get(array, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final T[] array, + final T value, + final int number, + final boolean notNull + ) { + return get(array, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNotNull( + final T[] array, + final T value + ) { + return get(array, value, 0, true, 0); + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNotNull( + final T[] array, + final T value, + final int number + ) { + return get(array, value, number, true, 0); + } + + // = + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value + ) { + return getPosition(array, value, 0, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number + ) { + return getPosition(array, value, number, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final boolean notNull + ) { + return getPosition(array, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final T[] array, + final T value, + final int number, + final boolean notNull + ) { + return getPosition(array, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final T[] array, + final T value + ) { + return getPosition(array, value, 0, true, 0); + } + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final T[] array, + final T value, + final int number + ) { + return getPosition(array, value, number, true, 0); + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static int get( + final int[] array, + final int value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + int valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final int[] array, + final int value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + int valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static byte get( + final byte[] array, + final byte value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + byte valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (byte) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final byte[] array, + final byte value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + byte valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static char get( + final char[] array, + final char value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + char valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (char) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final char[] array, + final char value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + char valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static short get( + final short[] array, + final short value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + short valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (short) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final short[] array, + final short value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + short valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static long get( + final long[] array, + final long value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + long valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1L; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final long[] array, + final long value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + long valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static float get( + final float[] array, + final float value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + float valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return (float) -1; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final float[] array, + final float value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + float valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static double get( + final double[] array, + final double value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + double valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return -1D; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final double[] array, + final double value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + double valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定值获取 value 所在位置 + 偏移量的值 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应索引的值 + */ + public static boolean get( + final boolean[] array, + final boolean value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + boolean valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return array[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return false; + } + + /** + * 根据指定值获取 value 所在位置 + 偏移量的索引 + * @param array 数组 + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param offset 偏移量 + * @return 对应的索引 + */ + public static int getPosition( + final boolean[] array, + final boolean value, + final int number, + final int offset + ) { + if (array != null) { + try { + // 保存当前临时次数 + int temp = number; + // 进行循环判断 + for (int i = 0, len = array.length; i < len; i++) { + boolean valueI = array[i]; + // 判断是否一样 + if (valueI == value) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // ========== + // = 转换处理 = + // ========== + + /** + * int[] 转换 Integer[] + * @param ints int[] + * @return {@link Integer[]} + */ + public static Integer[] intsToIntegers(final int[] ints) { + if (ints != null) { + int len = ints.length; + // 创建数组 + Integer[] array = new Integer[len]; + for (int i = 0; i < len; i++) { + array[i] = ints[i]; + } + return array; + } + return null; + } + + /** + * byte[] 转换 Byte[] + * @param bytes byte[] + * @return {@link Byte[]} + */ + public static Byte[] bytesToBytes(final byte[] bytes) { + if (bytes != null) { + int len = bytes.length; + // 创建数组 + Byte[] array = new Byte[len]; + for (int i = 0; i < len; i++) { + array[i] = bytes[i]; + } + return array; + } + return null; + } + + /** + * char[] 转换 Character[] + * @param chars char[] + * @return {@link Character[]} + */ + public static Character[] charsToCharacters(final char[] chars) { + if (chars != null) { + int len = chars.length; + // 创建数组 + Character[] array = new Character[len]; + for (int i = 0; i < len; i++) { + array[i] = chars[i]; + } + return array; + } + return null; + } + + /** + * short[] 转换 Short[] + * @param shorts short[] + * @return {@link Short[]} + */ + public static Short[] shortsToShorts(final short[] shorts) { + if (shorts != null) { + int len = shorts.length; + // 创建数组 + Short[] array = new Short[len]; + for (int i = 0; i < len; i++) { + array[i] = shorts[i]; + } + return array; + } + return null; + } + + /** + * long[] 转换 Long[] + * @param longs long[] + * @return {@link Long[]} + */ + public static Long[] longsToLongs(final long[] longs) { + if (longs != null) { + int len = longs.length; + // 创建数组 + Long[] array = new Long[len]; + for (int i = 0; i < len; i++) { + array[i] = longs[i]; + } + return array; + } + return null; + } + + /** + * float[] 转换 Float[] + * @param floats float[] + * @return {@link Float[]} + */ + public static Float[] floatsToFloats(final float[] floats) { + if (floats != null) { + int len = floats.length; + // 创建数组 + Float[] array = new Float[len]; + for (int i = 0; i < len; i++) { + array[i] = floats[i]; + } + return array; + } + return null; + } + + /** + * double[] 转换 Double[] + * @param doubles double[] + * @return {@link Double[]} + */ + public static Double[] doublesToDoubles(final double[] doubles) { + if (doubles != null) { + int len = doubles.length; + // 创建数组 + Double[] array = new Double[len]; + for (int i = 0; i < len; i++) { + array[i] = doubles[i]; + } + return array; + } + return null; + } + + /** + * boolean[] 转换 Boolean[] + * @param booleans boolean[] + * @return {@link Boolean[]} + */ + public static Boolean[] booleansToBooleans(final boolean[] booleans) { + if (booleans != null) { + int len = booleans.length; + // 创建数组 + Boolean[] array = new Boolean[len]; + for (int i = 0; i < len; i++) { + array[i] = booleans[i]; + } + return array; + } + return null; + } + + // = + + /** + * Integer[] 转换 int[] + * @param integers Integer[] + * @param defaultValue 转换失败使用得默认值 + * @return int[] + */ + public static int[] integersToInts( + final Integer[] integers, + final int defaultValue + ) { + if (integers != null) { + int len = integers.length; + // 创建数组 + int[] array = new int[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = integers[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Byte[] 转换 byte[] + * @param bytes Byte[] + * @param defaultValue 转换失败使用得默认值 + * @return byte[] + */ + public static byte[] bytesToBytes( + final Byte[] bytes, + final byte defaultValue + ) { + if (bytes != null) { + int len = bytes.length; + // 创建数组 + byte[] array = new byte[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = bytes[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Character[] 转换 char[] + * @param characters Character[] + * @param defaultValue 转换失败使用得默认值 + * @return char[] + */ + public static char[] charactersToChars( + final Character[] characters, + final char defaultValue + ) { + if (characters != null) { + int len = characters.length; + // 创建数组 + char[] array = new char[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = characters[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Short[] 转换 short[] + * @param shorts Short[] + * @param defaultValue 转换失败使用得默认值 + * @return short[] + */ + public static short[] shortsToShorts( + final Short[] shorts, + final short defaultValue + ) { + if (shorts != null) { + int len = shorts.length; + // 创建数组 + short[] array = new short[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = shorts[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Long[] 转换 long[] + * @param longs Long[] + * @param defaultValue 转换失败使用得默认值 + * @return long[] + */ + public static long[] longsToLongs( + final Long[] longs, + final long defaultValue + ) { + if (longs != null) { + int len = longs.length; + // 创建数组 + long[] array = new long[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = longs[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Float[] 转换 float[] + * @param floats Float[] + * @param defaultValue 转换失败使用得默认值 + * @return float[] + */ + public static float[] floatsToFloats( + final Float[] floats, + final float defaultValue + ) { + if (floats != null) { + int len = floats.length; + // 创建数组 + float[] array = new float[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = floats[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Double[] 转换 double[] + * @param doubles Double[] + * @param defaultValue 转换失败使用得默认值 + * @return double[] + */ + public static double[] doublesToDoubles( + final Double[] doubles, + final double defaultValue + ) { + if (doubles != null) { + int len = doubles.length; + // 创建数组 + double[] array = new double[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = doubles[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + /** + * Boolean[] 转换 boolean[] + * @param booleans Boolean[] + * @param defaultValue 转换失败使用得默认值 + * @return boolean[] + */ + public static boolean[] booleansToBooleans( + final Boolean[] booleans, + final boolean defaultValue + ) { + if (booleans != null) { + int len = booleans.length; + // 创建数组 + boolean[] array = new boolean[len]; + for (int i = 0; i < len; i++) { + try { + array[i] = booleans[i]; + } catch (Exception e) { + array[i] = defaultValue; + } + } + return array; + } + return null; + } + + // ============ + // = 转换 List = + // ============ + + /** + * 转换数组为集合 + * @param array 数组 + * @param 泛型 + * @return {@link List} + */ + public static List asList(final T[] array) { + if (array != null) { + try { + return new ArrayList<>(Arrays.asList(array)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "asList"); + } + } + return null; + } + + /** + * 转换数组为集合 + * @param array 数组 + * @param 泛型 + * @return {@link List} + */ + public static List asListArgs(final T... array) { + if (array != null) { + try { + return new ArrayList<>(Arrays.asList(array)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "asListArgs"); + } + } + return null; + } + + // = + + /** + * 转换数组为集合 + * @param ints int[] + * @return {@link List} + */ + public static List asList(final int[] ints) { + if (ints != null) { + List lists = new ArrayList<>(); + for (int value : ints) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param bytes byte[] + * @return {@link List} + */ + public static List asList(final byte[] bytes) { + if (bytes != null) { + List lists = new ArrayList<>(); + for (byte value : bytes) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param chars char[] + * @return {@link List} + */ + public static List asList(final char[] chars) { + if (chars != null) { + List lists = new ArrayList<>(); + for (char value : chars) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param shorts short[] + * @return {@link List} + */ + public static List asList(final short[] shorts) { + if (shorts != null) { + List lists = new ArrayList<>(); + for (short value : shorts) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param longs long[] + * @return {@link List} + */ + public static List asList(final long[] longs) { + if (longs != null) { + List lists = new ArrayList<>(); + for (long value : longs) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param floats float[] + * @return {@link List} + */ + public static List asList(final float[] floats) { + if (floats != null) { + List lists = new ArrayList<>(); + for (float value : floats) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param doubles double[] + * @return {@link List} + */ + public static List asList(final double[] doubles) { + if (doubles != null) { + List lists = new ArrayList<>(); + for (double value : doubles) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param booleans boolean[] + * @return {@link List} + */ + public static List asList(final boolean[] booleans) { + if (booleans != null) { + List lists = new ArrayList<>(); + for (boolean value : booleans) { + lists.add(value); + } + return lists; + } + return null; + } + + // = + + /** + * 转换数组为集合 + * @param ints int[] + * @return {@link List} + */ + public static List asListArgsInt(final int... ints) { + if (ints != null) { + List lists = new ArrayList<>(); + for (int value : ints) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param bytes byte[] + * @return {@link List} + */ + public static List asListArgsByte(final byte... bytes) { + if (bytes != null) { + List lists = new ArrayList<>(); + for (byte value : bytes) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param chars char[] + * @return {@link List} + */ + public static List asListArgsChar(final char... chars) { + if (chars != null) { + List lists = new ArrayList<>(); + for (char value : chars) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param shorts short[] + * @return {@link List} + */ + public static List asListArgsShort(final short... shorts) { + if (shorts != null) { + List lists = new ArrayList<>(); + for (short value : shorts) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param longs long[] + * @return {@link List} + */ + public static List asListArgsLong(final long... longs) { + if (longs != null) { + List lists = new ArrayList<>(); + for (long value : longs) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param floats float[] + * @return {@link List} + */ + public static List asListArgsFloat(final float... floats) { + if (floats != null) { + List lists = new ArrayList<>(); + for (float value : floats) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param doubles double[] + * @return {@link List} + */ + public static List asListArgsDouble(final double... doubles) { + if (doubles != null) { + List lists = new ArrayList<>(); + for (double value : doubles) { + lists.add(value); + } + return lists; + } + return null; + } + + /** + * 转换数组为集合 + * @param booleans boolean[] + * @return {@link List} + */ + public static List asListArgsBoolean(final boolean... booleans) { + if (booleans != null) { + List lists = new ArrayList<>(); + for (boolean value : booleans) { + lists.add(value); + } + return lists; + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // = + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @param 泛型 + * @return 拼接后的数组集合 + */ + public static T[] arrayCopy( + final T[] prefix, + final T[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建集合 + List lists = new ArrayList<>(prefixLength + suffixLength); + // 进行判断处理 + if (prefixLength != 0) { + lists.addAll(Arrays.asList(prefix).subList(0, prefixLength)); + } + if (suffixLength != 0) { + lists.addAll(Arrays.asList(suffix).subList(0, suffixLength)); + } + if (prefix != null) { + return (T[]) Arrays.copyOf(lists.toArray(), lists.size(), prefix.getClass()); + } else { + return (T[]) Arrays.copyOf(lists.toArray(), lists.size(), suffix.getClass()); + } + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static int[] arrayCopy( + final int[] prefix, + final int[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + int[] arrays = new int[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static byte[] arrayCopy( + final byte[] prefix, + final byte[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + byte[] arrays = new byte[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static char[] arrayCopy( + final char[] prefix, + final char[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + char[] arrays = new char[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static short[] arrayCopy( + final short[] prefix, + final short[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + short[] arrays = new short[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static long[] arrayCopy( + final long[] prefix, + final long[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + long[] arrays = new long[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static float[] arrayCopy( + final float[] prefix, + final float[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + float[] arrays = new float[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static double[] arrayCopy( + final double[] prefix, + final double[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + double[] arrays = new double[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + /** + * 拼接数组 + * @param prefix 第一个数组 + * @param suffix 第二个数组 + * @return 拼接后的数组 + */ + public static boolean[] arrayCopy( + final boolean[] prefix, + final boolean[] suffix + ) { + // 获取数据长度 + int prefixLength = (prefix != null) ? prefix.length : 0; + int suffixLength = (suffix != null) ? suffix.length : 0; + // 数据都为 null, 则直接跳过 + if (prefixLength + suffixLength == 0) return null; + // 创建数组 + boolean[] arrays = new boolean[prefixLength + suffixLength]; + // 进行判断处理 + if (prefixLength != 0) { + System.arraycopy(prefix, 0, arrays, 0, prefixLength); + } + if (suffixLength != 0) { + System.arraycopy(suffix, 0, arrays, prefixLength, suffixLength); + } + return arrays; + } + + // = + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @param 泛型 + * @return 指定长度数组 + */ + public static T[] newArray( + final T[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static int[] newArray( + final int[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static byte[] newArray( + final byte[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static char[] newArray( + final char[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static short[] newArray( + final short[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static long[] newArray( + final long[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static float[] newArray( + final float[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static double[] newArray( + final double[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + /** + * 创建指定长度数组 + * @param data 待处理数组 + * @param length 保留长度 + * @return 指定长度数组 + */ + public static boolean[] newArray( + final boolean[] data, + final int length + ) { + if (data != null && length >= 0) return Arrays.copyOf(data, length); + return null; + } + + // = + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @param 泛型 + * @return 裁剪后的数组 + */ + public static T[] subArray( + final T[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + List lists = new ArrayList<>(length); + lists.addAll(Arrays.asList(data).subList(off, off + length)); + return (T[]) Arrays.copyOf(lists.toArray(), length, data.getClass()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static int[] subArray( + final int[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + int[] arrays = new int[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static byte[] subArray( + final byte[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + byte[] arrays = new byte[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static char[] subArray( + final char[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + char[] arrays = new char[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static short[] subArray( + final short[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + short[] arrays = new short[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static long[] subArray( + final long[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + long[] arrays = new long[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static float[] subArray( + final float[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + float[] arrays = new float[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static double[] subArray( + final double[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + double[] arrays = new double[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + /** + * 从数组上截取一段 + * @param data 数组 + * @param off 起始值 + * @param length 所需长度 + * @return 裁剪后的数组 + */ + public static boolean[] subArray( + final boolean[] data, + final int off, + final int length + ) { + if (data == null || off < 0 || length < 0) return null; + try { + boolean[] arrays = new boolean[length]; + System.arraycopy(data, off, arrays, 0, length); + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subArray"); + } + return null; + } + + // = + + /** + * 追加数组内容字符串 + * @param data 数组 + * @param 泛型 + * @return 追加数组内容字符串 + */ + public static String appendToString(final T[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final int[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final byte[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final char[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final short[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final long[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final float[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final double[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + /** + * 追加数组内容字符串 + * @param data 数组 + * @return 追加数组内容字符串 + */ + public static String appendToString(final boolean[] data) { + if (data != null) { + int len = data.length; + if (len != 0) { + if (len == 1) { + return "\"" + data[0] + "\""; + } else { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < len - 1; i++) { + builder.append("\"").append(data[i]).append("\","); + } + builder.append("\"").append(data[len - 1]).append("\""); + return builder.toString(); + } + } + } + return ""; + } + + // ============ + // = 最小值索引 = + // ============ + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final int[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + int temp = data[index]; + for (int i = 1; i < len; i++) { + int value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final long[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + long temp = data[index]; + for (int i = 1; i < len; i++) { + long value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final float[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + float temp = data[index]; + for (int i = 1; i < len; i++) { + float value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最小值索引 + * @param data 数组 + * @return 最小值索引 + */ + public static int getMinimumIndex(final double[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + double temp = data[index]; + for (int i = 1; i < len; i++) { + double value = data[i]; + if (value < temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + // ============ + // = 最大值索引 = + // ============ + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final int[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + int temp = data[index]; + for (int i = 1; i < len; i++) { + int value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final long[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + long temp = data[index]; + for (int i = 1; i < len; i++) { + long value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final float[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + float temp = data[index]; + for (int i = 1; i < len; i++) { + float value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + /** + * 获取数组中最大值索引 + * @param data 数组 + * @return 最大值索引 + */ + public static int getMaximumIndex(final double[] data) { + if (data != null) { + int len = data.length; + if (len > 0) { + int index = 0; + double temp = data[index]; + for (int i = 1; i < len; i++) { + double value = data[i]; + if (value > temp) { + index = i; + temp = value; + } + } + return index; + } + } + return -1; + } + + // =========== + // = 获取最小值 = + // =========== + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static int getMinimum(final int[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static long getMinimum(final long[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0L; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static float getMinimum(final float[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0F; + } + + /** + * 获取数组中最小值 + * @param data 数组 + * @return 最小值 + */ + public static double getMinimum(final double[] data) { + try { + return data[getMinimumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimum"); + } + return 0D; + } + + // =========== + // = 获取最大值 = + // =========== + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static int getMaximum(final int[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static long getMaximum(final long[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0L; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static float getMaximum(final float[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0F; + } + + /** + * 获取数组中最大值 + * @param data 数组 + * @return 最大值 + */ + public static double getMaximum(final double[] data) { + try { + return data[getMaximumIndex(data)]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximum"); + } + return 0D; + } + + // ============= + // = 计算数组总和 = + // ============= + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static int sumArray(final int[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int end, + final int extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static int sumArray( + final int[] data, + final int start, + final int end, + final int extra + ) { + int total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static long sumArray(final long[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int end, + final long extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static long sumArray( + final long[] data, + final int start, + final int end, + final long extra + ) { + long total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static float sumArray(final float[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int end, + final float extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static float sumArray( + final float[] data, + final int start, + final int end, + final float extra + ) { + float total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } + + // = + + /** + * 计算数组总和 + * @param data 数组 + * @return 数组总和 + */ + public static double sumArray(final double[] data) { + return sumArray(data, 0, length(data), 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int end + ) { + return sumArray(data, 0, end, 0); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int end, + final double extra + ) { + return sumArray(data, 0, end, extra); + } + + /** + * 计算数组总和 + * @param data 数组 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 数组总和 + */ + public static double sumArray( + final double[] data, + final int start, + final int end, + final double extra + ) { + double total = 0; + if (data != null) { + for (int i = start; i < end; i++) { + try { + total += (data[i] + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumArray"); + } + } + } + return total; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/BigDecimalUtils.java b/lib/DevJava/src/main/java/dev/utils/common/BigDecimalUtils.java new file mode 100644 index 0000000000..4f048f0cf8 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/BigDecimalUtils.java @@ -0,0 +1,1775 @@ +package dev.utils.common; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 资金运算工具类 + * @author Ttt + *
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class BigDecimalUtils { + + private BigDecimalUtils() { + } + + // 日志 TAG + private static final String TAG = BigDecimalUtils.class.getSimpleName(); + + // 小数点位数 + private static int NEW_SCALE = 10; + // 舍入模式 ( 默认向下取 ) + private static int ROUNDING_MODE = BigDecimal.ROUND_DOWN; + + /** + * 设置全局小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + */ + public static void setScale( + final int scale, + final int roundingMode + ) { + NEW_SCALE = scale; + ROUNDING_MODE = roundingMode; + } + + /** + * 获取 BigDecimal + * @param value Value + * @return {@link BigDecimal} + */ + public static BigDecimal getBigDecimal(final Object value) { + if (value == null) return null; + try { + if (value instanceof Double) { + return BigDecimal.valueOf((Double) value); + } else if (value instanceof Long) { + return new BigDecimal((Long) value); + } else if (value instanceof Float) { + return BigDecimal.valueOf((Float) value); + } else if (value instanceof Integer) { + return new BigDecimal((Integer) value); + } else if (value instanceof BigInteger) { + return new BigDecimal((BigInteger) value); + } else if (value instanceof BigDecimal) { + return (BigDecimal) value; + } else { + String strValue = ConvertUtils.newStringNotArrayDecode(value); + if (strValue != null) { + return new BigDecimal(strValue); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBigDecimal"); + } + return null; + } + + /** + * 获取 Operation + * @param value Value + * @return {@link Operation} + */ + public static Operation operation(final Object value) { + return new Operation(value); + } + + /** + * 获取 Operation + * @param value Value + * @param config {@link Config} + * @return {@link Operation} + */ + public static Operation operation( + final Object value, + final Config config + ) { + return new Operation(value, config); + } + + // ======== + // = 包装类 = + // ======== + + /** + * detail: 计算异常 + * @author Ttt + */ + public static class CalculateException + extends RuntimeException { + public CalculateException() { + } + } + + /** + * detail: 配置信息 + * @author Ttt + */ + public static final class Config { + + // 小数点位数 + private final int mScale; + // 舍入模式 + private final int mRoundingMode; + + public Config() { + this(NEW_SCALE, ROUNDING_MODE); + } + + /** + * 初始化小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + */ + public Config( + final int scale, + final int roundingMode + ) { + this.mScale = scale; + this.mRoundingMode = roundingMode; + } + + /** + * 获取小数点保留位数 + * @return 小数点保留位数 + */ + public int getScale() { + return mScale; + } + + /** + * 获取舍入模式 + * @return 舍入模式 + */ + public int getRoundingMode() { + return mRoundingMode; + } + } + + /** + * detail: BigDecimal 操作包装类 + * @author Ttt + */ + public static final class Operation { + + // 计算数值 + private BigDecimal mValue; + // 配置信息 + private Config mConfig; + // 是否抛出异常 + private boolean mThrowError = false; + + public Operation(final Object value) { + this(value, null); + } + + public Operation( + final Object value, + final Config config + ) { + this.mValue = BigDecimalUtils.getBigDecimal(value); + this.mConfig = config; + } + + // = + + /** + * 检查 Value 是否为 null, 为 null 则抛出异常 + * @return {@link Operation} + * @throws NullPointerException null 异常 + */ + public Operation requireNonNull() { + if (mValue != null) return this; + throw new NullPointerException("mValue is null"); + } + + /** + * 内部抛出异常方法 + */ + private void throwException() { + if (mThrowError) throw new CalculateException(); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取 Value + * @return {@link BigDecimal} + */ + public BigDecimal getBigDecimal() { + return mValue; + } + + /** + * 设置 Value + * @param value {@link BigDecimal} + * @return {@link Operation} + */ + public Operation setBigDecimal(final Object value) { + this.mValue = BigDecimalUtils.getBigDecimal(value); + return this; + } + + /** + * 获取配置信息 + * @return {@link Config} + */ + public Config getConfig() { + return mConfig; + } + + /** + * 设置配置信息 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation setConfig(final Config config) { + return setConfig(config, true); + } + + /** + * 设置配置信息 + * @param config {@link Config} + * @param set 是否进行设置 + * @return {@link Operation} + */ + public Operation setConfig( + final Config config, + final boolean set + ) { + this.mConfig = config; + if (set) setScaleByConfig(); + return this; + } + + /** + * 移除配置信息 + * @return {@link Operation} + */ + public Operation removeConfig() { + return setConfig(null, false); + } + + // = + + /** + * 设置小数点保留位数、舍入模式 + * @param scale 小数点保留位数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation setScale( + final int scale, + final int roundingMode + ) { + return setScale(new Config(scale, roundingMode)); + } + + /** + * 设置小数点保留位数、舍入模式 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation setScale(final Config config) { + if (mValue != null && config != null) { + try { + mValue = mValue.setScale( + config.getScale(), + config.getRoundingMode() + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "setScale"); + throwException(); + } + } else { + throwException(); + } + return this; + } + + /** + * 设置小数点保留位数、舍入模式 + * @return {@link Operation} + */ + public Operation setScaleByConfig() { + return setScale(mConfig); + } + + // = + + /** + * 是否抛出异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isThrowError() { + return mThrowError; + } + + /** + * 设置是否抛出异常 + * @param throwError 是否抛出异常 + * @return {@link Operation} + */ + public Operation setThrowError(final boolean throwError) { + this.mThrowError = throwError; + return this; + } + + // ========== + // = 获取方法 = + // ========== + + /** + * 克隆对象 + * @return {@link Operation} + */ + public Operation clone() { + return new Operation(mValue, mConfig); + } + + /** + * 获取此 BigDecimal 的字符串表示形式科学记数法 + * @return 此 BigDecimal 的字符串表示形式科学记数法 + */ + public String toString() { + return (mValue != null) ? mValue.toString() : null; + } + + /** + * 获取此 BigDecimal 的字符串表示形式不带指数字段 + * @return 此 BigDecimal 的字符串表示形式不带指数字段 + */ + public String toPlainString() { + return (mValue != null) ? mValue.toPlainString() : null; + } + + /** + * 获取此 BigDecimal 的字符串表示形式工程计数法 + * @return 此 BigDecimal 的字符串表示形式工程计数法 + */ + public String toEngineeringString() { + return (mValue != null) ? mValue.toEngineeringString() : null; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public int intValue() { + return (mValue != null) ? mValue.intValue() : 0; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public float floatValue() { + return (mValue != null) ? mValue.floatValue() : 0F; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public long longValue() { + return (mValue != null) ? mValue.longValue() : 0L; + } + + /** + * 获取指定类型值 + * @return 指定类型值 + */ + public double doubleValue() { + return (mValue != null) ? mValue.doubleValue() : 0D; + } + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 + * @param value 加数 + * @return {@link Operation} + */ + public Operation add(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.add(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 + * @param value 减数 + * @return {@link Operation} + */ + public Operation subtract(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.subtract(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 + * @param value 乘数 + * @return {@link Operation} + */ + public Operation multiply(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.multiply(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 + * @param value 除数 + * @return {@link Operation} + */ + public Operation divide(final Object value) { + return divide(value, mConfig); + } + + /** + * 提供精确的除法运算 + * @param value 除数 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation divide( + final Object value, + final Config config + ) { + if (config != null) { + return divide(value, config.getScale(), config.getRoundingMode()); + } else { + return divide(value, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的除法运算 + * @param value 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation divide( + final Object value, + final int scale, + final int roundingMode + ) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + try { + mValue = mValue.divide(bigDecimal, scale, roundingMode); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "divide"); + throwException(); + } + } else { + throwException(); + } + return this; + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 + * @param value 除数 + * @return {@link Operation} + */ + public Operation remainder(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + mValue = mValue.remainder(bigDecimal); + } else { + throwException(); + } + return this; + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 + * @return {@link Operation} + */ + public Operation round() { + return round(mConfig); + } + + /** + * 提供精确的小数位四舍五入处理 + * @param config {@link Config} + * @return {@link Operation} + */ + public Operation round(final Config config) { + return divide(new BigDecimal("1"), config); + } + + /** + * 提供精确的小数位四舍五入处理 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return {@link Operation} + */ + public Operation round( + final int scale, + final int roundingMode + ) { + return divide(new BigDecimal("1"), scale, roundingMode); + } + + // ========== + // = 比较大小 = + // ========== + + /** + * 比较大小 + * @param value 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2]、[-2 = error] + */ + public int compareTo(final Object value) { + BigDecimal bigDecimal = BigDecimalUtils.getBigDecimal(value); + if (mValue != null && bigDecimal != null) { + try { + return mValue.compareTo(bigDecimal); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "compareTo"); + throwException(); + } + } else { + throwException(); + } + return -2; + } + + // ========== + // = 金额分割 = + // ========== + + /** + * 金额分割, 四舍五入金额 + * @return 指定格式处理的字符串 + */ + public String formatMoney() { + return formatMoney(mConfig); + } + + /** + * 金额分割, 四舍五入金额 + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final int splitNumber, + final String splitSymbol + ) { + return formatMoney(mConfig, splitNumber, splitSymbol); + } + + // = + + /** + * 金额分割, 四舍五入金额 + * @param config {@link Config} + * @return 指定格式处理的字符串 + */ + public String formatMoney(final Config config) { + return formatMoney(config, 3, ","); + } + + /** + * 金额分割, 四舍五入金额 + * @param config {@link Config} + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final Config config, + final int splitNumber, + final String splitSymbol + ) { + if (config != null) { + return formatMoney(config.getScale(), config.getRoundingMode(), splitNumber, splitSymbol); + } else { + return formatMoney(NEW_SCALE, ROUNDING_MODE, splitNumber, splitSymbol); + } + } + + /** + * 金额分割, 四舍五入金额 + * @param scale 小数点后保留几位 + * @param mode 处理模式 + * @param splitNumber 拆分位数 + * @param splitSymbol 拆分符号 + * @return 指定格式处理的字符串 + */ + public String formatMoney( + final int scale, + final int mode, + final int splitNumber, + final String splitSymbol + ) { + if (mValue == null) return null; + try { + // 如果等于 0, 直接返回 + if (mValue.doubleValue() == 0) { + return mValue.setScale(scale, mode).toPlainString(); + } + // 获取原始值字符串 ( 非科学计数法 ) + String valuePlain = mValue.toPlainString(); + // 判断是否负数 + boolean isNegative = valuePlain.startsWith("-"); + // 处理后的数据 + BigDecimal bigDecimal = new BigDecimal(isNegative ? valuePlain.substring(1) : valuePlain); + // 范围处理 + valuePlain = bigDecimal.setScale(scale, mode).toPlainString(); + // 进行拆分小数点处理 + String[] values = valuePlain.split("\\."); + // 判断是否存在小数点 + boolean isDecimal = (values.length == 2); + + // 拼接符号 + String symbol = (splitSymbol != null) ? splitSymbol : ""; + // 防止出现负数 + int number = Math.max(splitNumber, 0); + // 格式化数据 ( 拼接处理 ) + StringBuilder builder = new StringBuilder(); + // 进行处理小数点前的数值 + for (int len = values[0].length() - 1, i = len, splitPos = 1; i >= 0; i--) { + char ch = values[0].charAt(i); + builder.append(ch); + // 判断是否需要追加符号 + if (number > 0 && splitPos % number == 0 && i != 0) { + builder.append(symbol); + } + splitPos++; + } + // 倒序处理 + builder.reverse(); + // 存在小数点, 则进行拼接 + if (isDecimal) { + builder.append(".").append(values[1]); + } + // 判断是否负数 + return isNegative ? "-" + builder.toString() : builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatMoney"); + } + return null; + } + } + + // ================== + // = 获取指定格式字符串 = + // ================== + + /** + * 获取自己想要的数据格式 + * @param value 需处理的数据 + * @param numOfIntPart 整数位数 + * @param numOfDecimalPart 小数位数 + * @return 处理过的数据 + */ + public static String adjustDouble( + final String value, + final int numOfIntPart, + final int numOfDecimalPart + ) { + if (value == null) return null; + try { + // 按小数点的位置分割成整数部分和小数部分 + String[] array = value.split("\\."); + char[] tempA = new char[numOfIntPart]; + char[] tempB = new char[numOfDecimalPart]; + // 整数部分满足精度要求 + if (array[0].length() == numOfIntPart) { + // 直接获取整数部分长度字符 + for (int i = 0; i < array[0].length(); i++) { + tempA[i] = array[0].charAt(i); + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + if (numOfDecimalPart == 0) { + return String.valueOf(tempA) + String.valueOf(tempB); + } + return String.valueOf(tempA) + "." + String.valueOf(tempB); + } + // 整数部分位数大于精度要求 + if (array[0].length() > numOfIntPart) { + // 先倒序获取指定位数的整数 + for (int i = array[0].length() - 1, j = 0; (i >= array[0].length() - numOfIntPart) && (j < numOfIntPart); i--, j++) { + tempA[j] = array[0].charAt(i); + } + char[] tempA1 = new char[numOfIntPart]; + // 调整顺序 + for (int j = 0, k = tempA.length - 1; j < numOfIntPart && (k >= 0); j++, k--) { + tempA1[j] = tempA[k]; + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + return String.valueOf(tempA1) + "." + String.valueOf(tempB); + } + // 先倒序获取指定位数的整数 + char[] tempA1 = new char[numOfIntPart]; + for (int i = array[0].length() - 1, j = 0; (i >= 0) && (j < numOfIntPart); i--, j++) { + tempA1[j] = array[0].charAt(i); + } + // 补 0 + for (int i = array[0].length(); i < array[0].length() + numOfIntPart - array[0].length(); i++) { + tempA1[i] = '0'; + } + + char[] tempA2 = new char[numOfIntPart]; + // 调整顺序 + for (int j = 0, k = tempA1.length - 1; j < numOfIntPart && (k >= 0); j++) { + tempA2[j] = tempA1[k]; + k--; + } + // 小数部分精度小于指定的精度 + if (numOfDecimalPart > array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + if (i < array[1].length()) { + tempB[i] = array[1].charAt(i); + } else { + tempB[i] = '0'; + } + } + } + // 小数部分精度大于或等于指定的精度 + if (numOfDecimalPart <= array[1].length()) { + for (int i = 0; i < numOfDecimalPart; i++) { + tempB[i] = array[1].charAt(i); + } + } + if (numOfDecimalPart == 0) { + return String.valueOf(tempA2) + String.valueOf(tempB); + } + return String.valueOf(tempA2) + "." + String.valueOf(tempB); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "adjustDouble"); + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + // ========== + // = 比较大小 = + // ========== + + /** + * 比较大小 + * @param v1 输入的数值 + * @param v2 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2]、[-2 = error] + */ + public static int compareTo( + final Object v1, + final Object v2 + ) { + try { + return compareToThrow(v1, v2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "compareTo"); + } + return -2; + } + + /** + * 比较大小 ( 抛出异常 ) + * @param v1 输入的数值 + * @param v2 被比较的数字 + * @return [1 = v1 > v2]、[-1 = v1 < v2]、[0 = v1 = v2] + */ + public static int compareToThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return operation(v1).setThrowError(true).compareTo(v2); + } + + // ========== + // = 捕获异常 = + // ========== + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2 + ) { + try { + return addThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final int scale + ) { + try { + return addThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param config {@link Config} + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final Config config + ) { + try { + return addThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的加法运算 + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的和 + */ + public static double add( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return addThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2 + ) { + try { + return subtractThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final int scale + ) { + try { + return subtractThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param config {@link Config} + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final Config config + ) { + try { + return subtractThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的减法运算 + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的差 + */ + public static double subtract( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return subtractThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2 + ) { + try { + return multiplyThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final int scale + ) { + try { + return multiplyThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param config {@link Config} + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final Config config + ) { + try { + return multiplyThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的乘法运算 + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的积 + */ + public static double multiply( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return multiplyThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2 + ) { + try { + return divideThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final int scale + ) { + try { + return divideThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final Config config + ) { + try { + return divideThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的除法运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的商 + */ + public static double divide( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return divideThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2 + ) { + try { + return remainderThrow(v1, v2); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final int scale + ) { + try { + return remainderThrow(v1, v2, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final Config config + ) { + try { + return remainderThrow(v1, v2, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的取余运算 + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的余数 + */ + public static double remainder( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) { + try { + return remainderThrow(v1, v2, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @return 四舍五入后的结果 + */ + public static double round(final Object v1) { + try { + return roundThrow(v1); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final int scale + ) { + try { + return roundThrow(v1, scale); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param config {@link Config} + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final Config config + ) { + try { + return roundThrow(v1, config); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + /** + * 提供精确的小数位四舍五入处理 + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ + public static double round( + final Object v1, + final int scale, + final int roundingMode + ) { + try { + return roundThrow(v1, scale, roundingMode); + } catch (Exception e) { + return DevFinal.DEFAULT.DOUBLE; + } + } + + // ========== + // = 抛出异常 = + // ========== + + // ===== + // = 加 = + // ===== + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return addThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return addThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param config {@link Config} + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return addThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return addThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的加法运算 ( 抛出异常 ) + * @param v1 被加数 + * @param v2 加数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的和 + */ + public static double addThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .add(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 减 = + // ===== + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return subtractThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return subtractThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param config {@link Config} + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return subtractThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return subtractThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的减法运算 ( 抛出异常 ) + * @param v1 被减数 + * @param v2 减数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的差 + */ + public static double subtractThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .subtract(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 乘 = + // ===== + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return multiplyThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return multiplyThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param config {@link Config} + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return multiplyThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return multiplyThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的乘法运算 ( 抛出异常 ) + * @param v1 被乘数 + * @param v2 乘数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的积 + */ + public static double multiplyThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .multiply(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ===== + // = 除 = + // ===== + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return divideThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return divideThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return divideThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return divideThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的除法运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的商 + */ + public static double divideThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .divide(v2, scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ======= + // = 取余 = + // ======= + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2 + ) + throws Exception { + return remainderThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final int scale + ) + throws Exception { + return remainderThrow(v1, v2, scale, ROUNDING_MODE); + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param config {@link Config} + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final Config config + ) + throws Exception { + if (config != null) { + return remainderThrow(v1, v2, config.getScale(), config.getRoundingMode()); + } else { + return remainderThrow(v1, v2, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的取余运算 ( 抛出异常 ) + * @param v1 被除数 + * @param v2 除数 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 两个参数的余数 + */ + public static double remainderThrow( + final Object v1, + final Object v2, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .remainder(v2).setScale(scale, roundingMode) + .requireNonNull().doubleValue(); + } + + // ========== + // = 四舍五入 = + // ========== + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @return 四舍五入后的结果 + */ + public static double roundThrow(final Object v1) + throws Exception { + return roundThrow(v1, NEW_SCALE, ROUNDING_MODE); + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final int scale + ) + throws Exception { + return roundThrow(v1, scale, ROUNDING_MODE); + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param config {@link Config} + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final Config config + ) + throws Exception { + if (config != null) { + return roundThrow(v1, config.getScale(), config.getRoundingMode()); + } else { + return roundThrow(v1, NEW_SCALE, ROUNDING_MODE); + } + } + + /** + * 提供精确的小数位四舍五入处理 ( 抛出异常 ) + * @param v1 需要四舍五入的数值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ + public static double roundThrow( + final Object v1, + final int scale, + final int roundingMode + ) + throws Exception { + return operation(v1).setThrowError(true) + .round(scale, roundingMode) + .requireNonNull().doubleValue(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/CalendarUtils.java b/lib/DevJava/src/main/java/dev/utils/common/CalendarUtils.java new file mode 100644 index 0000000000..63a023c761 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/CalendarUtils.java @@ -0,0 +1,1057 @@ +package dev.utils.common; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 日历工具类 + * @author Ttt + *
+ *     公历、农历转换
+ *     @see 
+ *     关于日历实现代码里 0x04bd8, 0x04ae0, 0x0a570 的解释
+ *     @see 
+ *     Android 农历和节气相关工具类 ( 记录 )
+ *     @see 
+ * 
+ */ +public final class CalendarUtils { + + private CalendarUtils() { + } + + // 日志 TAG + private static final String TAG = CalendarUtils.class.getSimpleName(); + + // 支持转换的最小农历年份 + public static final int MIN_LUNAR_YEAR = 1900; + // 支持转换的最小公历年份 + public static final int MIN_SOLAR_YEAR = 1900; // 1901 + // 支持转换的最大公历、农历年份 + public static final int MAX_YEAR = 2099; + + /** + * 是否支持农历年份计算 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportLunar(final int year) { + return year <= MAX_YEAR && year >= MIN_LUNAR_YEAR; + } + + /** + * 是否支持公历年份计算 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSupportSolar(final int year) { + return year <= MAX_YEAR && year >= MIN_SOLAR_YEAR; + } + + // ========== + // = 农历转换 = + // ========== + + /** + * 公历转农历 + * @param year 公历年 + * @param month 公历月 + * @param day 公历日 + * @return [0] 农历年 [1] 农历月 [2] 农历日 [3] 是否闰月 0 false、1 true + */ + public static int[] solarToLunar( + final int year, + final int month, + final int day + ) { + // 不支持的公历年份, 则返回 null + if (!isSupportSolar(year)) return null; + + int[] lunarInt = new int[4]; + int index = year - SOLAR[0]; + int data = (year << 9) | (month << 5) | (day); + int solar11; + if (SOLAR[index] > data) { + index--; + } + solar11 = SOLAR[index]; + int y = getBitInt(solar11, 12, 9); + int m = getBitInt(solar11, 4, 5); + int d = getBitInt(solar11, 5, 0); + long offset = solarToInt(year, month, day) - solarToInt(y, m, d); + + int days = LUNAR_MONTH_DAYS[index]; + int leap = getBitInt(days, 4, 13); + + int lunarY = index + SOLAR[0]; + int lunarM = 1; + int lunarD; + offset += 1; + + for (int i = 0; i < 13; i++) { + int dm = getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; + if (offset > dm) { + lunarM++; + offset -= dm; + } else { + break; + } + } + lunarD = (int) (offset); + lunarInt[0] = lunarY; + lunarInt[1] = lunarM; + lunarInt[3] = 0; + + if (leap != 0 && lunarM > leap) { + lunarInt[1] = lunarM - 1; + if (lunarM == leap + 1) { + lunarInt[3] = 1; + } + } + lunarInt[2] = lunarD; + return lunarInt; + } + + /** + * 农历转公历 + * @param lunarYear 农历年 + * @param lunarMonth 农历月 + * @param lunarDay 农历日 + * @param isLeap 是否闰月 + * @return [0] 公历年 [1] 公历月 [2] 公历日 + */ + public static int[] lunarToSolar( + final int lunarYear, + final int lunarMonth, + final int lunarDay, + final boolean isLeap + ) { + // 不支持的农历年份, 则返回 null + if (!isSupportLunar(lunarYear)) return null; + + int days = LUNAR_MONTH_DAYS[lunarYear - LUNAR_MONTH_DAYS[0]]; + int leap = getBitInt(days, 4, 13); + int offset = 0; + int loop = leap; + if (!isLeap) { + if (lunarMonth <= leap || leap == 0) { + loop = lunarMonth - 1; + } else { + loop = lunarMonth; + } + } + for (int i = 0; i < loop; i++) { + offset += getBitInt(days, 1, 12 - i) == 1 ? 30 : 29; + } + offset += lunarDay; + + int solar11 = SOLAR[lunarYear - SOLAR[0]]; + + int y = getBitInt(solar11, 12, 9); + int m = getBitInt(solar11, 4, 5); + int d = getBitInt(solar11, 5, 0); + + return solarFromInt(solarToInt(y, m, d) + offset - 1); + } + + // = + + /** + * 获取农历年份总天数 + * @param year 农历年 + * @return 农历年份总天数 + */ + public static int getLunarYearDays(final int year) { + if (!isSupportLunar(year)) return 0; + int i, sum = 348; + for (i = 0x8000; i > 0x8; i >>= 1) { + if ((LUNAR_INFO[year - 1900] & i) != 0) { + sum += 1; + } + } + return (sum + getLunarLeapDays(year)); + } + + /** + * 获取农历年份闰月天数 + * @param year 农历年 + * @return 农历年份闰月天数 + */ + public static int getLunarLeapDays(final int year) { + if (!isSupportLunar(year)) return 0; + if (getLunarLeapMonth(year) != 0) { + if ((LUNAR_INFO[year - 1899] & 0xf) != 0) { + return 30; + } else { + return 29; + } + } else { + return 0; + } + } + + /** + * 获取农历年份哪个月是闰月 + *
+     *     返回 1 - 12 无闰月返回 0
+     * 
+ * @param year 农历年 + * @return 农历年份哪个月是闰月 + */ + public static int getLunarLeapMonth(final int year) { + if (!isSupportLunar(year)) return 0; + long var = LUNAR_INFO[year - 1900] & 0xf; + return (int) (var == 0xf ? 0 : var); + } + + /** + * 获取农历年份与月份总天数 + * @param year 农历年 + * @param month 农历月 + * @return 农历年份与月份总天数 + */ + public static int getLunarMonthDays( + final int year, + final int month + ) { + if (!isSupportLunar(year)) return 0; + if ((LUNAR_INFO[year - 1900] & (0x10000 >> month)) == 0) { + return 29; + } else { + return 30; + } + } + + /** + * 获取干支历 + * @param year 年份 + * @return 干支历 + */ + public static String getLunarGanZhi(final int year) { + int y = Math.abs(year - 4); + return TIAN_GAN[y % 10] + DI_ZHI[y % 12]; + } + + /** + * 获取农历中文月份 + * @param month 农历月 + * @param isLeap 是否闰月 + * @return 农历中文月份 + */ + public static String getLunarMonthChinese( + final int month, + final boolean isLeap + ) { + if (month > 12 || month < 1) return null; + return isLeap ? "闰" + CHINESE_NUMBER[month - 1] + "月" : CHINESE_NUMBER[month - 1] + "月"; + } + + /** + * 获取农历中文天数 + * @param day 天数 + * @return 农历中文天数 + */ + public static String getLunarDayChinese(final int day) { + if (day > 30 || day < 1) return null; + if (day == 10) return "初十"; + if (day == 20) return "二十"; + int dayIndex = (day % 10 == 0) ? 9 : day % 10 - 1; + return CHINESE_TEN[day / 10] + CHINESE_DAY[dayIndex]; + } + + /** + * 获取二十四节气 ( 公历 ) 索引 + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) 索引 + */ + public static int getSolarTermsIndex( + final int month, + final int day + ) { + if (month > 12 || month < 1) return -1; + int start = (month - 2) >= 0 ? month - 2 : 11; + int leftIndex = start * 2; // 左边节气索引 + int[] dates = solarTermsDateSplit(leftIndex); + if (dates != null && day >= dates[0] && day <= dates[1]) return leftIndex; + int rightIndex = leftIndex + 1; // 右边节气索引 + dates = solarTermsDateSplit(rightIndex); + if (dates != null && day >= dates[0] && day <= dates[1]) return rightIndex; + return -1; + } + + /** + * 获取二十四节气 ( 公历 ) + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) + */ + public static String getSolarTerms( + final int month, + final int day + ) { + int index = getSolarTermsIndex(month, day); + return index != -1 ? SOLAR_TERMS[index] : null; + } + + /** + * 获取二十四节气 ( 公历 ) 时间 + * @param month 公历月 + * @param day 公历天 + * @return 二十四节气 ( 公历 ) 时间 + */ + public static String getSolarTermsDate( + final int month, + final int day + ) { + int index = getSolarTermsIndex(month, day); + return index != -1 ? SOLAR_TERMS_DATE[index] : null; + } + + /** + * 校验是否相同节日 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFestival( + final Festival festival, + final int year, + final int month, + final int day + ) { + return isFestival(festival, year, month, day, sFestivalHook); + } + + /** + * 校验是否相同节日 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @param festivalHook 节日 Hook 接口 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFestival( + final Festival festival, + final int year, + final int month, + final int day, + final FestivalHook festivalHook + ) { + if (festival == null) return false; + List list = new ArrayList<>(); + list.add(festival); + return getFestival(list, year, month, day, festivalHook) != null; + } + + /** + * 获取符合条件的节日信息 + * @param list 节日集合 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@link Festival} + */ + public static Festival getFestival( + final List list, + final int year, + final int month, + final int day + ) { + return getFestival(list, year, month, day, sFestivalHook); + } + + /** + * 获取符合条件的节日信息 + *
+     *     list 不能混合公历、农历节日防止判断出错
+     * 
+ * @param list 节日集合 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @param festivalHook 节日 Hook 接口 + * @return {@link Festival} + */ + public static Festival getFestival( + final List list, + final int year, + final int month, + final int day, + final FestivalHook festivalHook + ) { + if (list == null) return null; + for (Festival festival : list) { + if (festival != null) { + if (festivalHook != null) { + Festival hook = festivalHook.hook(festival, year, month, day); + if (hook != null) return hook; + } + if (festival.isFestival(month, day)) return festival; + } + } + return null; + } + + /** + * 获取公历符合条件的节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return {@link Festival} + */ + public static Festival getSolarFestival( + final int year, + final int month, + final int day + ) { + return getFestival(SOLAR_FESTIVAL_LIST, year, month, day); + } + + /** + * 获取农历符合条件的节日信息 + * @param year 农历年 + * @param month 农历月 + * @param day 农历日 + * @return {@link Festival} + */ + public static Festival getLunarFestival( + final int year, + final int month, + final int day + ) { + return getFestival(LUNAR_FESTIVAL_LIST, year, month, day); + } + + // ======= + // = 常量 = + // ======= + + // 天干 + private static final String[] TIAN_GAN = { + "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸" + }; + // 地支 + private static final String[] DI_ZHI = { + "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" + }; + // 中文年份 + private static final String[] CHINESE_NUMBER = { + "正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊" + }; + // 中文十位数 + private static final String[] CHINESE_TEN = { + "初", "十", "廿", "三" + }; + // 中文一到十 + private static final String[] CHINESE_DAY = { + "一", "二", "三", "四", "五", "六", "七", "八", "九", "十" + }; + // 二十四节气 ( 公历 ) + private static final String[] SOLAR_TERMS = { + // 春季 2-4 + "立春", "雨水", + "惊蛰", "春分", + "清明", "谷雨", + // 夏季 5-7 + "立夏", "小满", + "芒种", "夏至", + "小暑", "大暑", + // 秋季 8-10 + "立秋", "处暑", + "白露", "秋分", + "寒露", "霜降", + // 冬季 11-1 + "立冬", "小雪", + "大雪", "冬至", + "小寒", "大寒" + }; + // 二十四节气 ( 公历 ) 时间 + private static final String[] SOLAR_TERMS_DATE = { + // 春季 2-4 + "2.3-5", "2.18-20", + "3.5-7", "3.20-22", + "4.4-6", "4.19-21", + // 夏季 5-7 + "5.5-7", "5.20-22", + "6.5-7", "6.21-22", + "7.6-8", "7.22-24", + // 秋季 8-10 + "8.7-9", "8.22-24", + "9.7-9", "9.22-24", + "10.8-9", "10.23-24", + // 冬季 11-1 + "11.7-8", "11.22-23", + "12.6-8", "12.21-23", + "1.5-7", "1.20-21" + }; + // 部分公历节日集合 + private static final List SOLAR_FESTIVAL_LIST = new ArrayList<>(); + // 部分农历节日集合 + private static final List LUNAR_FESTIVAL_LIST = new ArrayList<>(); + + private static final long[] LUNAR_INFO = { + 0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, 0xd950, 0x5554, 0x56af, 0x9ad0, 0x55d2, + 0x4ae0, 0xa5b6, 0xa4d0, 0xd250, 0xd255, 0xb54f, 0xd6a0, 0xada2, 0x95b0, 0x4977, + 0x497f, 0xa4b0, 0xb4b5, 0x6a50, 0x6d40, 0xab54, 0x2b6f, 0x9570, 0x52f2, 0x4970, + 0x6566, 0xd4a0, 0xea50, 0x6a95, 0x5adf, 0x2b60, 0x86e3, 0x92ef, 0xc8d7, 0xc95f, + 0xd4a0, 0xd8a6, 0xb55f, 0x56a0, 0xa5b4, 0x25df, 0x92d0, 0xd2b2, 0xa950, 0xb557, + 0x6ca0, 0xb550, 0x5355, 0x4daf, 0xa5b0, 0x4573, 0x52bf, 0xa9a8, 0xe950, 0x6aa0, + 0xaea6, 0xab50, 0x4b60, 0xaae4, 0xa570, 0x5260, 0xf263, 0xd950, 0x5b57, 0x56a0, + 0x96d0, 0x4dd5, 0x4ad0, 0xa4d0, 0xd4d4, 0xd250, 0xd558, 0xb540, 0xb6a0, 0x95a6, + 0x95bf, 0x49b0, 0xa974, 0xa4b0, 0xb27a, 0x6a50, 0x6d40, 0xaf46, 0xab60, 0x9570, + 0x4af5, 0x4970, 0x64b0, 0x74a3, 0xea50, 0x6b58, 0x5ac0, 0xab60, 0x96d5, 0x92e0, + 0xc960, 0xd954, 0xd4a0, 0xda50, 0x7552, 0x56a0, 0xabb7, 0x25d0, 0x92d0, 0xcab5, + 0xa950, 0xb4a0, 0xbaa4, 0xad50, 0x55d9, 0x4ba0, 0xa5b0, 0x5176, 0x52bf, 0xa930, + 0x7954, 0x6aa0, 0xad50, 0x5b52, 0x4b60, 0xa6e6, 0xa4e0, 0xd260, 0xea65, 0xd530, + 0x5aa0, 0x76a3, 0x96d0, 0x4afb, 0x4ad0, 0xa4d0, 0xd0b6, 0xd25f, 0xd520, 0xdd45, + 0xb5a0, 0x56d0, 0x55b2, 0x49b0, 0xa577, 0xa4b0, 0xaa50, 0xb255, 0x6d2f, 0xada0, + 0x4b63, 0x937f, 0x49f8, 0x4970, 0x64b0, 0x68a6, 0xea5f, 0x6b20, 0xa6c4, 0xaaef, + 0x92e0, 0xd2e3, 0xc960, 0xd557, 0xd4a0, 0xda50, 0x5d55, 0x56a0, 0xa6d0, 0x55d4, + 0x52d0, 0xa9b8, 0xa950, 0xb4a0, 0xb6a6, 0xad50, 0x55a0, 0xaba4, 0xa5b0, 0x52b0, + 0xb273, 0x6930, 0x7337, 0x6aa0, 0xad50, 0x4b55, 0x4b6f, 0xa570, 0x54e4, 0xd260, + 0xe968, 0xd520, 0xdaa0, 0x6aa6, 0x56df, 0x4ae0, 0xa9d4, 0xa4d0, 0xd150, 0xf252, + 0xd520 + }; + + private static final int[] LUNAR_MONTH_DAYS = { + 1887, 0x1694, 0x16aa, 0x4ad5, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a, 0x1d2a, + 0xd54, 0x75aa, 0x156a, 0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55, 0xad4, 0x135a, 0x495d, 0x95c, + 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8, 0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e, 0x1496, 0xb64b, 0xd4a, 0xda8, + 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94, 0x1d4a, 0xada9, 0xb5a, 0x56c, 0x726e, 0x125c, 0xf92d, + 0x192a, 0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b, 0x152a, 0xf695, 0xd94, 0x16aa, 0xaab5, + 0x9b4, 0x14b6, 0x6a57, 0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c, 0x94ae, 0x14ae, 0xa4c, 0x7d26, + 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d, 0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5, 0x16a8, 0x16d4, 0xd2da, + 0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4, 0x156c, 0x12ae, 0xa92f, 0x92e, 0xc96, 0x6d4a, + 0x1d4a, 0x10d65, 0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a, 0x4b55, 0xad4, 0xf55b, + 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694, 0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6, 0xca57, 0xa56, 0x1526, + 0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, 0xbd26, 0x1aa6, 0xb54, 0x6d6a, 0x12da, + 0x1695d, 0x95a, 0x149a, 0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936, 0xf497, 0x1496, + 0x154a, 0xb6a5, 0xda4, 0x15b4, 0x6ab6, 0x126e, 0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c, 0x155c, + 0x125c, 0x792e, 0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4, 0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a, + 0x1694, 0xd6aa, 0x15aa, 0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa, 0xa9b5, 0x96c, + 0x14ae, 0x8a4e, 0x1a4c, 0x11d26, 0x1aa4, 0x1b54, 0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25, + 0x1aa4, 0xfb52, 0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a, 0x136a5, 0xda4, 0x15ac + }; + + private static final int[] SOLAR = { + 1887, 0xec04c, 0xec23f, 0xec435, 0xec649, 0xec83e, 0xeca51, 0xecc46, 0xece3a, + 0xed04d, 0xed242, 0xed436, 0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244, 0xee439, 0xee64d, + 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052, 0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b, 0xefe41, + 0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d, 0xf0e42, 0xf1037, 0xf124a, 0xf143e, 0xf1651, + 0xf1846, 0xf1a3a, 0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848, 0xf2a3b, 0xf2c4f, 0xf2e45, + 0xf3039, 0xf324d, 0xf3442, 0xf3636, 0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443, 0xf4638, + 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f, 0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35, 0xf5e49, + 0xf603e, 0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b, 0xf703f, 0xf7252, 0xf7447, 0xf763c, + 0xf7850, 0xf7a45, 0xf7c39, 0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46, 0xf8c3b, 0xf8e4f, + 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853, 0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c, 0xfa641, + 0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e, 0xfb643, 0xfb837, 0xfba4a, 0xfbc3f, 0xfbe53, + 0xfc048, 0xfc23c, 0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a, 0xfd23d, 0xfd451, 0xfd646, + 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37, 0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44, 0xfee38, + 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51, 0xffc46, 0xffe3a, 0x10004e, 0x100242, 0x100437, + 0x10064b, 0x100841, 0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438, 0x10164c, 0x101842, + 0x101a35, 0x101c49, 0x101e3d, 0x102051, 0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b, + 0x102e3f, 0x103053, 0x103247, 0x10343b, 0x10364f, 0x103845, 0x103a38, 0x103c4c, 0x103e42, 0x104036, + 0x104249, 0x10443d, 0x104651, 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038, 0x10524a, 0x10543e, + 0x105652, 0x105847, 0x105a3b, 0x105c4f, 0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849, + 0x106a3d, 0x106c51, 0x106e47, 0x10703c, 0x10724f, 0x107444, 0x107638, 0x10784c, 0x107a3f, 0x107c53, + 0x107e48 + }; + + static { + // 部分公历节日集合 + SOLAR_FESTIVAL_LIST.clear(); + SOLAR_FESTIVAL_LIST.add(new Festival("元旦", 1, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国人民警察节", 1, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("情人节", 2, 14)); + SOLAR_FESTIVAL_LIST.add(new Festival("妇女节", 3, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("植树节", 3, 12)); + SOLAR_FESTIVAL_LIST.add(new Festival("消费者权益日", 3, 15)); + SOLAR_FESTIVAL_LIST.add(new Festival("愚人节", 4, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("清明节", 4, 4)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界卫生日", 4, 7)); + SOLAR_FESTIVAL_LIST.add(new Festival("地球日", 4, 22)); + SOLAR_FESTIVAL_LIST.add(new Festival("劳动节", 5, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("青年节", 5, 4)); + SOLAR_FESTIVAL_LIST.add(new Festival("护士节", 5, 12)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国科技工作者日", 5, 30)); + SOLAR_FESTIVAL_LIST.add(new Festival("儿童节", 6, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国爱眼日", 6, 6)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际禁毒日", 6, 26)); + SOLAR_FESTIVAL_LIST.add(new Festival("建党节", 7, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国航海日", 7, 11)); + SOLAR_FESTIVAL_LIST.add(new Festival("建军节", 8, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("中国医师节", 8, 19)); + SOLAR_FESTIVAL_LIST.add(new Festival("教师节", 9, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("九一八纪念日", 9, 18)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界阿尔茨海默病日", 9, 21)); + SOLAR_FESTIVAL_LIST.add(new Festival("烈士纪念日", 9, 30)); + SOLAR_FESTIVAL_LIST.add(new Festival("国庆节", 10, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国高血压日", 10, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("万圣节", 10, 31)); + SOLAR_FESTIVAL_LIST.add(new Festival("记者节", 11, 8)); + SOLAR_FESTIVAL_LIST.add(new Festival("全国消防日", 11, 9)); + SOLAR_FESTIVAL_LIST.add(new Festival("光棍节", 11, 11)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际大学生节", 11, 17)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界艾滋病日", 12, 1)); + SOLAR_FESTIVAL_LIST.add(new Festival("国际残疾人日", 12, 3)); + SOLAR_FESTIVAL_LIST.add(new Festival("世界人权日", 12, 10)); + SOLAR_FESTIVAL_LIST.add(new Festival("平安夜", 12, 24)); + SOLAR_FESTIVAL_LIST.add(new Festival("圣诞节", 12, 25)); + + // 部分农历节日集合 + LUNAR_FESTIVAL_LIST.clear(); + LUNAR_FESTIVAL_LIST.add(new Festival("春节", 1, 1, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("元宵节", 1, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("龙抬头", 2, 2, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("上巳节", 3, 3, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("端午节", 5, 5, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("七夕节", 7, 7, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("中元节", 7, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("地藏节", 7, 30, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("灶君诞", 8, 2, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("中秋节", 8, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("先师诞", 8, 27, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("重阳节", 9, 9, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("寒衣节", 10, 1, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("下元节", 10, 15, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("腊八节", 12, 8, false)); + LUNAR_FESTIVAL_LIST.add(new Festival("小年", 12, 23, false)); + // LUNAR_FESTIVAL_LIST.add(new Festival("除夕", 12, 30, false)); // 除夕得判断是 29 还是 30 需要特殊判断 + } + + // ========== + // = 内部方法 = + // ========== + + private static int getBitInt( + int data, + int length, + int shift + ) { + return (data & (((1 << length) - 1) << shift)) >> shift; + } + + private static long solarToInt( + int y, + int m, + int d + ) { + m = (m + 9) % 12; + y = y - m / 10; + return 365L * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 + (d - 1); + } + + private static int[] solarFromInt(long g) { + long y = (10000 * g + 14780) / 3652425; + long ddd = g - (365 * y + y / 4 - y / 100 + y / 400); + if (ddd < 0) { + y--; + ddd = g - (365 * y + y / 4 - y / 100 + y / 400); + } + long mi = (100 * ddd + 52) / 3060; + long mm = (mi + 2) % 12 + 1; + y = y + (mi + 2) / 12; + long dd = ddd - (mi * 306 + 5) / 10 + 1; + int[] solar = new int[3]; + solar[0] = (int) y; + solar[1] = (int) mm; + solar[2] = (int) dd; + return solar; + } + + /** + * 拆分二十四节气 ( 公历 ) 时间 + * @param index 二十四节气索引 + * @return [0] 开始日 [1] 结束日 + */ + private static int[] solarTermsDateSplit(final int index) { + try { + String date = SOLAR_TERMS_DATE[index]; + String[] splits = date.substring(date.indexOf('.') + 1).split("-"); + return new int[]{ + ConvertUtils.toInt(splits[0]), + ConvertUtils.toInt(splits[1]) + }; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "solarTermsDateSplit"); + } + return null; + } + + // ======== + // = 实体类 = + // ======== + + /** + * detail: 公历农历实体类 + * @author Ttt + */ + public static class SolarLunar { + + // 公历年 + public final int year; + // 公历月 + public final int month; + // 公历日 + public final int day; + // 公历农历互转是否成功 + public final boolean result; + // 农历年 + public final int lunarYear; + // 农历月 + public final int lunarMonth; + // 农历日 + public final int lunarDay; + // 农历月是否闰月 + public final boolean isLunarLeap; + + /** + * 公历转农历 + * @param year 公历年 + * @param month 公历月 + * @param day 公历日 + */ + public SolarLunar( + int year, + int month, + int day + ) { + this.year = year; + this.month = month; + this.day = day; + // 公历转农历 + int[] lunars = CalendarUtils.solarToLunar(year, month, day); + result = lunars != null; + if (result) { + lunarYear = lunars[0]; + lunarMonth = lunars[1]; + lunarDay = lunars[2]; + isLunarLeap = lunars[3] == 1; + } else { + lunarYear = 0; + lunarMonth = 0; + lunarDay = 0; + isLunarLeap = false; + } + } + + /** + * 农历转公历 + * @param lunarYear 农历年 + * @param lunarMonth 农历月 + * @param lunarDay 农历日 + * @param isLunarLeap 是否闰月 + */ + public SolarLunar( + int lunarYear, + int lunarMonth, + int lunarDay, + boolean isLunarLeap + ) { + this.lunarYear = lunarYear; + this.lunarMonth = lunarMonth; + this.lunarDay = lunarDay; + this.isLunarLeap = isLunarLeap; + // 农历转公历 + int[] solars = CalendarUtils.lunarToSolar(lunarYear, lunarMonth, lunarDay, isLunarLeap); + result = solars != null; + if (result) { + year = solars[0]; + month = solars[1]; + day = solars[2]; + } else { + year = 0; + month = 0; + day = 0; + } + } + + /** + * 获取农历年份总天数 + * @return 农历年份总天数 + */ + public int getLunarYearDays() { + return CalendarUtils.getLunarYearDays(lunarYear); + } + + /** + * 获取农历年份闰月天数 + * @return 农历年份闰月天数 + */ + public int getLunarLeapDays() { + return CalendarUtils.getLunarLeapDays(lunarYear); + } + + /** + * 获取农历闰月 + * @return 农历闰月 + */ + public int getLunarLeapMonth() { + return isLunarLeap ? lunarMonth : 0; + } + + /** + * 获取农历年份与月份总天数 + * @return 农历年份与月份总天数 + */ + public int getLunarMonthDays() { + return CalendarUtils.getLunarMonthDays(lunarYear, lunarMonth); + } + + /** + * 获取干支历 + * @return 干支历 + */ + public String getLunarGanZhi() { + return CalendarUtils.getLunarGanZhi(lunarYear); + } + + /** + * 获取农历中文月份 + * @return 农历中文月份 + */ + public String getLunarMonthChinese() { + return CalendarUtils.getLunarMonthChinese(lunarMonth, isLunarLeap); + } + + /** + * 获取农历中文天数 + * @return 农历中文天数 + */ + public String getLunarDayChinese() { + return CalendarUtils.getLunarDayChinese(lunarDay); + } + + /** + * 获取二十四节气 ( 公历 ) 索引 + * @return 二十四节气 ( 公历 ) 索引 + */ + public int getSolarTermsIndex() { + return CalendarUtils.getSolarTermsIndex(month, day); + } + + /** + * 获取二十四节气 ( 公历 ) + * @return 二十四节气 ( 公历 ) + */ + public String getSolarTerms() { + return CalendarUtils.getSolarTerms(month, day); + } + + /** + * 获取二十四节气 ( 公历 ) 时间 + * @return 二十四节气 ( 公历 ) 时间 + */ + public String getSolarTermsDate() { + return CalendarUtils.getSolarTermsDate(month, day); + } + } + + // = + + /** + * detail: 节日实体类 + * @author Ttt + */ + public static class Festival + implements Comparable { + + // 节日名 + public final String name; + // 节日月份 + public final int month; + // 节日天数 + public final int day; + // 是否公历节日 + public final boolean isSolarFestival; + // 内部排序值 + private final int compareValue; + + public Festival( + String name, + int month, + int day + ) { + this(name, month, day, true); + } + + public Festival( + String name, + int month, + int day, + boolean isSolarFestival + ) { + this.name = name; + this.month = month; + this.day = day; + this.isSolarFestival = isSolarFestival; + this.compareValue = ConvertUtils.toInt(month + NumberUtils.addZero(day)); + } + + /** + * 校验是否相同节日 + * @param festival {@link Festival} + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival(final Festival festival) { + if (festival == null) return false; + return isFestival(festival.month, festival.day, festival.isSolarFestival); + } + + /** + * 校验是否相同节日 + * @param month 月份 + * @param day 天数 + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival( + final int month, + final int day + ) { + return this.month == month && this.day == day; + } + + /** + * 校验是否相同节日 + * @param month 月份 + * @param day 天数 + * @param solarFestival 是否公历节日 + * @return {@code true} yes, {@code false} no + */ + public boolean isFestival( + final int month, + final int day, + final boolean solarFestival + ) { + return this.month == month && this.day == day && this.isSolarFestival == solarFestival; + } + + @Override + public int compareTo(Festival festival) { + return Integer.compare(festival.compareValue, compareValue); + } + + @Override + public String toString() { + return StringUtils.checkValue(name) + " " + NumberUtils.addZero(month) + + NumberUtils.addZero(day); + } + } + + // ======= + // = 接口 = + // ======= + + // 节日 Hook 接口 + private static FestivalHook sFestivalHook = new FestivalHook() { + @Override + public Festival hook( + Festival festival, + int year, + int month, + int day + ) { + if (festival != null) { + if (festival.isSolarFestival) { // 公历节日 + Calendar calendar; + int monthDay; // 该月存在天数 + int sundays = 0; // 星期天累加次数 + // 月份判断 + switch (month) { + case 5: + calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + monthDay = calendar.getActualMaximum(Calendar.DATE); + for (int i = 1; i <= monthDay; i++) { + calendar.set(Calendar.DATE, i); + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) { + sundays++; + if (sundays == 2) { + if (i == day) { // 母亲节每年 5 月的第二个星期日 + return new Festival("母亲节", month, day, true); + } + return null; + } + } + } + break; + case 6: + calendar = Calendar.getInstance(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month - 1); + monthDay = calendar.getActualMaximum(Calendar.DATE); + for (int i = 1; i <= monthDay; i++) { + calendar.set(Calendar.DATE, i); + if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) { + sundays++; + if (sundays == 3) { + if (i == day) { // 父亲节最广泛的日期在每年 6 月的第三个星期日 + return new Festival("父亲节", month, day, true); + } + return null; + } + } + } + break; + } + } else { // 农历节日 + if (month == 12) { // 腊月 + if (CalendarUtils.getLunarMonthDays(year, month) == day && day != 0) { + return new Festival("除夕", month, day, false); + } + } + } + } + return null; + } + }; + + /** + * detail: 节日 Hook 接口 + * @author Ttt + *
+     *     主要用于特殊节日判断, 如 除夕 非确定天数无法直接进行添加
+     * 
+ */ + public interface FestivalHook { + + /** + * 节日 hook 判断 + * @param festival 节日信息 + * @param year 年份 + * @param month 月份 + * @param day 天数 + * @return 如果特殊判断成功则返回节日 + */ + Festival hook( + Festival festival, + int year, + int month, + int day + ); + } + + /** + * 获取节日 Hook 接口 + * @return {@link FestivalHook} + */ + public static FestivalHook getFestivalHook() { + return sFestivalHook; + } + + /** + * 设置节日 Hook 接口 + * @param festivalHook {@link FestivalHook} + */ + public static void setFestivalHook(final FestivalHook festivalHook) { + CalendarUtils.sFestivalHook = festivalHook; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ChineseUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ChineseUtils.java new file mode 100644 index 0000000000..dd3d97b90c --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ChineseUtils.java @@ -0,0 +1,311 @@ +package dev.utils.common; + +import java.math.BigDecimal; +import java.util.Random; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 中文工具类 + * @author Ttt + */ +public final class ChineseUtils { + + private ChineseUtils() { + } + + // 日志 TAG + private static final String TAG = ChineseUtils.class.getSimpleName(); + + /** + * 随机生成汉字 + * @param number 字数 + * @return 随机汉字 + */ + public static String randomWord(final int number) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < number; i++) { + builder.append(randomWord()); + } + return builder.toString(); + } + + /** + * 随机生成汉字 + * @return 一个随机汉字 + */ + public static String randomWord() { + Random random = new Random(); + int heightPos = 176 + Math.abs(random.nextInt(39)); + int lowPos = 161 + Math.abs(random.nextInt(93)); + byte[] bytes = new byte[2]; + bytes[0] = Integer.valueOf(heightPos).byteValue(); + bytes[1] = Integer.valueOf(lowPos).byteValue(); + try { + return new String(bytes, DevFinal.ENCODE.GBK); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "randomWord"); + } + return ""; + } + + /** + * 随机生成名字 + * @param surnames 姓氏数组 + * @param names 名字数组 + * @return 随机名字 + */ + public static String randomName( + final String[] surnames, + final String[] names + ) { + return randomName(surnames, names, 1); + } + + /** + * 随机生成名字 + * @param surnames 姓氏数组 + * @param names 名字数组 + * @param nameLength 名字长度 + * @return 随机名字 + */ + public static String randomName( + final String[] surnames, + final String[] names, + final int nameLength + ) { + if (surnames != null && surnames.length != 0 && names != null && names.length != 0) { + return RandomUtils.getRandom(surnames, 1) + + RandomUtils.getRandom(names, nameLength); + } + return null; + } + + // =============== + // = 数字、中文互转 = + // =============== + + // 零索引 + private static final int ZERO = 0; + // 十位数索引 + private static final int TEN_POS = 10; + // 中文 ( 数字单位 ) + private static final String[] CHN_NUMBER_UNITS = { + "零", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "百", "千", "万", "亿", "兆" + }; + // 中文大写 ( 数字单位 ) + private static final String[] CHN_NUMBER_UPPER_UNITS = { + "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖", "拾", "佰", "仟", "万", "亿", "兆" + }; + // 数字单位对应数值 + private static final double[] NUMBER_UNITS = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000, 10000, 100000000, 1000000000000D + }; + + // ============= + // = 对外公开方法 = + // ============= + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final double number, + final boolean isUpper + ) { + try { + return numberToCHNNumber( + BigDecimal.valueOf(number), + isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "numberToCHN"); + } + return null; + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final String number, + final boolean isUpper + ) { + if (number != null) { + try { + return numberToCHNNumber( + new BigDecimal(number), + isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "numberToCHN"); + } + } + return null; + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final BigDecimal number, + final boolean isUpper + ) { + return numberToCHNNumber(number, isUpper ? CHN_NUMBER_UPPER_UNITS : CHN_NUMBER_UNITS); + } + + // ========== + // = 内部方法 = + // ========== + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param bigDecimal 数值 + * @param chnUnits 中文数字单位数组 + * @return 数字中文化字符串 + */ + private static String numberToCHNNumber( + final BigDecimal bigDecimal, + final String[] chnUnits + ) { + // 防止为 null + if (bigDecimal == null) return null; + // 去除小数点 + BigDecimal number = bigDecimal.setScale(0, BigDecimal.ROUND_DOWN); + // 防止小于 1, 直接返回 0 + if (number.doubleValue() < 1) return chnUnits[ZERO]; + + StringBuilder builder = new StringBuilder(); + // 索引记录 + int unitIndex = 0; // 当前数字单位 + int beforeUnitIndex = 0; // 之前的数字单位 + // 循环处理 + for (int i = NUMBER_UNITS.length - 1; i > 0; i--) { + if (number.doubleValue() >= NUMBER_UNITS[i]) { + final int multiple = (int) (number.doubleValue() / NUMBER_UNITS[i]); // 倍数 + number = number.subtract(BigDecimal.valueOf(multiple * NUMBER_UNITS[i])); // 递减 + // 如果倍数大于万倍, 则直接递归 + if (multiple >= 10000) { + // 拼接数值 + builder.append(numberToCHNNumber(new BigDecimal(multiple), chnUnits)); + builder.append(chnUnits[i]); // 数字单位 + // 判断是否需要补零 + if (unitIndex > i) { + builder.append(chnUnits[ZERO]); // 补零 + } + } else { + // 判断 兆级与亿级、亿级与万级 补零操作 + if ((i == 14 && unitIndex == 15) || (i == 13 && unitIndex == 14)) { + if (multiple < 1000) { + builder.append(chnUnits[ZERO]); // 补零 + } + } else if (unitIndex > i) { // 跨数字单位处理 + builder.append(chnUnits[ZERO]); // 补零 + } + // 拼接数值 + builder.append(thousandConvertCHN(multiple, chnUnits)); + builder.append(chnUnits[i]); // 数字单位 + } + // 保存旧的数字单位索引 + beforeUnitIndex = unitIndex; + // 保存新的数字单位索引 + unitIndex = i; + } + + double numberValue = number.doubleValue(); + // 如果位数小于万位 ( 属于千位 ), 则进行处理 + if (numberValue < 10000) { + // 判断是否需要补零 + if (unitIndex >= (TEN_POS + 3)) { + // 是否大于 1 ( 结尾零, 则不补充数字单位 ) + if (numberValue >= 1) { + if (beforeUnitIndex == 0) { + beforeUnitIndex = unitIndex; + } + // 如果旧的索引, 大于当前索引, 则补零 + if (unitIndex != 13 && (beforeUnitIndex == 14 || beforeUnitIndex == 15) + && beforeUnitIndex >= unitIndex) { // 属于亿、兆级别, 都需要补零 + builder.append(chnUnits[ZERO]); // 补零 + } else { // 当前数字单位属于万级 + if (numberValue < 1000) { + builder.append(chnUnits[ZERO]); // 补零 + } + } + } + } + // 拼接数值 + builder.append(thousandConvertCHN((int) numberValue, chnUnits)); + return builder.toString(); + } + } + return builder.toString(); + } + + /** + * 万位以下 ( 千位 ) 级别转换 + * @param value 数值 + * @param chnUnits 中文数字单位数组 + * @return 数字中文化字符串 + */ + private static String thousandConvertCHN( + final int value, + final String[] chnUnits + ) { + StringBuilder builder = new StringBuilder(); + // 转换数字 + int number = value; + // 对应数值 + int[] units = {10, 100, 1000}; + // 判断是否需要补零 + boolean[] zeros = new boolean[3]; + zeros[0] = number >= 10; // 大于等于十位, 才需要补零 + zeros[1] = number >= 100; // 大于等于百位, 才需要补零 + zeros[2] = false; // 千位数, 不需要补零 + // 循环处理 + for (int i = 2; i >= 0; i--) { + if (number >= units[i]) { + int multiple = (number / units[i]); + number -= multiple * units[i]; + // 拼接数值 + builder.append(chnUnits[multiple]); // 个位数 + builder.append(chnUnits[TEN_POS + i]); // 数字单位 + // 进行改变处理 + zeros[i] = false; + } + // 补零判断处理 + if (number >= 1 && zeros[i]) { + if (i == 1) { // 属于百位 + builder.append(chnUnits[ZERO]); // 补零 + } else { // 属于十位 + // 如果百位, 补零了, 个位数则不用补零 + if (!zeros[i + 1]) { + builder.append(chnUnits[ZERO]); // 补零 + } + } + } + } + // 判断最后值, 是否大于 1 ( 结尾零, 则不补充数字单位 ) + if (number >= 1) { + builder.append(chnUnits[number]); + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ClassUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ClassUtils.java new file mode 100644 index 0000000000..ef052d57de --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ClassUtils.java @@ -0,0 +1,331 @@ +package dev.utils.common; + +import java.lang.reflect.Constructor; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Collection; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 类 ( Class ) 工具类 + * @author Ttt + */ +public final class ClassUtils { + + private ClassUtils() { + } + + // 日志 TAG + private static final String TAG = ClassUtils.class.getSimpleName(); + + /** + * 根据类获取对象, 不再必须一个无参构造 + * @param clazz {@link Class} + * @param 泛型 + * @return 初始化后的对象 + */ + public static T newInstance(final Class clazz) { + if (clazz == null) return null; + try { + Constructor[] cons = clazz.getDeclaredConstructors(); + for (Constructor c : cons) { + Class[] cls = c.getParameterTypes(); + if (cls.length == 0) { + c.setAccessible(true); + return (T) c.newInstance(); + } else { + Object[] objects = new Object[cls.length]; + for (int i = 0; i < cls.length; i++) { + objects[i] = getDefaultPrimitiveValue(cls[i]); + } + c.setAccessible(true); + return (T) c.newInstance(objects); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + } + return null; + } + + /** + * 获取 Class 原始类型值 + * @param clazz {@link Class} + * @return 原始类型值 + */ + public static Object getDefaultPrimitiveValue(final Class clazz) { + if (clazz != null && clazz.isPrimitive()) { + return clazz == boolean.class ? false : 0; + } + return null; + } + + // = + + /** + * 获取 Object Class + * @param object {@link Object} + * @return Object Class + */ + public static Class getClass(final Object object) { + return (object != null) ? object.getClass() : null; + } + + /** + * 获取 Type Class + * @param type {@link Type} + * @return Type Class + */ + public static Class getClass(final Type type) { + if (type == null) return null; + try { + if (type.getClass() == Class.class) { + return (Class) type; + } + if (type instanceof ParameterizedType) { + return getClass(((ParameterizedType) type).getRawType()); + } + if (type instanceof TypeVariable) { + Type boundType = ((TypeVariable) type).getBounds()[0]; + return (Class) boundType; + } + if (type instanceof WildcardType) { + Type[] upperBounds = ((WildcardType) type).getUpperBounds(); + if (upperBounds.length == 1) { + return getClass(upperBounds[0]); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getClass"); + } + return Object.class; + } + + /** + * 判断 Class 是否为原始类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrimitive(final Class clazz) { + return (clazz != null && clazz.isPrimitive()); + } + + /** + * 判断是否 Collection 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isCollection(final Class clazz) { + return (clazz != null && Collection.class.isAssignableFrom(clazz)); + } + + /** + * 判断是否 Map 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isMap(final Class clazz) { + return (clazz != null && Map.class.isAssignableFrom(clazz)); + } + + /** + * 判断是否 Array 类型 + * @param clazz {@link Class} + * @return {@code true} yes, {@code false} no + */ + public static boolean isArray(final Class clazz) { + return (clazz != null && clazz.isArray()); + } + + /** + * 判断是否参数类型 + * @param type {@link Type} + * @return {@code true} yes, {@code false} no + */ + public static boolean isGenericParamType(final Type type) { + if (type != null) { + if (type instanceof ParameterizedType) { + return true; + } + if (type instanceof Class) { + try { + Type superType = ((Class) type).getGenericSuperclass(); + return superType != Object.class && isGenericParamType(superType); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isGenericParamType"); + } + } + } + return false; + } + + /** + * 获取参数类型 + * @param type {@link Type} + * @return {@link Type} + */ + public static Type getGenericParamType(final Type type) { + if (type != null) { + if (type instanceof ParameterizedType) { + return type; + } + if (type instanceof Class) { + try { + return getGenericParamType(((Class) type).getGenericSuperclass()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericParamType"); + } + } + } + return type; + } + + // = + + /** + * 获取父类泛型类型 + * @param object Object + * @return 泛型类型 + */ + public static Type getGenericSuperclass(final Object object) { + return getGenericSuperclass(object, 0); + } + + /** + * 获取父类泛型类型 + * @param object Object + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericSuperclass( + final Object object, + final int pos + ) { + return getGenericSuperclass(getClass(object), pos); + } + + // = + + /** + * 获取父类泛型类型 + * @param clazz {@link Class} + * @return 泛型类型 + */ + public static Type getGenericSuperclass(final Class clazz) { + return getGenericSuperclass(clazz, 0); + } + + /** + * 获取父类泛型类型 + * @param clazz {@link Class} + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericSuperclass( + final Class clazz, + final int pos + ) { + if (clazz != null && pos >= 0) { + try { + return ((ParameterizedType) clazz.getGenericSuperclass()) + .getActualTypeArguments()[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericSuperclass"); + } + } + return null; + } + + // = + + /** + * 获取接口泛型类型 + * @param object Object + * @param interfaceClazz 接口 Class + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Object object, + final Class interfaceClazz + ) { + return getGenericInterfaces(object, interfaceClazz, 0); + } + + /** + * 获取接口泛型类型 + * @param object Object + * @param interfaceClazz 接口 Class + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Object object, + final Class interfaceClazz, + final int pos + ) { + return getGenericInterfaces(getClass(object), interfaceClazz, pos); + } + + // = + + /** + * 获取接口泛型类型 + * @param clazz {@link Class} + * @param interfaceClazz 接口 Class + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Class clazz, + final Class interfaceClazz + ) { + return getGenericInterfaces(clazz, interfaceClazz, 0); + } + + /** + * 获取接口泛型类型 + * @param clazz {@link Class} + * @param interfaceClazz 接口 Class + * @param pos 泛型参数索引 + * @return 泛型类型 + */ + public static Type getGenericInterfaces( + final Class clazz, + final Class interfaceClazz, + final int pos + ) { + if (clazz != null && interfaceClazz != null && pos >= 0) { + try { + // 获取接口类名 + String iName = interfaceClazz.getName(); + if ("".equals(iName)) return null; + // 获取接口泛型类型数组 + Type[] types = clazz.getGenericInterfaces(); + // 循环类型 + for (Type type : types) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + // 判断是否属于该接口 + String rawType = parameterizedType.getRawType().toString(); + // 判断接口包名是否一致 + if (rawType.startsWith("interface ")) { + if (rawType.equals("interface " + iName)) { + return parameterizedType.getActualTypeArguments()[pos]; + } + } else { + if (rawType.equals(iName)) { + return parameterizedType.getActualTypeArguments()[pos]; + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericInterfaces"); + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/CloneUtils.java b/lib/DevJava/src/main/java/dev/utils/common/CloneUtils.java new file mode 100644 index 0000000000..1f3a3cd549 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/CloneUtils.java @@ -0,0 +1,124 @@ +package dev.utils.common; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 克隆工具类 + * @author Ttt + */ +public final class CloneUtils { + + private CloneUtils() { + } + + // 日志 TAG + private static final String TAG = CloneUtils.class.getSimpleName(); + + /** + * 进行克隆 + * @param data Object implements {@link Serializable} + * @param 泛型 + * @return 克隆后的对象 + */ + public static T deepClone(final Serializable data) { + if (data == null) return null; + return (T) ConvertUtils.bytesToObject(serializableToBytes(data)); + } + + /** + * 通过序列化实体类, 获取对应的 byte[] 数据 + * @param serializable Object implements {@link Serializable} + * @return 克隆后 byte[] + */ + public static byte[] serializableToBytes(final Serializable serializable) { + if (serializable == null) return null; + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(serializable); + return baos.toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "serializableToBytes"); + return null; + } finally { + CloseUtils.closeIOQuietly(oos); + } + } + + // = + + /** + * 进行克隆 + * @param map 存储集合 + * @param values 需要克隆的数据源 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean deepClone( + final Map map, + final Map values + ) { + if (map != null && values != null && values.size() > 0) { + Iterator> iterator = values.entrySet().iterator(); + while (iterator.hasNext()) { + try { + Map.Entry entry = iterator.next(); + // 获取 key + K key = entry.getKey(); + // 克隆对象 + V cloneObj = (V) ConvertUtils.bytesToObject( + serializableToBytes((Serializable) entry.getValue()) + ); + if (cloneObj != null) { + // 保存到集合 + map.put(key, cloneObj); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deepClone"); + } + } + return true; + } + return false; + } + + /** + * 进行克隆 + * @param collection 存储集合 + * @param values 需要克隆的数据源 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean deepClone( + final Collection collection, + final Collection values + ) { + if (collection != null && values != null && values.size() > 0) { + Iterator iterator = values.iterator(); + while (iterator.hasNext()) { + try { + // 克隆对象 + T cloneObj = (T) ConvertUtils.bytesToObject( + serializableToBytes((Serializable) iterator.next()) + ); + if (cloneObj != null) { + collection.add(cloneObj); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deepClone"); + } + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/CloseUtils.java b/lib/DevJava/src/main/java/dev/utils/common/CloseUtils.java new file mode 100644 index 0000000000..03637fccf9 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/CloseUtils.java @@ -0,0 +1,159 @@ +package dev.utils.common; + +import java.io.Closeable; +import java.io.Flushable; +import java.io.OutputStream; +import java.io.Writer; + +import dev.utils.JCLogUtils; + +/** + * detail: 关闭 ( IO 流 ) 工具类 + * @author Ttt + */ +public final class CloseUtils { + + private CloseUtils() { + } + + // 日志 TAG + private static final String TAG = CloseUtils.class.getSimpleName(); + + /** + * 关闭 IO + * @param closeables Closeable[] + */ + public static void closeIO(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "closeIO"); + } + } + } + } + + /** + * 安静关闭 IO + * @param closeables Closeable[] + */ + public static void closeIOQuietly(final Closeable... closeables) { + if (closeables == null) return; + for (Closeable closeable : closeables) { + if (closeable != null) { + try { + closeable.close(); + } catch (Exception ignore) { + } + } + } + } + + // = + + /** + * 将缓冲区数据输出 + * @param flushables Flushable[] + */ + public static void flush(final Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flush"); + } + } + } + } + + /** + * 安静将缓冲区数据输出 + * @param flushables Flushable[] + */ + public static void flushQuietly(final Flushable... flushables) { + if (flushables == null) return; + for (Flushable flushable : flushables) { + if (flushable != null) { + try { + flushable.flush(); + } catch (Exception ignore) { + } + } + } + } + + // = + + /** + * 将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + */ + public static void flushCloseIO(final OutputStream outputStream) { + if (outputStream == null) return; + try { + outputStream.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + try { + outputStream.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param outputStream {@link OutputStream} + */ + public static void flushCloseIOQuietly(final OutputStream outputStream) { + if (outputStream == null) return; + try { + outputStream.flush(); + } catch (Exception ignored) { + } + try { + outputStream.close(); + } catch (Exception ignored) { + } + } + + /** + * 将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + */ + public static void flushCloseIO(final Writer writer) { + if (writer == null) return; + try { + writer.flush(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + try { + writer.close(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "flushCloseIO"); + } + } + + /** + * 安静将缓冲区数据输出并关闭流 + * @param writer {@link Writer} + */ + public static void flushCloseIOQuietly(final Writer writer) { + if (writer == null) return; + try { + writer.flush(); + } catch (Exception ignored) { + } + try { + writer.close(); + } catch (Exception ignored) { + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/CollectionUtils.java b/lib/DevJava/src/main/java/dev/utils/common/CollectionUtils.java new file mode 100644 index 0000000000..beee45faac --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/CollectionUtils.java @@ -0,0 +1,2183 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import dev.utils.JCLogUtils; + +/** + * detail: 集合工具类 ( Collection - List、Set、Queue ) 等 + * @author Ttt + *
+ *     // 升序
+ *     Collections.sort(list);
+ *     // 降序
+ *     Collections.sort(list, Collections.reverseOrder());
+ *     // 逆序
+ *     Collections.reverse(list);
+ *     // 创建不可修改集合
+ *     Collections.unmodifiableList()
+ *     Arrays.asList()
+ * 
+ */ +public final class CollectionUtils { + + private CollectionUtils() { + } + + // 日志 TAG + private static final String TAG = CollectionUtils.class.getSimpleName(); + + // ============== + // = Collection = + // ============== + + /** + * 判断 Collection 是否为 null + * @param collection {@link Collection} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Collection collection) { + return (collection == null || collection.size() == 0); + } + + /** + * 判断 Collection 是否不为 null + * @param collection {@link Collection} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Collection collection) { + return (collection != null && collection.size() != 0); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取 Collection 长度 + * @param collection {@link Collection} + * @return 如果 Collection 为 null, 则返回默认长度, 如果不为 null, 则返回 collection.size() + */ + public static int length(final Collection collection) { + return length(collection, 0); + } + + /** + * 获取 Collection 长度 + * @param collection {@link Collection} + * @param defaultLength 集合为 null 默认长度 + * @return 如果 Collection 为 null, 则返回 defaultLength, 如果不为 null, 则返回 collection.size() + */ + public static int length( + final Collection collection, + final int defaultLength + ) { + return collection != null ? collection.size() : defaultLength; + } + + // = + + /** + * 获取长度 Collection 是否等于期望长度 + * @param collection {@link Collection} + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Collection collection, + final int length + ) { + return collection != null && collection.size() == length; + } + + // = + + /** + * 判断 Collection 长度是否大于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThan( + final Collection collection, + final int length + ) { + return collection != null && collection.size() > length; + } + + /** + * 判断 Collection 长度是否大于等于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThanOrEqual( + final Collection collection, + final int length + ) { + return collection != null && collection.size() >= length; + } + + // = + + /** + * 判断 Collection 长度是否小于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThan( + final Collection collection, + final int length + ) { + return collection != null && collection.size() < length; + } + + /** + * 判断 Collection 长度是否小于等于指定长度 + * @param collection {@link Collection} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThanOrEqual( + final Collection collection, + final int length + ) { + return collection != null && collection.size() <= length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取 Collection 数组长度总和 + * @param collections Collection[] + * @return Collection 数组长度总和 + */ + public static int getCount(final Collection... collections) { + if (collections == null) return 0; + int count = 0; + for (Collection collection : collections) { + count += length(collection); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取数据 + * @param collection {@link Collection} + * @param pos 索引 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final Collection collection, + final int pos + ) { + if (collection != null) { + // 防止索引为负数 + if (pos < 0) { + return null; + } + if (collection instanceof List) { + try { + return (T) ((List) collection).get(pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + } else { + try { + return (T) collection.toArray()[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get %s", pos); + } + } + } + return null; + } + + /** + * 获取第一条数据 + * @param collection {@link Collection} + * @param 泛型 + * @return 索引为 0 的值 + */ + public static T getFirst(final Collection collection) { + return get(collection, 0); + } + + /** + * 获取最后一条数据 + * @param collection {@link Collection} + * @param 泛型 + * @return 索引 length - 1 的值 + */ + public static T getLast(final Collection collection) { + return get(collection, length(collection) - 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应索引的值 + */ + public static T get( + final Collection collection, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (collection != null) { + if (notNull && value == null) { + return null; + } + try { + // 保存当前临时次数 + int temp = number; + // 转换数组 + T[] arrays = (T[]) collection.toArray(); + // 进行循环判断 + for (int i = 0, len = arrays.length; i < len; i++) { + T t = arrays[i]; + // 判断值是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return arrays[i + offset]; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 根据指定 value 获取 value 所在位置 + 偏移量的索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param offset 偏移量 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number, + final boolean notNull, + final int offset + ) { + if (collection != null) { + if (notNull && value == null) { + return -1; + } + try { + // 保存当前临时次数 + int temp = number; + // 转换数组 + T[] arrays = (T[]) collection.toArray(); + // 进行循环判断 + for (int i = 0, len = arrays.length; i < len; i++) { + T t = arrays[i]; + // 判断值是否一样 + if (equals(t, value)) { + if (temp - 1 < 0) { + return i + offset; + } + temp--; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPosition"); + } + } + return -1; + } + + // = + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value + ) { + return getPosition(collection, value, 0, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number + ) { + return getPosition(collection, value, number, false, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final boolean notNull + ) { + return getPosition(collection, value, 0, notNull, 0); + } + + /** + * 根据指定 value 获取索引 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应的索引 + */ + public static int getPosition( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return getPosition(collection, value, number, notNull, 0); + } + + // = + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final Collection collection, + final T value + ) { + return getPosition(collection, value, 0, true, 0); + } + + /** + * 根据指定 value 获取索引, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应的索引 + */ + public static int getPositionNotNull( + final Collection collection, + final T value, + final int number + ) { + return getPosition(collection, value, number, true, 0); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value + ) { + return get(collection, value, 0, false, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, false, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final boolean notNull + ) { + return get(collection, value, 0, notNull, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNext( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return get(collection, value, number, notNull, 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNextNotNull( + final Collection collection, + final T value + ) { + return get(collection, value, 0, true, 1); + } + + /** + * 根据指定 value 获取 value 所在位置的下一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getNextNotNull( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, true, 1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value + ) { + return get(collection, value, 0, false, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, false, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final boolean notNull + ) { + return get(collection, value, 0, notNull, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值 + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param notNull 是否不允许值为 null + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPrevious( + final Collection collection, + final T value, + final int number, + final boolean notNull + ) { + return get(collection, value, number, notNull, -1); + } + + // = + + /** + * 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPreviousNotNull( + final Collection collection, + final T value + ) { + return get(collection, value, 0, true, -1); + } + + /** + * 根据指定 value 获取 value 所在位置的上一个值, 不允许值为 null + * @param collection {@link Collection} + * @param value 值 + * @param number 符合条件次数 ( 从 0 开始, 0 = 1 ) + * @param 泛型 + * @return 对应索引的值 + */ + public static T getPreviousNotNull( + final Collection collection, + final T value, + final int number + ) { + return get(collection, value, number, true, -1); + } + + // ========== + // = 添加数据 = + // ========== + + /** + * 添加一条数据 + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final Collection collection, + final T value + ) { + return add(collection, value, false); + } + + /** + * 添加一条数据 ( value 不允许为 null ) + * @param collection {@link Collection} + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addNotNull( + final Collection collection, + final T value + ) { + return add(collection, value, true); + } + + /** + * 添加一条数据 + * @param collection {@link Collection} + * @param value 值 + * @param notNull 是否不允许添加 null 数据 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final Collection collection, + final T value, + final boolean notNull + ) { + if (collection != null) { + if (notNull) { + if (value != null) { + try { + return collection.add(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add notNull"); + } + } + } else { + try { + return collection.add(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add"); + } + } + } + return false; + } + + // = + + /** + * 添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final Collection collection, + final Collection values + ) { + return addAll(collection, values, false); + } + + /** + * 添加集合数据 ( values 内的值不允许为 null ) + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAllNotNull( + final Collection collection, + final Collection values + ) { + return addAll(collection, values, true); + } + + /** + * 添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final Collection collection, + final Collection values, + final boolean notNull + ) { + if (collection != null && values != null) { + if (notNull) { + try { + for (T value : values) { + if (value != null) { + collection.add(value); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll notNull"); + } + } else { + try { + return collection.addAll(values); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll"); + } + } + } + return false; + } + + // = + + /** + * 移除全部数据并添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAll( + final Collection collection, + final Collection values + ) { + return clearAndAddAll(collection, values, false); + } + + /** + * 移除全部数据并添加集合数据 ( values 内的值不允许为 null ) + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAllNotNull( + final Collection collection, + final Collection values + ) { + return clearAndAddAll(collection, values, true); + } + + /** + * 移除全部数据并添加集合数据 + * @param collection {@link Collection} + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAndAddAll( + final Collection collection, + final Collection values, + final boolean notNull + ) { + clearAll(collection); + return addAll(collection, values, notNull); + } + + // ========================= + // = 添加数据到指定索引 (List) = + // ========================= + + /** + * 添加一条数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final int index, + final List list, + final T value + ) { + return add(index, list, value, false); + } + + /** + * 添加一条数据到指定索引后 ( value 不允许为 null ) + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addNotNull( + final int index, + final List list, + final T value + ) { + return add(index, list, value, true); + } + + /** + * 添加一条数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param value 值 + * @param notNull 是否不允许添加 null 数据 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean add( + final int index, + final List list, + final T value, + final boolean notNull + ) { + if (list != null) { + if (notNull) { + if (value != null) { + try { + list.add(index, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add notNull"); + } + } + } else { + try { + list.add(index, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "add"); + } + } + } + return false; + } + + // = + + /** + * 添加集合数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final int index, + final List list, + final List values + ) { + return addAll(index, list, values, false); + } + + /** + * 添加集合数据到指定索引后 ( values 内的值不允许为 null ) + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAllNotNull( + final int index, + final List list, + final List values + ) { + return addAll(index, list, values, true); + } + + /** + * 添加集合数据到指定索引后 + * @param index 索引 + * @param list 集合 + * @param values 准备添加的值 ( 集合 ) + * @param notNull 是否不允许添加 null 值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean addAll( + final int index, + final List list, + final List values, + final boolean notNull + ) { + if (list != null && values != null) { + if (notNull) { + try { + List tempList = new ArrayList<>(); + for (T value : values) { + if (value != null) { + tempList.add(value); + } + } + // 添加到集合中 + list.addAll(index, tempList); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll notNull"); + } + } else { + try { + list.addAll(index, values); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "addAll"); + } + } + } + return false; + } + + // ========== + // = 删除数据 = + // ========== + + /** + * 移除一条数据 + * @param collection {@link Collection} + * @param value 准备删除的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + return collection.remove(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param list 集合 + * @param pos 准备删除的索引 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static T remove( + final List list, + final int pos + ) { + if (list != null) { + // 防止索引为负数 + if (pos < 0) { + return null; + } + try { + return list.remove(pos); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return null; + } + + /** + * 移除集合数据 + * @param collection {@link Collection} + * @param values 准备删除的集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean removeAll( + final Collection collection, + final Collection values + ) { + if (collection != null && values != null) { + try { + return collection.removeAll(values); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeAll"); + } + } + return false; + } + + // = + + /** + * 清空集合中符合指定 value 的全部数据 + * @param collection {@link Collection} + * @param value 准备对比移除的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clear( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + T t = iterator.next(); + // 判断值是否一样 + if (equals(t, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clear"); + } + } + return false; + } + + /** + * 保留集合中符合指定 value 的全部数据 + * @param collection {@link Collection} + * @param value 准备对比保留的值 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearNotBelong( + final Collection collection, + final T value + ) { + if (collection != null) { + try { + Iterator iterator = collection.iterator(); + while (iterator.hasNext()) { + T t = iterator.next(); + // 判断值是否不一样 ( 移除不一样的 ) + if (!equals(t, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearNotBelong"); + } + } + return false; + } + + /** + * 清空集合全部数据 + * @param collection {@link Collection} + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearAll(final Collection collection) { + if (collection != null) { + try { + collection.clear(); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearAll"); + } + } + return false; + } + + /** + * 清空集合中为 null 的值 + * @param collection {@link Collection} + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean clearNull(final Collection collection) { + return clear(collection, null); + } + + // ================= + // = 判断集合是否相同 = + // ================= + + /** + * 判断两个集合是否相同 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEqualCollection( + final Collection collection1, + final Collection collection2 + ) { + // 数据长度 + int len; + // 判断数据是否相同 + if (collection1 != null && collection2 != null && (len = collection1.size()) == collection2.size()) { + if (len == 0) return true; + + // 进行判断类型, 如果不同, 则直接跳过不处理 + if (!collection1.getClass().getName().equals(collection2.getClass().getName())) { + return false; + } + + // 如果集合相等, 直接跳过 + if (collection1.equals(collection2)) { + return true; + } + + T[] arrays1, arrays2; + try { + // 转换数组, 防止异常 + arrays1 = (T[]) collection1.toArray(); + arrays2 = (T[]) collection2.toArray(); + } catch (Exception e) { + return false; + } + for (int i = 0; i < len; i++) { + // 判断两个值是否一样 + boolean equals = equals(arrays1[i], arrays2[i]); + // 如果不一样, 直接 return + if (!equals) { + return false; + } + } + return true; + } + // 如果不符合条件, 防止两个集合都是为 null + return (collection1 == null && collection2 == null); + } + + /** + * 判断多个集合是否相同 + * @param collections 集合数组 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEqualCollections(final Collection... collections) { + if (collections != null && collections.length >= 2) { + // 获取长度 + int len = collections.length; + // 设置临时值为第一个 + Collection temp = collections[0]; + // 进行判断 + for (int i = 1; i < len; i++) { + // 判断是否一样 + boolean equalCollection = isEqualCollection(temp, collections[i]); + // 如果不一样, 直接返回 + if (!equalCollection) { + return false; + } + } + return true; + } + return false; + } + + // ========== + // = 集合处理 = + // ========== + + // ======= + // = 并集 = + // ======= + + /** + * 两个集合并集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 并集集合 + */ + public static Collection union( + final Collection collection1, + final Collection collection2 + ) { + if (collection1 != null && collection2 != null) { + try { + // 初始化新的集合, 默认保存第一个集合的数据 + Set sets = new LinkedHashSet<>(collection1); + sets.addAll(collection2); + // 返回集合 + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "union"); + } + return null; + } else if (collection1 != null) { + return collection1; + } + return collection2; + } + + /** + * 多个集合并集处理 + * @param collections 集合数组 + * @param 泛型 + * @return 并集集合 + */ + public static Collection unions(final Collection... collections) { + if (collections != null) { + int len = collections.length; + if (len >= 2) { + try { + // 保存第一个集合 + Set sets = new LinkedHashSet<>(); + // 防止集合为 null + if (collections[0] != null) { + sets.addAll(collections[0]); + } + // 进行循环处理 + for (int i = 1; i < len; i++) { + // 获取集合值 + Collection value = collections[i]; + if (value != null) { + sets.addAll(value); + } + } + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unions"); + } + return null; + } + return collections[0]; + } + return null; + } + + // ======= + // = 交集 = + // ======= + + /** + * 两个集合交集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 交集集合 + */ + public static Collection intersection( + final Collection collection1, + final Collection collection2 + ) { + if (collection1 != null && collection2 != null) { + try { + // 专门用于返回中转的集合 + Set sets = new LinkedHashSet<>(); + + // 初始化新的集合, 默认保存第一个集合的数据 + Set setsTemp = new LinkedHashSet<>(collection1); + // 循环第二个集合 + for (T value : collection2) { + // 判断是否存在, 存在则保存 + if (setsTemp.contains(value)) { + sets.add(value); + } + } + // 返回集合 + return sets; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intersection"); + } + return null; + } else if (collection1 != null) { + return collection1; + } + return collection2; + } + + /** + * 两个集合交集的补集处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 交集集合 + */ + public static Collection disjunction( + final Collection collection1, + final Collection collection2 + ) { + try { + // 先进行并集处理 + Collection unionC = union(collection1, collection2); + // 在进行交集处理 + Collection intersectionC = intersection(collection1, collection2); + // 再进行移除处理 + if (unionC != null && intersectionC != null) { + try { + // 移除数据 + unionC.removeAll(intersectionC); + return unionC; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "disjunction"); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "disjunction"); + } + return null; + } + + /** + * 两个集合差集 ( 扣除 ) 处理 + * @param collection1 第一个集合 + * @param collection2 第二个集合 + * @param 泛型 + * @return 差集 ( 扣除 ) 集合 + */ + public static Collection subtract( + final Collection collection1, + final Collection collection2 + ) { + try { + // 先进行交集处理 + Collection intersectionC = intersection(collection1, collection2); + if (intersectionC != null) { + // 保存到新的集合中 + Set sets = new LinkedHashSet<>(collection1); + // 进行移除 + sets.removeAll(intersectionC); + // 返回集合 + return sets; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subtract"); + } + return null; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // ========== + // = 转换处理 = + // ========== + + /** + * 转换数组 to Object + * @param collection {@link Collection} + * @param 泛型 + * @return 转换后的数组 + */ + public static Object[] toArray(final Collection collection) { + if (collection != null) { + try { + return collection.toArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toArray"); + } + } + return null; + } + + /** + * 转换数组 to T + * @param collection {@link Collection} + * @param 泛型 + * @return 转换后的泛型数组 + */ + public static T[] toArrayT(final Collection collection) { + if (collection != null) { + try { + return new ArrayWithTypeToken(collection).create(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toArrayT"); + } + } + return null; + } + + /** + * 集合翻转处理 + * @param collection {@link Collection} + * @param 泛型 + * @return 翻转后的集合 + */ + public static Collection reverse(final Collection collection) { + try { + // 返回集合 + List lists = new ArrayList<>(); + // 转换数据 + T[] arrays = (T[]) collection.toArray(); + // 循环处理 + for (int i = arrays.length - 1; i >= 0; i--) { + lists.add(arrays[i]); + } + // 保存新的数据 + return lists; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "reverse"); + } + return null; + } + + // ============ + // = 最小值索引 = + // ============ + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexI(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Integer temp = list.get(index); + for (int i = 1; i < len; i++) { + Integer value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexL(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Long temp = list.get(index); + for (int i = 1; i < len; i++) { + Long value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexF(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Float temp = list.get(index); + for (int i = 1; i < len; i++) { + Float value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最小值索引 + * @param list 集合 + * @return 最小值索引 + */ + public static int getMinimumIndexD(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Double temp = list.get(index); + for (int i = 1; i < len; i++) { + Double value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value < temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + // ============ + // = 最大值索引 = + // ============ + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexI(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Integer temp = list.get(index); + for (int i = 1; i < len; i++) { + Integer value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexL(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Long temp = list.get(index); + for (int i = 1; i < len; i++) { + Long value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexF(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Float temp = list.get(index); + for (int i = 1; i < len; i++) { + Float value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + /** + * 获取集合中最大值索引 + * @param list 集合 + * @return 最大值索引 + */ + public static int getMaximumIndexD(final List list) { + if (list != null) { + int len = list.size(); + if (len > 0) { + int index = 0; + Double temp = list.get(index); + for (int i = 1; i < len; i++) { + Double value = list.get(i); + if (value != null) { + if (temp == null) { + index = i; + temp = value; + } + if (value > temp) { + index = i; + temp = value; + } + } + } + if (temp == null) { + return -1; + } + return index; + } + } + return -1; + } + + // ============ + // = 获取最小值 = + // ============ + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static int getMinimumI(final List list) { + try { + return list.get(getMinimumIndexI(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumI"); + } + return 0; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static long getMinimumL(final List list) { + try { + return list.get(getMinimumIndexL(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumL"); + } + return 0L; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static float getMinimumF(final List list) { + try { + return list.get(getMinimumIndexF(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumF"); + } + return 0F; + } + + /** + * 获取集合中最小值 + * @param list 集合 + * @return 最小值 + */ + public static double getMinimumD(final List list) { + try { + return list.get(getMinimumIndexD(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMinimumD"); + } + return 0D; + } + + // ============ + // = 获取最大值 = + // ============ + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static int getMaximumI(final List list) { + try { + return list.get(getMaximumIndexI(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumI"); + } + return 0; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static long getMaximumL(final List list) { + try { + return list.get(getMaximumIndexL(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumL"); + } + return 0L; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static float getMaximumF(final List list) { + try { + return list.get(getMaximumIndexF(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumF"); + } + return 0F; + } + + /** + * 获取集合中最大值 + * @param list 集合 + * @return 最大值 + */ + public static double getMaximumD(final List list) { + try { + return list.get(getMaximumIndexD(list)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMaximumD"); + } + return 0D; + } + + // ============= + // = 计算集合总和 = + // ============= + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static int sumlistI(final List lists) { + return sumlistI(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int end + ) { + return sumlistI(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int end, + final int extra + ) { + return sumlistI(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static int sumlistI( + final List lists, + final int start, + final int end, + final int extra + ) { + int total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistI"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static long sumlistL(final List lists) { + return sumlistL(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int end + ) { + return sumlistL(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int end, + final long extra + ) { + return sumlistL(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static long sumlistL( + final List lists, + final int start, + final int end, + final long extra + ) { + long total = 0L; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistL"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static float sumlistF(final List lists) { + return sumlistF(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int end + ) { + return sumlistF(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int end, + final float extra + ) { + return sumlistF(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static float sumlistF( + final List lists, + final int start, + final int end, + final float extra + ) { + float total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistF"); + } + } + } + return total; + } + + // = + + /** + * 计算集合总和 + * @param lists 集合 + * @return 集合总和 + */ + public static double sumlistD(final List lists) { + return sumlistD(lists, 0, length(lists), 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int end + ) { + return sumlistD(lists, 0, end, 0); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int end, + final double extra + ) { + return sumlistD(lists, 0, end, extra); + } + + /** + * 计算集合总和 + * @param lists 集合 + * @param start 开始位置 + * @param end 结束位置 + * @param extra 额外值 + * @return 集合总和 + */ + public static double sumlistD( + final List lists, + final int start, + final int end, + final double extra + ) { + double total = 0; + if (lists != null) { + for (int i = start; i < end; i++) { + try { + total += (lists.get(i) + extra); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "sumlistD"); + } + } + } + return total; + } + + // ============ + // = 内部实现类 = + // ============ + + /** + * detail: 持有数组 TypeToken 实体类 + * @author Ttt + */ + public static class ArrayWithTypeToken { + + // 泛型数组 + private T[] array; + + public ArrayWithTypeToken(Collection collection) { + newInstance(collection); + } + + public ArrayWithTypeToken( + Class type, + int size + ) { + newInstance(type, size); + } + + /** + * 添加数据 + * @param index 索引 + * @param item 数据 + */ + public void put( + final int index, + final T item + ) { + array[index] = item; + } + + /** + * 获取对应索引的数据 + * @param index 索引 + * @return 对应索引的数据 + */ + public T get(final int index) { + return array[index]; + } + + /** + * 获取数组 + * @return 泛型数组 + */ + public T[] create() { + return array; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 创建数组方法 + * @param type 数组类型 + * @param size 数组长度 + */ + private void newInstance( + final Class type, + final int size + ) { + array = (T[]) Array.newInstance(type, size); + } + + /** + * 创建数组方法 + * @param collection 集合 + */ + private void newInstance(final Collection collection) { + // 泛型实体类 + T value = null; + // 数组 + Object[] objects = collection.toArray(); + // 获取不为 null 的泛型实体类 + for (Object object : objects) { + if (object != null) { + value = (T) object; + break; + } + } + newInstance((Class) value.getClass(), objects.length); + // 保存数据 + for (int i = 0, len = objects.length; i < len; i++) { + Object object = objects[i]; + put(i, (object != null) ? (T) object : null); + } + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ColorUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ColorUtils.java new file mode 100644 index 0000000000..13c1bb5e59 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ColorUtils.java @@ -0,0 +1,1275 @@ +package dev.utils.common; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 颜色工具类 ( 包括常用的色值 ) + * @author Ttt + *
+ *     颜色信息和转换工具
+ *     @see 
+ *     RGB 颜色空间、色调、饱和度、亮度、HSV 颜色空间详解
+ *     @see 
+ * 
+ */ +public final class ColorUtils { + + private ColorUtils() { + } + + // 日志 TAG + private static final String TAG = ColorUtils.class.getSimpleName(); + + // 透明 + public static final int TRANSPARENT = 0x00000000; + // 白色 + public static final int WHITE = 0xffffffff; + // 白色 ( 半透明 ) + public static final int WHITE_TRANSLUCENT = 0x80ffffff; + // 黑色 + public static final int BLACK = 0xff000000; + // 黑色 ( 半透明 ) + public static final int BLACK_TRANSLUCENT = 0x80000000; + // 红色 + public static final int RED = 0xffff0000; + // 红色 ( 半透明 ) + public static final int RED_TRANSLUCENT = 0x80ff0000; + // 绿色 + public static final int GREEN = 0xff00ff00; + // 绿色 ( 半透明 ) + public static final int GREEN_TRANSLUCENT = 0x8000ff00; + // 蓝色 + public static final int BLUE = 0xff0000ff; + // 蓝色 ( 半透明 ) + public static final int BLUE_TRANSLUCENT = 0x800000ff; + // 灰色 + public static final int GRAY = 0xff969696; + // 灰色 ( 半透明 ) + public static final int GRAY_TRANSLUCENT = 0x80969696; + // 天蓝 + public static final int SKYBLUE = 0xff87ceeb; + // 橙色 + public static final int ORANGE = 0xffffa500; + // 金色 + public static final int GOLD = 0xffffd700; + // 粉色 + public static final int PINK = 0xffffc0cb; + // 紫红色 + public static final int FUCHSIA = 0xffff00ff; + // 灰白色 + public static final int GRAYWHITE = 0xfff2f2f2; + // 紫色 + public static final int PURPLE = 0xff800080; + // 青色 + public static final int CYAN = 0xff00ffff; + // 黄色 + public static final int YELLOW = 0xffffff00; + // 巧克力色 + public static final int CHOCOLATE = 0xffd2691e; + // 番茄色 + public static final int TOMATO = 0xffff6347; + // 橙红色 + public static final int ORANGERED = 0xffff4500; + // 银白色 + public static final int SILVER = 0xffc0c0c0; + // 深灰色 + public static final int DKGRAY = 0xFF444444; + // 亮灰色 + public static final int LTGRAY = 0xFFCCCCCC; + // 洋红色 + public static final int MAGENTA = 0xFFFF00FF; + // 高光 + public static final int HIGHLIGHT = 0x33ffffff; + // 低光 + public static final int LOWLIGHT = 0x33000000; + + /* + * 0-255 十进值转换成十六进制, 如 255 就是 ff + * 255 * 0.x = 十进制 转 十六进制 + *

+ * 透明度 0 - 100 + * 00、19、33、4C、66、7F、99、B2、CC、E5、FF + * 透明度 5 - 95 + * 0D、26、40、59、73、8C、A6、BF、D9、F2 + */ + + static { + // 设置 Color 解析器 + setParser(new ColorInfo.ColorParser()); + } + + /** + * 获取十六进制透明度字符串 + * @param alpha 0-255 + * @return 透明度 ( 十六进制 ) 值 + */ + public static String hexAlpha(final int alpha) { + try { + if (alpha >= 0 && alpha <= 255) { + return Integer.toHexString(alpha); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hexAlpha"); + } + return null; + } + + // = + + /** + * 返回一个颜色 ARGB 色值数组 ( 返回十进制 ) + * @param color argb/rgb color + * @return ARGB 色值数组 { alpha, red, green, blue } + */ + public static int[] getARGB(final int color) { + int[] argb = new int[4]; + if (ColorUtils.isARGB(color)) { + argb[0] = alpha(color); + } else { + argb[0] = 255; + } + argb[1] = red(color); + argb[2] = green(color); + argb[3] = blue(color); + return argb; + } + + // = + + /** + * 返回一个颜色中的透明度值 ( 返回十进制 ) + * @param color argb color + * @return alpha 值 + */ + public static int alpha(final int color) { + return color >>> 24; + } + + /** + * 返回一个颜色中的透明度百分比值 + * @param color argb color + * @return alpha 百分比值 + */ + public static float alphaPercent(final int color) { + return NumberUtils.percentF(alpha(color), 255); + } + + // = + + /** + * 返回一个颜色中红色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return red 值 + */ + public static int red(final int color) { + return (color >> 16) & 0xFF; + } + + /** + * 返回一个颜色中红色的百分比值 + * @param color argb/rgb color + * @return red 百分比值 + */ + public static float redPercent(final int color) { + return NumberUtils.percentF(red(color), 255); + } + + // = + + /** + * 返回一个颜色中绿色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return green 值 + */ + public static int green(final int color) { + return (color >> 8) & 0xFF; + } + + /** + * 返回一个颜色中绿色的百分比值 + * @param color argb/rgb color + * @return green 百分比值 + */ + public static float greenPercent(final int color) { + return NumberUtils.percentF(green(color), 255); + } + + // = + + /** + * 返回一个颜色中蓝色的色值 ( 返回十进制 ) + * @param color argb/rgb color + * @return blue 值 + */ + public static int blue(final int color) { + return color & 0xFF; + } + + /** + * 返回一个颜色中蓝色的百分比值 + * @param color argb/rgb color + * @return blue 百分比值 + */ + public static float bluePercent(final int color) { + return NumberUtils.percentF(blue(color), 255); + } + + // = + + /** + * 根据对应的 red、green、blue 生成一个颜色值 + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return rgb 颜色值 + */ + public static int rgb( + final int red, + final int green, + final int blue + ) { + return 0xff000000 | (red << 16) | (green << 8) | blue; + } + + /** + * 根据对应的 red、green、blue 生成一个颜色值 + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return rgb 颜色值 + */ + public static int rgb( + final float red, + final float green, + final float blue + ) { + return 0xff000000 | + ((int) (red * 255.0F + 0.5F) << 16) | + ((int) (green * 255.0F + 0.5F) << 8) | + (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) + * @param alpha 透明度 [0-255] + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return argb 颜色值 + */ + public static int argb( + final int alpha, + final int red, + final int green, + final int blue + ) { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + /** + * 根据对应的 alpha、red、green、blue 生成一个颜色值 ( 含透明度 ) + * @param alpha 透明度 [0-255] + * @param red 红色值 [0-255] + * @param green 绿色值 [0-255] + * @param blue 蓝色值 [0-255] + * @return argb 颜色值 + */ + public static int argb( + final float alpha, + final float red, + final float green, + final float blue + ) { + return ((int) (alpha * 255.0F + 0.5F) << 24) | + ((int) (red * 255.0F + 0.5F) << 16) | + ((int) (green * 255.0F + 0.5F) << 8) | + (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 判断颜色 RGB 是否有效 + * @param color rgb color + * @return {@code true} yes, {@code false} no + */ + public static boolean isRGB(final int color) { + int red = red(color); + int green = green(color); + int blue = blue(color); + return (red <= 255 && red >= 0) && + (green <= 255 && green >= 0) && + (blue <= 255 && blue >= 0); + } + + /** + * 判断颜色 ARGB 是否有效 + * @param color argb color + * @return {@code true} yes, {@code false} no + */ + public static boolean isARGB(final int color) { + int alpha = alpha(color); + int red = red(color); + int green = green(color); + int blue = blue(color); + return (alpha <= 255 && alpha >= 0) && + (red <= 255 && red >= 0) && + (green <= 255 && green >= 0) && + (blue <= 255 && blue >= 0); + } + + // = + + /** + * 设置透明度 + * @param color argb/rgb color + * @param alpha 透明度 [0-255] + * @return argb 颜色值 + */ + public static int setAlpha( + final int color, + final int alpha + ) { + return (color & 0x00ffffff) | (alpha << 24); + } + + /** + * 设置透明度 + * @param color argb/rgb color + * @param alpha 透明度 [0-255] + * @return argb 颜色值 + */ + public static int setAlpha( + final int color, + final float alpha + ) { + return (color & 0x00ffffff) | ((int) (alpha * 255.0F + 0.5F) << 24); + } + + /** + * 改变颜色值中的红色色值 + * @param color argb/rgb color + * @param red 红色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setRed( + final int color, + final int red + ) { + return (color & 0xff00ffff) | (red << 16); + } + + /** + * 改变颜色值中的红色色值 + * @param color argb/rgb color + * @param red 红色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setRed( + final int color, + final float red + ) { + return (color & 0xff00ffff) | ((int) (red * 255.0F + 0.5F) << 16); + } + + /** + * 改变颜色值中的绿色色值 + * @param color argb/rgb color + * @param green 绿色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setGreen( + final int color, + final int green + ) { + return (color & 0xffff00ff) | (green << 8); + } + + /** + * 改变颜色值中的绿色色值 + * @param color argb/rgb color + * @param green 绿色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setGreen( + final int color, + final float green + ) { + return (color & 0xffff00ff) | ((int) (green * 255.0F + 0.5F) << 8); + } + + /** + * 改变颜色值中的蓝色色值 + * @param color argb/rgb color + * @param blue 蓝色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setBlue( + final int color, + final int blue + ) { + return (color & 0xffffff00) | blue; + } + + /** + * 改变颜色值中的蓝色色值 + * @param color argb/rgb color + * @param blue 蓝色值 [0-255] + * @return argb/rgb 颜色值 + */ + public static int setBlue( + final int color, + final float blue + ) { + return (color & 0xffffff00) | (int) (blue * 255.0F + 0.5F); + } + + // = + + /** + * 解析颜色字符串, 返回对应的颜色值 + * @param colorStr argb/rgb color String + * @return argb/rgb 颜色值 + */ + private static int innerParseColor(final String colorStr) { + if (colorStr.charAt(0) == '#') { + // Use a long to avoid rollovers on #ffXXXXXX + long color = Long.parseLong(colorStr.substring(1), 16); + if (colorStr.length() == 7) { + // Set the alpha value + color |= 0x00000000ff000000; + } else if (colorStr.length() != 9) { + throw new IllegalArgumentException("Unknown color"); + } + return (int) color; + } else { + Integer color = sColorNameMaps.get(colorStr.toLowerCase(Locale.ROOT)); + if (color != null) { + return color; + } + } + throw new IllegalArgumentException("Unknown color"); + } + + /** + * 解析颜色字符串, 返回对应的颜色值 + *
+     *     支持的格式:
+     *     #RRGGBB
+     *     #AARRGGBB
+     *     'red', 'blue', 'green', 'black', 'white', 'gray', 'cyan', 'magenta', 'yellow', 'lightgray', 'darkgray'
+     * 
+ * @param colorStr argb/rgb color String + * @return argb/rgb 颜色值 + */ + public static int parseColor(final String colorStr) { + if (colorStr != null) { + try { + return innerParseColor(colorStr); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseColor"); + } + } + return Integer.MAX_VALUE; + } + + /** + * 颜色值 转换 RGB 颜色字符串 + * @param colorInt rgb int color + * @return rgb color String + */ + public static String intToRgbString(final int colorInt) { + try { + int color = colorInt; + color = color & 0x00ffffff; + String colorStr = Integer.toHexString(color); + while (colorStr.length() < 6) { + colorStr = "0" + colorStr; + } + return "#" + colorStr; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intToRgbString"); + } + return null; + } + + /** + * 颜色值 转换 ARGB 颜色字符串 + * @param colorInt argb int color + * @return argb color String + */ + public static String intToArgbString(final int colorInt) { + try { + String colorString = Integer.toHexString(colorInt); + while (colorString.length() < 6) { + colorString = "0" + colorString; + } + while (colorString.length() < 8) { + colorString = "f" + colorString; + } + return "#" + colorString; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "intToArgbString"); + } + return null; + } + + // = + + /** + * 获取随机颜色值 + * @return 随机颜色值 + */ + public static int getRandomColor() { + return getRandomColor(true); + } + + /** + * 获取随机颜色值 + * @param supportAlpha 是否支持透明度 + * @return argb/rgb 颜色值 + */ + public static int getRandomColor(final boolean supportAlpha) { + int high = supportAlpha ? (int) (Math.random() * 0x100) << 24 : 0xFF000000; + return high | (int) (Math.random() * 0x1000000); + } + + /** + * 获取随机颜色值字符串 + * @return 随机颜色值 + */ + public static String getRandomColorString() { + return getRandomColorString(true); + } + + /** + * 获取随机颜色值字符串 + * @param supportAlpha 是否支持透明度 + * @return 随机颜色值 + */ + public static String getRandomColorString(final boolean supportAlpha) { + if (supportAlpha) { + return intToArgbString(getRandomColor(supportAlpha)); + } else { + return intToRgbString(getRandomColor(supportAlpha)); + } + } + + // = + + /** + * 判断是否为 ARGB 格式的十六进制颜色, 例如: FF990587 + * @param colorStr color String + * @return {@code true} yes, {@code false} no + */ + public static boolean judgeColorString(final String colorStr) { + if (colorStr != null && colorStr.length() == 8) { + char cc = colorStr.charAt(0); + return !(cc != '0' && cc != '1' && cc != '2' && cc != '3' && cc != '4' + && cc != '5' && cc != '6' && cc != '7' && cc != '8' && cc != '9' + && cc != 'A' && cc != 'B' && cc != 'C' && cc != 'D' && cc != 'E' + && cc != 'F' && cc != 'a' && cc != 'b' && cc != 'c' && cc != 'd' + && cc != 'e' && cc != 'f'); + } + return false; + } + + // = + + /** + * 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) + * @param colorStr color String + * @param darkValue 加深值 + * @return 加深后的颜色值 + */ + public static int setDark( + final String colorStr, + final int darkValue + ) { + int color = parseColor(colorStr); + return setDark(color, darkValue); + } + + /** + * 颜色加深 ( 单独修改 RGB 值, 不变动透明度 ) + * @param color int color + * @param darkValue 加深值 + * @return 加深后的颜色值 + */ + public static int setDark( + final int color, + final int darkValue + ) { + int red = red(color); + int green = green(color); + int blue = blue(color); + // 进行加深 ( 累减 ) + red -= darkValue; + green -= darkValue; + blue -= darkValue; + // 颜色值 + int colorTemp = color; + // 进行设置 + colorTemp = setRed(colorTemp, NumberUtils.clamp(red, 255, 0)); + colorTemp = setGreen(colorTemp, NumberUtils.clamp(green, 255, 0)); + colorTemp = setBlue(colorTemp, NumberUtils.clamp(blue, 255, 0)); + return colorTemp; + } + + /** + * 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) + * @param colorStr color String + * @param lightValue 变亮 ( 变浅 ) 值 + * @return 变亮 ( 变浅 ) 后的颜色值 + */ + public static int setLight( + final String colorStr, + final int lightValue + ) { + int color = parseColor(colorStr); + return setLight(color, lightValue); + } + + /** + * 颜色变浅, 变亮 ( 单独修改 RGB 值, 不变动透明度 ) + * @param color int color + * @param lightValue 变亮 ( 变浅 ) 值 + * @return 变亮 ( 变浅 ) 后的颜色值 + */ + public static int setLight( + final int color, + final int lightValue + ) { + int red = red(color); + int green = green(color); + int blue = blue(color); + // 进行变浅, 变亮 ( 累加 ) + red += lightValue; + green += lightValue; + blue += lightValue; + // 颜色值 + int colorTemp = color; + // 进行设置 + colorTemp = setRed(colorTemp, NumberUtils.clamp(red, 255, 0)); + colorTemp = setGreen(colorTemp, NumberUtils.clamp(green, 255, 0)); + colorTemp = setBlue(colorTemp, NumberUtils.clamp(blue, 255, 0)); + return colorTemp; + } + + /** + * 设置透明度加深 + * @param colorStr color String + * @param darkValue 加深值 + * @return 透明度加深后的颜色值 + */ + public static int setAlphaDark( + final String colorStr, + final int darkValue + ) { + int color = parseColor(colorStr); + return setAlphaDark(color, darkValue); + } + + /** + * 设置透明度加深 + * @param color int color + * @param darkValue 加深值 + * @return 透明度加深后的颜色值 + */ + public static int setAlphaDark( + final int color, + final int darkValue + ) { + int alpha = alpha(color); + // 透明度加深 + alpha += darkValue; + // 进行设置 + return setAlpha(color, NumberUtils.clamp(alpha, 255, 0)); + } + + /** + * 设置透明度变浅 + * @param colorStr color String + * @param lightValue 变浅值 + * @return 透明度变浅后的颜色值 + */ + public static int setAlphaLight( + final String colorStr, + final int lightValue + ) { + int color = parseColor(colorStr); + return setAlphaLight(color, lightValue); + } + + /** + * 设置透明度变浅 + * @param color int color + * @param lightValue 变浅值 + * @return 透明度变浅后的颜色值 + */ + public static int setAlphaLight( + final int color, + final int lightValue + ) { + int alpha = alpha(color); + // 透明度变浅 + alpha -= lightValue; + // 进行设置 + return setAlpha(color, NumberUtils.clamp(alpha, 255, 0)); + } + + // = + + /** + * 获取灰度值 + * @param colorStr color String + * @return 灰度值 + */ + public static int grayLevel(final String colorStr) { + int color = parseColor(colorStr); + int[] argb = getARGB(color); + return (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + } + + /** + * 获取灰度值 + * @param color int color + * @return 灰度值 + */ + public static int grayLevel(final int color) { + // [] { alpha, red, green, blue } + int[] argb = getARGB(color); + return (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + } + + // = + + // 颜色字典集合 + private static final Map sColorNameMaps; + + static { + sColorNameMaps = new HashMap<>(); + sColorNameMaps.put("transparent", TRANSPARENT); + sColorNameMaps.put("white", WHITE); + sColorNameMaps.put("black", BLACK); + sColorNameMaps.put("red", RED); + sColorNameMaps.put("green", GREEN); + sColorNameMaps.put("blue", BLUE); + sColorNameMaps.put("gray", GRAY); + sColorNameMaps.put("grey", GRAY); + sColorNameMaps.put("skyblue", SKYBLUE); + sColorNameMaps.put("orange", ORANGE); + sColorNameMaps.put("gold", GOLD); + sColorNameMaps.put("pink", PINK); + sColorNameMaps.put("fuchsia", FUCHSIA); + sColorNameMaps.put("graywhite", GRAYWHITE); + sColorNameMaps.put("purple", PURPLE); + sColorNameMaps.put("cyan", CYAN); + sColorNameMaps.put("yellow", YELLOW); + sColorNameMaps.put("chocolate", CHOCOLATE); + sColorNameMaps.put("tomato", TOMATO); + sColorNameMaps.put("orangered", ORANGERED); + sColorNameMaps.put("silver", SILVER); + sColorNameMaps.put("darkgray", DKGRAY); + sColorNameMaps.put("lightgray", LTGRAY); + sColorNameMaps.put("lightgrey", LTGRAY); + sColorNameMaps.put("magenta", MAGENTA); + sColorNameMaps.put("highlight", HIGHLIGHT); + sColorNameMaps.put("lowlight", LOWLIGHT); + sColorNameMaps.put("aqua", 0xFF00FFFF); + sColorNameMaps.put("lime", 0xFF00FF00); + sColorNameMaps.put("maroon", 0xFF800000); + sColorNameMaps.put("navy", 0xFF000080); + sColorNameMaps.put("olive", 0xFF808000); + sColorNameMaps.put("teal", 0xFF008080); + } + + // ========== + // = 颜色信息 = + // ========== + + // 内部解析器 + private static ColorInfo.Parser sParser; + + /** + * 设置 Color 解析器 + * @param parser {@link ColorInfo.Parser} + */ + public static void setParser(final ColorInfo.Parser parser) { + ColorUtils.sParser = parser; + } + + /** + * detail: 颜色信息 + * @author Ttt + */ + public static class ColorInfo { + + // key + private final String key; + // value ( 如: #000000 ) + private final String value; + // value 解析后的值 ( 如: #000 => #000000 ) + private String valueParser; + // ARGB/RGB color + private long valueColor; + // A、R、G、B + private int alpha = 255, red = 0, green = 0, blue = 0; + // 灰度值 + private int grayLevel; + // H、S、B ( V ) + private float hue, saturation, brightness; + + /** + * 构造函数 + * @param key Key + * @param value Value ( 如: #000000 ) + */ + public ColorInfo( + final String key, + final String value + ) { + this.key = key; + this.value = value; + innerConvert(); + } + + /** + * 构造函数 + * @param key Key + * @param valueColor ARGB/RGB color + */ + public ColorInfo( + final String key, + final int valueColor + ) { + this(key, ColorUtils.intToArgbString(valueColor)); + } + + /** + * 获取 Key + * @return key String + */ + public String getKey() { + return key; + } + + /** + * 获取 Value + * @return value String + */ + public String getValue() { + return value; + } + + /** + * 获取 Value 解析后的值 ( 如: #000 => #000000 ) + * @return value 解析后的值 ( 如: #000 => #000000 ) + */ + public String getValueParser() { + return valueParser; + } + + /** + * 获取 ARGB/RGB color + * @return ARGB/RGB color + */ + public long getValueColor() { + return valueColor; + } + + /** + * 返回颜色中的透明度值 ( 返回十进制 ) + * @return alpha 值 + */ + public int getAlpha() { + return alpha; + } + + /** + * 返回颜色中红色的色值 ( 返回十进制 ) + * @return red 值 + */ + public int getRed() { + return red; + } + + /** + * 返回颜色中绿色的色值 ( 返回十进制 ) + * @return green 值 + */ + public int getGreen() { + return green; + } + + /** + * 返回颜色中蓝色的色值 ( 返回十进制 ) + * @return blue 值 + */ + public int getBlue() { + return blue; + } + + /** + * 获取灰度值 + * @return 灰度值 + */ + public int getGrayLevel() { + return grayLevel; + } + + /** + * 获取颜色色调 + * @return 颜色色调 + */ + public float getHue() { + return hue; + } + + /** + * 获取颜色饱和度 + * @return 颜色饱和度 + */ + public float getSaturation() { + return saturation; + } + + /** + * 获取颜色亮度 + * @return 颜色亮度 + */ + public float getBrightness() { + return brightness; + } + + @Override + public String toString() { + return "key : " + key + + "\nvalue : " + value + + "\nvalueParser : " + valueParser + + "\nalpha : " + alpha + + "\nred : " + red + + "\ngreen : " + green + + "\nblue : " + blue + + "\ngrayLevel : " + grayLevel + + "\nintToRgbString : " + ColorUtils.intToRgbString((int) valueColor) + + "\nintToArgbString : " + ColorUtils.intToArgbString((int) valueColor); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 内部转换处理 + */ + private void innerConvert() { + String temp = value; + if (sParser != null) { + temp = sParser.handleColor(value); + // 保存解析后的值 + valueParser = temp; + } + if (temp == null) return; + // 转换 long 颜色值 + valueColor = ColorUtils.parseColor(temp); + + int[] argb = ColorUtils.getARGB((int) valueColor); + // 获取 ARGB + alpha = argb[0]; + red = argb[1]; + green = argb[2]; + blue = argb[3]; + // 获取灰度值 + grayLevel = (int) (argb[1] * 0.299F + argb[2] * 0.587F + argb[3] * 0.114F); + // 获取 HSB + float[] hsbArrays = RGBtoHSB(red, green, blue, null); + hue = hsbArrays[0]; // 色调 + saturation = hsbArrays[1]; // 饱和度 + brightness = hsbArrays[2]; // 亮度 + } + + // ============ + // = 解析器相关 = + // ============ + + /** + * detail: Color 解析器 + * @author Ttt + */ + public interface Parser { + + /** + * 处理 color + * @param value 如: #000000 + * @return 处理后的 value + */ + String handleColor(String value); + } + + /** + * detail: Color 解析器 + * @author Ttt + */ + public static class ColorParser + implements Parser { + @Override + public String handleColor(String value) { + if (value == null) return null; + String color = StringUtils.clearSpace(value); + char[] chars = color.toCharArray(); + int length = chars.length; + if (length != 0 && chars[0] == '#') { + if (length == 4) { + String colorSub = color.substring(1); + // #000 => #000000 + return color + colorSub; + } else if (length == 5) { + String colorSub = color.substring(3); + // #0011 => #00111111 + return color + colorSub + colorSub; + } + } + return color; + } + } + + // ========== + // = 转换处理 = + // ========== + + /** + * RGB 转换 HSB + *
+         *     HSB 等于 HSV, 不同的叫法
+         *     java.awt.Color#RGBtoHSB
+         *     {@link android.graphics.Color#RGBToHSV}
+         * 
+ * @param r 红色值 [0-255] + * @param g 绿色值 [0-255] + * @param b 蓝色值 [0-255] + * @param hsbArrays HSB 数组 + * @return [] { hue, saturation, brightness } + */ + private static float[] RGBtoHSB( + int r, + int g, + int b, + float[] hsbArrays + ) { + float hue, saturation, brightness; + if (hsbArrays == null) { + hsbArrays = new float[3]; + } + int cmax = Math.max(r, g); + if (b > cmax) cmax = b; + int cmin = Math.min(r, g); + if (b < cmin) cmin = b; + + brightness = ((float) cmax) / 255.0F; + if (cmax != 0) { + saturation = ((float) (cmax - cmin)) / ((float) cmax); + } else { + saturation = 0; + } + if (saturation == 0) { + hue = 0; + } else { + float redc = ((float) (cmax - r)) / ((float) (cmax - cmin)); + float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin)); + float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin)); + if (r == cmax) { + hue = bluec - greenc; + } else if (g == cmax) { + hue = 2.0F + redc - bluec; + } else { + hue = 4.0F + greenc - redc; + } + hue = hue / 6.0F; + if (hue < 0) { + hue = hue + 1.0F; + } + } + hsbArrays[0] = hue; + hsbArrays[1] = saturation; + hsbArrays[2] = brightness; + return hsbArrays; + } + } + + // ========== + // = 颜色排序 = + // ========== + + /** + * 灰度值排序 + * @param lists 待排序颜色集合 + */ + public static void sortGray(final List lists) { + Collections.sort(lists, (c1, c2) -> { + long diff = c1.getGrayLevel() - c2.getGrayLevel(); + if (diff < 0) { + return 1; + } else if (diff > 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) HUE 色相排序 + * @param lists 待排序颜色集合 + */ + public static void sortHUE(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getHue() - c2.getHue(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) Saturation 饱和度排序 + * @param lists 待排序颜色集合 + */ + public static void sortSaturation(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getSaturation() - c2.getSaturation(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + /** + * HSB ( HSV ) Brightness 亮度排序 + * @param lists 待排序颜色集合 + */ + public static void sortBrightness(final List lists) { + Collections.sort(lists, (c1, c2) -> { + float diff = c1.getBrightness() - c2.getBrightness(); + if (diff > 0) { + return 1; + } else if (diff < 0) { + return -1; + } + return 0; + }); + } + + // ========== + // = 混合颜色 = + // ========== + + /** + * 使用给定的比例在两种 ARGB 颜色之间进行混合 + * @param color1 第一种 ARGB 颜色 + * @param color2 第二种 ARGB 颜色 + * @param ratio 两种颜色混合比例 + * @return 混合后的颜色 + */ + public static int blendColor( + final String color1, + final String color2, + final float ratio + ) { + return blendColor(parseColor(color1), parseColor(color2), ratio); + } + + /** + * 使用给定的比例在两种 ARGB 颜色之间进行混合 + *
+     *     android.support.v4.graphics.ColorUtils#blendARGB
+     *     混合比:
+     *     0.0 将产生 color1
+     *     0.5 将产生均匀的混合
+     *     1.0 将产生 color2
+     * 
+ * @param color1 第一种 ARGB 颜色 + * @param color2 第二种 ARGB 颜色 + * @param ratio 两种颜色混合比例 + * @return 混合后的颜色 + */ + public static int blendColor( + final int color1, + final int color2, + final float ratio + ) { + int[] color1Argb = getARGB(color1); + int[] color2Argb = getARGB(color2); + + final float inverseRatio = 1 - ratio; + float a = color1Argb[0] * inverseRatio + color2Argb[0] * ratio; + float r = color1Argb[1] * inverseRatio + color2Argb[1] * ratio; + float g = color1Argb[2] * inverseRatio + color2Argb[2] * ratio; + float b = color1Argb[3] * inverseRatio + color2Argb[3] * ratio; + return argb((int) a, (int) r, (int) g, (int) b); + } + + // ========== + // = 颜色过渡 = + // ========== + + /** + * 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 + * @param startColor 开始颜色值 + * @param endColor 结束颜色值 + * @param ratio 过渡百分比 + * @return 计算后颜色值 + */ + public static int transitionColor( + final String startColor, + final String endColor, + final float ratio + ) { + return transitionColor(parseColor(startColor), parseColor(endColor), ratio); + } + + /** + * 计算从 startColor 过渡到 endColor 过程中百分比为 ratio 时的颜色值 + * @param startColor 开始颜色值 + * @param endColor 结束颜色值 + * @param ratio 过渡百分比 + * @return 计算后颜色值 + */ + public static int transitionColor( + final int startColor, + final int endColor, + final float ratio + ) { + int[] startArgb = getARGB(startColor); + int[] endArgb = getARGB(endColor); + + int startAlpha = startArgb[0]; + int startRed = startArgb[1]; + int startGreen = startArgb[2]; + int startBlue = startArgb[3]; + + int endAlpha = endArgb[0]; + int endRed = endArgb[1]; + int endGreen = endArgb[2]; + int endBlue = endArgb[3]; + + float a = (endAlpha - startAlpha) * ratio + startAlpha; + float r = (endRed - startRed) * ratio + startRed; + float g = (endGreen - startGreen) * ratio + startGreen; + float b = (endBlue - startBlue) * ratio + startBlue; + return argb((int) a, (int) r, (int) g, (int) b); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ConvertUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ConvertUtils.java new file mode 100644 index 0000000000..9cf43e15e2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ConvertUtils.java @@ -0,0 +1,2052 @@ +package dev.utils.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 转换工具类 ( Byte、Hex 等 ) + * @author Ttt + *
+ *     byte 是字节数据类型、有符号型的、占 1 个字节、大小范围为 [ -128 - 127]
+ *     当大于 127 时则开始缩进 127 = 127, 128 = -128, 129 = -127
+ *     char 是字符数据类型、无符号型的、占 2 个字节 (unicode 码 )、大小范围为 [0 - 65535]
+ *     

+ * Binary( 二进制 ) toBinaryString + * Oct( 八进制 ) + * Dec( 十进制 ) + * Hex( 十六进制 ) 以 0x 开始的数据表示十六进制 + *

+ * 位移加密: bytesBitwiseAND(byte[] bytes) + * @see
+ *
+ */ +public final class ConvertUtils { + + private ConvertUtils() { + } + + // 日志 TAG + private static final String TAG = ConvertUtils.class.getSimpleName(); + + // 用于建立十六进制字符的输出的小写字符数组 + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // 用于建立十六进制字符的输出的大写字符数组 + private static final char[] HEX_DIGITS_UPPER = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + + /** + * Object 转换所需类型对象 + * @param object Object + * @param 泛型 + * @return Object convert T object + */ + public static T convert(final Object object) { + if (object == null) return null; + try { + return (T) object; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert"); + } + return null; + } + + /** + * Object 转 String + * @param value Value + * @return {@link String} + */ + public static String newString(final Object value) { + return newString(value, null, true); + } + + /** + * Object 转 String + * @param value Value + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newString( + final Object value, + final String defaultStr + ) { + return newString(value, defaultStr, true); + } + + /** + * Object 转 String + * @param value Value + * @param defaultStr 默认字符串 + * @param arrayDecode 是否 byte、char 数组进行 String 接码 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newString( + final Object value, + final String defaultStr, + final boolean arrayDecode + ) { + if (value != null) { + try { + if (arrayDecode) { + if (value instanceof byte[]) { + return new String((byte[]) value); + } + if (value instanceof char[]) { + return new String((char[]) value); + } + } + if (value instanceof String) { + return (String) value; + } + if (value instanceof StringBuffer) { + return new String((StringBuffer) value); + } + if (value instanceof StringBuilder) { + return new String((StringBuilder) value); + } + if (value instanceof CharSequence) { + return ((CharSequence) value).toString(); + } + throw new Exception("can not new string, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newString"); + } + } + return defaultStr; + } + + /** + * Object 转 String ( 不进行 Array 解码转 String ) + * @param value Value + * @return {@link String} + */ + public static String newStringNotArrayDecode(final Object value) { + return newString(value, null, false); + } + + /** + * Object 转 String ( 不进行 Array 解码转 String ) + * @param value Value + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String newStringNotArrayDecode( + final Object value, + final String defaultStr + ) { + return newString(value, defaultStr, false); + } + + // = + + /** + * Object 转 String + * @param object Object + * @return {@link String} + */ + public static String toString(final Object object) { + return toString(object, null); + } + + /** + * Object 转 String + * @param object Object + * @param defaultStr 默认字符串 + * @return {@link String} 如果转换失败则返回 defaultStr + */ + public static String toString( + final Object object, + final String defaultStr + ) { + if (object != null) { + try { + String strValue = newStringNotArrayDecode(object); + if (strValue != null) { + return strValue; + } + if (object instanceof Integer) { + return Integer.toString((Integer) object); + } + if (object instanceof Boolean) { + return Boolean.toString((Boolean) object); + } + if (object instanceof Long) { + return Long.toString((Long) object); + } + if (object instanceof Double) { + return Double.toString((Double) object); + } + if (object instanceof Float) { + return Float.toString((Float) object); + } + if (object instanceof Byte) { + return Byte.toString((Byte) object); + } + if (object instanceof Character) { + return Character.toString((Character) object); + } + if (object instanceof Short) { + return Short.toString((Short) object); + } + Class clazz = object.getClass(); + // 判断是否数组类型 + if (clazz.isArray()) { + // = 基本数据类型 = + if (clazz.isAssignableFrom(int[].class)) { + return Arrays.toString((int[]) object); + } else if (clazz.isAssignableFrom(boolean[].class)) { + return Arrays.toString((boolean[]) object); + } else if (clazz.isAssignableFrom(long[].class)) { + return Arrays.toString((long[]) object); + } else if (clazz.isAssignableFrom(double[].class)) { + return Arrays.toString((double[]) object); + } else if (clazz.isAssignableFrom(float[].class)) { + return Arrays.toString((float[]) object); + } else if (clazz.isAssignableFrom(byte[].class)) { + return Arrays.toString((byte[]) object); + } else if (clazz.isAssignableFrom(char[].class)) { + return Arrays.toString((char[]) object); + } else if (clazz.isAssignableFrom(short[].class)) { + return Arrays.toString((short[]) object); + } + return Arrays.toString((Object[]) object); + } + return object.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toString"); + } + } + return defaultStr; + } + + /** + * Object 转 Integer + * @param value Value + * @return Integer + */ + public static Integer toInt(final Object value) { + return toInt(value, DevFinal.DEFAULT.INT); + } + + /** + * Object 转 Integer + * @param value Value + * @param defaultValue 默认值 + * @return Integer, 如果转换失败则返回 defaultValue + */ + public static Integer toInt( + final Object value, + final Integer defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Number) { + return ((Number) value).intValue(); + } + if (value instanceof Boolean) { + return (Boolean) value ? 1 : 0; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Integer.parseInt(strValue); + } + throw new Exception("can not cast to int, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toInt"); + } + return defaultValue; + } + + /** + * Object 转 Boolean + * @param value Value + * @return Boolean + */ + public static Boolean toBoolean(final Object value) { + return toBoolean(value, DevFinal.DEFAULT.BOOLEAN); + } + + /** + * Object 转 Boolean + * @param value Value + * @param defaultValue 默认值 + * @return Boolean, 如果转换失败则返回 defaultValue + */ + public static Boolean toBoolean( + final Object value, + final Boolean defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof Number) { + return ((Number) value).intValue() == 1; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + strValue = strValue.toLowerCase(); + // true、t、yes、y、1 + if ("true".equals(strValue) + || "t".equals(strValue) + || "yes".equals(strValue) + || "y".equals(strValue) + || "1".equals(strValue) + ) { + return Boolean.TRUE; + } + // false、f、no、n、0 + if ("false".equals(strValue) + || "f".equals(strValue) + || "no".equals(strValue) + || "n".equals(strValue) + || "0".equals(strValue) + ) { + return Boolean.FALSE; + } + } + throw new Exception("can not cast to boolean, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBoolean"); + } + return defaultValue; + } + + /** + * Object 转 Float + * @param value Value + * @return Float + */ + public static Float toFloat(final Object value) { + return toFloat(value, DevFinal.DEFAULT.FLOAT); + } + + /** + * Object 转 Float + * @param value Value + * @param defaultValue 默认值 + * @return Float, 如果转换失败则返回 defaultValue + */ + public static Float toFloat( + final Object value, + final Float defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Float) { + return (Float) value; + } + if (value instanceof Number) { + return ((Number) value).floatValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Float.parseFloat(strValue); + } + throw new Exception("can not cast to float, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toFloat"); + } + return defaultValue; + } + + /** + * Object 转 Double + * @param value Value + * @return Double + */ + public static Double toDouble(final Object value) { + return toDouble(value, DevFinal.DEFAULT.DOUBLE); + } + + /** + * Object 转 Double + * @param value Value + * @param defaultValue 默认值 + * @return Double, 如果转换失败则返回 defaultValue + */ + public static Double toDouble( + final Object value, + final Double defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Double) { + return (Double) value; + } + if (value instanceof Number) { + return ((Number) value).doubleValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Double.parseDouble(strValue); + } + throw new Exception("can not cast to double, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toDouble"); + } + return defaultValue; + } + + /** + * Object 转 Long + * @param value Value + * @return Long + */ + public static Long toLong(final Object value) { + return toLong(value, DevFinal.DEFAULT.LONG); + } + + /** + * Object 转 Long + * @param value Value + * @param defaultValue 默认值 + * @return Long, 如果转换失败则返回 defaultValue + */ + public static Long toLong( + final Object value, + final Long defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Long) { + return (Long) value; + } + if (value instanceof Number) { + return ((Number) value).longValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.indexOf(',') != 0) { + strValue = strValue.replaceAll(",", ""); + } + return Long.parseLong(strValue); + } + throw new Exception("can not cast to long, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toLong"); + } + return defaultValue; + } + + /** + * Object 转 Short + * @param value Value + * @return Short + */ + public static Short toShort(final Object value) { + return toShort(value, DevFinal.DEFAULT.SHORT); + } + + /** + * Object 转 Short + * @param value Value + * @param defaultValue 默认值 + * @return Short, 如果转换失败则返回 defaultValue + */ + public static Short toShort( + final Object value, + final Short defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Short) { + return (Short) value; + } + if (value instanceof Number) { + return ((Number) value).shortValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return Short.parseShort(strValue); + } + throw new Exception("can not cast to short, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toShort"); + } + return defaultValue; + } + + /** + * Object 转 Character + * @param value Value + * @return Character + */ + public static Character toChar(final Object value) { + return toChar(value, DevFinal.DEFAULT.CHAR); + } + + /** + * Object 转 Character + * @param value Value + * @param defaultValue 默认值 + * @return Character, 如果转换失败则返回 defaultValue + */ + public static Character toChar( + final Object value, + final Character defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Character) { + return (Character) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + if (strValue.length() == 1) { + return strValue.charAt(0); + } + } + throw new Exception("can not cast to char, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toChar"); + } + return defaultValue; + } + + /** + * Object 转 Byte + * @param value Value + * @return Byte + */ + public static byte toByte(final Object value) { + return toByte(value, DevFinal.DEFAULT.BYTE); + } + + /** + * Object 转 Byte + * @param value Value + * @param defaultValue 默认值 + * @return Byte, 如果转换失败则返回 defaultValue + */ + public static byte toByte( + final Object value, + final Byte defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof Byte) { + return (Byte) value; + } + if (value instanceof Number) { + return ((Number) value).byteValue(); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return Byte.parseByte(strValue); + } + throw new Exception("can not cast to byte, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toByte"); + } + return defaultValue; + } + + /** + * Object 转 BigDecimal + * @param value Value + * @return BigDecimal + */ + public static BigDecimal toBigDecimal(final Object value) { + return toBigDecimal(value, DevFinal.DEFAULT.BIG_DECIMAL); + } + + /** + * Object 转 BigDecimal + * @param value Value + * @param defaultValue 默认值 + * @return BigDecimal, 如果转换失败则返回 defaultValue + */ + public static BigDecimal toBigDecimal( + final Object value, + final BigDecimal defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + if (value instanceof BigInteger) { + return new BigDecimal((BigInteger) value); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return new BigDecimal(strValue); + } + throw new Exception("can not cast to BigDecimal, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBigDecimal"); + } + return defaultValue; + } + + /** + * Object 转 BigInteger + * @param value Value + * @return BigInteger + */ + public static BigInteger toBigInteger(final Object value) { + return toBigInteger(value, DevFinal.DEFAULT.BIG_INTEGER); + } + + /** + * Object 转 BigInteger + * @param value Value + * @param defaultValue 默认值 + * @return BigInteger, 如果转换失败则返回 defaultValue + */ + public static BigInteger toBigInteger( + final Object value, + final BigInteger defaultValue + ) { + if (value == null) return defaultValue; + try { + if (value instanceof BigInteger) { + return (BigInteger) value; + } + if (value instanceof Float || value instanceof Double) { + return BigInteger.valueOf(((Number) value).longValue()); + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return new BigInteger(strValue); + } + throw new Exception("can not cast to BigInteger, value : " + value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBigInteger"); + } + return defaultValue; + } + + /** + * Object 获取 char[] + * @param value Value + * @return char[] + */ + public static char[] toChars(final Object value) { + try { + if (value instanceof char[]) { + return (char[]) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return strValue.toCharArray(); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toChars"); + } + return null; + } + + /** + * Object 获取 byte[] + * @param value Value + * @return byte[] + */ + public static byte[] toBytes(final Object value) { + try { + if (value instanceof byte[]) { + return (byte[]) value; + } + String strValue = newStringNotArrayDecode(value); + if (strValue != null) { + return strValue.getBytes(); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBytes"); + } + return null; + } + + /** + * char 转换 unicode 编码 + * @param value char + * @return int + */ + public static int toCharInt(final char value) { + return value; + } + + /** + * Object 获取 char ( 默认第一位 ) + * @param value Value + * @param defaultValue 默认值 + * @return 第一位值, 如果获取失败则返回 defaultValue + */ + public static char charAt( + final Object value, + final char defaultValue + ) { + return charAt(value, 0, defaultValue); + } + + /** + * Object 获取 char + * @param value Value + * @param pos 索引 + * @param defaultValue 默认值 + * @return 指定索引的值, 如果获取失败则返回 defaultValue + */ + public static char charAt( + final Object value, + final int pos, + final char defaultValue + ) { + if (value == null || pos < 0) return defaultValue; + try { + return toChars(value)[pos]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "charAt"); + } + return defaultValue; + } + + // = + + /** + * 字符串转换对应的进制 + *
+     *     如: parseInt("1f603", 16) = 128515
+     * 
+ * @param str 待处理字符串 + * @param radix 进制 + * @return 对应进制的值 + */ + public static int parseInt( + final String str, + final int radix + ) { + if (str == null) return -1; + try { + return Integer.parseInt(str, radix); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseInt"); + } + return -1; + } + + /** + * 字符串转换对应的进制 + * @param str 待处理字符串 + * @param radix 进制 + * @return 对应进制的值 + */ + public static long parseLong( + final String str, + final int radix + ) { + if (str == null) return -1L; + try { + return Long.parseLong(str, radix); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseLong"); + } + return -1L; + } + + // = + + /** + * 将 short 转换成字节数组 + * @param data short + * @return byte[] + */ + public static byte[] valueOf(final short data) { + try { + byte[] bytes = new byte[2]; + for (int i = 0; i < 2; i++) { + int offset = (bytes.length - 1 - i) * 8; + bytes[i] = (byte) ((data >>> offset) & 0xff); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "valueOf"); + } + return null; + } + + /** + * 将 int 转换成字节数组 + * @param data int + * @return byte[] + */ + public static byte[] valueOf(final int data) { + try { + byte[] bytes = new byte[4]; + for (int i = 0; i < 4; i++) { + int offset = (bytes.length - 1 - i) * 8; + bytes[i] = (byte) ((data >>> offset) & 0xFF); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "valueOf"); + } + return null; + } + + // = + + /** + * byte[] 转为 Object + * @param bytes byte[] + * @return {@link Object} + */ + public static Object bytesToObject(final byte[] bytes) { + if (bytes == null) return null; + ObjectInputStream ois = null; + try { + ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); + return ois.readObject(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToObject"); + } finally { + CloseUtils.closeIOQuietly(ois); + } + return null; + } + + /** + * Object 转为 byte[] + * @param object Object + * @return byte[] + */ + public static byte[] objectToBytes(final Object object) { + if (object == null) return null; + ObjectOutputStream oos = null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(object); + return baos.toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "objectToBytes"); + } finally { + CloseUtils.closeIOQuietly(oos); + } + return null; + } + + // = + + /** + * byte[] 转换 char[], 并且进行补码 + * @param data byte[] + * @return char[] + */ + public static char[] bytesToChars(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + char[] chars = new char[len]; + for (int i = 0; i < len; i++) { + chars[i] = (char) (data[i] & 0xff); + } + return chars; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToChars"); + } + return null; + } + + /** + * char[] 转换 byte[] + * @param data char[] + * @return byte[] + */ + public static byte[] charsToBytes(final char[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + byte[] bytes = new byte[len]; + for (int i = 0; i < len; i++) { + bytes[i] = (byte) (data[i]); + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "charsToBytes"); + } + return null; + } + + // ============================================ + // = (int、double、long、float)[] 转换 String[] = + // ============================================ + + /** + * int[] 转换 string[] + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings(final int[] datas) { + return intsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 string[] + * @param off 起始值 + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings( + final int off, + final int[] datas + ) { + return intsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return String[] + */ + public static String[] intsToStrings( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * double[] 转换 string[] + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings(final double[] datas) { + return doublesToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 string[] + * @param off 起始值 + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings( + final int off, + final double[] datas + ) { + return doublesToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas double[] + * @return String[] + */ + public static String[] doublesToStrings( + final int off, + final int length, + final double[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * long[] 转换 string[] + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings(final long[] datas) { + return longsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 string[] + * @param off 起始值 + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings( + final int off, + final long[] datas + ) { + return longsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas long[] + * @return String[] + */ + public static String[] longsToStrings( + final int off, + final int length, + final long[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // = + + /** + * float[] 转换 string[] + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings(final float[] datas) { + return floatsToStrings(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 string[] + * @param off 起始值 + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings( + final int off, + final float[] datas + ) { + return floatsToStrings(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 string[] + * @param off 起始值 + * @param length 所需长度 + * @param datas float[] + * @return String[] + */ + public static String[] floatsToStrings( + final int off, + final int length, + final float[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + String[] strings = new String[length - off]; + for (int i = 0, len = strings.length; i < len; i++) { + strings[i] = String.valueOf(datas[off + i]); + } + return strings; + } + + // ==================================== + // = int[] 转换 (double、long、float)[] = + // ==================================== + + /** + * int[] 转换 double[] + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles(final int[] datas) { + return intsToDoubles(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 double[] + * @param off 起始值 + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles( + final int off, + final int[] datas + ) { + return intsToDoubles(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return double[] + */ + public static double[] intsToDoubles( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + double[] doubles = new double[length - off]; + for (int i = 0, len = doubles.length; i < len; i++) { + doubles[i] = datas[off + i]; + } + return doubles; + } + + // = + + /** + * int[] 转换 long[] + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs(final int[] datas) { + return intsToLongs(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 long[] + * @param off 起始值 + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs( + final int off, + final int[] datas + ) { + return intsToLongs(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return long[] + */ + public static long[] intsToLongs( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + long[] longs = new long[length - off]; + for (int i = 0, len = longs.length; i < len; i++) { + longs[i] = datas[off + i]; + } + return longs; + } + + // = + + /** + * int[] 转换 float[] + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats(final int[] datas) { + return intsToFloats(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 float[] + * @param off 起始值 + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats( + final int off, + final int[] datas + ) { + return intsToFloats(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * int[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param datas int[] + * @return float[] + */ + public static float[] intsToFloats( + final int off, + final int length, + final int[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + float[] floats = new float[length - off]; + for (int i = 0, len = floats.length; i < len; i++) { + floats[i] = datas[off + i]; + } + return floats; + } + + // ============================================ + // = String[] 转换 (int、double、long、float)[] = + // ============================================ + + /** + * string[] 转换 int[] + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts(final String... datas) { + return stringsToInts(0, (datas != null) ? datas.length : 0, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final String... datas + ) { + return stringsToInts(off, (datas != null) ? datas.length : 0, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final int length, + final String... datas + ) { + return stringsToInts(off, length, -1, datas); + } + + /** + * string[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return int[] + */ + public static int[] stringsToInts( + final int off, + final int length, + final int errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = Integer.parseInt(datas[off + i]); + } catch (Exception e) { + ints[i] = errorValue; + } + } + return ints; + } + + // = + + /** + * string[] 转换 double[] + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles(final String... datas) { + return stringsToDoubles(0, (datas != null) ? datas.length : 0, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final String... datas + ) { + return stringsToDoubles(off, (datas != null) ? datas.length : 0, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final int length, + final String... datas + ) { + return stringsToDoubles(off, length, -1D, datas); + } + + /** + * string[] 转换 double[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return double[] + */ + public static double[] stringsToDoubles( + final int off, + final int length, + final double errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + double[] doubles = new double[length - off]; + for (int i = 0, len = doubles.length; i < len; i++) { + try { + doubles[i] = Double.parseDouble(datas[off + i]); + } catch (Exception e) { + doubles[i] = errorValue; + } + } + return doubles; + } + + // = + + /** + * string[] 转换 long[] + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs(final String... datas) { + return stringsToLongs(0, (datas != null) ? datas.length : 0, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final String... datas + ) { + return stringsToLongs(off, (datas != null) ? datas.length : 0, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final int length, + final String... datas + ) { + return stringsToLongs(off, length, -1L, datas); + } + + /** + * string[] 转换 long[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return long[] + */ + public static long[] stringsToLongs( + final int off, + final int length, + final long errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + long[] longs = new long[length - off]; + for (int i = 0, len = longs.length; i < len; i++) { + try { + longs[i] = Long.parseLong(datas[off + i]); + } catch (Exception e) { + longs[i] = errorValue; + } + } + return longs; + } + + // = + + /** + * string[] 转换 float[] + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats(final String... datas) { + return stringsToFloats(0, (datas != null) ? datas.length : 0, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final String... datas + ) { + return stringsToFloats(off, (datas != null) ? datas.length : 0, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final int length, + final String... datas + ) { + return stringsToFloats(off, length, -1F, datas); + } + + /** + * string[] 转换 float[] + * @param off 起始值 + * @param length 所需长度 + * @param errorValue 转换错误使用值 + * @param datas String[] + * @return float[] + */ + public static float[] stringsToFloats( + final int off, + final int length, + final float errorValue, + final String... datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + float[] floats = new float[length - off]; + for (int i = 0, len = floats.length; i < len; i++) { + try { + floats[i] = Float.parseFloat(datas[off + i]); + } catch (Exception e) { + floats[i] = errorValue; + } + } + return floats; + } + + // ==================================== + // = (double、long、float)[] 转换 int[] = + // ==================================== + + /** + * double[] 转换 int[] + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts(final double[] datas) { + return doublesToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 int[] + * @param off 起始值 + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts( + final int off, + final double[] datas + ) { + return doublesToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * double[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas double[] + * @return int[] + */ + public static int[] doublesToInts( + final int off, + final int length, + final double[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // = + + /** + * long[] 转换 int[] + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts(final long[] datas) { + return longsToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 int[] + * @param off 起始值 + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts( + final int off, + final long[] datas + ) { + return longsToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * long[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas long[] + * @return int[] + */ + public static int[] longsToInts( + final int off, + final int length, + final long[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // = + + /** + * float[] 转换 int[] + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts(final float[] datas) { + return floatsToInts(0, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 int[] + * @param off 起始值 + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts( + final int off, + final float[] datas + ) { + return floatsToInts(off, (datas != null) ? datas.length : 0, datas); + } + + /** + * float[] 转换 int[] + * @param off 起始值 + * @param length 所需长度 + * @param datas float[] + * @return int[] + */ + public static int[] floatsToInts( + final int off, + final int length, + final float[] datas + ) { + if (off < 0 || length < 1 || off >= length || datas == null || length > datas.length) { + return null; + } + int[] ints = new int[length - off]; + for (int i = 0, len = ints.length; i < len; i++) { + try { + ints[i] = (int) datas[off + i]; + } catch (Exception ignored) { + } + } + return ints; + } + + // =================== + // = Binary ( 二进制 ) = + // =================== + + /** + * 将 字节转换 为 二进制字符串 + * @param bytes byte[] + * @return 二进制字符串 + */ + public static String toBinaryString(final byte... bytes) { + if (bytes == null || bytes.length == 0) return null; + try { + StringBuilder builder = new StringBuilder(); + for (byte value : bytes) { + for (int j = 7; j >= 0; --j) { + builder.append(((value >> j) & 0x01) == 0 ? '0' : '1'); + } + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toBinaryString"); + } + return null; + } + + /** + * 二进制字符串 转换 byte[] 解码 + *
+     *     例: "011000010111001101100100" 传入 decodeBinary
+     *     返回 byte[], 通过 new String(byte()) 获取配合 toBinaryString 使用
+     * 
+ * @param str 待处理字符串 + * @return 解码后的 byte[] + */ + public static byte[] decodeBinary(final String str) { + if (str == null) return null; + try { + String data = str; + int lenMod = data.length() % 8; + int byteLen = data.length() / 8; + // add "0" until length to 8 times + if (lenMod != 0) { + for (int i = lenMod; i < 8; i++) { + data = "0" + data; + } + byteLen++; + } + byte[] bytes = new byte[byteLen]; + for (int i = 0; i < byteLen; ++i) { + for (int j = 0; j < 8; ++j) { + bytes[i] <<= 1; + bytes[i] |= data.charAt(i * 8 + j) - '0'; + } + } + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decodeBinary"); + } + return null; + } + + // ================== + // = Hex ( 十六进制 ) = + // ================== + + /** + * 判断是否十六进制数据 + * @param data 待检验数据 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHex(final String data) { + if (data == null) return false; + // 获取数据长度 + int len = data.length(); + if (len > 0) { + for (int i = len - 1; i >= 0; i--) { + char c = data.charAt(i); + if (!(c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f')) { + return false; + } + } + return true; + } + return false; + } + + /** + * 将十六进制字节数组解码 + * @param data 十六进制 byte[] + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final byte[] data) { + return decodeHex((ArrayUtils.length(data) == 0) ? null : bytesToChars(data)); + } + + /** + * 将十六进制字符串解码 + * @param str 十六进制字符串 + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final String str) { + return decodeHex(StringUtils.isEmpty(str) ? null : str.toCharArray()); + } + + /** + * 将十六进制字符数组解码 + * @param data 十六进制 char[] + * @return 十六进制转 ( 解 ) 码后的数据 + */ + public static byte[] decodeHex(final char[] data) { + if (data == null) return null; + try { + int len = data.length; + byte[] out = new byte[len >> 1]; + // 十六进制由两个字符组成 + for (int i = 0, j = 0; j < len; i++) { + int d = toDigit(data[j], j) << 4; + j++; + d = d | toDigit(data[j], j); + j++; + out[i] = (byte) (d & 0xFF); + } + return out; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decodeHex"); + } + return null; + } + + /** + * 十六进制 char 转换 int + * @param hexChar 十六进制 char + * @return 十六进制转 ( 解 ) 码后的整数 + */ + public static int hexToInt(final char hexChar) { + if (hexChar >= '0' && hexChar <= '9') { + return hexChar - '0'; + } else if (hexChar >= 'A' && hexChar <= 'F') { + return hexChar - 'A' + 10; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * 将十六进制字符转换成一个整数 + * @param ch 十六进制 char + * @param index 十六进制字符在字符数组中的位置 + * @return 一个整数 + * @throws Exception 当 ch 不是一个合法的十六进制字符时, 抛出运行时异常 + */ + private static int toDigit( + final char ch, + final int index + ) + throws Exception { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new Exception( + String.format( + "Illegal hexadecimal character %s at index %s", + ch, index + ) + ); + } + return digit; + } + + // = + + // toHexString(0x1f603) = 1f603 + // parseInt("1f603", 16) = 128515 + // toHexString(128515) = 1f603 + + /** + * int 转换十六进制 + *
+     *     如: toHexString(0x1f603) 返回: 1f603
+     * 
+ * @param value int + * @return 十六进制字符串 + */ + public static String toHexString(final int value) { + try { + return Integer.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * long 转换十六进制 + * @param value long + * @return 十六进制字符串 + */ + public static String toHexString(final long value) { + try { + return Long.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * double 转换十六进制 + * @param value double + * @return 十六进制字符串 + */ + public static String toHexString(final double value) { + try { + return Double.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + /** + * float 转换十六进制 + * @param value float + * @return 十六进制字符串 + */ + public static String toHexString(final float value) { + try { + return Float.toHexString(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + // = + + /** + * 将 string 转换为 十六进制 char[] + * @param str 待处理字符串 + * @return 十六进制 char[] + */ + public static char[] toHexChars(final String str) { + return toHexChars(str, true); + } + + /** + * 将 string 转换为 十六进制 char[] + * @param str 待处理字符串 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制 char[] + */ + public static char[] toHexChars( + final String str, + final boolean toLowerCase + ) { + return toHexChars( + StringUtils.isEmpty(str) ? null : str.getBytes(), + toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER + ); + } + + // = + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @return 十六进制 char[] + */ + public static char[] toHexChars(final byte[] data) { + return toHexChars(data, true); + } + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制 char[] + */ + public static char[] toHexChars( + final byte[] data, + final boolean toLowerCase + ) { + return toHexChars(data, toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER); + } + + /** + * 将 byte[] 转换为 十六进制 char[] + * @param data byte[] + * @param hexDigits {@link #HEX_DIGITS}、{@link #HEX_DIGITS_UPPER} + * @return 十六进制 char[] + */ + private static char[] toHexChars( + final byte[] data, + final char[] hexDigits + ) { + if (data == null || hexDigits == null) return null; + try { + return toHexString(data, hexDigits).toCharArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexChars"); + } + return null; + } + + // = + + /** + * 将 string 转换 十六进制字符串 + * @param str 待转换数据 + * @return 十六进制字符串 + */ + public static String toHexString(final String str) { + return toHexString(str, true); + } + + /** + * 将 string 转换 十六进制字符串 + * @param str 待转换数据 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制字符串 + */ + public static String toHexString( + final String str, + final boolean toLowerCase + ) { + return toHexString( + StringUtils.isEmpty(str) ? null : str.getBytes(), + toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER + ); + } + + // = + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @return 十六进制字符串 + */ + public static String toHexString(final byte[] data) { + return toHexString(data, true); + } + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @param toLowerCase {@code true} 小写格式, {@code false} 大写格式 + * @return 十六进制字符串 + */ + public static String toHexString( + final byte[] data, + final boolean toLowerCase + ) { + return toHexString(data, toLowerCase ? HEX_DIGITS : HEX_DIGITS_UPPER); + } + + /** + * 将 byte[] 转换 十六进制字符串 + * @param data 待转换数据 + * @param hexDigits {@link #HEX_DIGITS}、{@link #HEX_DIGITS_UPPER} + * @return 十六进制字符串 + */ + private static String toHexString( + final byte[] data, + final char[] hexDigits + ) { + if (data == null || hexDigits == null) return null; + try { + int len = data.length; + StringBuilder builder = new StringBuilder(len); + for (byte value : data) { + builder.append(hexDigits[(value & 0xf0) >>> 4]); + builder.append(hexDigits[value & 0x0f]); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toHexString"); + } + return null; + } + + // = + +// String data = "test"; +// // 转换二进制字符串 +// String result = toBinaryString(data.getBytes()); +// // 获取二进制数据 +// byte[] bytes = result.getBytes(); +// // 位移编码 +// bytesBitwiseAND(bytes); +// // = +// // 位移解码 +// bytesBitwiseAND(bytes); +// // 二进制数据解码 +// byte[] byteResult = decodeBinary(new String(bytes)); +// // 转换为原始数据 +// String data1 = new String(byteResult); +// // 判断是否一致 +// boolean equals = data.equals(data1); + + /** + * 按位求补 byte[] 位移编解码 ( 共用同一个方法 ) + * @param data byte[] + */ + public static void bytesBitwiseAND(final byte[] data) { + if (data == null) return; + for (int i = 0, len = data.length; i < len; i++) { + int d = data[i]; + d = ~d; // 按位补运算符, 翻转操作数的每一位, 即 0 变成 1, 1 变成 0, 再通过反转后的二进制初始化回十六进制 + data[i] = (byte) d; + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/CoordinateUtils.java b/lib/DevJava/src/main/java/dev/utils/common/CoordinateUtils.java new file mode 100644 index 0000000000..c77b647312 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/CoordinateUtils.java @@ -0,0 +1,378 @@ +package dev.utils.common; + +import static java.lang.Math.PI; + +/** + * detail: 坐标 ( GPS 纠偏 ) 相关工具类 + * @author Ttt + *
+ *     地球坐标系 (WGS-84)
+ *     火星坐标系 (GCJ-02)
+ *     百度坐标系 (BD09)
+ *     

+ * @see
+ * @see + * 根据两点经纬度计算距离 + * @see + * 根据经纬度计算两点之间的距离的公式推导过程以及 google.maps 的测距函数 + * @see + *

+ * 1. WGS84 坐标系: 即地球坐标系, 国际上通用的坐标系, 设备一般包含 GPS 芯片或者北斗芯片获取的经纬度为 WGS84 地理坐标系 + * 谷歌地图采用的是 WGS84 地理坐标系 ( 中国范围除外 ) GPS 设备得到的经纬度就是在 WGS84 坐标系下的经纬度, 通常通过底层接口得到的定位信息都是 WGS84 坐标系 + *

+ * 2. GCJ02 坐标系: 即火星坐标系, 是由中国国家测绘局制订的地理信息系统的坐标系统, 由 WGS84 坐标系经加密后的坐标系 + * 国家规定, 中国大陆所有公开地理数据都需要至少用 GCJ-02 进行加密, 也就是说我们从国内公司的产品中得到的数据, 一定是经过了加密的 + * 绝大部分国内互联网地图提供商都是使用 GCJ-02 坐标系, 包括高德地图, 谷歌地图中国区等 + *

+ * 3. BD09 坐标系: 即百度坐标系, 其在 GCJ-02 上多增加了一次变换, 用来保护用户隐私, 从百度产品中得到的坐标都是 BD-09 坐标系 + *
+ */ +public final class CoordinateUtils { + + private CoordinateUtils() { + } + + private static final double X_PI = 3.14159265358979324 * 3000.0 / 180.0; + private static final double A = 6378245.0; + private static final double EE = 0.00669342162296594323; + + /** + * BD09 坐标转 GCJ02 坐标 + * @param lng BD09 坐标纬度 + * @param lat BD09 坐标经度 + * @return GCJ02 坐标 [ 经度, 纬度 ] + */ + public static double[] bd09ToGcj02( + final double lng, + final double lat + ) { + double x = lng - 0.0065; + double y = lat - 0.006; + double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * X_PI); + double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * X_PI); + double gg_lng = z * Math.cos(theta); + double gg_lat = z * Math.sin(theta); + return new double[]{gg_lng, gg_lat}; + } + + /** + * GCJ02 坐标转 BD09 坐标 + * @param lng GCJ02 坐标经度 + * @param lat GCJ02 坐标纬度 + * @return BD09 坐标 [ 经度, 纬度 ] + */ + public static double[] gcj02ToBd09( + final double lng, + final double lat + ) { + double z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * X_PI); + double theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * X_PI); + double bd_lng = z * Math.cos(theta) + 0.0065; + double bd_lat = z * Math.sin(theta) + 0.006; + return new double[]{bd_lng, bd_lat}; + } + + /** + * GCJ02 坐标转 WGS84 坐标 + * @param lng GCJ02 坐标经度 + * @param lat GCJ02 坐标纬度 + * @return WGS84 坐标 [ 经度, 纬度 ] + */ + public static double[] gcj02ToWGS84( + final double lng, + final double lat + ) { + if (outOfChina(lng, lat)) return new double[]{lng, lat}; + double dlat = transformLat(lng - 105.0, lat - 35.0); + double dlng = transformLng(lng - 105.0, lat - 35.0); + double radlat = lat / 180.0 * PI; + double magic = Math.sin(radlat); + magic = 1 - EE * magic * magic; + double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); + double mglat = lat + dlat; + double mglng = lng + dlng; + return new double[]{lng * 2 - mglng, lat * 2 - mglat}; + } + + /** + * WGS84 坐标转 GCJ02 坐标 + * @param lng WGS84 坐标经度 + * @param lat WGS84 坐标纬度 + * @return GCJ02 坐标 [ 经度, 纬度 ] + */ + public static double[] wgs84ToGcj02( + final double lng, + final double lat + ) { + if (outOfChina(lng, lat)) return new double[]{lng, lat}; + double dlat = transformLat(lng - 105.0, lat - 35.0); + double dlng = transformLng(lng - 105.0, lat - 35.0); + double radlat = lat / 180.0 * PI; + double magic = Math.sin(radlat); + magic = 1 - EE * magic * magic; + double sqrtmagic = Math.sqrt(magic); + dlat = (dlat * 180.0) / ((A * (1 - EE)) / (magic * sqrtmagic) * PI); + dlng = (dlng * 180.0) / (A / sqrtmagic * Math.cos(radlat) * PI); + double mglat = lat + dlat; + double mglng = lng + dlng; + return new double[]{mglng, mglat}; + } + + /** + * BD09 坐标转 WGS84 坐标 + * @param lng BD09 坐标经度 + * @param lat BD09 坐标纬度 + * @return WGS84 坐标 [ 经度, 纬度 ] + */ + public static double[] bd09ToWGS84( + final double lng, + final double lat + ) { + double[] gcj = bd09ToGcj02(lng, lat); + return gcj02ToWGS84(gcj[0], gcj[1]); + } + + /** + * WGS84 坐标转 BD09 坐标 + * @param lng WGS84 坐标经度 + * @param lat WGS84 坐标纬度 + * @return BD09 坐标 [ 经度, 纬度 ] + */ + public static double[] wgs84ToBd09( + final double lng, + final double lat + ) { + double[] gcj = wgs84ToGcj02(lng, lat); + return gcj02ToBd09(gcj[0], gcj[1]); + } + + /** + * 转换经度 + * @param lng 经度 + * @param lat 纬度 + * @return 转换后的经度 + */ + private static double transformLat( + final double lng, + final double lat + ) { + double ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat + * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lat * PI) + 40.0 * Math.sin(lat / 3.0 * PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(lat / 12.0 * PI) + 320 * Math.sin(lat * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + /** + * 转换纬度 + * @param lng 经度 + * @param lat 纬度 + * @return 转换后的纬度 + */ + private static double transformLng( + final double lng, + final double lat + ) { + double ret = 300.0 + lng + 2.0 * lat + 0.1 * lng + * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); + ret += (20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(lng * PI) + 40.0 * Math.sin(lng / 3.0 * PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(lng / 12.0 * PI) + 300.0 * Math.sin(lng / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } + + /** + * 判断是否中国境外 + * @param lng 经度 + * @param lat 纬度 + * @return {@code true} yes, {@code false} no + */ + public static boolean outOfChina( + final double lng, + final double lat + ) { + return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271; + } + + // ========== + // = 计算坐标 = + // ========== + + // 赤道半径 + private static final double EARTH_RADIUS = 6378.137; + + /** + * 计算弧度角 + * @param degree 度数 + * @return 弧度角 + */ + private static double rad(final double degree) { + return degree * Math.PI / 180.0; + } + + /** + * 计算两个坐标相距距离 ( 单位: 米 ) + *
+     *     计算点与点直线间距离
+     * 
+ * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标相距距离 ( 单位: 米 ) + */ + public static double getDistance( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double radLat1 = rad(originLat); + double radLat2 = rad(targetLat); + double a = radLat1 - radLat2; + double b = rad(originLng) - rad(targetLng); + double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2))); + s = s * EARTH_RADIUS; + // 保留两位小数 + s = Math.round(s * 100D) / 100D; + s = s * 1000; + return s; + } + + /** + * 计算两个坐标的方向角度 + *
+     *     以 origin 为参考点坐标, 获取目标坐标位于参考点坐标方向
+     * 
+ * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标的方向角度 + */ + public static double getAngle( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double radLat1 = rad(originLat); + double radLng1 = rad(originLng); + double radLat2 = rad(targetLat); + double radLng2 = rad(targetLng); + double ret; + if (radLng1 == radLng2) { + if (radLat1 > radLat2) { + return 270; // 北半球的情况, 南半球忽略 + } else if (radLat1 < radLat2) { + return 90; + } else { + return Integer.MAX_VALUE; // 位置完全相同 + } + } + ret = 4 * Math.pow(Math.sin((radLat1 - radLat2) / 2), 2) + - Math.pow(Math.sin((radLng1 - radLng2) / 2) + * (Math.cos(radLat1) - Math.cos(radLat2)), 2); + ret = Math.sqrt(ret); + ret = ret / Math.sin(Math.abs(radLng1 - radLng2) / 2) + * (Math.cos(radLat1) + Math.cos(radLat2)); + ret = Math.atan(ret) / Math.PI * 180; + if (radLng1 > radLng2) { // 以 origin 为参考点坐标 + if (radLat1 > radLat2) { + ret += 180; + } else { + ret = 180 - ret; + } + } else if (radLat1 > radLat2) { + ret = 360 - ret; + } + return ret; + } + + /** + * 计算两个坐标的方向 + * @param originLng 起点经度 + * @param originLat 起点纬度 + * @param targetLng 目标经度 + * @param targetLat 目标纬度 + * @return 两个坐标的方向 + */ + public static Direction getDirection( + final double originLng, + final double originLat, + final double targetLng, + final double targetLat + ) { + double angle = getAngle(originLng, originLat, targetLng, targetLat); + return getDirection(angle); + } + + /** + * 通过角度获取方向 + * @param angle 角度 + * @return 方向 + */ + public static Direction getDirection(final double angle) { + if (angle == Integer.MAX_VALUE) return Direction.SAME; + if ((angle <= 10) || (angle > 350)) { + return Direction.RIGHT; + } + if ((angle > 10) && (angle <= 80)) { + return Direction.RIGHT_TOP; + } + if ((angle > 80) && (angle <= 100)) { + return Direction.TOP; + } + if ((angle > 100) && (angle <= 170)) { + return Direction.LEFT_TOP; + } + if ((angle > 170) && (angle <= 190)) { + return Direction.LEFT; + } + if ((angle > 190) && (angle <= 260)) { + return Direction.LEFT_BOTTOM; + } + if ((angle > 260) && (angle <= 280)) { + return Direction.BOTTOM; + } + if ((angle > 280) && (angle <= 350)) { + return Direction.RIGHT_BOTTOM; + } + return Direction.SAME; + } + + /** + * detail: 坐标方向 + * @author Ttt + */ + public enum Direction { + + SAME("相同"), // 坐标相同 + TOP("北"), // 上 ( 北 ) + BOTTOM("南"), // 下 ( 南 ) + LEFT("西"), // 左 ( 西 ) + RIGHT("东"), // 右 ( 东 ) + LEFT_TOP("西北"), // 左上 ( 西北 ) + LEFT_BOTTOM("西南"), // 左下 ( 西南 ) + RIGHT_TOP("东北"), // 右上 ( 东北 ) + RIGHT_BOTTOM("东南"); // 右下 ( 东南 ) + + private final String value; + + Direction(String value) { + this.value = value; + } + + /** + * 获取中文方向值 + * @return 中文方向值 + */ + public String getValue() { + return value; + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/DateUtils.java b/lib/DevJava/src/main/java/dev/utils/common/DateUtils.java new file mode 100644 index 0000000000..b70ec63e02 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/DateUtils.java @@ -0,0 +1,2921 @@ +package dev.utils.common; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 日期工具类 + * @author Ttt + */ +public final class DateUtils { + + private DateUtils() { + } + + // 日志 TAG + private static final String TAG = DateUtils.class.getSimpleName(); + + // 线程安全 SimpleDateFormat Map + private static final ThreadLocal> SDEF_THREAD_LOCAL + = new ThreadLocal>() { + @Override + protected Map initialValue() { + return new HashMap<>(); + } + }; + + /** + * 获取默认 SimpleDateFormat ( yyyy-MM-dd HH:mm:ss ) + * @return {@link SimpleDateFormat} + */ + public static SimpleDateFormat getDefaultFormat() { + return getSafeDateFormat(DevFinal.TIME.yyyyMMddHHmmss_HYPHEN); + } + + /** + * 获取对应时间格式线程安全 SimpleDateFormat + * @param pattern 时间格式 + * @return {@link SimpleDateFormat} + */ + public static SimpleDateFormat getSafeDateFormat(final String pattern) { + if (pattern == null) return null; + Map sdfMap = SDEF_THREAD_LOCAL.get(); + SimpleDateFormat format = sdfMap.get(pattern); + if (format == null) { + format = new SimpleDateFormat(pattern); + sdfMap.put(pattern, format); + } + return format; + } + + // = + + /** + * 获取 Calendar + * @return {@link Calendar} + */ + public static Calendar getCalendar() { + return Calendar.getInstance(); + } + + /** + * 获取 Calendar + * @param millis 时间毫秒 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final long millis) { + if (millis == -1L) return null; + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(millis); + return calendar; + } + + /** + * 获取 Calendar + * @param date 日期 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final Date date) { + if (date == null) return null; + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } + + /** + * 获取 Calendar + * @param time 时间 + * @return {@link Calendar} + */ + public static Calendar getCalendar(final String time) { + return getCalendar(parseLong(time, getDefaultFormat())); + } + + /** + * 获取 Calendar + * @param time 时间 + * @param pattern 时间格式 + * @return {@link Calendar} + */ + public static Calendar getCalendar( + final String time, + final String pattern + ) { + return getCalendar(parseLong(time, getSafeDateFormat(pattern))); + } + + /** + * 获取 Calendar + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@link Calendar} + */ + public static Calendar getCalendar( + final String time, + final SimpleDateFormat format + ) { + return getCalendar(parseLong(time, format)); + } + + // = + + /** + * 获取当前时间 Date + * @return 当前时间 Date + */ + public static Date getCurrentTime() { + return Calendar.getInstance().getTime(); + } + + /** + * 获取当前时间毫秒 + * @return 当前时间毫秒 + */ + public static long getCurrentTimeMillis() { + return getDateTime(Calendar.getInstance().getTime()); + } + + /** + * 获取 Date Time + * @param date 日期 + * @return Date Time + */ + public static long getDateTime(final Date date) { + if (date != null) return date.getTime(); + return -1L; + } + + // = + + /** + * 获取当前时间的字符串 + * @return 当前时间的字符串 + */ + public static String getDateNow() { + return formatTime(getCurrentTimeMillis(), getDefaultFormat()); + } + + /** + * 获取当前时间的字符串 + * @param pattern 时间格式 + * @return 当前时间的字符串 + */ + public static String getDateNow(final String pattern) { + return formatTime(getCurrentTimeMillis(), getSafeDateFormat(pattern)); + } + + /** + * 获取当前时间的字符串 + * @param format {@link SimpleDateFormat} + * @return 当前时间的字符串 + */ + public static String getDateNow(final SimpleDateFormat format) { + return formatTime(getCurrentTimeMillis(), format); + } + + // = + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @return 按照指定格式的日期字符串 + */ + public static String formatDate(final Date date) { + return formatTime(getDateTime(date), getDefaultFormat()); + } + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @param pattern 时间格式 + * @return 按照指定格式的日期字符串 + */ + public static String formatDate( + final Date date, + final String pattern + ) { + return formatTime(getDateTime(date), getSafeDateFormat(pattern)); + } + + /** + * 将 Date 转换日期字符串 + * @param date 日期 + * @param format {@link SimpleDateFormat} + * @return 按照指定格式的日期字符串 + */ + public static String formatDate( + final Date date, + final SimpleDateFormat format + ) { + return formatTime(getDateTime(date), format); + } + + // = + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @return 按照指定格式的日期字符串 + */ + public static String formatTime(final long millis) { + return formatTime(millis, getDefaultFormat()); + } + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @param pattern 时间格式 + * @return 按照指定格式的日期字符串 + */ + public static String formatTime( + final long millis, + final String pattern + ) { + return formatTime(millis, getSafeDateFormat(pattern)); + } + + /** + * 将时间毫秒转换日期字符串 + * @param millis 时间毫秒 + * @param format {@link SimpleDateFormat} + * @return 按照指定格式的日期字符串 + */ + public static String formatTime( + final long millis, + final SimpleDateFormat format + ) { + if (millis == -1L || format == null) return null; + try { + return format.format(millis); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatTime"); + } + return null; + } + + // = + + /** + * 将时间毫秒转换成 Date + * @param millis 时间毫秒 + * @return {@link Date} + */ + public static Date parseDate(final long millis) { + if (millis == -1L) return null; + return new Date(millis); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @return {@link Date} + */ + public static Date parseDate(final String time) { + return parseDate(parseLong(time, getDefaultFormat())); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @param pattern 时间格式 + * @return {@link Date} + */ + public static Date parseDate( + final String time, + final String pattern + ) { + return parseDate(parseLong(time, getSafeDateFormat(pattern))); + } + + /** + * 解析时间字符串转换为 Date + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@link Date} + */ + public static Date parseDate( + final String time, + final SimpleDateFormat format + ) { + return parseDate(parseLong(time, format)); + } + + // = + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @return 毫秒时间 + */ + public static long parseLong(final String time) { + return parseLong(time, getDefaultFormat()); + } + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @param pattern 时间格式 + * @return 毫秒时间 + */ + public static long parseLong( + final String time, + final String pattern + ) { + return parseLong(time, getSafeDateFormat(pattern)); + } + + /** + * 解析时间字符串转换为 long 毫秒 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 毫秒时间 + */ + public static long parseLong( + final String time, + final SimpleDateFormat format + ) { + if (time == null || format == null) return -1L; + try { + return format.parse(time).getTime(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseLong"); + } + return -1L; + } + + // = + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param pattern 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseStringDefault( + final String time, + final String pattern + ) { + return parseString( + time, getDefaultFormat(), + getSafeDateFormat(pattern) + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param format 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseStringDefault( + final String time, + final SimpleDateFormat format + ) { + return parseString( + time, getDefaultFormat(), format + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param timePattern time 的时间格式 + * @param pattern 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseString( + final String time, + final String timePattern, + final String pattern + ) { + return parseString( + time, getSafeDateFormat(timePattern), + getSafeDateFormat(pattern) + ); + } + + /** + * 解析时间字符串转换为指定格式字符串 + * @param time 需要转换的时间 + * @param timeFormat time 的时间格式 + * @param format 把 time 转换成需要的格式 + * @return 转换指定格式的时间字符串 + */ + public static String parseString( + final String time, + final SimpleDateFormat timeFormat, + final SimpleDateFormat format + ) { + if (time != null && timeFormat != null && format != null) { + try { + long timeLong = parseLong(time, timeFormat); + // 把时间转为所需格式字符串 + return formatTime(timeLong, format); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "parseString"); + } + } + return null; + } + + // ========== + // = 获取时间 = + // ========== + + // ===== + // = 年 = + // ===== + + /** + * 获取年份 + * @param calendar {@link Calendar} + * @return 年份 + */ + public static int getYear(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.YEAR); + return -1; + } + + // = + + /** + * 获取年份 + * @return 年份 + */ + public static int getYear() { + return getYear(getCalendar()); + } + + /** + * 获取年份 + * @param millis 时间毫秒 + * @return 年份 + */ + public static int getYear(final long millis) { + return getYear(getCalendar(millis)); + } + + /** + * 获取年份 + * @param date 日期 + * @return 年份 + */ + public static int getYear(final Date date) { + return getYear(getCalendar(date)); + } + + /** + * 获取年份 + * @param time 时间 + * @return 年份 + */ + public static int getYear(final String time) { + return getYear(parseLong(time)); + } + + /** + * 获取年份 + * @param time 时间 + * @param pattern 时间格式 + * @return 年份 + */ + public static int getYear( + final String time, + final String pattern + ) { + return getYear(parseLong(time, pattern)); + } + + /** + * 获取年份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 年份 + */ + public static int getYear( + final String time, + final SimpleDateFormat format + ) { + return getYear(parseLong(time, format)); + } + + // ===== + // = 月 = + // ===== + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param calendar {@link Calendar} + * @return 月份 + */ + public static int getMonth(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.MONTH) + 1; + return -1; + } + + // = + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @return 月份 + */ + public static int getMonth() { + return getMonth(getCalendar()); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param millis 时间毫秒 + * @return 月份 + */ + public static int getMonth(final long millis) { + return getMonth(getCalendar(millis)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param date 日期 + * @return 月份 + */ + public static int getMonth(final Date date) { + return getMonth(getCalendar(date)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @return 月份 + */ + public static int getMonth(final String time) { + return getMonth(parseLong(time)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @param pattern 时间格式 + * @return 月份 + */ + public static int getMonth( + final String time, + final String pattern + ) { + return getMonth(parseLong(time, pattern)); + } + + /** + * 获取月份 ( 0 - 11 ) + 1 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 月份 + */ + public static int getMonth( + final String time, + final SimpleDateFormat format + ) { + return getMonth(parseLong(time, format)); + } + + // ======= + // = 天数 = + // ======= + + /** + * 获取天数 + * @param calendar {@link Calendar} + * @return 天数 + */ + public static int getDay(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.DAY_OF_MONTH); + return -1; + } + + // = + + /** + * 获取天数 + * @return 天数 + */ + public static int getDay() { + return getDay(getCalendar()); + } + + /** + * 获取天数 + * @param millis 时间毫秒 + * @return 天数 + */ + public static int getDay(final long millis) { + return getDay(getCalendar(millis)); + } + + /** + * 获取天数 + * @param date 日期 + * @return 天数 + */ + public static int getDay(final Date date) { + return getDay(getCalendar(date)); + } + + /** + * 获取天数 + * @param time 时间 + * @return 天数 + */ + public static int getDay(final String time) { + return getDay(parseLong(time)); + } + + /** + * 获取天数 + * @param time 时间 + * @param pattern 时间格式 + * @return 天数 + */ + public static int getDay( + final String time, + final String pattern + ) { + return getDay(parseLong(time, pattern)); + } + + /** + * 获取天数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 天数 + */ + public static int getDay( + final String time, + final SimpleDateFormat format + ) { + return getDay(parseLong(time, format)); + } + + // ======= + // = 星期 = + // ======= + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param calendar {@link Calendar} + * @return 星期数 + */ + public static int getWeek(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.DAY_OF_WEEK); + return -1; + } + + // = + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @return 星期数 + */ + public static int getWeek() { + return getWeek(getCalendar()); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param millis 时间毫秒 + * @return 星期数 + */ + public static int getWeek(final long millis) { + return getWeek(getCalendar(millis)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param date 日期 + * @return 星期数 + */ + public static int getWeek(final Date date) { + return getWeek(getCalendar(date)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @return 星期数 + */ + public static int getWeek(final String time) { + return getWeek(parseLong(time)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 星期数 + */ + public static int getWeek( + final String time, + final String pattern + ) { + return getWeek(parseLong(time, pattern)); + } + + /** + * 获取星期数 ( 1 - 7、日 - 六 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 星期数 + */ + public static int getWeek( + final String time, + final SimpleDateFormat format + ) { + return getWeek(parseLong(time, format)); + } + + // ======= + // = 24H = + // ======= + + /** + * 获取小时 ( 24 ) + * @param calendar {@link Calendar} + * @return 小时 + */ + public static int get24Hour(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.HOUR_OF_DAY); + return -1; + } + + // = + + /** + * 获取小时 ( 24 ) + * @return 小时 + */ + public static int get24Hour() { + return get24Hour(getCalendar()); + } + + /** + * 获取小时 ( 24 ) + * @param millis 时间毫秒 + * @return 小时 + */ + public static int get24Hour(final long millis) { + return get24Hour(getCalendar(millis)); + } + + /** + * 获取小时 ( 24 ) + * @param date 日期 + * @return 小时 + */ + public static int get24Hour(final Date date) { + return get24Hour(getCalendar(date)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @return 小时 + */ + public static int get24Hour(final String time) { + return get24Hour(parseLong(time)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 小时 + */ + public static int get24Hour( + final String time, + final String pattern + ) { + return get24Hour(parseLong(time, pattern)); + } + + /** + * 获取小时 ( 24 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 小时 + */ + public static int get24Hour( + final String time, + final SimpleDateFormat format + ) { + return get24Hour(parseLong(time, format)); + } + + // ======= + // = 12H = + // ======= + + /** + * 获取小时 ( 12 ) + * @param calendar {@link Calendar} + * @return 小时 + */ + public static int get12Hour(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.HOUR); + return -1; + } + + // = + + /** + * 获取小时 ( 12 ) + * @return 小时 + */ + public static int get12Hour() { + return get12Hour(getCalendar()); + } + + /** + * 获取小时 ( 12 ) + * @param millis 时间毫秒 + * @return 小时 + */ + public static int get12Hour(final long millis) { + return get12Hour(getCalendar(millis)); + } + + /** + * 获取小时 ( 12 ) + * @param date 日期 + * @return 小时 + */ + public static int get12Hour(final Date date) { + return get12Hour(getCalendar(date)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @return 小时 + */ + public static int get12Hour(final String time) { + return get12Hour(parseLong(time)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @param pattern 时间格式 + * @return 小时 + */ + public static int get12Hour( + final String time, + final String pattern + ) { + return get12Hour(parseLong(time, pattern)); + } + + /** + * 获取小时 ( 12 ) + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 小时 + */ + public static int get12Hour( + final String time, + final SimpleDateFormat format + ) { + return get12Hour(parseLong(time, format)); + } + + // ======= + // = 分钟 = + // ======= + + /** + * 获取分钟 + * @param calendar {@link Calendar} + * @return 分钟 + */ + public static int getMinute(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.MINUTE); + return -1; + } + + // = + + /** + * 获取分钟 + * @return 分钟 + */ + public static int getMinute() { + return getMinute(getCalendar()); + } + + /** + * 获取分钟 + * @param millis 时间毫秒 + * @return 分钟 + */ + public static int getMinute(final long millis) { + return getMinute(getCalendar(millis)); + } + + /** + * 获取分钟 + * @param date 日期 + * @return 分钟 + */ + public static int getMinute(final Date date) { + return getMinute(getCalendar(date)); + } + + /** + * 获取分钟 + * @param time 时间 + * @return 分钟 + */ + public static int getMinute(final String time) { + return getMinute(parseLong(time)); + } + + /** + * 获取分钟 + * @param time 时间 + * @param pattern 时间格式 + * @return 分钟 + */ + public static int getMinute( + final String time, + final String pattern + ) { + return getMinute(parseLong(time, pattern)); + } + + /** + * 获取分钟 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 分钟 + */ + public static int getMinute( + final String time, + final SimpleDateFormat format + ) { + return getMinute(parseLong(time, format)); + } + + // ======= + // = 秒数 = + // ======= + + /** + * 获取秒数 + * @param calendar {@link Calendar} + * @return 秒数 + */ + public static int getSecond(final Calendar calendar) { + if (calendar != null) return calendar.get(Calendar.SECOND); + return -1; + } + + // = + + /** + * 获取秒数 + * @return 秒数 + */ + public static int getSecond() { + return getSecond(getCalendar()); + } + + /** + * 获取秒数 + * @param millis 时间毫秒 + * @return 秒数 + */ + public static int getSecond(final long millis) { + return getSecond(getCalendar(millis)); + } + + /** + * 获取秒数 + * @param date 日期 + * @return 秒数 + */ + public static int getSecond(final Date date) { + return getSecond(getCalendar(date)); + } + + /** + * 获取秒数 + * @param time 时间 + * @return 秒数 + */ + public static int getSecond(final String time) { + return getSecond(parseLong(time)); + } + + /** + * 获取秒数 + * @param time 时间 + * @param pattern 时间格式 + * @return 秒数 + */ + public static int getSecond( + final String time, + final String pattern + ) { + return getSecond(parseLong(time, pattern)); + } + + /** + * 获取秒数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return 秒数 + */ + public static int getSecond( + final String time, + final SimpleDateFormat format + ) { + return getSecond(parseLong(time, format)); + } + + // ======= + // = 上午 = + // ======= + + /** + * 是否上午 + * @param calendar {@link Calendar} + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final Calendar calendar) { + if (calendar != null) { + return calendar.get(GregorianCalendar.AM_PM) == Calendar.AM; + } + return false; + } + + // = + + /** + * 是否上午 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM() { + return isAM(getCalendar()); + } + + /** + * 是否上午 + * @param millis 时间毫秒 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final long millis) { + return isAM(getCalendar(millis)); + } + + /** + * 是否上午 + * @param date 日期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final Date date) { + return isAM(getCalendar(date)); + } + + /** + * 是否上午 + * @param time 时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM(final String time) { + return isAM(parseLong(time)); + } + + /** + * 是否上午 + * @param time 时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM( + final String time, + final String pattern + ) { + return isAM(parseLong(time, pattern)); + } + + /** + * 是否上午 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isAM( + final String time, + final SimpleDateFormat format + ) { + return isAM(parseLong(time, format)); + } + + // ======= + // = 下午 = + // ======= + + /** + * 是否下午 + * @param calendar {@link Calendar} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final Calendar calendar) { + if (calendar != null) { + return calendar.get(GregorianCalendar.AM_PM) == Calendar.PM; + } + return false; + } + + // = + + /** + * 是否下午 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM() { + return isPM(getCalendar()); + } + + /** + * 是否下午 + * @param millis 时间毫秒 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final long millis) { + return isPM(getCalendar(millis)); + } + + /** + * 是否下午 + * @param date 日期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final Date date) { + return isPM(getCalendar(date)); + } + + /** + * 是否下午 + * @param time 时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM(final String time) { + return isPM(parseLong(time)); + } + + /** + * 是否下午 + * @param time 时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM( + final String time, + final String pattern + ) { + return isPM(parseLong(time, pattern)); + } + + /** + * 是否下午 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isPM( + final String time, + final SimpleDateFormat format + ) { + return isPM(parseLong(time, format)); + } + + // ========== + // = 年份判断 = + // ========== + + /** + * 是否对应年份 + * @param calendar {@link Calendar} + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final Calendar calendar, + final int year + ) { + return year != -1 && year == getYear(calendar); + } + + // = + + /** + * 是否对应年份 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear(final int year) { + return isYear(getCalendar(), year); + } + + /** + * 是否对应年份 + * @param millis 时间毫秒 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final long millis, + final int year + ) { + return isYear(getCalendar(millis), year); + } + + /** + * 是否对应年份 + * @param date 日期 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final Date date, + final int year + ) { + return isYear(getCalendar(date), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final int year + ) { + return isYear(parseLong(time), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param pattern 时间格式 + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final String pattern, + final int year + ) { + return isYear(parseLong(time, pattern), year); + } + + /** + * 是否对应年份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param year 待判断年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isYear( + final String time, + final SimpleDateFormat format, + final int year + ) { + return isYear(parseLong(time, format), year); + } + + // ========== + // = 月份判断 = + // ========== + + /** + * 是否对应月份 + * @param calendar {@link Calendar} + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final Calendar calendar, + final int month + ) { + return month != -1 && month == getMonth(calendar); + } + + // = + + /** + * 是否对应月份 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth(final int month) { + return isMonth(getCalendar(), month); + } + + /** + * 是否对应月份 + * @param millis 时间毫秒 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final long millis, + final int month + ) { + return isMonth(getCalendar(millis), month); + } + + /** + * 是否对应月份 + * @param date 日期 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final Date date, + final int month + ) { + return isMonth(getCalendar(date), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final int month + ) { + return isMonth(parseLong(time), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param pattern 时间格式 + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final String pattern, + final int month + ) { + return isMonth(parseLong(time, pattern), month); + } + + /** + * 是否对应月份 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param month 待判断月份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMonth( + final String time, + final SimpleDateFormat format, + final int month + ) { + return isMonth(parseLong(time, format), month); + } + + // ========== + // = 天数判断 = + // ========== + + /** + * 是否对应天数 + * @param calendar {@link Calendar} + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final Calendar calendar, + final int day + ) { + return day != -1 && day == getDay(calendar); + } + + // = + + /** + * 是否对应天数 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay(final int day) { + return isDay(getCalendar(), day); + } + + /** + * 是否对应天数 + * @param millis 时间毫秒 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final long millis, + final int day + ) { + return isDay(getCalendar(millis), day); + } + + /** + * 是否对应天数 + * @param date 日期 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final Date date, + final int day + ) { + return isDay(getCalendar(date), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final int day + ) { + return isDay(parseLong(time), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param pattern 时间格式 + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final String pattern, + final int day + ) { + return isDay(parseLong(time, pattern), day); + } + + /** + * 是否对应天数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param day 待判断天数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDay( + final String time, + final SimpleDateFormat format, + final int day + ) { + return isDay(parseLong(time, format), day); + } + + // ========== + // = 星期判断 = + // ========== + + /** + * 是否对应星期 + * @param calendar {@link Calendar} + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final Calendar calendar, + final int week + ) { + return week != -1 && week == getWeek(calendar); + } + + // = + + /** + * 是否对应星期 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek(final int week) { + return isWeek(getCalendar(), week); + } + + /** + * 是否对应星期 + * @param millis 时间毫秒 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final long millis, + final int week + ) { + return isWeek(getCalendar(millis), week); + } + + /** + * 是否对应星期 + * @param date 日期 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final Date date, + final int week + ) { + return isWeek(getCalendar(date), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final int week + ) { + return isWeek(parseLong(time), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param pattern 时间格式 + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final String pattern, + final int week + ) { + return isWeek(parseLong(time, pattern), week); + } + + /** + * 是否对应星期 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param week 待判断星期 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWeek( + final String time, + final SimpleDateFormat format, + final int week + ) { + return isWeek(parseLong(time, format), week); + } + + // ========== + // = 小时判断 = + // ========== + + /** + * 是否对应小时 + * @param calendar {@link Calendar} + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final Calendar calendar, + final int hour + ) { + return hour != -1 && hour == get24Hour(calendar); + } + + // = + + /** + * 是否对应小时 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour(final int hour) { + return isHour(getCalendar(), hour); + } + + /** + * 是否对应小时 + * @param millis 时间毫秒 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final long millis, + final int hour + ) { + return isHour(getCalendar(millis), hour); + } + + /** + * 是否对应小时 + * @param date 日期 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final Date date, + final int hour + ) { + return isHour(getCalendar(date), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final int hour + ) { + return isHour(parseLong(time), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param pattern 时间格式 + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final String pattern, + final int hour + ) { + return isHour(parseLong(time, pattern), hour); + } + + /** + * 是否对应小时 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param hour 待判断小时 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHour( + final String time, + final SimpleDateFormat format, + final int hour + ) { + return isHour(parseLong(time, format), hour); + } + + // ========== + // = 分钟判断 = + // ========== + + /** + * 是否对应分钟 + * @param calendar {@link Calendar} + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final Calendar calendar, + final int minute + ) { + return minute != -1 && minute == getMinute(calendar); + } + + // = + + /** + * 是否对应分钟 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute(final int minute) { + return isMinute(getCalendar(), minute); + } + + /** + * 是否对应分钟 + * @param millis 时间毫秒 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final long millis, + final int minute + ) { + return isMinute(getCalendar(millis), minute); + } + + /** + * 是否对应分钟 + * @param date 日期 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final Date date, + final int minute + ) { + return isMinute(getCalendar(date), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final int minute + ) { + return isMinute(parseLong(time), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param pattern 时间格式 + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final String pattern, + final int minute + ) { + return isMinute(parseLong(time, pattern), minute); + } + + /** + * 是否对应分钟 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param minute 待判断分钟 + * @return {@code true} yes, {@code false} no + */ + public static boolean isMinute( + final String time, + final SimpleDateFormat format, + final int minute + ) { + return isMinute(parseLong(time, format), minute); + } + + // ========== + // = 秒数判断 = + // ========== + + /** + * 是否对应秒数 + * @param calendar {@link Calendar} + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final Calendar calendar, + final int second + ) { + return second != -1 && second == getSecond(calendar); + } + + // = + + /** + * 是否对应秒数 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond(final int second) { + return isSecond(getCalendar(), second); + } + + /** + * 是否对应秒数 + * @param millis 时间毫秒 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final long millis, + final int second + ) { + return isSecond(getCalendar(millis), second); + } + + /** + * 是否对应秒数 + * @param date 日期 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final Date date, + final int second + ) { + return isSecond(getCalendar(date), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final int second + ) { + return isSecond(parseLong(time), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param pattern 时间格式 + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final String pattern, + final int second + ) { + return isSecond(parseLong(time, pattern), second); + } + + /** + * 是否对应秒数 + * @param time 时间 + * @param format {@link SimpleDateFormat} + * @param second 待判断秒数 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSecond( + final String time, + final SimpleDateFormat format, + final int second + ) { + return isSecond(parseLong(time, format), second); + } + + // = + + /** + * 获取秒数倍数 + * @param millis 时间毫秒 + * @return 秒数倍数 + */ + public static int getSecondMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.SECOND_MS); + } + + /** + * 获取分钟倍数 + * @param millis 时间毫秒 + * @return 分钟倍数 + */ + public static int getMinuteMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.MINUTE_MS); + } + + /** + * 获取小时倍数 + * @param millis 时间毫秒 + * @return 小时倍数 + */ + public static int getHourMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.HOUR_MS); + } + + /** + * 获取天数倍数 + * @param millis 时间毫秒 + * @return 天数倍数 + */ + public static int getDayMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.DAY_MS); + } + + /** + * 获取周数倍数 + * @param millis 时间毫秒 + * @return 周数倍数 + */ + public static int getWeekMultiple(final long millis) { + return getMillisMultiple(millis, DevFinal.TIME.WEEK_MS); + } + + /** + * 获取对应单位倍数 + * @param millis 时间毫秒 + * @param unit 毫秒单位 ( 除数 ) + * @return 对应单位倍数 + */ + public static int getMillisMultiple( + final long millis, + final long unit + ) { + if (millis == -1L) return -1; + return NumberUtils.multipleI(millis, unit); + } + + // ============ + // = 时间差计算 = + // ============ + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param millis 时间毫秒 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final long millis) { + if (millis == -1L) return -1L; + return millis - System.currentTimeMillis(); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param date 日期 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final Date date) { + return getTimeDiffByCurrent(getDateTime(date)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent(final String time) { + return getTimeDiffByCurrent(parseLong(time)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @param pattern 把 time 转换成需要的格式 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent( + final String time, + final String pattern + ) { + return getTimeDiffByCurrent(parseLong(time, pattern)); + } + + /** + * 获取时间差 ( 传入时间 - 当前时间 ) + * @param time 需要转换的时间 + * @param format 把 time 转换成需要的格式 + * @return 与当前时间的时间差 ( 毫秒 ) + */ + public static long getTimeDiffByCurrent( + final String time, + final SimpleDateFormat format + ) { + return getTimeDiffByCurrent(parseLong(time, format)); + } + + // = + + /** + * 获取时间差 + * @param time1 时间 + * @param time2 对比时间 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final String time2 + ) { + return getTimeDiff( + time1, getDefaultFormat(), + time2, getDefaultFormat() + ); + } + + /** + * 获取时间差 + * @param time1 时间 + * @param pattern1 时间格式 + * @param time2 对比时间 + * @param pattern2 对比时间格式 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final String pattern1, + final String time2, + final String pattern2 + ) { + return getTimeDiff( + time1, getSafeDateFormat(pattern1), + time2, getSafeDateFormat(pattern2) + ); + } + + /** + * 获取时间差 + * @param time1 时间 + * @param timeFormat1 时间格式 + * @param time2 对比时间 + * @param timeFormat2 对比时间格式 + * @return 时间差 ( 毫秒 ) + */ + public static long getTimeDiff( + final String time1, + final SimpleDateFormat timeFormat1, + final String time2, + final SimpleDateFormat timeFormat2 + ) { + long timeLong1 = parseLong(time1, timeFormat1); + if (timeLong1 == -1L) return -1L; + long timeLong2 = parseLong(time2, timeFormat2); + if (timeLong2 == -1L) return -1L; + return timeLong1 - timeLong2; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断是否闰年 + * @param year 年份 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLeapYear(final int year) { + return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0; + } + + /** + * 根据年份、月份, 获取对应的天数 ( 完整天数, 无判断是否属于未来日期 ) + * @param year 年份 + * @param month 月份 + * @return 指定年份所属的月份的天数 + */ + public static int getMonthDayNumberAll( + final int year, + final int month + ) { + int number = 31; + // 判断返回的标识数字 + switch (month) { + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + number = 31; + break; + case 2: + if (isLeapYear(year)) { + number = 29; + } else { + number = 28; + } + break; + case 4: + case 6: + case 9: + case 11: + number = 30; + break; + } + return number; + } + + /** + * 根据年份, 获取对应的月份 + *
+     *     传入历史、以及未来年份都返回对应年月份
+     *     传入当前年份则返回当前月份
+     * 
+ * @param year 年份 + * @return 内部判断是否相同一年, 不能超过限制未来的月份 + */ + public static int getYearMonthNumber(final int year) { + if (year == getYear()) return getMonth(); + return 12; + } + + /** + * 根据年份、月份, 获取对应的天数 + *
+     *     传入历史、以及未来年月份都返回对应年月份的天数
+     *     传入当前年月份则返回当前天数
+     * 
+ * @param year 年份 + * @param month 月份 + * @return 内部判断是否相同一年、月份, 不能超过限制未来的天数 + */ + public static int getMonthDayNumber( + final int year, + final int month + ) { + if (year == getYear() && month == getMonth()) { + return getDay(); + } + return getMonthDayNumberAll(year, month); + } + + // = + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero(final int time) { + return timeAddZero(time, true); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @param appendZero 是否自动补 0 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero( + final int time, + final boolean appendZero + ) { + return NumberUtils.addZero(time, appendZero); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero(final long time) { + return timeAddZero(time, true); + } + + /** + * 时间补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param time 待处理时间 + * @param appendZero 是否自动补 0 + * @return 自动补 0 时间字符串 + */ + public static String timeAddZero( + final long time, + final boolean appendZero + ) { + return NumberUtils.addZero(time, appendZero); + } + + // = + + /** + * 生成 HH 按时间排序数组 + * @param appendZero 是否自动补 0 + * @return 按小时排序的数组 + */ + public static String[] getArrayToHH(final boolean appendZero) { + List lists = getListToHH(appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 HH 按时间排序集合 + * @param appendZero 是否自动补 0 + * @return 按小时排序的集合 + */ + public static List getListToHH(final boolean appendZero) { + List lists = new ArrayList<>(); + for (int i = 0; i < 24; i++) { + lists.add(timeAddZero(i, appendZero)); + } + return lists; + } + + // = + + /** + * 生成 MM 按时间排序数组 + * @param appendZero 是否自动补 0 + * @return 按分钟排序的数组 + */ + public static String[] getArrayToMM(final boolean appendZero) { + List lists = getListToMM(appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 MM 按时间排序集合 + * @param appendZero 是否自动补 0 + * @return 按分钟排序的集合 + */ + public static List getListToMM(final boolean appendZero) { + List lists = new ArrayList<>(); + for (int i = 0; i < 60; i++) { + lists.add(timeAddZero(i, appendZero)); + } + return lists; + } + + // = + + /** + * 生成 HH:mm 按间隔时间排序数组 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @param appendZero 是否自动补 0 + * @return 指定格式的数组 + */ + public static String[] getArrayToHHMM( + final int type, + final boolean appendZero + ) { + List lists = getListToHHMM(type, appendZero); + return lists.toArray(new String[0]); + } + + /** + * 生成 HH:mm 按间隔时间排序集合 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @param appendZero 是否自动补 0 + * @return 指定格式的集合 + */ + public static List getListToHHMM( + final int type, + final boolean appendZero + ) { + List lists = new ArrayList<>(); + switch (type) { + case 0: + for (int i = 0; i < 24; i++) { + lists.add(timeAddZero(i, appendZero) + ":00"); + } + break; + case 1: + for (int i = 0; i < 96; i++) { // 00 15 30 45 = 4 (24 * 4) + if (i % 2 == 0) { // 判断是否偶数 00、30 + // 小时数 + String hour = timeAddZero(i / 4, appendZero); + // 分钟数 + String minute = i % 4 == 0 ? "00" : "30"; + // 累加时间 + lists.add(hour + ":" + minute); + } else { // 15、45 + // 小时数 + String hour = timeAddZero(i / 4, appendZero); + // 分钟数 + String minute = (i - 1) % 4 == 0 ? "15" : "45"; + // 累加时间 + lists.add(hour + ":" + minute); + } + } + break; + case 2: + for (int i = 0; i < 48; i++) { // 00 30 = 2 (24 * 2) + // 小时处理 + int hour = i / 2; + // 属于偶数 + if (i % 2 == 0) { + lists.add(timeAddZero(hour, appendZero) + ":00"); + } else { + lists.add(timeAddZero(hour, appendZero) + ":30"); + } + } + break; + } + return lists; + } + + /** + * 获取 HH:mm 按间隔时间排序的集合中, 指定时间所在索引 + * @param time HH:mm 格式 + * @param type 0 = 00:00 - 23:00 = 每 1 小时间隔 + * 1 = 00:00 - 23:45 = 每 15 分钟间隔 + * 2 = 00:00 - 23:30 = 每 30 分钟间隔 + * @return 指定数据, 在对应格式类型内的索引 + */ + public static int getListToHHMMPosition( + final String time, + final int type + ) { + if (time != null && time.length() != 0) { + // 进行拆分 + String[] timeSplit = time.split(":"); + if (timeSplit.length == 2) { + // 转换小时 + int hour = ConvertUtils.toInt(timeSplit[0], -1); + // 判断是否小于 0 + if (hour < 0) { + return -1; + } else if (hour > 24) { + return -1; + } + + // 判断格式, 进行格式处理 + switch (type) { + case 0: + return hour; + case 1: + case 2: + // 转换分钟 + int minute = ConvertUtils.toInt(timeSplit[1], -1); + // 判断是否小于 0 + if (minute < 0) { + return -1; + } else if (minute > 59) { + return -1; + } + // 判断间隔 + if (type == 1) { + if (minute < 15) { + return hour * 4; + } else if (minute < 30) { + return hour * 4 + 1; + } else if (minute < 45) { + return hour * 4 + 2; + } else { + return hour * 4 + 3; + } + } else { // 30 分钟一个间隔 + if (minute >= 30) { // 属于 30, 需要加 1 + return hour * 2 + 1; + } else { + return hour * 2; + } + } + } + } + } + return -1; + } + + // ============= + // = Unit Span = + // ============= + + // 毫秒格式化范围 + private static final long[] MILLIS_UNIT_SPANS = { + 86400000, 3600000, 60000, 1000, 1 + }; + // 毫秒格式化单位 + private static final String[] MILLIS_UNITS = { + "天", "小时", "分钟", "秒", "毫秒" + }; + + /** + * 转换时间 + * @param millis 时间毫秒 + * @param precision precision = 1, return 天 + * precision = 2, return 天, 小时 + * precision = 3, return 天, 小时, 分钟 + * precision = 4, return 天, 小时, 分钟, 秒 + * precision = 5, return 天, 小时, 分钟, 秒, 毫秒 + * @param appendZero 是否自动补 0 + * @return 转换指定格式的时间字符串 + */ + public static String millisToFitTimeSpan( + final long millis, + final int precision, + final boolean appendZero + ) { + if (precision >= 1 && precision <= 5) { + return FormatUtils.unitSpanOf( + precision, appendZero, "" + ).formatBySpan( + millis, MILLIS_UNIT_SPANS, MILLIS_UNITS + ); + } + return ""; + } + + /** + * 转换时间为数组 + * @param millis 时间毫秒 + * @return int[5] { 天, 小时, 分钟, 秒, 毫秒 } + */ + public static int[] millisToTimeArrays(final long millis) { + if (millis > 0) { + long[] values = NumberUtils.calculateUnitL(millis, MILLIS_UNIT_SPANS); + return ConvertUtils.longsToInts(values); + } + return new int[5]; + } + + // = + + /** + * 传入时间毫秒, 获取 00:00:00 格式 ( 不处理大于一天 ) + * @param millis 时间毫秒 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertByMillis(final long millis) { + return timeConvertByMillis(millis, false); + } + + /** + * 传入时间毫秒, 获取 00:00:00 格式 + *
+     *     小时:分钟:秒
+     * 
+ * @param millis 时间毫秒 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertByMillis( + final long millis, + final boolean handlerMoreThanDay + ) { + int[] result = millisToTimeArrays(millis); + // 如果大于一天但不处理大于一天情况则返回 null + if (result[0] > 0 && !handlerMoreThanDay) { + return null; + } + return timeAddZero(result[0] * 24 + result[1]) + + ":" + timeAddZero(result[2]) + + ":" + timeAddZero(result[3]); + } + + // = + + /** + * 传入时间秒, 获取 00:00:00 格式 ( 不处理大于一天 ) + * @param second 时间 ( 秒 ) + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertBySecond(final long second) { + return timeConvertBySecond(second, false); + } + + /** + * 传入时间秒, 获取 00:00:00 格式 + * @param second 时间 ( 秒 ) + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return 转换 ( 00:00:00 ) 时间格式字符串 + */ + public static String timeConvertBySecond( + final long second, + final boolean handlerMoreThanDay + ) { + return timeConvertByMillis(second * 1000L, handlerMoreThanDay); + } + + // ================== + // = 判断是否在区间范围 = + // ================== + + /** + * 判断时间是否在 [startTime, endTime] 区间 + * @param time 待判断时间 ( 毫秒 ) + * @param startTime 开始时间 ( 毫秒 ) + * @param endTime 结束时间 ( 毫秒 ) + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTime( + final long time, + final long startTime, + final long endTime + ) { + if (time == -1L || startTime == -1L || endTime == -1L) return false; + // 待校验时间 + Calendar check = Calendar.getInstance(); + check.setTimeInMillis(time); + // 开始时间 + Calendar begin = Calendar.getInstance(); + begin.setTimeInMillis(startTime); + // 结束时间 + Calendar end = Calendar.getInstance(); + end.setTimeInMillis(endTime); + // 判断是否在 begin 之后的时间, 并且在 end 之前的时间 + if (check.after(begin) && check.before(end)) { + return true; + } + // 判断时间相同情况 + return time == startTime || time == endTime; + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTime( + final Date time, + final Date startTime, + final Date endTime + ) { + return isInTime(getDateTime(time), getDateTime(startTime), getDateTime(endTime)); + } + + // = + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param pattern 时间格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final String pattern + ) { + return isInTimeFormat(time, startTime, endTime, pattern, false); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param pattern 时间格式 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final String pattern, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + getSafeDateFormat(pattern), + handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format {@link SimpleDateFormat} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final SimpleDateFormat format + ) { + return isInTimeFormat(time, startTime, endTime, format, false); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( 自定义格式 ) + *
+     *     handlerMoreThanDay 参数注意事项
+     *     用于 {@link DevFinal.TIME#HHmm_COLON}、{@link DevFinal.TIME#HHmmss_COLON} 判断, 只有该格式判断可传入 true
+     *     其他都用于 false
+     * 
+ * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format {@link SimpleDateFormat} + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeFormat( + final String time, + final String startTime, + final String endTime, + final SimpleDateFormat format, + final boolean handlerMoreThanDay + ) { + if (time == null || startTime == null || endTime == null) return false; + long check = parseLong(time, format); + long start = parseLong(startTime, format); + long end = parseLong(endTime, format); + if (check == -1L || start == -1L || end == -1L) return false; + // 大于一天的情况 ( 指的是结束时间在开始时间之前 ) + if (handlerMoreThanDay && end < start) { + // 结束属于第二天区域 + return check >= start || check <= end; + } + // 时间是否在 [startTime, endTime] 区间 + return check >= start && check <= end; + } + + // ======== + // = HHmm = + // ======== + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String startTime, + final String endTime + ) { + return isInTimeHHmm(startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + getDateNow(DevFinal.TIME.HHmm_COLON), startTime, endTime, + DevFinal.TIME.HHmm_COLON, handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String time, + final String startTime, + final String endTime + ) { + return isInTimeHHmm(time, startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmm 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmm( + final String time, + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + DevFinal.TIME.HHmm_COLON, handlerMoreThanDay + ); + } + + // ========== + // = HHmmss = + // ========== + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String startTime, + final String endTime + ) { + return isInTimeHHmmss(startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + getDateNow(DevFinal.TIME.HHmmss_COLON), startTime, endTime, + DevFinal.TIME.HHmmss_COLON, handlerMoreThanDay + ); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String time, + final String startTime, + final String endTime + ) { + return isInTimeHHmmss(time, startTime, endTime, true); + } + + /** + * 判断时间是否在 [startTime, endTime] 区间 ( HHmmss 格式 ) + * @param time 待判断时间 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param handlerMoreThanDay 是否处理大于一天的时间 + * @return {@code true} yes, {@code false} no + */ + public static boolean isInTimeHHmmss( + final String time, + final String startTime, + final String endTime, + final boolean handlerMoreThanDay + ) { + return isInTimeFormat( + time, startTime, endTime, + DevFinal.TIME.HHmmss_COLON, handlerMoreThanDay + ); + } + + // = + + /** + * 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) + * @param endTime 结束时间 HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiffHHmm(final String endTime) { + return getEndTimeDiff(System.currentTimeMillis(), endTime, DevFinal.TIME.HHmm_COLON); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间 ( 判断凌晨情况 ) + * @param startTime 开始时间 + * @param endTime 结束时间 HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiffHHmm( + final long startTime, + final String endTime + ) { + return getEndTimeDiff(startTime, endTime, DevFinal.TIME.HHmm_COLON); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) + * @param endTime 结束时间 + * @param format 格式 如: HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiff( + final String endTime, + final String format + ) { + return getEndTimeDiff(System.currentTimeMillis(), endTime, format); + } + + /** + * 获取指定时间距离该时间第二天的指定时段的时间差 ( 判断凌晨情况 ) + *
+     *     如当前时间 2018-12-07 15:27:23, 判断距离 14:39:20 (endTime) 有多久
+     *     如果过了这个时间段, 则返回 2018-12-08 14:39:20 ( 明天的这个时间段时间 )
+     *     如果没有过这个时间段 ( 如: 17:39:20) 则返回当天时间段 2018-12-07 17:39:20 (2018-12-07 + endTime)
+     * 
+ * @param startTime 开始时间 + * @param endTime 结束时间 + * @param format 格式 如: HH:mm + * @return 距离指定结束时间还有多少毫秒 + */ + public static long getEndTimeDiff( + final long startTime, + final String endTime, + final String format + ) { + if (startTime < 1 || endTime == null || format == null) return -1L; + try { + // 判断格式是否加了秒 + boolean isSecond = format.endsWith(":ss"); + // 获取开始时间 + String start = formatTime(startTime, format); + // 转换时间 + int startNumber = Integer.parseInt(start.replace(":", "")); + // 获取结束时间转换 + int endNumber = Integer.parseInt(endTime.replace(":", "")); + // 时间处理 + Calendar calendar = Calendar.getInstance(); + calendar.setTime(new Date(startTime)); // 设置当前时间 + // 如果当前时间大于结束时间, 表示非第二天 + if (startNumber > endNumber) { + // 时间累加一天 + calendar.add(Calendar.DATE, 1); // 当前日期加一天 + } + // 获取天数时间 + String yyyyMMddDate = formatDate(calendar.getTime(), DevFinal.TIME.yyyyMMdd_HYPHEN); + // 累加时间 + String yyyyMMddHHmmssDate = yyyyMMddDate + " " + endTime + (isSecond ? "" : ":00"); + // 返回转换后的时间 + return parseLong(yyyyMMddHHmmssDate, DevFinal.TIME.yyyyMMddHHmmss_HYPHEN); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getEndTimeDiff"); + } + return -1L; + } + + // ============ + // = 生肖、星座 = + // ============ + + // 生肖数组 + private static final String[] ZODIAC = { + "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊" + }; + + // 星座截止天数 + private static final int[] CONSTELLATION_DAY = { + 20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22 + }; + + // 星座对应日期 + private static final String[] CONSTELLATION_DATE = { + "01.20-02.18", "02.19-03.20", "03.21-04.19", "04.20-05.20", "05.21-06.21", "06.22-07.22", + "07.23-08.22", "08.23-09.22", "09.23-10.23", "10.24-11.22", "11.23-12.21", "12.22-01.19" + }; + + // 星座数组 + private static final String[] CONSTELLATION = { + "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", + "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座" + }; + + /** + * 获取生肖 + * @param year 年份 + * @return 生肖 + */ + public static String getZodiac(final int year) { + return ZODIAC[Math.abs(year) % 12]; + } + + /** + * 获取星座 + * @param month 月份 + * @param day 天数 + * @return 星座 + */ + public static String getConstellation( + final int month, + final int day + ) { + if (month > 12 || month < 1) return null; + return CONSTELLATION[day >= CONSTELLATION_DAY[month - 1] ? month - 1 : (month + 10) % 12]; + } + + /** + * 获取星座日期 + * @param month 月份 + * @param day 天数 + * @return 星座日期 + */ + public static String getConstellationDate( + final int month, + final int day + ) { + if (month > 12 || month < 1) return null; + return CONSTELLATION_DATE[day >= CONSTELLATION_DAY[month - 1] ? month - 1 : (month + 10) % 12]; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/DevCommonUtils.java b/lib/DevJava/src/main/java/dev/utils/common/DevCommonUtils.java new file mode 100644 index 0000000000..f4b093e32e --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/DevCommonUtils.java @@ -0,0 +1,253 @@ +package dev.utils.common; + +import java.util.Random; +import java.util.UUID; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.encrypt.MD5Utils; + +/** + * detail: 开发常用方法工具类 + * @author Ttt + */ +public final class DevCommonUtils { + + private DevCommonUtils() { + } + + // 日志 TAG + private static final String TAG = DevCommonUtils.class.getSimpleName(); + + // ============= + // = 计时相关处理 = + // ============= + + /** + * 耗时时间记录 + * @param builder 拼接 Builder + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@link StringBuilder} + */ + public static StringBuilder timeRecord( + final StringBuilder builder, + final long startTime, + final long endTime + ) { + return timeRecord(builder, null, startTime, endTime); + } + + /** + * 耗时时间记录 + * @param builder 拼接 Builder + * @param title 标题 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @return {@link StringBuilder} + */ + public static StringBuilder timeRecord( + final StringBuilder builder, + final String title, + final long startTime, + final long endTime + ) { + if (builder == null) return builder; + // 使用时间 + long diffTime = endTime - startTime; + // 计算时间 + if (!StringUtils.isEmpty(title)) { + builder.append(DevFinal.SYMBOL.NEW_LINE); + builder.append(title); + } + // 计算时间 + builder.append(DevFinal.SYMBOL.NEW_LINE).append("开始时间: ") + .append(DateUtils.formatTime(startTime)) + .append(DevFinal.SYMBOL.NEW_LINE).append("结束时间: ") + .append(DateUtils.formatTime(endTime)) + .append(DevFinal.SYMBOL.NEW_LINE).append("所用时间(毫秒): ") + .append(diffTime) + .append(DevFinal.SYMBOL.NEW_LINE).append("所用时间(秒): ") + .append(diffTime / 1000); + return builder; + } + + // = + + /** + * 获取操作时间 + * @param operateTime 操作时间 ( 毫秒 ) + * @return 操作时间 + */ + public static long getOperateTime(final long operateTime) { + return getOperateTime(operateTime, -1); + } + + /** + * 获取操作时间 + * @param operateTime 操作时间 ( 毫秒 ) + * @param randomTime 随机时间范围 ( 毫秒 ) + * @return 操作时间 + */ + public static long getOperateTime( + final long operateTime, + final int randomTime + ) { + int random = 0; + // 大于 2 才处理 + if (randomTime >= 2) { + // 随机时间 + random = RandomUtils.getRandom(randomTime); + } + // 返回操作时间 + return Math.max(0, operateTime) + random; + } + + /** + * 堵塞操作 + * @param sleepTime 堵塞时间 ( 毫秒 ) + */ + public static void sleepOperate(final long sleepTime) { + sleepOperate(sleepTime, -1); + } + + /** + * 堵塞操作 + * @param sleepTime 堵塞时间 ( 毫秒 ) + * @param randomTime 随机时间范围 ( 毫秒 ) + */ + public static void sleepOperate( + final long sleepTime, + final int randomTime + ) { + long time = getOperateTime(sleepTime, randomTime); + if (time > 0) { + try { + Thread.sleep(time); + } catch (Throwable throwable) { + JCLogUtils.eTag(TAG, throwable, "sleepOperate"); + } + } + } + + // = + + /** + * 判断是否网络资源 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHttpRes(final String str) { + if (!StringUtils.isEmpty(str)) { + // 属于第一位开始, 才是属于网络资源 + return str.toLowerCase().startsWith("http:") || + str.toLowerCase().startsWith("https:"); + } + return false; + } + + /** + * 循环 MD5 加密处理 + * @param str 待处理数据 + * @param number MD5 加密次数 + * @param isUppercase 是否大写处理 + * @param salts 特殊 salt 拼接 + * @return 循环加密后的字符串 + */ + public static String whileMD5( + final String str, + final int number, + final boolean isUppercase, + final String... salts + ) { + if (str != null && number >= 1) { + int saltLen = (salts != null) ? salts.length : 0; + // 临时字符串 + String tempString = str; + // 判断是否大写 + if (isUppercase) { + // 循环加密 + for (int i = 0; i < number; i++) { + if (saltLen > i) { + String salt = salts[i]; + if (salt != null) { + tempString = MD5Utils.md5Upper(tempString + salt); + } else { + tempString = MD5Utils.md5Upper(tempString); + } + } else { + tempString = MD5Utils.md5Upper(tempString); + } + } + } else { + // 循环加密 + for (int i = 0; i < number; i++) { + if (saltLen > i) { + String salt = salts[i]; + if (salt != null) { + tempString = MD5Utils.md5(tempString + salt); + } else { + tempString = MD5Utils.md5(tempString); + } + } else { + tempString = MD5Utils.md5(tempString); + } + } + } + return tempString; + } + return str; + } + + // ============ + // = 获取唯一数 = + // ============ + + /** + * 获取随机唯一数 + * @return {@link UUID} + */ + public static UUID randomUUID() { + return UUID.randomUUID(); + } + + /** + * 获取随机唯一数 HashCode + * @return 随机 UUID hashCode + */ + public static int randomUUIDToHashCode() { + return UUID.randomUUID().hashCode(); + } + + /** + * 获取随机唯一数 HashCode + * @param uuid {@link UUID} + * @return 随机 UUID hashCode + */ + public static int randomUUIDToHashCode(final UUID uuid) { + return (uuid != null) ? uuid.hashCode() : 0; + } + + /** + * 获取随机规则生成 UUID + * @return 随机规则生成 UUID + */ + public static UUID getRandomUUID() { + // 获取随机数 + String random1 = String.valueOf(900000 + new Random().nextInt(10000)); + // 获取随机数 + String random2 = String.valueOf(900000 + new Random().nextInt(10000)); + // 获取当前时间 + String time = System.currentTimeMillis() + random1 + random2; + // 生成唯一随机 UUID + return new UUID(time.hashCode(), ((long) random1.hashCode() << 32) | random2.hashCode()); + } + + /** + * 获取随机规则生成 UUID 字符串 + * @return 随机规则生成 UUID 字符串 + */ + public static String getRandomUUIDToString() { + return getRandomUUID().toString(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/EncodeUtils.java b/lib/DevJava/src/main/java/dev/utils/common/EncodeUtils.java new file mode 100644 index 0000000000..13d2abdff6 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/EncodeUtils.java @@ -0,0 +1,260 @@ +package dev.utils.common; + +import dev.utils.common.cipher.Base64; + +/** + * detail: 编码工具类 + * @author Ttt + *
+ *     Base64 flags
+ *     

+ * CRLF 这个参数看起来比较眼熟, 它就是 Win 风格的换行符, 意思就是使用 CR LF 这一对作为一行的结尾而不是 Unix 风格的 LF + * DEFAULT 这个参数是默认, 使用默认的方法来加密 + * NO_PADDING 这个参数是略去加密字符串最后的 = + * NO_WRAP 这个参数意思是略去所有的换行符 ( 设置后 CRLF 就没用了 ) + * URL_SAFE 这个参数意思是加密时不使用对 URL 和文件名有特殊意义的字符来作为加密字符, 具体就是以 - 和 _ 取代 + 和 / + *
+ */ +public final class EncodeUtils { + + private EncodeUtils() { + } + + // ========== + // = Base64 = + // ========== + + // ============== + // = Base64 编码 = + // ============== + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode(final String input) { + return base64Encode(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode( + final String input, + final int flags + ) { + return base64Encode(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode(final byte[] input) { + return base64Encode(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] + */ + public static byte[] base64Encode( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return Base64.encode(input, flags); + } + + // = + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString(final String input) { + return base64EncodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString( + final String input, + final int flags + ) { + return base64EncodeToString(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString(final byte[] input) { + return base64EncodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 编码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 编码后的 byte[] 转 String + */ + public static String base64EncodeToString( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return ConvertUtils.newString(Base64.encode(input, flags)); + } + + // ============== + // = Base64 解码 = + // ============== + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode(final String input) { + return base64Decode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode( + final String input, + final int flags + ) { + return base64Decode(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode(final byte[] input) { + return base64Decode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] + */ + public static byte[] base64Decode( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return Base64.decode(input, flags); + } + + // = + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString(final String input) { + return base64DecodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString( + final String input, + final int flags + ) { + return base64DecodeToString(ConvertUtils.toBytes(input), flags); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString(final byte[] input) { + return base64DecodeToString(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待处理数据 + * @param flags Base64 编解码 flags + * @return Base64 解码后的 byte[] 转 String + */ + public static String base64DecodeToString( + final byte[] input, + final int flags + ) { + if (input == null) return null; + return ConvertUtils.newString(Base64.decode(input, flags)); + } + + // ======== + // = Html = + // ======== + + /** + * Html 字符串编码 + * @param input 待处理数据 + * @return Html 编码后的数据 + */ + public static String htmlEncode(final CharSequence input) { + if (input == null) return null; + StringBuilder builder = new StringBuilder(); + char ch; + for (int i = 0, len = input.length(); i < len; i++) { + ch = input.charAt(i); + switch (ch) { + case '<': + builder.append("<"); //$NON-NLS-1$ + break; + case '>': + builder.append(">"); //$NON-NLS-1$ + break; + case '&': + builder.append("&"); //$NON-NLS-1$ + break; + case '\'': + //http://www.w3.org/TR/xhtml1 + // The named character reference ' (the apostrophe, U+0027) was introduced in + // XML 1.0 but does not appear in HTML. Authors should therefore use ' instead + // of ' to work as expected in HTML 4 user agents. + builder.append("'"); //$NON-NLS-1$ + break; + case '"': + builder.append("""); //$NON-NLS-1$ + break; + default: + builder.append(ch); + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/FieldUtils.java b/lib/DevJava/src/main/java/dev/utils/common/FieldUtils.java new file mode 100644 index 0000000000..1aee8c40f0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/FieldUtils.java @@ -0,0 +1,417 @@ +package dev.utils.common; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 变量字段工具类 + * @author Ttt + */ +public final class FieldUtils { + + private FieldUtils() { + } + + // 日志 TAG + private static final String TAG = FieldUtils.class.getSimpleName(); + + // = + + /** + * 获取变量对象 + * @param object {@link Object} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getField( + final Object object, + final String name + ) { + return getField(ClassUtils.getClass(object), name); + } + + /** + * 获取变量对象 + *
+     *     public 成员变量, 包括基类
+     * 
+ * @param clazz {@link Class} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getField( + final Class clazz, + final String name + ) { + if (clazz != null && name != null) { + try { + return clazz.getField(name); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getField"); + } + } + return null; + } + + // = + + /** + * 获取变量对象 + * @param object {@link Object} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getDeclaredField( + final Object object, + final String name + ) { + return getDeclaredField(ClassUtils.getClass(object), name); + } + + /** + * 获取变量对象 + *
+     *     所有成员变量, 不包括基类
+     * 
+ * @param clazz {@link Class} + * @param name 变量名 + * @return {@link Field} + */ + public static Field getDeclaredField( + final Class clazz, + final String name + ) { + if (clazz != null && name != null) { + try { + return clazz.getDeclaredField(name); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredField"); + } + } + return null; + } + + // = + + /** + * 获取变量对象数组 + * @param object {@link Object} + * @return Field[] + */ + public static Field[] getFields(final Object object) { + return getFields(ClassUtils.getClass(object)); + } + + /** + * 获取变量对象数组 + *
+     *     public 成员变量, 包括基类
+     * 
+ * @param clazz {@link Class} + * @return Field[] + */ + public static Field[] getFields(final Class clazz) { + if (clazz != null) { + try { + return clazz.getFields(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFields"); + } + } + return null; + } + + // = + + /** + * 获取变量对象数组 + * @param object {@link Object} + * @return Field[] + */ + public static Field[] getDeclaredFields(final Object object) { + return getDeclaredFields(ClassUtils.getClass(object)); + } + + /** + * 获取变量对象数组 + *
+     *     所有成员变量, 不包括基类
+     * 
+ * @param clazz {@link Class} + * @return Field[] + */ + public static Field[] getDeclaredFields(final Class clazz) { + if (clazz != null) { + try { + return clazz.getDeclaredFields(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredFields"); + } + } + return null; + } + + // = + + /** + * 设置字段的值 + * @param field {@link Field} + * @param object Object + * @param value Object-Value + * @return 对应的 Object + */ + public static Object set( + final Field field, + final Object object, + final Object value + ) { + if (field == null || object == null) return null; + try { + field.setAccessible(true); + field.set(object, value); + return field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "set"); + } + return null; + } + + /** + * 获取字段的值 + * @param field {@link Field} + * @param object Object + * @return 对应的 Object + */ + public static Object get( + final Field field, + final Object object + ) { + if (field == null || object == null) return null; + try { + field.setAccessible(true); + return field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + return null; + } + + // = + + /** + * 是否 long/Long 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isLong(final Field field) { + return field != null && (field.getType() == long.class || field.getType() == Long.class); + } + + /** + * 是否 float/Float 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFloat(final Field field) { + return field != null && (field.getType() == float.class || field.getType() == Float.class); + } + + /** + * 是否 double/Double 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isDouble(final Field field) { + return field != null && (field.getType() == double.class || field.getType() == Double.class); + } + + /** + * 是否 int/Integer 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInteger(final Field field) { + return field != null && (field.getType() == int.class || field.getType() == Integer.class); + } + + /** + * 是否 boolean/Boolean 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isBoolean(final Field field) { + return field != null && (field.getType() == boolean.class || field.getType() == Boolean.class); + } + + /** + * 是否 char/Character 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isCharacter(final Field field) { + return field != null && (field.getType() == char.class || field.getType() == Character.class); + } + + /** + * 是否 byte/Byte 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isByte(final Field field) { + return field != null && (field.getType() == byte.class || field.getType() == Byte.class); + } + + /** + * 是否 short/Short 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isShort(final Field field) { + return field != null && (field.getType() == short.class || field.getType() == Short.class); + } + + /** + * 是否 String 类型 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isString(final Field field) { + return field != null && (field.getType() == String.class); + } + + // = + + /** + * 判断是否序列化 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSerializable(final Field field) { + if (field == null) return false; + try { + Class[] clazzs = field.getType().getInterfaces(); + for (Class clazz : clazzs) { + if (Serializable.class == clazz) { + return true; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isSerializable"); + } + return false; + } + + /** + * 是否静态常量或者内部结构属性 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isInvalid(final Field field) { + return isStaticFinal(field) || isSynthetic(field); + } + + /** + * 是否静态变量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStatic(final Field field) { + return field != null && Modifier.isStatic(field.getModifiers()); + } + + /** + * 是否常量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isFinal(final Field field) { + return field != null && Modifier.isFinal(field.getModifiers()); + } + + /** + * 是否静态变量 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isStaticFinal(final Field field) { + return field != null && Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()); + } + + /** + * 是否内部结构属性 + * @param field {@link Field} + * @return {@code true} yes, {@code false} no + */ + public static boolean isSynthetic(final Field field) { + return field != null && field.isSynthetic(); + } + + // = + + /** + * 获取字段的泛型类型, 如果不带泛型返回 null + * @param field {@link Field} + * @param 未知类型 + * @return 泛型类型 + */ + public static Class getGenericType(final Field field) { + if (field == null) return null; + try { + Type type = field.getGenericType(); + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getActualTypeArguments()[0]; + } + if (type instanceof Class) { + return (Class) type; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenericType"); + } + return null; + } + + /** + * 获取数组的类型 + * @param field {@link Field} + * @param 未知类型 + * @return 数组类型 + */ + public static Class getComponentType(final Field field) { + if (field == null) return null; + return field.getType().getComponentType(); + } + + /** + * 获取全部 Field, 包括父类 + * @param clazz {@link Class} + * @return {@link List} + */ + public static List getAllDeclaredFields(final Class clazz) { + if (clazz == null) return null; + try { + Class clazzTemp = clazz; + // find all field. + LinkedList fieldList = new LinkedList<>(); + while (clazzTemp != null && clazzTemp != Object.class) { + Field[] fields = clazzTemp.getDeclaredFields(); + for (Field field : fields) { + if (!isInvalid(field)) { + fieldList.addLast(field); + } + } + clazzTemp = clazzTemp.getSuperclass(); + } + return fieldList; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAllDeclaredFields"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/FileIOUtils.java b/lib/DevJava/src/main/java/dev/utils/common/FileIOUtils.java new file mode 100644 index 0000000000..88ca5064c4 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/FileIOUtils.java @@ -0,0 +1,820 @@ +package dev.utils.common; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 文件 ( IO 流 ) 工具类 + * @author Ttt + */ +public final class FileIOUtils { + + private FileIOUtils() { + } + + // 日志 TAG + private static final String TAG = FileIOUtils.class.getSimpleName(); + + // 缓存大小 + private static int sBufferSize = 8192; + // 无数据读取 + public static final int EOF = -1; + + /** + * 设置缓冲区的大小, 默认大小等于 8192 字节 + * @param bufferSize 缓冲 Buffer 大小 + */ + public static void setBufferSize(final int bufferSize) { + sBufferSize = bufferSize; + } + + /** + * 获取输入流 + * @param filePath 文件路径 + * @return {@link FileInputStream} + */ + public static FileInputStream getFileInputStream(final String filePath) { + return getFileInputStream(FileUtils.getFile(filePath)); + } + + /** + * 获取输入流 + * @param file 文件 + * @return {@link FileInputStream} + */ + public static FileInputStream getFileInputStream(final File file) { + if (file == null) return null; + try { + return new FileInputStream(file); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileInputStream"); + } + return null; + } + + /** + * 获取输出流 + * @param filePath 文件路径 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream(final String filePath) { + return getFileOutputStream(FileUtils.getFile(filePath)); + } + + /** + * 获取输出流 + * @param filePath 文件路径 + * @param append 是否追加到结尾 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream( + final String filePath, + final boolean append + ) { + return getFileOutputStream(FileUtils.getFile(filePath), append); + } + + /** + * 获取输出流 + * @param file 文件 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream(final File file) { + return getFileOutputStream(file, false); + } + + /** + * 获取输出流 + * @param file 文件 + * @param append 是否追加到结尾 + * @return {@link FileOutputStream} + */ + public static FileOutputStream getFileOutputStream( + final File file, + final boolean append + ) { + if (file == null) return null; + try { + return new FileOutputStream(file, append); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileOutputStream"); + } + return null; + } + + // = + + /** + * 通过输入流写入文件 + * @param filePath 文件路径 + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final String filePath, + final InputStream inputStream + ) { + return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, false); + } + + /** + * 通过输入流写入文件 + * @param filePath 文件路径 + * @param inputStream {@link InputStream} + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final String filePath, + final InputStream inputStream, + final boolean append + ) { + return writeFileFromIS(FileUtils.getFileByPath(filePath), inputStream, append); + } + + /** + * 通过输入流写入文件 + * @param file 文件 + * @param inputStream {@link InputStream} + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final File file, + final InputStream inputStream + ) { + return writeFileFromIS(file, inputStream, false); + } + + /** + * 通过输入流写入文件 + * @param file 文件 + * @param inputStream {@link InputStream} + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromIS( + final File file, + final InputStream inputStream, + final boolean append + ) { + if (inputStream == null || !FileUtils.createOrExistsFile(file)) return false; + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file, append)); + byte[] data = new byte[sBufferSize]; + int len; + while ((len = inputStream.read(data, 0, sBufferSize)) != EOF) { + os.write(data, 0, len); + } + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromIS"); + return false; + } finally { + CloseUtils.closeIOQuietly(inputStream, os); + } + } + + /** + * 通过字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final String filePath, + final byte[] bytes + ) { + return writeFileFromBytesByStream(FileUtils.getFileByPath(filePath), bytes, false); + } + + /** + * 通过字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final String filePath, + final byte[] bytes, + final boolean append + ) { + return writeFileFromBytesByStream(FileUtils.getFileByPath(filePath), bytes, append); + } + + /** + * 通过字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final File file, + final byte[] bytes + ) { + return writeFileFromBytesByStream(file, bytes, false); + } + + /** + * 通过字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByStream( + final File file, + final byte[] bytes, + final boolean append + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + BufferedOutputStream bos = null; + try { + bos = new BufferedOutputStream(new FileOutputStream(file, append)); + bos.write(bytes); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByStream"); + return false; + } finally { + CloseUtils.closeIOQuietly(bos); + } + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final String filePath, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByChannel( + FileUtils.getFileByPath(filePath), bytes, false, isForce + ); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final String filePath, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + return writeFileFromBytesByChannel( + FileUtils.getFileByPath(filePath), bytes, append, isForce + ); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final File file, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByChannel(file, bytes, false, isForce); + } + + /** + * 通过 FileChannel 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByChannel( + final File file, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + FileChannel fc = null; + try { + fc = new FileOutputStream(file, append).getChannel(); + fc.position(fc.size()); + fc.write(ByteBuffer.wrap(bytes)); + if (isForce) fc.force(true); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByChannel"); + return false; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final String filePath, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByMap(filePath, bytes, false, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param filePath 文件路径 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final String filePath, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + return writeFileFromBytesByMap(FileUtils.getFileByPath(filePath), bytes, append, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final File file, + final byte[] bytes, + final boolean isForce + ) { + return writeFileFromBytesByMap(file, bytes, false, isForce); + } + + /** + * 通过 MappedByteBuffer 把字节流写入文件 + * @param file 文件 + * @param bytes byte[] + * @param append 是否追加到结尾 + * @param isForce 是否强制写入 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromBytesByMap( + final File file, + final byte[] bytes, + final boolean append, + final boolean isForce + ) { + if (bytes == null || !FileUtils.createOrExistsFile(file)) return false; + FileChannel fc = null; + try { + fc = new FileOutputStream(file, append).getChannel(); + MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, fc.size(), bytes.length); + mbb.put(bytes); + if (isForce) mbb.force(); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromBytesByMap"); + return false; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过字符串写入文件 + * @param filePath 文件路径 + * @param content 写入内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final String filePath, + final String content + ) { + return writeFileFromString(FileUtils.getFileByPath(filePath), content, false); + } + + /** + * 通过字符串写入文件 + * @param filePath 文件路径 + * @param content 写入内容 + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final String filePath, + final String content, + final boolean append + ) { + return writeFileFromString(FileUtils.getFileByPath(filePath), content, append); + } + + /** + * 通过字符串写入文件 + * @param file 文件 + * @param content 写入内容 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final File file, + final String content + ) { + return writeFileFromString(file, content, false); + } + + /** + * 通过字符串写入文件 + * @param file 文件 + * @param content 写入内容 + * @param append 是否追加到结尾 + * @return {@code true} success, {@code false} fail + */ + public static boolean writeFileFromString( + final File file, + final String content, + final boolean append + ) { + if (content == null || !FileUtils.createOrExistsFile(file)) return false; + BufferedWriter bw = null; + try { + bw = new BufferedWriter(new FileWriter(file, append)); + bw.write(content); + return true; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "writeFileFromString"); + return false; + } finally { + CloseUtils.closeIOQuietly(bw); + } + } + + // ============ + // = 读写分界线 = + // ============ + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @return 换行 {@link List} + */ + public static List readFileToList(final String filePath) { + return readFileToList(FileUtils.getFileByPath(filePath), null); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final String charsetName + ) { + return readFileToList(FileUtils.getFileByPath(filePath), charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @return 换行 {@link List} + */ + public static List readFileToList(final File file) { + return readFileToList(file, 0, Integer.MAX_VALUE, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final String charsetName + ) { + return readFileToList(file, 0, Integer.MAX_VALUE, charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param start 开始位置 + * @param end 结束位置 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final int start, + final int end + ) { + return readFileToList(FileUtils.getFileByPath(filePath), start, end, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param filePath 文件路径 + * @param start 开始位置 + * @param end 结束位置 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final String filePath, + final int start, + final int end, + final String charsetName + ) { + return readFileToList(FileUtils.getFileByPath(filePath), start, end, charsetName); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param start 开始位置 + * @param end 结束位置 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final int start, + final int end + ) { + return readFileToList(file, start, end, null); + } + + /** + * 读取文件内容, 返回换行 List + * @param file 文件 + * @param start 开始位置 + * @param end 结束位置 + * @param charsetName 字符编码 + * @return 换行 {@link List} + */ + public static List readFileToList( + final File file, + final int start, + final int end, + final String charsetName + ) { + if (!FileUtils.isFileExists(file)) return null; + if (start > end) return null; + BufferedReader br = null; + try { + String line; + int curLine = 1; + List list = new ArrayList<>(); + if (StringUtils.isEmpty(charsetName)) { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + } else { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName)); + } + while ((line = br.readLine()) != null) { + if (curLine > end) break; + if (start <= curLine && curLine <= end) list.add(line); + ++curLine; + } + return list; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToList"); + return null; + } finally { + CloseUtils.closeIOQuietly(br); + } + } + + // = + + /** + * 读取文件内容, 返回字符串 + * @param filePath 文件路径 + * @return 文件内容字符串 + */ + public static String readFileToString(final String filePath) { + return readFileToString(FileUtils.getFileByPath(filePath), null); + } + + /** + * 读取文件内容, 返回字符串 + * @param filePath 文件路径 + * @param charsetName 字符编码 + * @return 文件内容字符串 + */ + public static String readFileToString( + final String filePath, + final String charsetName + ) { + return readFileToString(FileUtils.getFileByPath(filePath), charsetName); + } + + /** + * 读取文件内容, 返回字符串 + * @param file 文件 + * @return 文件内容字符串 + */ + public static String readFileToString(final File file) { + return readFileToString(file, null); + } + + /** + * 读取文件内容, 返回字符串 + * @param file 文件 + * @param charsetName 字符编码 + * @return 文件内容字符串 + */ + public static String readFileToString( + final File file, + final String charsetName + ) { + if (!FileUtils.isFileExists(file)) return null; + BufferedReader br = null; + try { + StringBuilder builder = new StringBuilder(); + if (StringUtils.isEmpty(charsetName)) { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + } else { + br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charsetName)); + } + String line; + if ((line = br.readLine()) != null) { + builder.append(line); + while ((line = br.readLine()) != null) { + builder.append(DevFinal.SYMBOL.NEW_LINE).append(line); + } + } + return builder.toString(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToString"); + return null; + } finally { + CloseUtils.closeIOQuietly(br); + } + } + + /** + * 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByStream(final String filePath) { + return readFileToBytesByStream(FileUtils.getFileByPath(filePath)); + } + + /** + * 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByStream(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileInputStream fis = null; + ByteArrayOutputStream baos = null; + try { + fis = new FileInputStream(file); + baos = new ByteArrayOutputStream(); + byte[] b = new byte[sBufferSize]; + int len; + while ((len = fis.read(b, 0, sBufferSize)) != EOF) { + baos.write(b, 0, len); + } + return baos.toByteArray(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(fis, baos); + } + } + + /** + * 通过 FileChannel, 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByChannel(final String filePath) { + return readFileToBytesByChannel(FileUtils.getFileByPath(filePath)); + } + + /** + * 通过 FileChannel, 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByChannel(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileChannel fc = null; + try { + fc = new RandomAccessFile(file, "r").getChannel(); + ByteBuffer byteBuffer = ByteBuffer.allocate((int) fc.size()); + while (true) { + if (!((fc.read(byteBuffer)) > 0)) break; + } + return byteBuffer.array(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByChannel"); + return null; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + /** + * 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByMap(final String filePath) { + return readFileToBytesByMap(FileUtils.getFileByPath(filePath)); + } + + /** + * 通过 MappedByteBuffer, 读取文件内容, 返回 byte[] + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileToBytesByMap(final File file) { + if (!FileUtils.isFileExists(file)) return null; + FileChannel fc = null; + try { + fc = new RandomAccessFile(file, "r").getChannel(); + int size = (int) fc.size(); + MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_ONLY, 0, size).load(); + byte[] result = new byte[size]; + mbb.get(result, 0, size); + return result; + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "readFileToBytesByMap"); + return null; + } finally { + CloseUtils.closeIOQuietly(fc); + } + } + + // = + + /** + * 复制 InputStream 到 OutputStream + * @param inputStream {@link InputStream} 读取流 + * @param outputStream {@link OutputStream} 写入流 + * @return bytes number + */ + public static long copyLarge( + final InputStream inputStream, + final OutputStream outputStream + ) { + if (inputStream == null || outputStream == null) return -1L; + try { + byte[] data = new byte[sBufferSize]; + long count = 0; + int n; + while (EOF != (n = inputStream.read(data))) { + outputStream.write(data, 0, n); + count += n; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "copyLarge"); + } finally { + CloseUtils.closeIOQuietly(inputStream, outputStream); + } + return -1L; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/FileUtils.java b/lib/DevJava/src/main/java/dev/utils/common/FileUtils.java new file mode 100644 index 0000000000..2d238b58e2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/FileUtils.java @@ -0,0 +1,2526 @@ +package dev.utils.common; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.encrypt.MD5Utils; + +/** + * detail: 文件操作工具类 + * @author Ttt + */ +public final class FileUtils { + + private FileUtils() { + } + + // 日志 TAG + private static final String TAG = FileUtils.class.getSimpleName(); + + /** + * 获取文件 + * @param filePath 文件路径 + * @return 文件 {@link File} + */ + public static File getFile(final String filePath) { + return getFileByPath(filePath); + } + + /** + * 获取文件 + * @param filePath 文件路径 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFile( + final String filePath, + final String fileName + ) { + return (filePath != null && fileName != null) ? new File(filePath, fileName) : null; + } + + /** + * 获取文件 + * @param parent 文件路径 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFile( + final File parent, + final String fileName + ) { + return (parent != null && fileName != null) ? new File(parent, fileName) : null; + } + + /** + * 获取文件 + * @param filePath 文件路径 + * @return 文件 {@link File} + */ + public static File getFileByPath(final String filePath) { + return filePath != null ? new File(filePath) : null; + } + + /** + * 获取路径, 并且进行创建目录 + * @param filePath 存储目录 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static File getFileCreateFolder( + final String filePath, + final String fileName + ) { + // 防止不存在目录文件, 自动创建 + createFolder(filePath); + // 返回处理过后的 File + return getFile(filePath, fileName); + } + + /** + * 获取路径, 并且进行创建目录 + * @param filePath 存储目录 + * @param fileName 文件名 + * @return 文件 {@link File} + */ + public static String getFilePathCreateFolder( + final String filePath, + final String fileName + ) { + // 防止不存在目录文件, 自动创建 + createFolder(filePath); + // 返回处理过后的 File + File file = getFile(filePath, fileName); + // 返回文件路径 + return getAbsolutePath(file); + } + + /** + * 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) + * @param dirPath 文件夹路径 ( 无文件名字. 后缀 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolder(final String dirPath) { + return createFolder(getFileByPath(dirPath)); + } + + /** + * 判断某个文件夹是否创建, 未创建则创建 ( 纯路径无文件名 ) + * @param file 文件夹路径 ( 无文件名字. 后缀 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolder(final File file) { + if (file != null) { + try { + // 当这个文件夹不存在的时候则创建文件夹 + if (!file.exists()) { + // 允许创建多级目录 + return file.mkdirs(); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "createFolder"); + } + } + return false; + } + + /** + * 创建文件夹目录 ( 可以传入文件名 ) + * @param filePath 文件路径 + 文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPath(final String filePath) { + return createFolderByPath(getFileByPath(filePath)); + } + + /** + * 创建文件夹目录 ( 可以传入文件名 ) + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPath(final File file) { + // 创建文件夹 ( 如果失败才创建 ) + if (file != null) { + if (file.exists()) { + return true; + } else if (!file.getParentFile().mkdirs()) { + return createFolder(file.getParent()); + } + } + return false; + } + + /** + * 创建多个文件夹, 如果不存在则创建 + * @param filePaths 文件路径数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPaths(final String... filePaths) { + if (filePaths != null && filePaths.length != 0) { + for (String filePath : filePaths) { + createFolder(filePath); + } + return true; + } + return false; + } + + /** + * 创建多个文件夹, 如果不存在则创建 + * @param files 文件数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean createFolderByPaths(final File... files) { + if (files != null && files.length != 0) { + for (File file : files) { + createFolder(file); + } + return true; + } + return false; + } + + // = + + /** + * 判断目录是否存在, 不存在则判断是否创建成功 + * @param dirPath 目录路径 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsDir(final String dirPath) { + return createOrExistsDir(getFileByPath(dirPath)); + } + + /** + * 判断目录是否存在, 不存在则判断是否创建成功 + * @param file 文件 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsDir(final File file) { + // 如果存在, 是目录则返回 true, 是文件则返回 false, 不存在则返回是否创建成功 + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + /** + * 判断文件是否存在, 不存在则判断是否创建成功 + * @param filePath 文件路径 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsFile(final String filePath) { + return createOrExistsFile(getFileByPath(filePath)); + } + + /** + * 判断文件是否存在, 不存在则判断是否创建成功 + * @param file 文件 + * @return {@code true} 存在或创建成功, {@code false} 不存在或创建失败 + */ + public static boolean createOrExistsFile(final File file) { + if (file == null) return false; + // 如果存在, 是文件则返回 true, 是目录则返回 false + if (file.exists()) return file.isFile(); + // 判断文件是否存在, 不存在则直接返回 + if (!createOrExistsDir(file.getParentFile())) return false; + try { + // 存在, 则返回新的路径 + return file.createNewFile(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "createOrExistsFile"); + return false; + } + } + + /** + * 判断文件是否存在, 存在则在创建之前删除 + * @param filePath 文件路径 + * @return {@code true} 创建成功, {@code false} 创建失败 + */ + public static boolean createFileByDeleteOldFile(final String filePath) { + return createFileByDeleteOldFile(getFileByPath(filePath)); + } + + /** + * 判断文件是否存在, 存在则在创建之前删除 + * @param file 文件 + * @return {@code true} 创建成功, {@code false} 创建失败 + */ + public static boolean createFileByDeleteOldFile(final File file) { + if (file == null) return false; + // 文件存在并且删除失败返回 false + if (file.exists() && !file.delete()) return false; + // 创建目录失败返回 false + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "createFileByDeleteOldFile"); + return false; + } + } + + /** + * 通过文件后缀创建时间戳文件名 + * @param extension 文件后缀 ( 有无 . 都行 ) + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileName(final String extension) { + // 临时后缀名 + String temp = StringUtils.clearSpace(extension); + if (StringUtils.isNotEmpty(temp)) { + temp = StringUtils.clearSEWiths(temp, "."); + if (StringUtils.isNotEmpty(temp)) { + return System.currentTimeMillis() + "." + temp; + } + } + return null; + } + + /** + * 通过文件名创建时间戳文件名 + * @param fileName 文件名 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByName(final String fileName) { + return createTimestampFileName(FileUtils.getFileExtension(fileName)); + } + + /** + * 通过文件创建时间戳文件名 + * @param file 文件 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByFile(final File file) { + return createTimestampFileName(FileUtils.getFileExtension(file)); + } + + /** + * 通过文件路径创建时间戳文件名 + * @param filePath 文件路径 + * @return 时间戳文件名 ( 包含后缀 ) + */ + public static String createTimestampFileNameByPath(final String filePath) { + return createTimestampFileName(FileUtils.getFileExtension(filePath)); + } + + // = + + /** + * Path List 转 File List + * @param paths Path List + * @return File List + */ + public static List convertFiles(final List paths) { + return convertFiles(paths, true); + } + + /** + * Path List 转 File List + * @param paths Path List + * @param ignore 是否忽略 null + * @return File List + */ + public static List convertFiles( + final List paths, + final boolean ignore + ) { + List files = new ArrayList<>(); + if (paths != null && !paths.isEmpty()) { + for (int i = 0, len = paths.size(); i < len; i++) { + String path = paths.get(i); + if (path == null) { + if (!ignore) files.add(null); + continue; + } + files.add(new File(path)); + } + } + return files; + } + + /** + * File List 转 Path List + * @param files File List + * @return Path List + */ + public static List convertPaths(final List files) { + return convertPaths(files, true); + } + + /** + * File List 转 Path List + * @param files File List + * @param ignore 是否忽略 null + * @return Path List + */ + public static List convertPaths( + final List files, + final boolean ignore + ) { + List paths = new ArrayList<>(); + if (files != null && !files.isEmpty()) { + for (int i = 0, len = files.size(); i < len; i++) { + File file = files.get(i); + if (file == null) { + if (!ignore) paths.add(null); + continue; + } + paths.add(file.getAbsolutePath()); + } + } + return paths; + } + + // = + + /** + * 获取文件路径 + * @param file 文件 + * @return 文件路径 + */ + public static String getPath(final File file) { + return file != null ? file.getPath() : null; + } + + /** + * 获取文件绝对路径 + * @param file 文件 + * @return 文件绝对路径 + */ + public static String getAbsolutePath(final File file) { + return file != null ? file.getAbsolutePath() : null; + } + + // = + + /** + * 获取文件名 + * @param file 文件 + * @return 文件名 + */ + public static String getName(final File file) { + return file != null ? file.getName() : null; + } + + /** + * 获取文件名 + * @param filePath 文件路径 + * @return 文件名 + */ + public static String getName(final String filePath) { + return getName(filePath, ""); + } + + /** + * 获取文件名 + * @param filePath 文件路径 + * @param defaultStr 默认字符串 + * @return 文件名, 如果文件路径为 null 时, 返回默认字符串 + */ + public static String getName( + final String filePath, + final String defaultStr + ) { + return StringUtils.isEmpty(filePath) ? defaultStr : new File(filePath).getName(); + } + + /** + * 获取文件后缀名 ( 无 "." 单独后缀 ) + * @param file 文件 + * @return 文件后缀名 ( 无 "." 单独后缀 ) + */ + public static String getFileSuffix(final File file) { + return getFileSuffix(getAbsolutePath(file)); + } + + /** + * 获取文件后缀名 ( 无 "." 单独后缀 ) + * @param filePath 文件路径或文件名 + * @return 文件后缀名 ( 无 "." 单独后缀 ) + */ + public static String getFileSuffix(final String filePath) { + // 获取最后的索引 + int lastIndexOf; + // 判断是否存在 + if (filePath != null && (lastIndexOf = filePath.lastIndexOf('.')) != -1) { + String result = filePath.substring(lastIndexOf); + if (result.startsWith(".")) { + return result.substring(1); + } + return result; + } + return null; + } + + /** + * 获取文件名 ( 无后缀 ) + * @param file 文件 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffix(final File file) { + return getFileNotSuffix(getName(file)); + } + + /** + * 获取文件名 ( 无后缀 ) + * @param filePath 文件路径 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffixToPath(final String filePath) { + return getFileNotSuffix(getName(filePath)); + } + + /** + * 获取文件名 ( 无后缀 ) + * @param fileName 文件名 + * @return 文件名 ( 无后缀 ) + */ + public static String getFileNotSuffix(final String fileName) { + if (fileName != null) { + if (fileName.lastIndexOf('.') != -1) { + return fileName.substring(0, fileName.lastIndexOf('.')); + } else { + return fileName; + } + } + return null; + } + + /** + * 获取路径中的不带扩展名的文件名 + * @param file 文件 + * @return 不带扩展名的文件名 + */ + public static String getFileNameNoExtension(final File file) { + if (file == null) return null; + return getFileNameNoExtension(file.getPath()); + } + + /** + * 获取路径中的不带扩展名的文件名 + * @param filePath 文件路径 + * @return 不带扩展名的文件名 + */ + public static String getFileNameNoExtension(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + /** + * 获取路径中的文件扩展名 + * @param file 文件 + * @return 文件扩展名 + */ + public static String getFileExtension(final File file) { + if (file == null) return null; + return getFileExtension(file.getPath()); + } + + /** + * 获取路径中的文件扩展名 + * @param filePath 文件路径 + * @return 文件扩展名 + */ + public static String getFileExtension(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + // = + + /** + * 检查是否存在某个文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + /** + * 检查是否存在某个文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists(final String filePath) { + return isFileExists(getFileByPath(filePath)); + } + + /** + * 检查是否存在某个文件 + * @param filePath 文件路径 + * @param fileName 文件名 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileExists( + final String filePath, + final String fileName + ) { + return filePath != null && fileName != null && new File(filePath, fileName).exists(); + } + + /** + * 判断是否文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFile(final String filePath) { + return isFile(getFileByPath(filePath)); + } + + /** + * 判断是否文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFile(final File file) { + return file != null && file.exists() && file.isFile(); + } + + /** + * 判断是否文件夹 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDirectory(final String filePath) { + return isDirectory(getFileByPath(filePath)); + } + + /** + * 判断是否文件夹 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isDirectory(final File file) { + return file != null && file.exists() && file.isDirectory(); + } + + /** + * 判断是否隐藏文件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHidden(final String filePath) { + return isHidden(getFileByPath(filePath)); + } + + /** + * 判断是否隐藏文件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHidden(final File file) { + return file != null && file.exists() && file.isHidden(); + } + + /** + * 文件是否可读 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canRead(final String filePath) { + return canRead(getFileByPath(filePath)); + } + + /** + * 文件是否可读 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canRead(final File file) { + return file != null && file.exists() && file.canRead(); + } + + /** + * 文件是否可写 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canWrite(final String filePath) { + return canWrite(getFileByPath(filePath)); + } + + /** + * 文件是否可写 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canWrite(final File file) { + return file != null && file.exists() && file.canWrite(); + } + + /** + * 文件是否可读写 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean canReadWrite(final String filePath) { + return canReadWrite(getFileByPath(filePath)); + } + + /** + * 文件是否可读写 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean canReadWrite(final File file) { + return file != null && file.exists() && file.canRead() && file.canWrite(); + } + + // = + + /** + * 获取文件最后修改的毫秒时间戳 + * @param filePath 文件路径 + * @return 文件最后修改的毫秒时间戳 + */ + public static long getFileLastModified(final String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + /** + * 获取文件最后修改的毫秒时间戳 + * @param file 文件 + * @return 文件最后修改的毫秒时间戳 + */ + public static long getFileLastModified(final File file) { + if (file == null) return 0L; + return file.lastModified(); + } + + /** + * 获取文件编码格式 + * @param filePath 文件路径 + * @return 文件编码格式 + */ + public static String getFileCharsetSimple(final String filePath) { + return getFileCharsetSimple(getFileByPath(filePath)); + } + + /** + * 获取文件编码格式 + * @param file 文件 + * @return 文件编码格式 + */ + public static String getFileCharsetSimple(final File file) { + if (!isFileExists(file)) return null; + int pos = 0; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + pos = (is.read() << 8) + is.read(); + } catch (IOException e) { + JCLogUtils.eTag(TAG, e, "getFileCharsetSimple"); + } finally { + CloseUtils.closeIOQuietly(is); + } + switch (pos) { + case 0xefbb: + return DevFinal.ENCODE.UTF_8; + case 0xfffe: + return DevFinal.ENCODE.UNICODE; + case 0xfeff: + return DevFinal.ENCODE.UTF_16BE; + default: + return DevFinal.ENCODE.GBK; + } + } + + /** + * 获取文件行数 + * @param filePath 文件路径 + * @return 文件行数 + */ + public static int getFileLines(final String filePath) { + return getFileLines(getFileByPath(filePath)); + } + + /** + * 获取文件行数 ( 比 readLine 要快很多 ) + * @param file 文件 + * @return 文件行数 + */ + public static int getFileLines(final File file) { + if (!isFileExists(file)) return 0; + int lineCount = 1; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[1024]; + int readChars; + if (DevFinal.SYMBOL.NEW_LINE.endsWith("\n")) { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\n') ++lineCount; + } + } + } else { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\r') ++lineCount; + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileLines"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return lineCount; + } + + // = + + /** + * 获取文件大小 + * @param filePath 文件路径 + * @return 文件大小 + */ + public static String getFileSize(final String filePath) { + return getFileSize(getFileByPath(filePath)); + } + + /** + * 获取文件大小 + * @param file 文件 + * @return 文件大小 + */ + public static String getFileSize(final File file) { + return formatByteMemorySize(getFileLength(file)); + } + + /** + * 获取目录大小 + * @param dirPath 目录路径 + * @return 文件大小 + */ + public static String getDirSize(final String dirPath) { + return getDirSize(getFileByPath(dirPath)); + } + + /** + * 获取目录大小 + * @param dir 目录 + * @return 文件大小 + */ + public static String getDirSize(final File dir) { + return formatByteMemorySize(getDirLength(dir)); + } + + /** + * 获取文件大小 + * @param filePath 文件路径 + * @return 文件大小 + */ + public static long getFileLength(final String filePath) { + return getFileLength(getFileByPath(filePath)); + } + + /** + * 获取文件大小 + * @param file 文件 + * @return 文件大小 + */ + public static long getFileLength(final File file) { + return file != null ? file.length() : 0L; + } + + /** + * 获取目录全部文件大小 + * @param dirPath 目录路径 + * @return 目录全部文件大小 + */ + public static long getDirLength(final String dirPath) { + return getDirLength(getFileByPath(dirPath)); + } + + /** + * 获取目录全部文件大小 + * @param dir 目录 + * @return 目录全部文件大小 + */ + public static long getDirLength(final File dir) { + if (!isDirectory(dir)) return 0L; + long len = 0; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + + /** + * 获取文件大小 ( 网络资源 ) + * @param httpUri 文件网络链接 + * @return 文件大小 + */ + public static long getFileLengthNetwork(final String httpUri) { + if (StringUtils.isEmpty(httpUri)) return 0L; + // 判断是否网络资源 + boolean isHttpRes = httpUri.toLowerCase().startsWith("http:") || httpUri.toLowerCase().startsWith("https:"); + if (isHttpRes) { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(httpUri).openConnection(); + conn.setRequestProperty("Accept-Encoding", "identity"); + conn.connect(); + if (conn.getResponseCode() == 200) { + return conn.getContentLength(); + } + return 0L; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileLengthNetwork"); + } + } + return 0L; + } + + /** + * 获取路径中的文件名 + * @param file 文件 + * @return 文件名 + */ + public static String getFileName(final File file) { + if (file == null) return null; + return getFileName(file.getPath()); + } + + /** + * 获取路径中的文件名 + * @param filePath 文件路径 + * @return 文件名 + */ + public static String getFileName(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + + /** + * 获取路径中的最长目录地址 + * @param file 文件 + * @return 最长目录地址 + */ + public static String getDirName(final File file) { + if (file == null) return null; + return getDirName(file.getPath()); + } + + /** + * 获取全路径中的最长目录地址 + * @param filePath 文件路径 + * @return 最长目录地址 + */ + public static String getDirName(final String filePath) { + if (StringUtils.isEmpty(filePath)) return filePath; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); + } + + // = + + /** + * 重命名文件 ( 同个目录下, 修改文件名 ) + * @param filePath 文件路径 + * @param newFileName 文件新名称 + * @return {@code true} yes, {@code false} no + */ + public static boolean rename( + final String filePath, + final String newFileName + ) { + return rename(getFileByPath(filePath), newFileName); + } + + /** + * 重命名文件 ( 同个目录下, 修改文件名 ) + * @param file 文件 + * @param newFileName 文件新名称 + * @return {@code true} yes, {@code false} no + */ + public static boolean rename( + final File file, + final String newFileName + ) { + if (StringUtils.isEmpty(newFileName)) return false; + // 文件为空返回 false + if (file == null) return false; + // 文件不存在返回 false + if (!file.exists()) return false; + // 如果文件名没有改变返回 true + if (newFileName.equals(file.getName())) return true; + // 拼接新的文件路径 + File newFile = new File(file.getParent() + File.separator + newFileName); + // 如果重命名的文件已存在返回 false + return !newFile.exists() && file.renameTo(newFile); + } + + // ============= + // = 文件大小处理 = + // ============= + + /** + * 传入文件路径, 返回对应的文件大小 + * @param filePath 文件路径 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final String filePath) { + File file = getFileByPath(filePath); + return formatFileSize(file != null ? file.length() : 0); + } + + /** + * 传入文件路径, 返回对应的文件大小 + * @param file 文件 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final File file) { + return formatFileSize(file != null ? file.length() : 0); + } + + /** + * 传入对应的文件大小, 返回转换后文件大小 + * @param fileSize 文件大小 + * @return 文件大小转换字符串 + */ + public static String formatFileSize(final double fileSize) { + // 转换文件大小 + DecimalFormat df = new DecimalFormat("#.00"); + String fileSizeStr; + if (fileSize <= 0) { + fileSizeStr = "0B"; + } else if (fileSize < 1024) { + fileSizeStr = df.format(fileSize) + "B"; + } else if (fileSize < 1048576) { + fileSizeStr = df.format(fileSize / 1024) + "KB"; + } else if (fileSize < 1073741824) { + fileSizeStr = df.format(fileSize / 1048576) + "MB"; + } else if (fileSize < 1099511627776D) { + fileSizeStr = df.format(fileSize / 1073741824) + "GB"; + } else { + fileSizeStr = df.format(fileSize / 1099511627776D) + "TB"; + } + return fileSizeStr; + } + + /** + * 字节数转合适内存大小 保留 3 位小数 + * @param byteSize 字节数 + * @return 合适内存大小字符串 + */ + public static String formatByteMemorySize(final double byteSize) { + return formatByteMemorySize(3, byteSize); + } + + /** + * 字节数转合适内存大小 保留 number 位小数 + * @param number 保留小数位数 + * @param byteSize 字节数 + * @return 合适内存大小字符串 + */ + public static String formatByteMemorySize( + final int number, + final double byteSize + ) { + if (byteSize < 0D) { + return "0B"; + } else if (byteSize < 1024D) { + return String.format("%." + number + "fB", byteSize); + } else if (byteSize < 1048576D) { + return String.format("%." + number + "fKB", byteSize / 1024D); + } else if (byteSize < 1073741824D) { + return String.format("%." + number + "fMB", byteSize / 1048576D); + } else if (byteSize < 1099511627776D) { + return String.format("%." + number + "fGB", byteSize / 1073741824D); + } else { + return String.format("%." + number + "fTB", byteSize / 1099511627776D); + } + } + + // ========== + // = 文件操作 = + // ========== + + /** + * 删除文件 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFile(final String filePath) { + return deleteFile(getFileByPath(filePath)); + } + + /** + * 删除文件 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFile(final File file) { + // 文件存在, 并且不是目录文件, 则直接删除 + if (file != null && file.exists() && !file.isDirectory()) { + return file.delete(); + } + return false; + } + + /** + * 删除多个文件 + * @param filePaths 文件路径数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFiles(final String... filePaths) { + if (filePaths != null && filePaths.length != 0) { + for (String filePath : filePaths) { + deleteFile(filePath); + } + return true; + } + return false; + } + + /** + * 删除多个文件 + * @param files 文件数组 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFiles(final File... files) { + if (files != null && files.length != 0) { + for (File file : files) { + deleteFile(file); + } + return true; + } + return false; + } + + // = + + /** + * 删除文件夹 + * @param filePath 文件路径 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFolder(final String filePath) { + return deleteFolder(getFileByPath(filePath)); + } + + /** + * 删除文件夹 + * @param file 文件 + * @return {@code true} success, {@code false} fail + */ + public static boolean deleteFolder(final File file) { + if (file != null) { + try { + // 文件存在, 并且不是目录文件, 则直接删除 + if (file.exists()) { + if (file.isDirectory()) { // 属于文件目录 + File[] files = file.listFiles(); + if (files != null) { + for (File deleteFile : files) { + deleteFolder(deleteFile.getPath()); + } + } + return file.delete(); + } else { // 属于文件 + return deleteFile(file); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "deleteFolder"); + } + } + return false; + } + + /** + * 保存文件 + * @param filePath 文件路径 + * @param data 待存储数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveFile( + final String filePath, + final byte[] data + ) { + return saveFile(FileUtils.getFile(filePath), data); + } + + /** + * 保存文件 + * @param file 文件 + * @param data 待存储数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean saveFile( + final File file, + final byte[] data + ) { + if (file != null && data != null) { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + // 防止文件夹没创建 + createFolder(getDirName(file)); + // 写入文件 + fos = new FileOutputStream(file); + bos = new BufferedOutputStream(fos); + bos.write(data); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "saveFile"); + } finally { + CloseUtils.closeIOQuietly(bos, fos); + } + } + return false; + } + + /** + * 追加文件 + * @param filePath 文件路径 + * @param data 待追加数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean appendFile( + final String filePath, + final byte[] data + ) { + return appendFile(FileUtils.getFile(filePath), data); + } + + /** + * 追加文件 + *
+     *     如果未创建文件, 则会创建并写入数据 ( 等同 {@link #saveFile} )
+     *     如果已创建文件, 则在结尾追加数据
+     * 
+ * @param file 文件 + * @param data 待追加数据 + * @return {@code true} success, {@code false} fail + */ + public static boolean appendFile( + final File file, + final byte[] data + ) { + FileOutputStream fos = null; + BufferedOutputStream bos = null; + try { + // 防止文件夹没创建 + createFolder(getDirName(file)); + // 写入文件 + fos = new FileOutputStream(file, true); + bos = new BufferedOutputStream(fos); + bos.write(data); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "appendFile"); + } finally { + CloseUtils.closeIOQuietly(bos, fos); + } + return false; + } + + // = + + /** + * 读取文件 + * @param filePath 文件路径 + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final String filePath) { + return readFileBytes(getFileByPath(filePath)); + } + + /** + * 读取文件 + * @param file 文件 + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final File file) { + if (file != null && file.exists()) { + FileInputStream fis = null; + try { + fis = new FileInputStream(file); + int length = fis.available(); + byte[] buffer = new byte[length]; + fis.read(buffer); + return buffer; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFileBytes"); + } finally { + CloseUtils.closeIOQuietly(fis); + } + } + return null; + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} + * @return 文件内容 byte[] + */ + public static byte[] readFileBytes(final InputStream inputStream) { + if (inputStream != null) { + try { + int length = inputStream.available(); + byte[] buffer = new byte[length]; + inputStream.read(buffer); + return buffer; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFileBytes"); + } finally { + CloseUtils.closeIOQuietly(inputStream); + } + } + return null; + } + + /** + * 读取文件 + *
+     *     获取换行内容可以通过
+     *     {@link FileIOUtils#readFileToList(File)}
+     *     {@link FileIOUtils#readFileToString(File)}
+     * 
+ * @param filePath 文件路径 + * @return 文件内容字符串 + */ + public static String readFile(final String filePath) { + return readFile(getFileByPath(filePath)); + } + + /** + * 读取文件 + * @param file 文件 + * @return 文件内容字符串 + */ + public static String readFile(final File file) { + if (file != null && file.exists()) { + try { + return readFile(new FileInputStream(file)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFile"); + } + } + return null; + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} new FileInputStream(path) + * @return 文件内容字符串 + */ + public static String readFile(final InputStream inputStream) { + return readFile(inputStream, null); + } + + /** + * 读取文件 + * @param inputStream {@link InputStream} new FileInputStream(path) + * @param encode 编码格式 + * @return 文件内容字符串 + */ + public static String readFile( + final InputStream inputStream, + final String encode + ) { + if (inputStream != null) { + BufferedReader br = null; + try { + InputStreamReader isr; + if (encode != null) { + isr = new InputStreamReader(inputStream, encode); + } else { + isr = new InputStreamReader(inputStream); + } + br = new BufferedReader(isr); + StringBuilder builder = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "readFile"); + } finally { + CloseUtils.closeIOQuietly(br); + } + } + return null; + } + + // = + + /** + * 复制单个文件 + * @param inputStream 文件流 ( 被复制 ) + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFile( + final InputStream inputStream, + final String destFilePath, + final boolean overlay + ) { + if (inputStream == null || destFilePath == null) { + return false; + } + File destFile = new File(destFilePath); + // 如果属于文件夹则跳过 + if (destFile.isDirectory()) { + return false; + } + if (destFile.exists()) { + // 如果目标文件存在并允许覆盖 + if (overlay) { + // 删除已经存在的目标文件, 无论目标文件是目录还是单个文件 + destFile.delete(); + } else { // 如果文件存在, 但是不覆盖, 则返回 false 表示失败 + return false; + } + } else { + // 如果目标文件所在目录不存在, 则创建目录 + if (!destFile.getParentFile().exists()) { + // 目标文件所在目录不存在 + if (!destFile.getParentFile().mkdirs()) { + // 复制文件失败: 创建目标文件所在目录失败 + return false; + } + } + } + // 复制文件 + int len; // 读取的字节数 + InputStream is = inputStream; + OutputStream os = null; + try { + os = new FileOutputStream(destFile); + byte[] buffer = new byte[1024]; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "copyFile"); + return false; + } finally { + CloseUtils.closeIOQuietly(os, is); + } + } + + /** + * 复制单个文件 + * @param srcFilePath 待复制的文件地址 + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFile( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + if (destFilePath == null) return false; + if (!FileUtils.isFile(srcFilePath)) return false; + return copyFile( + FileIOUtils.getFileInputStream(srcFilePath), + destFilePath, overlay + ); + } + + /** + * 复制文件夹 + * @param srcFolderPath 待复制的文件夹地址 + * @param destFolderPath 存储目标文件夹地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean copyFolder( + final String srcFolderPath, + final String destFolderPath, + final boolean overlay + ) { + return copyFolder(srcFolderPath, destFolderPath, srcFolderPath, overlay); + } + + /** + * 复制文件夹 + * @param srcFolderPath 待复制的文件夹地址 + * @param destFolderPath 存储目标文件夹地址 + * @param sourcePath 源文件地址 ( 用于保递归留记录 ) + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + private static boolean copyFolder( + final String srcFolderPath, + final String destFolderPath, + final String sourcePath, + final boolean overlay + ) { + if (srcFolderPath == null || destFolderPath == null || sourcePath == null) { + return false; + } + File srcFile = new File(srcFolderPath); + // 判断源文件是否存在 + if (!srcFile.exists()) { + return false; + } else if (!srcFile.isDirectory()) { // 不属于文件夹则跳过 + return false; + } + // 判断目标文件是否存在 + File destFile = new File(destFolderPath); + // 如果文件夹没创建, 则创建 + if (!destFile.exists()) { + // 允许创建多级目录 + destFile.mkdirs(); + } + // 判断是否属于文件夹 ( 不属于文件夹则跳过 ) + if (!destFile.isDirectory()) { + return false; + } + // 判断是否存在 + if (srcFile.exists()) { + // 获取文件路径 + File[] files = srcFile.listFiles(); + // 防止不存在文件 + if (files != null && files.length != 0) { + // 进行遍历 + for (File file : files) { + // 文件存在才进行处理 + if (file.exists()) { + // 属于文件夹 + if (file.isDirectory()) { + copyFolder(file.getAbsolutePath(), destFolderPath, sourcePath, overlay); + } else { // 属于文件 + // 复制的文件地址 + String filePath = file.getAbsolutePath(); + // 获取源文件地址并且进行判断 + String dealSource = new File(sourcePath).getAbsolutePath(); + // 属于最前才进行处理 + if (filePath.indexOf(dealSource) == 0) { + // 获取处理后的地址 + dealSource = filePath.substring(dealSource.length()); + // 获取需要复制保存的地址 + String savePath = new File(destFolderPath, dealSource).getAbsolutePath(); + // 进行复制文件 + boolean result = copyFile(filePath, savePath, overlay); + } + } + } + } + } + } + return true; + } + + // = + + /** + * 移动 ( 剪切 ) 文件 + * @param srcFilePath 待移动的文件地址 + * @param destFilePath 目标文件地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean moveFile( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + // 复制文件 + if (copyFile(srcFilePath, destFilePath, overlay)) { + // 删除文件 + return deleteFile(srcFilePath); + } + return false; + } + + /** + * 移动 ( 剪切 ) 文件夹 + * @param srcFilePath 待移动的文件夹地址 + * @param destFilePath 存储目标文件夹地址 + * @param overlay 如果目标文件存在, 是否覆盖 + * @return {@code true} success, {@code false} fail + */ + public static boolean moveFolder( + final String srcFilePath, + final String destFilePath, + final boolean overlay + ) { + // 复制文件夹 + if (copyFolder(srcFilePath, destFilePath, overlay)) { + // 删除文件夹 + return deleteFolder(srcFilePath); + } + return false; + } + + // = + + /** + * detail: 覆盖 / 替换事件 + * @author Ttt + */ + public interface OnReplaceListener { + + /** + * 是否覆盖 / 替换文件 + * @return {@code true} yes, {@code false} no + */ + boolean onReplace(); + } + + /** + * 复制或移动目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener, + final boolean isMove + ) { + return copyOrMoveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener, isMove); + } + + /** + * 复制或移动目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener, + final boolean isMove + ) { + if (srcDir == null || destDir == null || listener == null) return false; + // 为防止以上这种情况出现出现误判, 需分别在后面加个路径分隔符 + String srcPath = srcDir.getPath() + File.separator; + String destPath = destDir.getPath() + File.separator; + if (destPath.contains(srcPath)) return false; + // 源文件不存在或者不是目录则返回 false + if (!srcDir.exists() || !srcDir.isDirectory()) return false; + if (destDir.exists()) { + if (listener.onReplace()) { // 需要覆盖则删除旧目录 + if (!deleteAllInDir(destDir)) { // 删除文件失败的话返回 false + return false; + } + } else { // 不需要覆盖直接返回即可 true + return true; + } + } + // 目标目录不存在返回 false + if (!createOrExistsDir(destDir)) return false; + File[] files = srcDir.listFiles(); + if (files == null) return false; + for (File file : files) { + File oneDestFile = new File(destPath + file.getName()); + if (file.isFile()) { + // 如果操作失败返回 false + if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; + } else if (file.isDirectory()) { + // 如果操作失败返回 false + if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; + } + } + return !isMove || deleteDir(srcDir); + } + + /** + * 复制或移动文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener, + final boolean isMove + ) { + return copyOrMoveFile( + getFileByPath(srcFilePath), + getFileByPath(destFilePath), + listener, isMove + ); + } + + /** + * 复制或移动文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @param isMove 是否移动 + * @return {@code true} 复制或移动成功, {@code false} 复制或移动失败 + */ + public static boolean copyOrMoveFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener, + final boolean isMove + ) { + if (srcFile == null || destFile == null || listener == null) return false; + // 如果源文件和目标文件相同则返回 false + if (srcFile.equals(destFile)) return false; + // 源文件不存在或者不是文件则返回 false + if (!srcFile.exists() || !srcFile.isFile()) return false; + if (destFile.exists()) { // 目标文件存在 + if (listener.onReplace()) { // 需要覆盖则删除旧文件 + if (!destFile.delete()) { // 删除文件失败的话返回 false + return false; + } + } else { // 不需要覆盖直接返回即可 true + return true; + } + } + // 目标目录不存在返回 false + if (!createOrExistsDir(destFile.getParentFile())) return false; + try { + return FileIOUtils.writeFileFromIS( + destFile, new FileInputStream(srcFile), false + ) && !(isMove && !deleteFile(srcFile)); + } catch (FileNotFoundException e) { + JCLogUtils.eTag(TAG, e, "copyOrMoveFile"); + return false; + } + } + + /** + * 复制目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener + ) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * 复制目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener + ) { + return copyOrMoveDir(srcDir, destDir, listener, false); + } + + /** + * 复制文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener + ) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * 复制文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @return {@code true} 复制成功, {@code false} 复制失败 + */ + public static boolean copyFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener + ) { + return copyOrMoveFile(srcFile, destFile, listener, false); + } + + /** + * 移动目录 + * @param srcDirPath 源目录路径 + * @param destDirPath 目标目录路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveDir( + final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener + ) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * 移动目录 + * @param srcDir 源目录 + * @param destDir 目标目录 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveDir( + final File srcDir, + final File destDir, + final OnReplaceListener listener + ) { + return copyOrMoveDir(srcDir, destDir, listener, true); + } + + /** + * 移动文件 + * @param srcFilePath 源文件路径 + * @param destFilePath 目标文件路径 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveFile( + final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener + ) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * 移动文件 + * @param srcFile 源文件 + * @param destFile 目标文件 + * @param listener 是否覆盖监听器 + * @return {@code true} 移动成功, {@code false} 移动失败 + */ + public static boolean moveFile( + final File srcFile, + final File destFile, + final OnReplaceListener listener + ) { + return copyOrMoveFile(srcFile, destFile, listener, true); + } + + /** + * 删除目录 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteDir(final String dirPath) { + return deleteDir(getFileByPath(dirPath)); + } + + /** + * 删除目录 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteDir(final File dir) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + return dir.delete(); + } + + /** + * 删除目录下所有文件 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteAllInDir(final String dirPath) { + return deleteAllInDir(getFileByPath(dirPath)); + } + + /** + * 删除目录下所有文件 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteAllInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, pathname -> true); + } + + /** + * 删除目录下所有文件 + * @param dirPath 目录路径 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDir(final String dirPath) { + return deleteFilesInDir(getFileByPath(dirPath)); + } + + /** + * 删除目录下所有文件 + * @param dir 目录 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, pathname -> pathname.isFile()); + } + + /** + * 删除目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDirWithFilter( + final String dirPath, + final FileFilter filter + ) { + return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); + } + + /** + * 删除目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @return {@code true} 删除成功, {@code false} 删除失败 + */ + public static boolean deleteFilesInDirWithFilter( + final File dir, + final FileFilter filter + ) { + if (filter == null) return false; + if (dir == null) return false; + if (!dir.exists()) return true; + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + } + return true; + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @return 文件链表 + */ + public static List listFilesInDir(final String dirPath) { + return listFilesInDir(dirPath, false); + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dir 目录 + * @return 文件链表 + */ + public static List listFilesInDir(final File dir) { + return listFilesInDir(dir, false); + } + + /** + * 获取目录下所有文件 + * @param dirPath 目录路径 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDir( + final String dirPath, + final boolean isRecursive + ) { + return listFilesInDir(getFileByPath(dirPath), isRecursive); + } + + /** + * 获取目录下所有文件 + * @param dir 目录 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDir( + final File dir, + final boolean isRecursive + ) { + return listFilesInDirWithFilter(dir, pathname -> true, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final String dirPath, + final FileFilter filter + ) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dir 目录 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final File dir, + final FileFilter filter + ) { + return listFilesInDirWithFilter(dir, filter, false); + } + + /** + * 获取目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final String dirPath, + final FileFilter filter, + final boolean isRecursive + ) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilter( + final File dir, + final FileFilter filter, + final boolean isRecursive + ) { + if (!isDirectory(dir) || filter == null) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + list.add(file); + } + if (isRecursive && file.isDirectory()) { + List fileLists = listFilesInDirWithFilter(file, filter, true); + if (fileLists != null) { + list.addAll(fileLists); + } + } + } + } + return list; + } + + // = + + /** + * detail: 文件列表 + * @author Ttt + */ + public static class FileList { + + // 当前文件夹 + private final File mFile; + // 文件夹内子文件列表 + private final List mSubFiles; + + /** + * 构造函数 + * @param file 当前文件夹 + */ + public FileList(File file) { + this(file, new ArrayList<>(0)); + } + + /** + * 构造函数 + * @param file 当前文件夹 + * @param lists 文件夹内子文件列表 + */ + public FileList( + File file, + List lists + ) { + this.mFile = file; + this.mSubFiles = lists; + } + + // = + + /** + * 获取当前文件夹 + * @return {@link File} + */ + public File getFile() { + return mFile; + } + + /** + * 获取文件夹内子文件列表 + * @return {@link List} + */ + public List getSubFiles() { + return mSubFiles; + } + } + + // = + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @return 文件链表 + */ + public static List listFilesInDirBean(final String dirPath) { + return listFilesInDirBean(dirPath, false); + } + + /** + * 获取目录下所有文件 ( 不递归进子目录 ) + * @param dir 目录 + * @return 文件链表 + */ + public static List listFilesInDirBean(final File dir) { + return listFilesInDirBean(dir, false); + } + + /** + * 获取目录下所有文件 + * @param dirPath 目录路径 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirBean( + final String dirPath, + final boolean isRecursive + ) { + return listFilesInDirBean(getFileByPath(dirPath), isRecursive); + } + + /** + * 获取目录下所有文件 + * @param dir 目录 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirBean( + final File dir, + final boolean isRecursive + ) { + return listFilesInDirWithFilterBean(dir, pathname -> true, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dirPath 目录路径 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final String dirPath, + final FileFilter filter + ) { + return listFilesInDirWithFilterBean(getFileByPath(dirPath), filter, false); + } + + /** + * 获取目录下所有过滤的文件 ( 不递归进子目录 ) + * @param dir 目录 + * @param filter 过滤器 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final File dir, + final FileFilter filter + ) { + return listFilesInDirWithFilterBean(dir, filter, false); + } + + /** + * 获取目录下所有过滤的文件 + * @param dirPath 目录路径 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final String dirPath, + final FileFilter filter, + final boolean isRecursive + ) { + return listFilesInDirWithFilterBean(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * 获取目录下所有过滤的文件 + * @param dir 目录 + * @param filter 过滤器 + * @param isRecursive 是否递归进子目录 + * @return 文件链表 + */ + public static List listFilesInDirWithFilterBean( + final File dir, + final FileFilter filter, + final boolean isRecursive + ) { + if (!isDirectory(dir) || filter == null) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + FileList fileList; + if (isRecursive && file.isDirectory()) { + List subs = listFilesInDirWithFilterBean(file, filter, true); + fileList = new FileList(file, subs); + } else { + fileList = new FileList(file); + } + list.add(fileList); + } + } + } + return list; + } + + // = + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @return 文件目录列表 + */ + public static List listOrEmpty(final String dirPath) { + return listOrEmpty(getFile(dirPath)); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @return 文件目录列表 + */ + public static List listOrEmpty(final File dir) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.list()); + if (list != null) return list; + } + return new ArrayList<>(); + } + + // = + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty(final String dirPath) { + return listFilesOrEmpty(getFile(dirPath)); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty(final File dir) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.listFiles()); + if (list != null) return list; + } + return new ArrayList<>(); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dirPath 目录路径 + * @param filter 文件过滤 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty( + final String dirPath, + final FilenameFilter filter + ) { + return listFilesOrEmpty(getFile(dirPath), filter); + } + + /** + * 获取文件夹下的文件目录列表 ( 非全部子目录 ) + * @param dir 目录 + * @param filter 文件过滤 + * @return 文件目录列表 + */ + public static List listFilesOrEmpty( + final File dir, + final FilenameFilter filter + ) { + if (isFileExists(dir)) { + List list = ArrayUtils.asList(dir.listFiles(filter)); + if (list != null) return list; + } + return new ArrayList<>(); + } + + // ============= + // = 图片类型判断 = + // ============= + + // 图片格式 + private static final String[] IMAGE_FORMATS = { + ".PNG", ".JPG", ".JPEG", ".BMP", ".GIF", ".WEBP" + }; + + /** + * 根据文件名判断文件是否为图片 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final File file) { + return file != null && isImageFormats(file.getPath(), IMAGE_FORMATS); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats(final String filePath) { + return isImageFormats(filePath, IMAGE_FORMATS); + } + + /** + * 根据文件名判断文件是否为图片 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isImageFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // ============= + // = 音频类型判断 = + // ============= + + // 音频格式 + private static final String[] AUDIO_FORMATS = { + ".MP3", ".AAC", ".OGG", ".WMA", ".APE", ".FLAC", ".RA" + }; + + /** + * 根据文件名判断文件是否为音频 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats(final File file) { + return file != null && isAudioFormats(file.getPath(), AUDIO_FORMATS); + } + + /** + * 根据文件名判断文件是否为音频 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats(final String filePath) { + return isAudioFormats(filePath, AUDIO_FORMATS); + } + + /** + * 根据文件名判断文件是否为音频 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isAudioFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // ============= + // = 视频类型判断 = + // ============= + + // 视频格式 + private static final String[] VIDEO_FORMATS = { + ".MP4", ".AVI", ".MOV", ".ASF", ".MPG", ".MPEG", ".WMV", ".RM", ".RMVB", ".3GP", ".MKV" + }; + + /** + * 根据文件名判断文件是否为视频 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats(final File file) { + return file != null && isVideoFormats(file.getPath(), VIDEO_FORMATS); + } + + /** + * 根据文件名判断文件是否为视频 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats(final String filePath) { + return isVideoFormats(filePath, VIDEO_FORMATS); + } + + /** + * 根据文件名判断文件是否为视频 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isVideoFormats( + final String filePath, + final String[] fileFormats + ) { + return isFileFormats(filePath, fileFormats); + } + + // = + + /** + * 根据文件名判断文件是否为指定格式 + * @param file 文件 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileFormats( + final File file, + final String[] fileFormats + ) { + return file != null && isFileFormats(file.getPath(), fileFormats); + } + + /** + * 根据文件名判断文件是否为指定格式 + * @param filePath 文件路径 + * @param fileFormats 文件格式 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFileFormats( + final String filePath, + final String[] fileFormats + ) { + if (filePath == null || fileFormats == null || fileFormats.length == 0) return false; + String path = filePath.toUpperCase(); + for (String format : fileFormats) { + if (format != null) { + if (path.endsWith(format.toUpperCase())) { + return true; + } + } + } + return false; + } + + // ============ + // = MD5Utils = + // ============ + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] getFileMD5(final String filePath) { + return MD5Utils.getFileMD5(filePath); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final String filePath) { + return MD5Utils.getFileMD5ToHexString(filePath); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final File file) { + return MD5Utils.getFileMD5ToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] getFileMD5(final File file) { + return MD5Utils.getFileMD5(file); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ForUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ForUtils.java new file mode 100644 index 0000000000..13f5f58856 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ForUtils.java @@ -0,0 +1,627 @@ +package dev.utils.common; + +/** + * detail: 循环工具类 + * @author Ttt + */ +public final class ForUtils { + + private ForUtils() { + } + + // 日志 TAG + private static final String TAG = ForUtils.class.getSimpleName(); + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface Consumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + T value + ); + } + + // ========== + // = 可变数组 = + // ========== + + // ========== + // = 对象类型 = + // ========== + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forArgs( + final Consumer action, + final T... args + ) { + return forArgs(action, false, args); + } + + /** + * 循环可变数组 + *
+     *     基础类型需要传入包装类型, 如 int 传入为 Integer 为泛型类型
+     * 
+ * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forArgs( + final Consumer action, + final boolean checkLength, + final T... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + T value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========== + // = Simple = + // ========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ConsumerSimple { + + /** + * 循环消费方法 + * @param value 对应索引值 + */ + void accept(T value); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forSimpleArgs( + final ConsumerSimple action, + final T... args + ) { + return forSimpleArgs(action, false, args); + } + + /** + * 循环可变数组 + *
+     *     基础类型需要传入包装类型, 如 int 传入为 Integer 为泛型类型
+     * 
+ * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean forSimpleArgs( + final ConsumerSimple action, + final boolean checkLength, + final T... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (T value : args) { + action.accept(value); + } + return true; + } + return false; + } + + // ========== + // = 基础类型 = + // ========== + + // ======= + // = Int = + // ======= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface IntConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + int value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forInts( + final IntConsumer action, + final int... args + ) { + return forInts(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forInts( + final IntConsumer action, + final boolean checkLength, + final int... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + int value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========== + // = Double = + // ========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface DoubleConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + double value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forDoubles( + final DoubleConsumer action, + final double... args + ) { + return forDoubles(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forDoubles( + final DoubleConsumer action, + final boolean checkLength, + final double... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + double value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========= + // = Float = + // ========= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface FloatConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + float value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forFloats( + final FloatConsumer action, + final float... args + ) { + return forFloats(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forFloats( + final FloatConsumer action, + final boolean checkLength, + final float... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + float value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Long = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface LongConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + long value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forLongs( + final LongConsumer action, + final long... args + ) { + return forLongs(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forLongs( + final LongConsumer action, + final boolean checkLength, + final long... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + long value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // =========== + // = Boolean = + // =========== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface BooleanConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + boolean value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBooleans( + final BooleanConsumer action, + final boolean... args + ) { + return forBooleans(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBooleans( + final BooleanConsumer action, + final boolean checkLength, + final boolean... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + boolean value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Byte = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ByteConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + byte value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBytes( + final ByteConsumer action, + final byte... args + ) { + return forBytes(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forBytes( + final ByteConsumer action, + final boolean checkLength, + final byte... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + byte value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ======== + // = Char = + // ======== + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface CharConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + char value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forChars( + final CharConsumer action, + final char... args + ) { + return forChars(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forChars( + final CharConsumer action, + final boolean checkLength, + final char... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + char value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } + + // ========= + // = Short = + // ========= + + /** + * detail: 循环消费者 + * @author Ttt + */ + public interface ShortConsumer { + + /** + * 循环消费方法 + * @param index 索引 + * @param value 对应索引值 + */ + void accept( + int index, + short value + ); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forShorts( + final ShortConsumer action, + final short... args + ) { + return forShorts(action, false, args); + } + + /** + * 循环可变数组 + * @param action 循环消费对象 + * @param checkLength 是否检查长度 + * @param args 参数 + * @return {@code true} success, {@code false} fail + */ + public static boolean forShorts( + final ShortConsumer action, + final boolean checkLength, + final short... args + ) { + if (action != null && args != null) { + int len = args.length; + // 是否需要判断长度 + if (len == 0) return !checkLength; + + for (int i = 0; i < len; i++) { + short value = args[i]; + action.accept(i, value); + } + return true; + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/FormatUtils.java b/lib/DevJava/src/main/java/dev/utils/common/FormatUtils.java new file mode 100644 index 0000000000..309be894ee --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/FormatUtils.java @@ -0,0 +1,142 @@ +package dev.utils.common; + +import dev.utils.common.format.ArgsFormatter; +import dev.utils.common.format.UnitSpanFormatter; + +/** + * detail: 格式化工具类 + * @author Ttt + */ +public final class FormatUtils { + + private FormatUtils() { + } + + // 日志 TAG + private static final String TAG = FormatUtils.class.getSimpleName(); + + /** + * 字符串格式化 + * @param format 待格式化字符串 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String format( + final String format, + final Object... args + ) { + return StringUtils.format(format, args); + } + + // ============= + // = Unit Span = + // ============= + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision + ) { + return UnitSpanFormatter.get(precision); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final String defaultValue + ) { + return UnitSpanFormatter.get(precision, defaultValue); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final boolean appendZero + ) { + return UnitSpanFormatter.get(precision, appendZero); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter unitSpanOf( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + return UnitSpanFormatter.get(precision, appendZero, defaultValue); + } + + // ======== + // = args = + // ======== + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier + ) { + return ArgsFormatter.get(startSpecifier, middleSpecifier); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier + ) { + return ArgsFormatter.get( + startSpecifier, middleSpecifier, endSpecifier + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter argsOf( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + return ArgsFormatter.get( + startSpecifier, middleSpecifier, endSpecifier, + throwError, defaultValue + ); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/HtmlUtils.java b/lib/DevJava/src/main/java/dev/utils/common/HtmlUtils.java new file mode 100644 index 0000000000..9cea7aee20 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/HtmlUtils.java @@ -0,0 +1,248 @@ +package dev.utils.common; + +/** + * detail: Html 工具类 + * @author Ttt + */ +public final class HtmlUtils { + + private HtmlUtils() { + } + + // 移除 padding、margin style + public static final String REMOVE_PADDING_MARGIN_STYLE = ""; + + /** + * 为给定的 Html 移除 padding、margin + * @param html HTML 字符串 + * @return Html 内容字符串 + */ + public static String addRemovePaddingMargin(final String html) { + return REMOVE_PADDING_MARGIN_STYLE + html; + } + + /** + * 为给定的字符串添加 HTML 颜色标记 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColor( + final String content, + final String color + ) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 颜色标记 + * @param format 格式化字符串 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColor( + final String format, + final String content, + final String color + ) { + return StringUtils.format(format, addHtmlColor(content, color)); + } + + /** + * 为给定的字符串添加 HTML 加粗标记 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlBold(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 颜色标记并加粗 + * @param content 给定的字符串 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String addHtmlColorAndBold( + final String content, + final String color + ) { + return addHtmlBold(addHtmlColor(content, color)); + } + + /** + * 为给定的字符串添加 HTML 下划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlUnderline(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 中划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlStrikeThruLine(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 上划线 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlOverLine(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML 字体倾斜 + *
+     *     如果需要倾斜自定义角度, 需要自定义 TextView, 在 onDraw 里面加上倾斜度, 上下左右居中
+     *     canvas.rotate( 倾斜角度, getMeasuredWidth() / 3, getMeasuredHeight() / 3);
+     * 
+ * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlIncline(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML SPAN 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlSPAN(final String content) { + return "" + content + ""; + } + + /** + * 为给定的字符串添加 HTML P 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlP(final String content) { + return "

" + content + "

"; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @return Html 内容字符串 + */ + public static String addHtmlIMG(final String url) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param width 图片宽度 + * @param height 图片高度 + * @return Html 内容字符串 + */ + public static String addHtmlIMG( + final String url, + final String width, + final String height + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param width 图片宽度 + * @return Html 内容字符串 + */ + public static String addHtmlIMGByWidth( + final String url, + final String width + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML IMG 标签 + * @param url 图片链接 + * @param height 图片高度 + * @return Html 内容字符串 + */ + public static String addHtmlIMGByHeight( + final String url, + final String height + ) { + return ""; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @return Html 内容字符串 + */ + public static String addHtmlDIV(final String content) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param margin margin 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByMargin( + final String content, + final String margin + ) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param padding padding 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByPadding( + final String content, + final String padding + ) { + return "
" + content + "
"; + } + + /** + * 为给定的字符串添加 HTML DIV 标签 + * @param content 给定的字符串 + * @param margin margin 边距 + * @param padding padding 边距 + * @return Html 内容字符串 + */ + public static String addHtmlDIVByMarginPadding( + final String content, + final String margin, + final String padding + ) { + return "
" + content + "
"; + } + + // = + + /** + * 将给定的字符串中所有给定的关键字标色 + * @param content 给定的字符串 + * @param keyword 给定的关键字 + * @param color 颜色值, 如: #000000 + * @return Html 内容字符串 + */ + public static String keywordReplaceHtmlColor( + final String content, + final String keyword, + final String color + ) { + return StringUtils.replaceAll(content, keyword, addHtmlColor(keyword, color)); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/HttpParamsUtils.java b/lib/DevJava/src/main/java/dev/utils/common/HttpParamsUtils.java new file mode 100644 index 0000000000..1b22c3ddd0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/HttpParamsUtils.java @@ -0,0 +1,352 @@ +package dev.utils.common; + +import java.net.URLEncoder; +import java.util.LinkedHashMap; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Http 参数工具类 + * @author Ttt + */ +public final class HttpParamsUtils { + + private HttpParamsUtils() { + } + + // 日志 TAG + private static final String TAG = HttpParamsUtils.class.getSimpleName(); + + /** + * 获取 Url 携带参数 + * @param url URL 链接 + * @return Url 携带参数 + */ + public static String getUrlParams(final String url) { + return getUrlParamsArray(url)[1]; + } + + /** + * 获取 Url、携带参数 数组 + * @param url URL 链接 + * @return 0 = url, 1 = params + */ + public static String[] getUrlParamsArray(final String url) { + String[] result = new String[2]; + if (StringUtils.isNotEmpty(url)) { + // 清除掉前后空格 + String urlClean = StringUtils.clearSEWiths(url, " "); + // 清除掉结尾的 ? + urlClean = StringUtils.clearEndsWith(urlClean, "?"); + // 进行拆分 + int index = urlClean.indexOf("?"); + if (index != -1) { + result[0] = urlClean.substring(0, index); + result[1] = urlClean.substring(index + 1); + } else { + result[0] = urlClean; + } + } + return result; + } + + /** + * 判断是否存在参数 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean existsParams(final String params) { + return splitParams(params).size() != 0; + } + + /** + * 通过 Url 判断是否存在参数 + * @param url URL 链接 + * @return {@code true} yes, {@code false} no + */ + public static boolean existsParamsByURL(final String url) { + return splitParams(getUrlParams(url)).size() != 0; + } + + /** + * 拼接 Url 及携带参数 + * @param url URL 链接 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static String joinUrlParams( + final String url, + final String params + ) { + if (StringUtils.isEmpty(params)) return url; + // 获取拼接符号 + String symbol = getUrlParamsJoinSymbol(url, params); + return url + symbol + params; + } + + /** + * 获取 Url 及携带参数 拼接符号 + * @param url URL 链接 + * @param params 请求参数字符串 + * @return {@code true} yes, {@code false} no + */ + public static String getUrlParamsJoinSymbol( + final String url, + final String params + ) { + if (StringUtils.isEmpty(params)) return ""; + // 判断是否存在参数 + if (existsParamsByURL(url)) { + return "&"; + } else { + return "?"; + } + } + + // = + + /** + * 通过 Url 拆分参数 + * @param url URL 链接 + * @return 拆分后的参数 Map + */ + public static Map splitParamsByUrl(final String url) { + return splitParamsByUrl(url, false); + } + + /** + * 通过 Url 拆分参数 + * @param url URL 链接 + * @param urlEncode 是否需要 URL 编码 + * @return 拆分后的参数 Map + */ + public static Map splitParamsByUrl( + final String url, + final boolean urlEncode + ) { + return splitParams(getUrlParams(url), urlEncode); + } + + /** + * 拆分参数 + * @param params 请求参数字符串 + * @return 拆分后的参数 Map + */ + public static Map splitParams(final String params) { + return splitParams(params, false); + } + + /** + * 拆分参数 + * @param params 请求参数字符串 + * @param urlEncode 是否需要 URL 编码 + * @return 拆分后的参数 Map + */ + public static Map splitParams( + final String params, + final boolean urlEncode + ) { + Map mapParams = new LinkedHashMap<>(); + if (StringUtils.isNotEmpty(params)) { + // 拆分数据 + String[] keyValues = params.split("&"); + // 数据长度 + int valLength; + // 进行循环遍历 + for (String val : keyValues) { + // 数据不为 null + if (val != null && (valLength = val.length()) != 0) { + // 获取首位 = 索引 + int indexOf = val.indexOf('='); + // 不存在则不处理 + if (indexOf != -1) { + // 获取 key + String key = val.substring(0, indexOf); + // 获取 value + String value; + // 防止资源浪费 + if (indexOf + 1 == valLength) { + value = ""; + } else { + value = val.substring(indexOf + 1, valLength); + } + // 判断是否编码 + if (urlEncode) { + mapParams.put(key, urlEncode(value)); + } else { + mapParams.put(key, value); + } + } + } + } + } + return mapParams; + } + + // = + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @return 拼接后的参数 + */ + public static String joinParams(final Map mapParams) { + return joinParams(mapParams, false); + } + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @param urlEncode 是否需要 URL 编码 + * @return 拼接后的参数 + */ + public static String joinParams( + final Map mapParams, + final boolean urlEncode + ) { + if (mapParams != null) { + int index = 0; + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : mapParams.entrySet()) { + if (index > 0) builder.append('&'); + builder.append(entry.getKey()); + builder.append('='); + builder.append(urlEncode ? urlEncode(entry.getValue()) : entry.getValue()); + index++; + } + return builder.toString(); + } + return null; + } + + // = + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @return 拼接后的参数 + */ + public static String joinParamsObj(final Map mapParams) { + return joinParamsObj(mapParams, false); + } + + /** + * 拼接请求参数 + * @param mapParams Map 请求参数 + * @param urlEncode 是否需要 URL 编码 + * @return 拼接后的参数 + */ + public static String joinParamsObj( + final Map mapParams, + final boolean urlEncode + ) { + if (mapParams != null) { + int index = 0; + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : mapParams.entrySet()) { + if (index > 0) builder.append('&'); + builder.append(entry.getKey()); + builder.append('='); + if (urlEncode) { + String strValue = ConvertUtils.newStringNotArrayDecode( + entry.getValue() + ); + if (strValue != null) { + builder.append(urlEncode(strValue)); + } + } else { + builder.append(entry.getValue()); + } + index++; + } + return builder.toString(); + } + return null; + } + + // ====================================== + // = 拼接成, 模拟 JavaScript 传递对象数组格式 = + // ====================================== + + /** + * 进行转换对象处理 ( 请求发送对象 ) + * @param mapParams Map 请求参数 + * @param objStr 数组名 + * @param key 数组 key + * @param value 数组 [key] 保存值 + * @return {@code true} success, {@code false} fail + */ + public static boolean convertObjToMS( + final Map mapParams, + final String objStr, + final String key, + final String value + ) { + if (mapParams != null) { + String data = null; + try { + data = URLEncoder.encode(value, DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertObjToMS"); + } + mapParams.put(objStr + "[" + key + "]", data); + return true; + } + return false; + } + + /** + * 进行转换对象处理 ( 请求发送对象 ) + * @param mapParams Map 请求参数 + * @param objStr 数组名 + * @param key 数组 key + * @param value 数组 [key] 保存值 + * @return {@code true} success, {@code false} fail + */ + public static boolean convertObjToMO( + final Map mapParams, + final String objStr, + final String key, + final Object value + ) { + if (mapParams != null) { + Object data = null; + try { + data = URLEncoder.encode(value.toString(), DevFinal.ENCODE.UTF_8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertObjToMO"); + } + mapParams.put(objStr + "[" + key + "]", data); + return true; + } + return false; + } + + // =============== + // = StringUtils = + // =============== + + /** + * 进行 URL 编码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 编码后的字符串 + */ + public static String urlEncode(final String str) { + return StringUtils.urlEncode(str); + } + + /** + * 进行 URL 编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式 URL 编码后的字符串 + */ + public static String urlEncode( + final String str, + final String enc + ) { + return StringUtils.urlEncode(str, enc); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/HttpURLConnectionUtils.java b/lib/DevJava/src/main/java/dev/utils/common/HttpURLConnectionUtils.java new file mode 100644 index 0000000000..c71ae4b752 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/HttpURLConnectionUtils.java @@ -0,0 +1,271 @@ +package dev.utils.common; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: HttpURLConnection 网络工具类 + * @author Ttt + *
+ *     详细解释 HttpURLConnection 类
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class HttpURLConnectionUtils { + + private HttpURLConnectionUtils() { + } + + // 日志 TAG + private static final String TAG = HttpURLConnectionUtils.class.getSimpleName(); + + // 请求超时时间 + private static final int TIMEOUT_IN_MILLIONS = 5000; + + /** + * detail: 请求回调 + * @author Ttt + */ + public interface Callback { + + /** + * 请求响应回调 + * @param result 请求结果 + * @param response 请求响应时间 + */ + void onResponse( + String result, + long response + ); + + /** + * 请求失败 + * @param error 失败异常 + */ + void onFail(Throwable error); + } + + /** + * 异步的 Get 请求 + * @param urlStr 请求地址 + * @param callback 请求回调接口 + */ + public static void doGetAsync( + final String urlStr, + final Callback callback + ) { + new Thread(() -> { + try { + request("GET", urlStr, null, null, callback); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doGetAsync"); + } + }).start(); + } + + /** + * 异步的 Post 请求 + * @param urlStr 请求地址 + * @param params 请求参数 + * @param callback 请求回调接口 + */ + public static void doPostAsync( + final String urlStr, + final String params, + final Callback callback + ) { + new Thread(() -> { + try { + request("POST", urlStr, null, params, callback); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doPostAsync"); + } + }).start(); + } + + /** + * 发送请求 + * @param method 请求方法 + * @param urlStr 请求地址字符串 + * @param headers 请求头信息 + * @param params 请求参数 + * @param callback 请求回调接口 + */ + public static void request( + final String method, + final String urlStr, + final Map headers, + final String params, + final Callback callback + ) { + // 获取连接对象 + HttpURLConnection connection = null; + InputStream is = null; + ByteArrayOutputStream baos = null; + try { + // 请求路径 + URL url = new URL(urlStr); + // 获取连接对象 + connection = (HttpURLConnection) url.openConnection(); + // 设置请求方法 + connection.setRequestMethod(method); + // 设置请求头信息 + if (headers != null) { + for (Map.Entry entry : headers.entrySet()) { + connection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + // 判断是否需要写入数据 + if (params != null && params.length() != 0) { + // 允许写入 + connection.setDoInput(true); + // 设置是否向 connection 输出, 如果是 post 请求, 参数要放在 http 正文内, 因此需要设为 true + connection.setDoOutput(true); + // post 请求不能使用缓存 + connection.setUseCaches(false); + // 写入数据 + OutputStream os = connection.getOutputStream(); + os.write(params.getBytes()); + os.flush(); + os.close(); + } + // 单位是毫秒 + connection.setConnectTimeout(TIMEOUT_IN_MILLIONS); // 设置连接超时 + connection.setReadTimeout(TIMEOUT_IN_MILLIONS); // 设置读取超时 + // 获取请求状态码 + int responseCode = connection.getResponseCode(); + // 判断请求码是否是 200 + if (responseCode >= 200 && responseCode < 300) { + // 输入流 + is = connection.getInputStream(); + baos = new ByteArrayOutputStream(); + // 设置缓存流大小 + byte[] buffer = new byte[1024]; + int len; + while (((len = is.read(buffer)) != -1)) { + baos.write(buffer, 0, len); + } + // 获取请求结果 + String result = baos.toString(); + // 判断是否回调 + if (callback != null) { + // 请求成功, 触发回调 + callback.onResponse(result, connection.getDate()); + } + } else { + // 响应成功, 非 200 直接返回 null + if (callback != null) { + callback.onFail(new Exception("responseCode not >= 200 or < 300, code: " + responseCode)); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "request"); + if (callback != null) { + callback.onFail(e); + } + } finally { + CloseUtils.closeIOQuietly(baos, is); + + if (connection != null) { + try { // 关闭底层连接 Socket + connection.disconnect(); + } catch (Exception ignore) { + } + } + } + } + + // ================= + // = 获取网络时间处理 = + // ================= + + public static final String BAIDU_URL = "https://www.baidu.com"; + + /** + * detail: 时间回调 + * @author Ttt + */ + public interface TimeCallback { + + /** + * 请求响应回调 + * @param millis 响应时间 ( 毫秒 ) + */ + void onResponse(long millis); + + /** + * 请求失败 + * @param error 失败异常 + */ + void onFail(Throwable error); + } + + /** + * 获取网络时间 ( 默认使用百度链接 ) + * @param callback 请求时间回调接口 + */ + public static void getNetTime(final TimeCallback callback) { + getNetTime(BAIDU_URL, callback); + } + + /** + * 获取网络时间 + * @param urlStr 请求地址 + * @param callback 请求时间回调接口 + */ + public static void getNetTime( + final String urlStr, + final TimeCallback callback + ) { + new Thread(() -> reqNetTime(urlStr, callback)).start(); + } + + /** + * 请求网络时间 ( 内部私有 ) + * @param urlStr 请求地址 + * @param callback 请求时间回调接口 + */ + private static void reqNetTime( + final String urlStr, + final TimeCallback callback + ) { + // 获取连接对象 + HttpURLConnection connection = null; + try { + // 请求路径 + URL url = new URL(urlStr); + // 获取连接对象 + connection = (HttpURLConnection) url.openConnection(); + // 获取时间 + long date = connection.getDate(); + // 获取失败, 则进行修改 + if (date <= 0) { + date = -1L; + } + // 触发回调 + if (callback != null) { + callback.onResponse(date); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNetTime"); + // 触发回调 + if (callback != null) { + callback.onFail(e); + } + } finally { + if (connection != null) { + try { // 关闭底层连接 Socket + connection.disconnect(); + } catch (Exception ignore) { + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/MapUtils.java b/lib/DevJava/src/main/java/dev/utils/common/MapUtils.java new file mode 100644 index 0000000000..6f8d484c34 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/MapUtils.java @@ -0,0 +1,1344 @@ +package dev.utils.common; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: Map 工具类 + * @author Ttt + */ +public final class MapUtils { + + private MapUtils() { + } + + // 日志 TAG + private static final String TAG = MapUtils.class.getSimpleName(); + + // ======= + // = Map = + // ======= + + /** + * 判断 Map 是否为 null + * @param map {@link Map} + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Map map) { + return (map == null || map.size() == 0); + } + + /** + * 判断 Map 是否不为 null + * @param map {@link Map} + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Map map) { + return (map != null && map.size() != 0); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取 Map 长度 + * @param map {@link Map} + * @return 如果 Map 为 null, 则返回默认长度, 如果不为 null, 则返回 map.size() + */ + public static int length(final Map map) { + return length(map, 0); + } + + /** + * 获取 Map 长度 + * @param map {@link Map} + * @param defaultLength 集合为 null 默认长度 + * @return 如果 Map 为 null, 则返回 defaultLength, 如果不为 null, 则返回 map.size() + */ + public static int length( + final Map map, + final int defaultLength + ) { + return map != null ? map.size() : defaultLength; + } + + // = + + /** + * 获取长度 Map 是否等于期望长度 + * @param map {@link Map} + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final Map map, + final int length + ) { + return map != null && map.size() == length; + } + + // = + + /** + * 判断 Map 长度是否大于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThan( + final Map map, + final int length + ) { + return map != null && map.size() > length; + } + + /** + * 判断 Map 长度是否大于等于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean greaterThanOrEqual( + final Map map, + final int length + ) { + return map != null && map.size() >= length; + } + + // = + + /** + * 判断 Map 长度是否小于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThan( + final Map map, + final int length + ) { + return map != null && map.size() < length; + } + + /** + * 判断 Map 长度是否小于等于指定长度 + * @param map {@link Map} + * @param length 指定长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean lessThanOrEqual( + final Map map, + final int length + ) { + return map != null && map.size() <= length; + } + + // ============= + // = 获取长度总和 = + // ============= + + /** + * 获取 Map 数组长度总和 + * @param maps Map[] + * @return Map 数组长度总和 + */ + public static int getCount(final Map... maps) { + if (maps == null) return 0; + int count = 0; + for (Map map : maps) { + count += length(map); + } + return count; + } + + // ========== + // = 数据获取 = + // ========== + + /** + * 获取 value + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 的 value + */ + public static V get( + final Map map, + final K key + ) { + if (map != null) { + try { + return map.get(key); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 获取 value 如果 value 为 null, 则返回 defaultValue + * @param map {@link Map} + * @param key key + * @param defaultValue 默认 value + * @param key + * @param value + * @return 指定 key 的 value 如果 value 为 null, 则返回 defaultValue + */ + public static V get( + final Map map, + final K key, + final V defaultValue + ) { + if (map != null) { + try { + V value = map.get(key); + if (value == null) { + return defaultValue; + } else { + return value; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "get"); + } + } + return null; + } + + /** + * 通过 value 获取 key + * @param map {@link Map} + * @param value Value + * @param key + * @param value + * @return 等于 value 的 key + */ + public static K getKeyByValue( + final Map map, + final V value + ) { + if (map != null) { + try { + for (Map.Entry entry : map.entrySet()) { + V v = entry.getValue(); + if (equals(v, value)) { + return entry.getKey(); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeyByValue"); + } + } + return null; + } + + /** + * 通过 value 获取 key 集合 ( 返回等于 value 的 key 集合 ) + * @param map {@link Map} + * @param value Value + * @param key + * @param value + * @return 等于 value 的 key 集合 + */ + public static List getKeysByValue( + final Map map, + final V value + ) { + if (map != null) { + try { + List lists = new ArrayList<>(); + for (Map.Entry entry : map.entrySet()) { + V v = entry.getValue(); + if (equals(v, value)) { + lists.add(entry.getKey()); + } + } + return lists; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeysByValue"); + } + } + return null; + } + + // = + + /** + * 通过 Map 获取 key 集合 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 key 集合 + */ + public static List getKeys(final Map map) { + if (map != null) { + try { + return new ArrayList<>(map.keySet()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeys"); + } + } + return null; + } + + /** + * 通过 Map 获取 key 数组 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 key 数组 + */ + public static K[] getKeysToArrays(final Map map) { + if (map != null) { + try { + return CollectionUtils.toArrayT(getKeys(map)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getKeysToArrays"); + } + } + return null; + } + + /** + * 通过 Map 获取 value 集合 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 value 数组 + */ + public static List getValues(final Map map) { + if (map != null) { + try { + return new ArrayList<>(map.values()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getValues"); + } + } + return null; + } + + /** + * 通过 Map 获取 value 数组 + * @param map {@link Map} + * @param key + * @param value + * @return 全部存储 value 数组 + */ + public static V[] getValuesToArrays(final Map map) { + if (map != null) { + try { + return CollectionUtils.toArrayT(getValues(map)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getValuesToArrays"); + } + } + return null; + } + + // = + + /** + * 获取第一条数据 + * @param map {@link Map} + * @param key + * @param value + * @return 第一条数据 {@link Map.Entry} + */ + public static Map.Entry getFirst(final LinkedHashMap map) { + if (map != null) { + try { + return map.entrySet().iterator().next(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFirst"); + } + } + return null; + } + + /** + * 获取最后一条数据 + * @param map {@link Map} + * @param key + * @param value + * @return 最后一条数据 {@link Map.Entry} + */ + public static Map.Entry getLast(final LinkedHashMap map) { + return getLast(map, true); + } + + /** + * 获取最后一条数据 + * @param map {@link Map} + * @param isReflection 是否使用反射 + * @param key + * @param value + * @return 最后一条数据 {@link Map.Entry} + */ + public static Map.Entry getLast( + final LinkedHashMap map, + final boolean isReflection + ) { + if (map != null) { + if (isReflection) { + try { + // 反射方式 + Field tail = map.getClass().getDeclaredField("tail"); + tail.setAccessible(true); + return (Map.Entry) tail.get(map); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getLast"); + } + } else { + try { + // 遍历方式 + Iterator> iterator = map.entrySet().iterator(); + Map.Entry tail = null; + while (iterator.hasNext()) { + tail = iterator.next(); + } + return tail; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getLast"); + } + } + } + return null; + } + + // = + + /** + * 根据指定 key 获取 key 所在位置的下一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 下一条数据 {@link Map.Entry} + */ + public static Map.Entry getNext( + final LinkedHashMap map, + final K key + ) { + if (map != null) { + try { + for (Map.Entry entry : map.entrySet()) { + K k = entry.getKey(); + if (equals(k, key)) { + return entry; + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNext"); + } + } + return null; + } + + /** + * 根据指定 key 获取 key 所在位置的上一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return 指定 key 上一条数据 {@link Map.Entry} + */ + public static Map.Entry getPrevious( + final LinkedHashMap map, + final K key + ) { + if (map != null) { + try { + Iterator> iterator = map.entrySet().iterator(); + // 临时保存处理 + Map.Entry temp = null; + // 循环处理 + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + K k = entry.getKey(); + // 判断 key 是否相同 + if (equals(k, key)) { + return temp; + } + // 赋值上一条数据 + temp = entry; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPrevious"); + } + } + return null; + } + + // ========== + // = 添加数据 = + // ========== + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final K key, + final V value + ) { + return put(map, key, value, false); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final K key, + final V value, + final boolean notNull + ) { + if (map != null) { + if (notNull && key == null) { + return false; + } + try { + map.put(key, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "put"); + } + } + return false; + } + + /** + * 添加一条数据 ( 不允许 key 为 null ) + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putNotNull( + final Map map, + final K key, + final V value + ) { + return put(map, key, value, true); + } + + // = + + /** + * 添加一条数据 + * @param map {@link Map} + * @param entry entry + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final Map.Entry entry + ) { + return put(map, entry, false); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param entry entry + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean put( + final Map map, + final Map.Entry entry, + final boolean notNull + ) { + if (map != null && entry != null) { + if (notNull && entry.getKey() == null) { + return false; + } + try { + map.put(entry.getKey(), entry.getValue()); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "put"); + } + } + return false; + } + + /** + * 添加一条数据 ( 不允许 key 为 null ) + * @param map {@link Map} + * @param entry entry + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putNotNull( + final Map map, + final Map.Entry entry + ) { + return put(map, entry, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final List listKeys, + final List listValues + ) { + return putAll(map, listKeys, listValues, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final List listKeys, + final List listValues, + final boolean notNull + ) { + if (map != null && listKeys != null && listValues != null + && listKeys.size() == listValues.size()) { + try { + // 循环保存 + for (int i = 0, len = listKeys.size(); i < len; i++) { + K key = listKeys.get(i); + if (notNull && key == null) { + continue; // 忽略进行下一个 + } + // 添加数据 + map.put(key, listValues.get(i)); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param listKeys keys + * @param listValues values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final List listKeys, + final List listValues + ) { + return putAll(map, listKeys, listValues, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param keys keys + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final K[] keys, + final V[] values + ) { + return putAll(map, keys, values, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param keys keys + * @param values values + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final K[] keys, + final V[] values, + final boolean notNull + ) { + if (map != null && keys != null && values != null && keys.length == values.length) { + try { + // 循环保存 + for (int i = 0, len = keys.length; i < len; i++) { + K key = keys[i]; + if (notNull && key == null) { + continue; // 忽略进行下一个 + } + // 添加数据 + map.put(key, values[i]); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param keys keys + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final K[] keys, + final V[] values + ) { + return putAll(map, keys, values, true); + } + + // = + + /** + * 添加多条数据 + * @param map {@link Map} + * @param mapData map 数据 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final Map mapData + ) { + return putAll(map, mapData, false); + } + + /** + * 添加多条数据 + * @param map {@link Map} + * @param mapData map 数据 + * @param notNull 是否不允许 key 为 null + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAll( + final Map map, + final Map mapData, + final boolean notNull + ) { + if (map != null && mapData != null) { + if (notNull) { + try { + for (Map.Entry entry : mapData.entrySet()) { + K key = entry.getKey(); + if (key != null) { + map.put(key, entry.getValue()); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } else { + try { + map.putAll(mapData); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putAll"); + } + } + } + return false; + } + + /** + * 添加多条数据, 不允许 key 为 null + * @param map {@link Map} + * @param mapData map 数据 + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean putAllNotNull( + final Map map, + final Map mapData + ) { + return putAll(map, mapData, true); + } + + // ========== + // = 删除数据 = + // ========== + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Map map, + final K key + ) { + if (map != null) { + try { + map.remove(key); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean remove( + final Map map, + final K key, + final V value + ) { + if (map != null) { + try { + // 判断值是否一样, 一样则移除 key + if (equals(value, map.get(key))) { + map.remove(key); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "remove"); + } + } + return false; + } + + /** + * 移除多条数据 + * @param map {@link Map} + * @param keys keys + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToKeys( + final Map map, + final Collection keys + ) { + if (map != null && keys != null) { + try { + for (K key : keys) { + map.remove(key); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToKeys"); + } + } + return false; + } + + /** + * 移除等于 value 的所有数据 + * @param map {@link Map} + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToValue( + final Map map, + final V value + ) { + if (map != null) { + try { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + V v = entry.getValue(); + if (equals(v, value)) { + iterator.remove(); + } + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToValue"); + } + } + return false; + } + + /** + * 移除等于 value 的所有数据 ( Collection ) + * @param map {@link Map} + * @param values values + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToValues( + final Map map, + final Collection values + ) { + if (map != null && values != null) { + try { + for (V value : values) { + removeToValue(map, value); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToValues"); + } + } + return false; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + // = + + /** + * 切换保存状态 + *
+     *     1. 如果存在, 则删除
+     *     2. 如果不存在, 则保存
+     * 
+ * @param map {@link Map} + * @param key key + * @param value value + * @param key + * @param value + * @return {@code true} success, {@code false} fail + */ + public static boolean toggle( + final Map map, + final K key, + final V value + ) { + if (map != null) { + // 判断是否存在 key + boolean existKey = map.containsKey(key); + try { + if (existKey) { // 存在则删除 + map.remove(key); + } else { // 不存在则保存 + map.put(key, value); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "toggle"); + } + } + return false; + } + + /** + * 判断指定 key 的 value 是否为 null + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullToValue( + final Map map, + final K key + ) { + if (map != null) { + return map.get(key) == null; + } + return true; + } + + /** + * 判断 Map 是否存储 key + * @param map {@link Map} + * @param key key + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean containsKey( + final Map map, + final K key + ) { + if (map != null) { + try { + return map.containsKey(key); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "containsKey"); + } + } + return false; + } + + /** + * 判断 Map 是否存储 value + * @param map {@link Map} + * @param value value + * @param key + * @param value + * @return {@code true} yes, {@code false} no + */ + public static boolean containsValue( + final Map map, + final V value + ) { + if (map != null) { + try { + return map.containsValue(value); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "containsValue"); + } + } + return false; + } + + // =============== + // = 特殊 Map 操作 = + // =============== + + /** + * 添加一条数据 + * @param map 待添加 {@link Map} + * @param key key + * @param value value, add to list + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean putToList( + final Map> map, + final K key, + final T value + ) { + return putToList(map, key, value, true); + } + + /** + * 添加一条数据 + * @param map {@link Map} + * @param key key + * @param value value, add to list + * @param isNew 当指定 (key) 的 value 为 null, 是否创建 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean putToList( + final Map> map, + final K key, + final T value, + final boolean isNew + ) { + if (map != null) { + if (map.containsKey(key)) { + List lists = map.get(key); + if (lists != null) { + try { + lists.add(value); + map.put(key, lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putToList"); + } + } + } else { + // 判断是否创建 + if (isNew) { + try { + List lists = new ArrayList<>(); + lists.add(value); + map.put(key, lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "putToList"); + } + } + } + } + return false; + } + + // = + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToList( + final Map> map, + final K key + ) { + if (map != null) { + try { + map.remove(key); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToList"); + } + } + return false; + } + + /** + * 移除一条数据 + * @param map {@link Map} + * @param key key + * @param value value, remove to list + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToList( + final Map> map, + final K key, + final T value + ) { + if (map != null) { + if (map.containsKey(key)) { + List lists = map.get(key); + if (lists != null) { + try { + lists.remove(value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToList"); + } + } + } + } + return false; + } + + /** + * 移除多条数据 + * @param map {@link Map} + * @param key key + * @param lists 删除的 list 数据源 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToLists( + final Map> map, + final K key, + final List lists + ) { + if (map != null && lists != null) { + if (map.containsKey(key)) { + List list = map.get(key); + if (list != null) { + try { + list.removeAll(lists); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToLists"); + } + } + } + } + return false; + } + + // = + + /** + * 移除多条数据 ( 通过 Map 进行移除 ) + * @param map {@link Map} + * @param removeMap {@link Map} 移除对比数据源 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToMap( + final Map> map, + final Map> removeMap + ) { + return removeToMap(map, removeMap, true, false); + } + + /** + * 移除多条数据 ( 通过 Map 进行移除 ) + * @param map {@link Map} + * @param removeMap {@link Map} 移除对比数据源 + * @param removeEmpty 是否移除 null、长度为 0 的数据 + * @param isNullRemoveAll 如果待移除的 List 是 null, 是否移除全部 + * @param key + * @param value type + * @return {@code true} success, {@code false} fail + */ + public static boolean removeToMap( + final Map> map, + final Map> removeMap, + final boolean removeEmpty, + final boolean isNullRemoveAll + ) { + if (map != null && removeMap != null) { + for (Map.Entry> entry : removeMap.entrySet()) { + K key = entry.getKey(); + // 进行移除处理 + if (map.containsKey(key)) { + List value = entry.getValue(); + try { + if (value != null) { + map.get(key).removeAll(value); + } else { + if (isNullRemoveAll) { + map.remove(key); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToMap - removeAll"); + } + // 判断是否移除 null、长度为 0 的数据 + if (removeEmpty) { + List lists = map.get(key); + try { + // 不存在数据了, 则移除 + if (lists == null || lists.size() == 0) { + map.remove(key); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "removeToMap"); + } + } + } + } + return true; + } + return false; + } + + // ========== + // = 拼接处理 = + // ========== + + /** + * 键值对拼接 + * @param map {@link Map} + * @param symbol 拼接符号 + * @param key + * @param value + * @return {@link StringBuilder} + */ + public static StringBuilder mapToString( + final Map map, + final String symbol + ) { + return mapToString(map, symbol, new StringBuilder()); + } + + /** + * 键值对拼接 + * @param map {@link Map} + * @param symbol 拼接符号 + * @param builder Builder + * @param key + * @param value + * @return {@link StringBuilder} + */ + public static StringBuilder mapToString( + final Map map, + final String symbol, + final StringBuilder builder + ) { + if (map != null && builder != null) { + Iterator> iterator = map.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + builder.append(ConvertUtils.toString(entry.getKey())); + builder.append(symbol); + builder.append(ConvertUtils.toString(entry.getValue())); + // 如果还有下一行则追加换行 + if (iterator.hasNext()) { + builder.append(DevFinal.SYMBOL.NEW_LINE); + } + } + } + return builder; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/NumberUtils.java b/lib/DevJava/src/main/java/dev/utils/common/NumberUtils.java new file mode 100644 index 0000000000..1758d11edf --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/NumberUtils.java @@ -0,0 +1,544 @@ +package dev.utils.common; + +import java.math.BigDecimal; + +import dev.utils.common.validator.ValidatorUtils; + +/** + * detail: 数字 ( 计算 ) 工具类 + * @author Ttt + */ +public final class NumberUtils { + + private NumberUtils() { + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @return 自动补 0 字符串 + */ + public static String addZero(final int value) { + return addZero(value, true); + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @param appendZero 是否自动补 0 + * @return 自动补 0 字符串 + */ + public static String addZero( + final int value, + final boolean appendZero + ) { + if (!appendZero) return String.valueOf(value); + int temp = Math.max(0, value); + return temp >= 10 ? String.valueOf(temp) : "0" + temp; + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @return 自动补 0 字符串 + */ + public static String addZero(final long value) { + return addZero(value, true); + } + + /** + * 补 0 处理 ( 小于 10, 则自动补充 0x ) + * @param value 待处理值 + * @param appendZero 是否自动补 0 + * @return 自动补 0 字符串 + */ + public static String addZero( + final long value, + final boolean appendZero + ) { + if (!appendZero) return String.valueOf(value); + long temp = Math.max(0, value); + return temp >= 10 ? String.valueOf(temp) : "0" + temp; + } + + /** + * 去掉结尾多余的 . 与 0 + * @param value 待处理数值 + * @return 处理后的数值字符串 + */ + public static String subZeroAndDot(final double value) { + return subZeroAndDot(String.valueOf(value)); + } + + /** + * 去掉结尾多余的 . 与 0 + * @param value 待处理数值 + * @return 处理后的数值字符串 + */ + public static String subZeroAndDot(final String value) { + if (StringUtils.isNotEmpty(value)) { + String str = value; + if (str.contains(".")) { + // 去掉多余的 0 + str = str.replaceAll("0+?$", ""); + // 最后一位是 . 则去掉 + str = str.replaceAll("[.]$", ""); + } + return str; + } + return value; + } + + // ============= + // = Unit Span = + // ============= + + /** + * 计算指定单位倍数 + *
+     *     返回数组前面都为整数倍, 最后一位可能存在小数点
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static double[] calculateUnitD( + final double value, + final double[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + double[] result = new double[len]; + double temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + double multiple = temp / unitSpans[i]; + if (i != len - 1) { + multiple = (int) multiple; + } + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static int[] calculateUnitI( + final int value, + final int[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + int[] result = new int[len]; + int temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + int multiple = temp / unitSpans[i]; + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static long[] calculateUnitL( + final long value, + final long[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + long[] result = new long[len]; + long temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + long multiple = temp / unitSpans[i]; + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + /** + * 计算指定单位倍数 + *
+     *     返回数组前面都为整数倍, 最后一位可能存在小数点
+     *     需要最后一位为最后余数, 则设置 units[last] 为 1 即可
+     * 
+ * @param value 待计算数值 + * @param unitSpans 单位范围数组 + * @return 单位范围数组对应倍数值 + */ + public static float[] calculateUnitF( + final float value, + final float[] unitSpans + ) { + if (value <= 0) return null; + if (unitSpans == null) return null; + int len = unitSpans.length; + float[] result = new float[len]; + float temp = value; + for (int i = 0; i < len; i++) { + if (temp >= unitSpans[i]) { + float multiple = temp / unitSpans[i]; + if (i != len - 1) { + multiple = (int) multiple; + } + temp -= multiple * unitSpans[i]; + + result[i] = multiple; + } + } + return result; + } + + // =========== + // = percent = + // =========== + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static Double percentD( + final double value, + final double max + ) { + if (max <= 0) return 0D; + if (value <= 0) return 0D; + if (value >= max) return 1D; + return value / max; + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static int percentI( + final double value, + final double max + ) { + return percentD(value, max).intValue(); + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static long percentL( + final double value, + final double max + ) { + return percentD(value, max).longValue(); + } + + /** + * 计算百分比值 ( 最大 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static float percentF( + final double value, + final double max + ) { + return percentD(value, max).floatValue(); + } + + // ============================ + // = 计算百分比值 ( 可超出 100% ) = + // ============================ + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static Double percentD2( + final double value, + final double max + ) { + if (max <= 0) return 0D; + if (value <= 0) return 0D; + return value / max; + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static int percentI2( + final double value, + final double max + ) { + return percentD2(value, max).intValue(); + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static long percentL2( + final double value, + final double max + ) { + return percentD2(value, max).longValue(); + } + + /** + * 计算百分比值 ( 可超出 100% ) + * @param value 指定值 + * @param max 最大值 + * @return 百分比值 + */ + public static float percentF2( + final double value, + final double max + ) { + return percentD2(value, max).floatValue(); + } + + // ============ + // = Multiple = + // ============ + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static Double multipleD( + final double value, + final double divisor + ) { + if (value <= 0D || divisor <= 0D) return 0D; + return value / divisor; + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static int multipleI( + final double value, + final double divisor + ) { + return multipleD(value, divisor).intValue(); + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static long multipleL( + final double value, + final double divisor + ) { + return multipleD(value, divisor).longValue(); + } + + /** + * 获取倍数 + * @param value 被除数 + * @param divisor 除数 + * @return 倍数 + */ + public static float multipleF( + final double value, + final double divisor + ) { + return multipleD(value, divisor).floatValue(); + } + + // = + + /** + * 获取整数倍数 ( 自动补 1 ) + *
+     *     能够整除返回整数倍数
+     *     不能够整除返回整数倍数 + 1
+     * 
+ * @param value 被除数 + * @param divisor 除数 + * @return 整数倍数 + */ + public static int multiple( + final double value, + final double divisor + ) { + if (value <= 0 || divisor <= 0) return 0; + if (value <= divisor) return 1; + int result = (int) (value / divisor); + return ((value - divisor * result == 0D) ? result : result + 1); + } + + // ========= + // = clamp = + // ========= + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static double clamp( + final double value, + final double max, + final double min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static int clamp( + final int value, + final int max, + final int min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static long clamp( + final long value, + final long max, + final long min + ) { + return value > max ? max : Math.max(value, min); + } + + /** + * 返回的 value 介于 max、min 之间, 若 value 小于 min, 返回 min, 若大于 max, 返回 max + * @param value 指定值 + * @param max 最大值 + * @param min 最小值 + * @return 介于 max、min 之间的 value + */ + public static float clamp( + final float value, + final float max, + final float min + ) { + return value > max ? max : Math.max(value, min); + } + + // ============ + // = 数字转中文 = + // ============ + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final double number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final String number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + /** + * 数字转中文数值 + * @param number 数值 + * @param isUpper 是否大写金额 + * @return 数字中文化字符串 + */ + public static String numberToCHN( + final BigDecimal number, + final boolean isUpper + ) { + return ChineseUtils.numberToCHN(number, isUpper); + } + + // ================== + // = ValidatorUtils = + // ================== + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumber(final String str) { + return ValidatorUtils.isNumber(str); + } + + /** + * 检验数字或包含小数点 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberDecimal(final String str) { + return ValidatorUtils.isNumberDecimal(str); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ObjectUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ObjectUtils.java new file mode 100644 index 0000000000..f2fb029ad2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ObjectUtils.java @@ -0,0 +1,169 @@ +package dev.utils.common; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 对象相关工具类 + * @author Ttt + */ +public final class ObjectUtils { + + private ObjectUtils() { + } + + // 日志 TAG + private static final String TAG = ObjectUtils.class.getSimpleName(); + + // ========== + // = Object = + // ========== + + /** + * 判断对象是否为空 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final Object object) { + if (object == null) return true; + try { + if (object.getClass().isArray() && Array.getLength(object) == 0) { + return true; + } + if (object instanceof CharSequence && object.toString().length() == 0) { + return true; + } + if (object instanceof Collection && ((Collection) object).isEmpty()) { + return true; + } + if (object instanceof Map && ((Map) object).isEmpty()) { + return true; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isEmpty"); + } + return false; + } + + /** + * 判断对象是否非空 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final Object object) { + return !isEmpty(object); + } + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + // 两个值都不为 null + if (value1 != null && value2 != null) { + try { + if (value1 instanceof String && value2 instanceof String) { + return value1.equals(value2); + } else if (value1 instanceof CharSequence && value2 instanceof CharSequence) { + CharSequence v1 = (CharSequence) value1; + CharSequence v2 = (CharSequence) value2; + // 获取数据长度 + int length = v1.length(); + // 判断数据长度是否一致 + if (length == v2.length()) { + for (int i = 0; i < length; i++) { + if (v1.charAt(i) != v2.charAt(i)) { + return false; + } + } + return true; + } + return false; + } + // 其他都使用 equals 判断 + return value1.equals(value2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "equals"); + } + return false; + } + // 防止两个值都为 null + return (value1 == null && value2 == null); + } + + /** + * 检查对象是否为 null, 为 null 则抛出异常, 不为 null 则返回该对象 + * @param object 对象 + * @param message 报错信息 + * @param 泛型 + * @return 非空对象 + * @throws NullPointerException null 异常 + */ + public static T requireNonNull( + final T object, + final String message + ) + throws NullPointerException { + if (object == null) throw new NullPointerException(message); + return object; + } + + /** + * 获取非空或默认对象 + * @param object 对象 + * @param defaultObject 默认值 + * @param 泛型 + * @return 非空或默认对象 + */ + public static T getOrDefault( + final T object, + final T defaultObject + ) { + return (object != null) ? object : defaultObject; + } + + /** + * 获取对象哈希值 + * @param object 对象 + * @return 哈希值 + */ + public static int hashCode(final Object object) { + return object != null ? object.hashCode() : 0; + } + + /** + * 获取一个对象的独一无二的标记 + * @param object 对象 + * @return 对象唯一标记 + */ + public static String getObjectTag(final Object object) { + if (object == null) return null; + // 对象所在的包名 + 对象的内存地址 + return object.getClass().getName() + Integer.toHexString(object.hashCode()); + } + + /** + * Object 转换所需类型对象 + * @param object Object + * @param 泛型 + * @return Object convert T object + */ + public static T convert(final Object object) { + if (object == null) return null; + try { + return (T) object; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/RandomUtils.java b/lib/DevJava/src/main/java/dev/utils/common/RandomUtils.java new file mode 100644 index 0000000000..e31a4804ea --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/RandomUtils.java @@ -0,0 +1,625 @@ +package dev.utils.common; + +import java.util.Random; + +import dev.utils.JCLogUtils; + +/** + * detail: 随机工具类 + * @author Ttt + */ +public final class RandomUtils { + + private RandomUtils() { + } + + // 日志 TAG + private static final String TAG = RandomUtils.class.getSimpleName(); + + // 0123456789 + private static final char[] NUMBERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 + }; + + // abcdefghijklmnopqrstuvwxyz + private static final char[] LOWER_CASE_LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, + 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 + }; + + // ABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] CAPITAL_LETTERS = { + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, + 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] LETTERS = { + 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, + 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, + 84, 85, 86, 87, 88, 89, 90 + }; + + // 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ + private static final char[] NUMBERS_AND_LETTERS = { + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 65, 66, 67, 68, 69, 70, 71, 72, + 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90 + }; + + /** + * 获取伪随机 boolean 值 + * @param random Random + * @return 随机 boolean 值 + */ + public static boolean nextBoolean(final Random random) { + return random != null ? random.nextBoolean() : new Random().nextBoolean(); + } + + /** + * 获取伪随机 byte[] + * @param random Random + * @param data 随机数据源 + * @return 随机 byte[] + */ + public static byte[] nextBytes( + final Random random, + final byte[] data + ) { + if (random == null || data == null) return data; + try { + random.nextBytes(data); + } catch (Exception ignored) { + } + return data; + } + + /** + * 获取伪随机 double 值 + * @param random Random + * @return 随机 double 值 + */ + public static double nextDouble(final Random random) { + return random != null ? random.nextDouble() : new Random().nextDouble(); + } + + /** + * 获取伪随机高斯分布值 + * @param random Random + * @return 伪随机高斯分布值 + */ + public static double nextGaussian(final Random random) { + return random != null ? random.nextGaussian() : new Random().nextGaussian(); + } + + /** + * 获取伪随机 float 值 + * @param random Random + * @return 随机 float 值 + */ + public static float nextFloat(final Random random) { + return random != null ? random.nextFloat() : new Random().nextFloat(); + } + + /** + * 获取伪随机 int 值 + * @param random Random + * @return 随机 int 值 + */ + public static int nextInt(final Random random) { + return random != null ? random.nextInt() : new Random().nextInt(); + } + + /** + * 获取伪随机 int 值 ( 该值介于 [0, n) 的区间 ) + * @param random Random + * @param number 最大随机值 + * @return 随机介于 [0, n) 的区间值 + */ + public static int nextInt( + final Random random, + final int number + ) { + if (number <= 0) return 0; + return random != null ? random.nextInt(number) : new Random().nextInt(number); + } + + /** + * 获取伪随机 long 值 + * @param random Random + * @return 随机 long 值 + */ + public static long nextLong(final Random random) { + return random != null ? random.nextLong() : new Random().nextLong(); + } + + // = + + /** + * 获取伪随机 boolean 值 + * @return 随机 boolean 值 + */ + public static boolean nextBoolean() { + return new Random().nextBoolean(); + } + + /** + * 获取伪随机 byte[] + * @param data 随机数据源 + * @return 随机 byte[] + */ + public static byte[] nextBytes(final byte[] data) { + if (data == null) return null; + try { + new Random().nextBytes(data); + } catch (Exception ignored) { + } + return data; + } + + /** + * 获取伪随机 double 值 + * @return 随机 double 值 + */ + public static double nextDouble() { + return new Random().nextDouble(); + } + + /** + * 获取伪随机高斯分布值 + * @return 伪随机高斯分布值 + */ + public static double nextGaussian() { + return new Random().nextGaussian(); + } + + /** + * 获取伪随机 float 值 + * @return 随机 float 值 + */ + public static float nextFloat() { + return new Random().nextFloat(); + } + + /** + * 获取伪随机 int 值 + * @return 随机 int 值 + */ + public static int nextInt() { + return new Random().nextInt(); + } + + /** + * 获取伪随机 int 值 ( 该值介于 [0, n) 的区间 ) + * @param number 最大随机值 + * @return 随机介于 [0, n) 的区间值 + */ + public static int nextInt(final int number) { + if (number <= 0) return 0; + return new Random().nextInt(number); + } + + /** + * 获取伪随机 long 值 + * @return 随机 long 值 + */ + public static long nextLong() { + return new Random().nextLong(); + } + + // = + + /** + * 获取数字自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomNumbers(final int length) { + return getRandom(NUMBERS, length); + } + + /** + * 获取小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomLowerCaseLetters(final int length) { + return getRandom(LOWER_CASE_LETTERS, length); + } + + /** + * 获取大写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomCapitalLetters(final int length) { + return getRandom(CAPITAL_LETTERS, length); + } + + /** + * 获取大小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomLetters(final int length) { + return getRandom(LETTERS, length); + } + + /** + * 获取数字、大小写字母自定义长度的随机数 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandomNumbersAndLetters(final int length) { + return getRandom(NUMBERS_AND_LETTERS, length); + } + + /** + * 获取自定义数据自定义长度的随机数 + * @param source 随机的数据源 + * @param length 长度 + * @return 随机字符串 + */ + public static String getRandom( + final String source, + final int length + ) { + if (source == null) return null; + return getRandom(source.toCharArray(), length); + } + + /** + * 获取 char[] 内的随机数 + * @param chars 随机的数据源 + * @param length 需要最终长度 + * @return 随机字符串 + */ + public static String getRandom( + final char[] chars, + final int length + ) { + if (length > 0 && chars != null && chars.length != 0) { + StringBuilder builder = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) { + builder.append(chars[random.nextInt(chars.length)]); + } + return builder.toString(); + } + return null; + } + + /** + * 获取 String[] 内的随机数 + * @param strings 随机的数据源 + * @param length 需要最终长度 + * @return 随机字符串 + */ + public static String getRandom( + final String[] strings, + final int length + ) { + if (length > 0 && strings != null && strings.length != 0) { + StringBuilder builder = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) { + builder.append(strings[random.nextInt(strings.length)]); + } + return builder.toString(); + } + return null; + } + + /** + * 获取 0 - 最大随机数之间的随机数 + * @param max 最大随机数 + * @return 随机介于 [0, max) 的区间值 + */ + public static int getRandom(final int max) { + return getRandom(0, max); + } + + /** + * 获取两个数之间的随机数 ( 不含最大随机数, 需要 + 1) + * @param min 最小随机数 + * @param max 最大随机数 + * @return 随机介于 [min, max) 的区间值 + */ + public static int getRandom( + final int min, + final int max + ) { + if (min > max) { + return 0; + } else if (min == max) { + return min; + } + return min + new Random().nextInt(max - min); + } + + // = + + // 内置洗牌算法 + // java.util.Collections.shuffle(List list); + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param objects 随机数据源 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle(final Object[] objects) { + if (objects == null) return false; + return shuffle(objects, getRandom(1, objects.length)); + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param objects 随机数据源 + * @param shuffleCount 洗牌次数 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle( + final Object[] objects, + final int shuffleCount + ) { + int length; + if (shuffleCount > 0 && objects != null && (length = objects.length) >= shuffleCount) { + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(0, length - i); + Object temp = objects[length - i]; + objects[length - i] = objects[random]; + objects[random] = temp; + } + return true; + } + return false; + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param ints 随机数据源 + * @return 随机 int[] + */ + public static int[] shuffle(final int[] ints) { + if (ints == null) return null; + return shuffle(ints, getRandom(1, ints.length)); + } + + /** + * 洗牌算法 ( 第一种 ) 随机置换指定的数组使用的默认源的随机性 ( 随机数据源小于三个, 则无效 ) + * @param ints 随机数据源 + * @param shuffleCount 洗牌次数 + * @return 随机 int[] + */ + public static int[] shuffle( + final int[] ints, + final int shuffleCount + ) { + int length; + if (shuffleCount > 0 && ints != null && (length = ints.length) >= shuffleCount) { + int[] out = new int[shuffleCount]; + for (int i = 1; i <= shuffleCount; i++) { + int random = getRandom(0, length - i); + out[i - 1] = ints[random]; + int temp = ints[length - i]; + ints[length - i] = ints[random]; + ints[random] = temp; + } + return out; + } + return null; + } + + // = + + /** + * 洗牌算法 ( 第二种 ) 随机置换指定的数组使用的默认源的随机性 + * @param objects 随机数据源 + * @return {@code true} success, {@code false} fail + */ + public static boolean shuffle2(final Object[] objects) { + if (objects == null) return false; + int len = objects.length; + if (len > 0) { + for (int i = 0; i < len - 1; i++) { + int idx = (int) (Math.random() * (len - i)); + Object temp = objects[idx]; + objects[idx] = objects[len - i - 1]; + objects[len - i - 1] = temp; + } + return true; + } + return false; + } + + // = + + /** + * 获取指定范围 int 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 int 值 + * @throws IllegalArgumentException 参数错误 + */ + public static int nextIntRange( + final int origin, + final int bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + Random random = new Random(); + int n = bound - origin; + if (n > 0) { + return random.nextInt(n) + origin; + } else { + int r; + do { + r = random.nextInt(); + } while (r < origin || r >= bound); + return r; + } + } + + /** + * 获取指定范围 long 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 long 值 + * @throws IllegalArgumentException 参数错误 + */ + public static long nextLongRange( + final long origin, + final long bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + Random random = new Random(); + long r = random.nextLong(); + long n = bound - origin, m = n - 1; + if ((n & m) == 0L) // power of two + { + r = (r & m) + origin; + } else if (n > 0L) { // reject over-represented candidates + for (long u = r >>> 1; // ensure nonnegative + u + m - (r = u % n) < 0L; // rejection check + u = random.nextLong() >>> 1) // retry + { + } + r += origin; + } else { // range not representable as long + while (r < origin || r >= bound) { + r = random.nextLong(); + } + } + return r; + } + + /** + * 获取指定范围 double 值 + * @param origin 开始值 + * @param bound 范围值 + * @return 属于指定范围随机 double 值 + * @throws IllegalArgumentException 参数错误 + */ + public static double nextDoubleRange( + final double origin, + final double bound + ) + throws IllegalArgumentException { + if (origin > bound) { + throw new IllegalArgumentException("bound must be greater than origin"); + } else if (origin == bound) { + return origin; + } + double r = new Random().nextDouble(); + r = r * (bound - origin) + origin; + if (r >= bound) // correct for rounding + { + r = Double.longBitsToDouble(Double.doubleToLongBits(bound) - 1); + } + return r; + } + + /** + * 获取随机 int[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 int[] + */ + public static int[] ints( + final int streamSize, + final int randomNumberOrigin, + final int randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// IntStream intStream = new Random().ints(streamSize, randomNumberOrigin, randomNumberBound); +// return intStream.toArray(); + try { + int[] ints = new int[streamSize]; + for (int i = 0; i < streamSize; i++) { + ints[i] = nextIntRange(randomNumberOrigin, randomNumberBound); + } + return ints; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "ints"); + } + return null; + } + + /** + * 获取随机 long[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 long[] + */ + public static long[] longs( + final int streamSize, + final long randomNumberOrigin, + final long randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// LongStream longStream = new Random().longs(streamSize, randomNumberOrigin, randomNumberBound); +// return longStream.toArray(); + try { + long[] longs = new long[streamSize]; + for (int i = 0; i < streamSize; i++) { + longs[i] = nextLongRange(randomNumberOrigin, randomNumberBound); + } + return longs; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "longs"); + } + return null; + } + + /** + * 获取随机 double[] + * @param streamSize 数组长度 + * @param randomNumberOrigin 开始值 + * @param randomNumberBound 结束值 ( 最大值范围 ) + * @return 指定范围随机 double[] + */ + public static double[] doubles( + final int streamSize, + final double randomNumberOrigin, + final double randomNumberBound + ) { + if (randomNumberOrigin >= randomNumberBound) { + return null; + } else if (streamSize < 0) { + return null; + } +// DoubleStream doubleStream = new Random().doubles(streamSize, randomNumberOrigin, randomNumberBound); +// return doubleStream.toArray(); + try { + double[] doubles = new double[streamSize]; + for (int i = 0; i < streamSize; i++) { + doubles[i] = nextDoubleRange(randomNumberOrigin, randomNumberBound); + } + return doubles; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "doubles"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/Reflect2Utils.java b/lib/DevJava/src/main/java/dev/utils/common/Reflect2Utils.java new file mode 100644 index 0000000000..6516ebf274 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/Reflect2Utils.java @@ -0,0 +1,699 @@ +package dev.utils.common; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import dev.utils.JCLogUtils; + +/** + * detail: 反射相关工具类 + * @author Ttt + *
+ *     有两个方法: getMethod, getDeclaredMethod
+ *     

+ * getDeclaredMethod() 获取的是类自身声明的所有方法, 包含 public、protected 和 private 方法 + * getMethod() 获取的是类的所有共有方法, 这就包括自身的所有 public 方法, 和从基类继承的、从接口实现的所有 public 方法 + *

+ * getMethod 只能调用 public 声明的方法, 而 getDeclaredMethod 基本可以调用任何类型声明的方法 + * 反射多用 getDeclaredMethod 尽量少用 getMethod + *
+ */ +public final class Reflect2Utils { + + private Reflect2Utils() { + } + + // 日志 TAG + private static final String TAG = Reflect2Utils.class.getSimpleName(); + + // ========== + // = 对象变量 = + // ========== + + /** + * 设置某个对象变量值 ( 可设置静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param value 字段值 + * @return {@code true} success, {@code false} fail + */ + public static boolean setProperty( + final Object object, + final String fieldName, + final Object value + ) { + if (object == null || fieldName == null) return false; + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, value); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "setProperty"); + } + return false; + } + + /** + * 获取某个对象的变量 ( 可获取静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getProperty( + final Object object, + final String fieldName + ) { + if (object == null || fieldName == null) return null; + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProperty"); + } + return null; + } + + // =================== + // = 获取某个类的静态变量 = + // =================== + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param object 对象 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final Object object, + final String fieldName + ) { + if (object == null) return null; + return getStaticProperty(object.getClass().getName(), fieldName); + } + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param clazz 类 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final Class clazz, + final String fieldName + ) { + if (clazz == null) return null; + return getStaticProperty(clazz.getName(), fieldName); + } + + /** + * 获取某个类的静态变量 ( 只能获取静态变量 ) + * @param className 类名 + * @param fieldName 属性名 + * @param 泛型 + * @return 该变量对象 + */ + public static T getStaticProperty( + final String className, + final String fieldName + ) { + if (className == null || fieldName == null) return null; + try { + Class clazz = Class.forName(className); + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(clazz); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getStaticProperty"); + } + return null; + } + + // ================ + // = 执行某个对象方法 = + // ================ + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName + ) { + return invokeMethod(object, methodName, null, null); + } + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName, + final Object[] args + ) { + return invokeMethod(object, methodName, args, getArgsClass(args)); + } + + /** + * 执行某个对象方法 ( 可执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeMethod( + final Object object, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (object == null || methodName == null) return null; + try { + Class clazz = object.getClass(); + if (args != null && argsClass != null) { // 参数、参数类型不为 null, 并且数量相等 + if (args.length == argsClass.length && args.length != 0) { + Method method = clazz.getDeclaredMethod(methodName, argsClass); + method.setAccessible(true); + return (T) method.invoke(object, args); + } + } else { + // 无参数、参数类型, 才执行 + if (args == null && argsClass == null) { + Method method = clazz.getDeclaredMethod(methodName); + method.setAccessible(true); + return (T) method.invoke(object); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "invokeMethod"); + } + return null; + } + + // ==================== + // = 执行某个类的静态方法 = + // ==================== + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName, + final Object[] args + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param object 对象 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Object object, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (object == null) return null; + return invokeStaticMethod(object.getClass().getName(), methodName, args, argsClass); + } + + // = + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName, + final Object[] args + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param clazz 类 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final Class clazz, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (clazz == null) return null; + return invokeStaticMethod(clazz.getName(), methodName, args, argsClass); + } + + // = + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName + ) { + if (className == null) return null; + return invokeStaticMethod(className, methodName, null, null); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param args 参数数组 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName, + final Object[] args + ) { + if (className == null) return null; + return invokeStaticMethod(className, methodName, args, getArgsClass(args)); + } + + /** + * 执行某个类的静态方法 ( 只能执行静态方法 ) + * @param className 类名 + * @param methodName 方法名 + * @param args 参数数组 + * @param argsClass 参数类型 + * @param 泛型 + * @return 执行方法返回的结果 + */ + public static T invokeStaticMethod( + final String className, + final String methodName, + final Object[] args, + final Class[] argsClass + ) { + if (className == null || methodName == null) return null; + try { + Class clazz = Class.forName(className); + if (args != null && argsClass != null) { // 参数、参数类型不为 null, 并且数量相等 + if (args.length == argsClass.length && args.length != 0) { + Method method = clazz.getDeclaredMethod(methodName, argsClass); + method.setAccessible(true); + return (T) method.invoke(clazz, args); + } + } else { + // 无参数、参数类型, 才执行 + if (args == null && argsClass == null) { + Method method = clazz.getDeclaredMethod(methodName); + method.setAccessible(true); + return (T) method.invoke(clazz); + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "invokeStaticMethod"); + } + return null; + } + + // ========================= + // = 新建实例 ( 构造函数创建 ) = + // ========================= + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final Object object) { + if (object == null) return null; + return newInstance(object.getClass().getName(), null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Object object, + final Object[] args + ) { + if (object == null) return null; + return newInstance(object.getClass().getName(), args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param object 对象 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Object object, + final Object[] args, + final Class[] argsClass + ) { + if (object == null) return null; + return newInstance(object.getClass().getName(), args, argsClass); + } + + // = + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final Class clazz) { + if (clazz == null) return null; + return newInstance(clazz.getName(), null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Class clazz, + final Object[] args + ) { + if (clazz == null) return null; + return newInstance(clazz.getName(), args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param clazz 类 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final Class clazz, + final Object[] args, + final Class[] argsClass + ) { + if (clazz == null) return null; + return newInstance(clazz.getName(), args, argsClass); + } + + // = + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance(final String className) { + if (className == null) return null; + return newInstance(className, null, null); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param args 参数 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final String className, + final Object[] args + ) { + if (className == null) return null; + return newInstance(className, args, getArgsClass(args)); + } + + /** + * 新建实例 ( 构造函数创建 ) + * @param className 类名 + * @param args 参数 + * @param argsClass 参数类型 + * @param 泛型 + * @return 新建的实例 + */ + public static T newInstance( + final String className, + final Object[] args, + final Class[] argsClass + ) { + if (className == null) return null; + try { + Class newClass = Class.forName(className); + if (args == null) { + return (T) newClass.newInstance(); + } else { + Constructor cons = newClass.getConstructor(argsClass); + return (T) cons.newInstance(args); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + } + return null; + } + + // = + + /** + * 是不是某个类的实例 + * @param object 实例 + * @param clazz 待判断类 + * @return 如果 obj 是此类的实例, 则返回 true + */ + public static boolean isInstance( + final Object object, + final Class clazz + ) { + if (object == null || clazz == null) return false; + try { + return clazz.isInstance(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isInstance"); + } + return false; + } + + /** + * 获取参数类型 + * @param args 参数 + * @return 参数类型数组 + */ + public static Class[] getArgsClass(final Object... args) { + if (args != null) { + try { + Class[] argsClass = new Class[args.length]; + for (int i = 0, len = args.length; i < len; i++) { + argsClass[i] = args[i].getClass(); + } + return argsClass; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getArgsClass"); + } + } + return new Class[0]; + } + + // = + + /** + * 获取某个对象的变量 + *
+     *     例: 获取父类中的变量
+     *     Object object = 对象;
+     *     getObject(getDeclaredFieldParent(object, "父类中变量名"), object);
+     * 
+ * @param object 对象 + * @param field {@link Field} + * @param 泛型 + * @return 该变量对象 + */ + public static T getProperty( + final Object object, + final Field field + ) { + if (object == null || field == null) return null; + try { + field.setAccessible(true); + return (T) field.get(object); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProperty"); + } + return null; + } + + /** + * 获取父类中的变量对象 + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param 泛型 + * @return 父类中的变量对象 + */ + public static T getPropertyByParent( + final Object object, + final String fieldName + ) { + return getPropertyByParent(object, fieldName, 1); + } + + /** + * 获取父类中的变量对象 + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param fieldNumber 字段出现次数, 如果父类还有父类, 并且有相同变量名, 设置负数 一直会跟到最后的变量 + * @param 泛型 + * @return 父类中的变量对象 + */ + public static T getPropertyByParent( + final Object object, + final String fieldName, + final int fieldNumber + ) { + if (object == null || fieldName == null) return null; + try { + Field field = getDeclaredFieldParent(object, fieldName, fieldNumber); + return getProperty(object, field); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getPropertyByParent"); + } + return null; + } + + /** + * 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) + * @param object 对象 + * @param fieldName 属性名 + * @return {@link Field} + */ + public static Field getDeclaredFieldParent( + final Object object, + final String fieldName + ) { + return getDeclaredFieldParent(object, fieldName, 1); + } + + /** + * 获取父类中的变量对象 ( 循环向上转型, 获取对象的 DeclaredField ) + * @param object 子类对象 + * @param fieldName 父类中的属性名 + * @param fieldNumber 字段出现次数, 如果父类还有父类, 并且有相同变量名, 设置负数 一直会跟到最后的变量 + * @return {@link Field} 父类中的属性对象 + */ + public static Field getDeclaredFieldParent( + final Object object, + final String fieldName, + final int fieldNumber + ) { + if (object == null || fieldName == null) return null; + try { + if (fieldNumber == 0) return null; + // 获取当前出现次数 + int number = 0; + // 限制值 + int limitNumber = (fieldNumber >= 0) ? fieldNumber : Integer.MAX_VALUE; + // = + Field field = null; + Class clazz = object.getClass(); + for (; clazz != Object.class; clazz = clazz.getSuperclass()) { + try { + field = clazz.getDeclaredField(fieldName); + number++; + } catch (Exception e) { + // 这里甚么都不要做, 并且这里的异常必须这样写, 不能抛出去 + // 如果这里的异常打印或者往外抛, 则就不会执行 clazz = clazz.getSuperclass(), 最后就不会进入到父类中了 + } + if (number >= limitNumber) { + return field; + } + } + // 负数表示跟到最后 + if (fieldNumber < 0) { + return field; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDeclaredFieldParent"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ReflectUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ReflectUtils.java new file mode 100644 index 0000000000..49be738951 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ReflectUtils.java @@ -0,0 +1,781 @@ +package dev.utils.common; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import dev.utils.JCLogUtils; + +/** + * detail: 反射相关工具类 + * @author Ttt + */ +public final class ReflectUtils { + + // 日志 TAG + private static final String TAG = ReflectUtils.class.getSimpleName(); + + private final Class mType; + + private final Object mObject; + + private ReflectUtils(final Class type) { + this(type, type); + } + + private ReflectUtils( + final Class type, + final Object object + ) { + this.mType = type; + this.mObject = object; + } + + // =========== + // = reflect = + // =========== + + /** + * 设置要反射的类 + * @param className 完整类名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public static ReflectUtils reflect(final String className) + throws ReflectException { + return reflect(forName(className)); + } + + /** + * 设置要反射的类 + * @param className 完整类名 + * @param classLoader 类加载器 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public static ReflectUtils reflect( + final String className, + final ClassLoader classLoader + ) + throws ReflectException { + return reflect(forName(className, classLoader)); + } + + /** + * 设置要反射的类 + * @param clazz 类的类型 + * @return {@link ReflectUtils} + */ + public static ReflectUtils reflect(final Class clazz) { + return new ReflectUtils(clazz); + } + + /** + * 设置要反射的类 + * @param object 类对象 + * @return {@link ReflectUtils} + */ + public static ReflectUtils reflect(final Object object) { + return new ReflectUtils(object == null ? Object.class : object.getClass(), object); + } + + // = + + /** + * 获取 Class + * @param className 类名 + * @param 未知类型 + * @return 指定类 + * @throws ReflectException 反射异常 + */ + private static Class forName(final String className) + throws ReflectException { + try { + return Class.forName(className); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "forName"); + throw new ReflectException(e); + } + } + + /** + * 获取 Class + * @param name 类名 + * @param classLoader 类加载器 + * @param 未知类型 + * @return 指定类 + * @throws ReflectException 反射异常 + */ + private static Class forName( + final String name, + final ClassLoader classLoader + ) + throws ReflectException { + try { + return Class.forName(name, true, classLoader); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "forName"); + throw new ReflectException(e); + } + } + + // =============== + // = newInstance = + // =============== + + /** + * 实例化反射对象 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils newInstance() + throws ReflectException { + return newInstance(new Object[0]); + } + + /** + * 实例化反射对象 + * @param args 实例化需要的参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils newInstance(final Object... args) + throws ReflectException { + Class[] types = getArgsType(args); + try { + Constructor constructor = type().getDeclaredConstructor(types); + return newInstance(constructor, args); + } catch (NoSuchMethodException e) { + List> list = new ArrayList<>(); + for (Constructor constructor : type().getDeclaredConstructors()) { + if (match(constructor.getParameterTypes(), types)) { + list.add(constructor); + } + } + if (list.isEmpty()) { + throw new ReflectException(e); + } else { + sortConstructors(list); + return newInstance(list.get(0), args); + } + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 获取参数类型 + * @param args 参数数组 + * @param 未知类型 + * @return 参数类型数组 + */ + private Class[] getArgsType(final Object... args) { + if (args == null) return new Class[0]; + Class[] result = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + Object value = args[i]; + result[i] = value == null ? NULL.class : value.getClass(); + } + return result; + } + + /** + * 进行排序 + * @param list 类构造函数信息集合 + */ + private void sortConstructors(final List> list) { + if (list == null) return; + Collections.sort(list, new Comparator>() { + @Override + public int compare( + Constructor o1, + Constructor o2 + ) { + Class[] types1 = o1.getParameterTypes(); + Class[] types2 = o2.getParameterTypes(); + int len = types1.length; + for (int i = 0; i < len; i++) { + if (!types1[i].equals(types2[i])) { + if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { + return 1; + } else { + return -1; + } + } + } + return 0; + } + }); + } + + /** + * 获取实例对象 + * @param constructor 类构造函数信息 + * @param args 构造参数数组 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + private ReflectUtils newInstance( + final Constructor constructor, + final Object... args + ) + throws ReflectException { + try { + return new ReflectUtils( + constructor.getDeclaringClass(), + accessible(constructor).newInstance(args) + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "newInstance"); + throw new ReflectException(e); + } + } + + // ========= + // = field = + // ========= + + /** + * 设置反射的字段 + * @param name 字段名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils field(final String name) + throws ReflectException { + try { + Field field = getField(name); + return new ReflectUtils(field.getType(), field.get(mObject)); + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 设置反射的字段 + * @param name 字段名 + * @param value 字段值 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils field( + final String name, + final Object value + ) + throws ReflectException { + try { + Field field = getField(name); + field.set(mObject, unwrap(value)); + return this; + } catch (Exception e) { + throw new ReflectException(e); + } + } + + /** + * 获取 Field 对象 + * @param name 字段名 + * @return {@link Field} + * @throws ReflectException 反射异常 + */ + private Field getField(final String name) + throws ReflectException { + Field field = getAccessibleField(name); + if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) { + try { + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + } catch (NoSuchFieldException ignore) { + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getField"); + throw new ReflectException(e); + } + } + return field; + } + + /** + * 获取可访问字段, 返回 Field 对象 + * @param name 字段名 + * @return {@link Field} + * @throws ReflectException 反射异常 + */ + private Field getAccessibleField(final String name) + throws ReflectException { + Class type = type(); + try { + return accessible(type.getField(name)); + } catch (NoSuchFieldException e) { + do { + try { + return accessible(type.getDeclaredField(name)); + } catch (NoSuchFieldException ignore) { + } + type = type.getSuperclass(); + } while (type != null); + JCLogUtils.eTag(TAG, e, "getAccessibleField"); + throw new ReflectException(e); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAccessibleField"); + throw new ReflectException(e); + } + } + + /** + * 获取对象 + * @param object 对象 + * @return 需要反射的对象 + */ + private Object unwrap(final Object object) { + if (object instanceof ReflectUtils) { + return ((ReflectUtils) object).get(); + } + return object; + } + + // = + + /** + * 设置枚举值 + * @param clazz 类型 + * @param name 字段名 + * @param value 字段值 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils setEnumVal( + final Class clazz, + final String name, + final String value + ) + throws ReflectException { + try { + return field(name, Enum.valueOf((Class) clazz, value)); + } catch (Exception e) { + throw new ReflectException(e); + } + } + + // ========== + // = method = + // ========== + + /** + * 设置反射的方法 + * @param name 方法名 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils method(final String name) + throws ReflectException { + return method(name, new Object[0]); + } + + /** + * 设置反射的方法 + * @param name 方法名 + * @param args 方法需要的参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + public ReflectUtils method( + final String name, + final Object... args + ) + throws ReflectException { + Class[] types = getArgsType(args); + try { + Method method = exactMethod(name, types); + return method(method, mObject, args); + } catch (Exception e) { + try { + Method method = similarMethod(name, types); + return method(method, mObject, args); + } catch (Exception e1) { + throw new ReflectException(e1); + } + } + } + + /** + * 设置反射的方法处理 + * @param method 方法 + * @param object 对象 + * @param args 参数 + * @return {@link ReflectUtils} + * @throws ReflectException 反射异常 + */ + private ReflectUtils method( + final Method method, + final Object object, + final Object... args + ) + throws ReflectException { + try { + accessible(method); + if (method.getReturnType() == void.class) { + method.invoke(object, args); + return reflect(object); + } else { + return reflect(method.invoke(object, args)); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "method"); + throw new ReflectException(e); + } + } + + /** + * 获取准确参数的方法 + * @param name 方法 + * @param types 参数类型 + * @return {@link Method} + * @throws ReflectException 反射异常 + */ + private Method exactMethod( + final String name, + final Class[] types + ) + throws ReflectException { + Class type = type(); + try { + return type.getMethod(name, types); + } catch (Exception e) { + do { + try { + return type.getDeclaredMethod(name, types); + } catch (Exception ignore) { + } + type = type.getSuperclass(); + } while (type != null); + JCLogUtils.eTag(TAG, e, "exactMethod"); + throw new ReflectException(e); + } + } + + /** + * 获取相似参数的方法 + * @param name 方法 + * @param types 参数类型 + * @return {@link Method} + * @throws ReflectException 反射异常 + */ + private Method similarMethod( + final String name, + final Class[] types + ) + throws ReflectException { + Class type = type(); + List methods = new ArrayList<>(); + for (Method method : type.getMethods()) { + if (isSimilarSignature(method, name, types)) { + methods.add(method); + } + } + if (!methods.isEmpty()) { + sortMethods(methods); + return methods.get(0); + } + do { + for (Method method : type.getDeclaredMethods()) { + if (isSimilarSignature(method, name, types)) { + methods.add(method); + } + } + if (!methods.isEmpty()) { + sortMethods(methods); + return methods.get(0); + } + type = type.getSuperclass(); + } while (type != null); + throw new ReflectException( + String.format( + "No similar method %s with params %s could be found on type %s", + name, Arrays.toString(types), type() + ) + ); + } + + /** + * 进行方法排序 + * @param methods 方法集合 + */ + private void sortMethods(final List methods) { + if (methods == null) return; + Collections.sort(methods, (o1, o2) -> { + Class[] types1 = o1.getParameterTypes(); + Class[] types2 = o2.getParameterTypes(); + int len = types1.length; + for (int i = 0; i < len; i++) { + if (!types1[i].equals(types2[i])) { + if (wrapper(types1[i]).isAssignableFrom(wrapper(types2[i]))) { + return 1; + } else { + return -1; + } + } + } + return 0; + }); + } + + /** + * 判断是否相似方法 + * @param possiblyMatchingMethod 可能的匹配方法 + * @param desiredMethodName 期望方法名 + * @param desiredParamTypes 所需参数类型 + * @return {@code true} yes, {@code false} no + */ + private boolean isSimilarSignature( + final Method possiblyMatchingMethod, + final String desiredMethodName, + final Class[] desiredParamTypes + ) { + return possiblyMatchingMethod.getName().equals(desiredMethodName) + && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes); + } + + /** + * 对比处理, 判断是否一样 + * @param declaredTypes 声明类型 + * @param actualTypes 实际类型 + * @return {@code true} yes, {@code false} no + */ + private boolean match( + final Class[] declaredTypes, + final Class[] actualTypes + ) { + if (declaredTypes != null && actualTypes != null + && declaredTypes.length == actualTypes.length) { + for (int i = 0; i < actualTypes.length; i++) { + if (actualTypes[i] == NULL.class + || wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i]))) { + continue; + } + return false; + } + return true; + } else { + return false; + } + } + + /** + * 设置对象可访问处理 + * @param accessible 对象 + * @param 泛型 + * @return 传入的对象 + */ + private T accessible(final T accessible) { + if (accessible == null) return null; + if (accessible instanceof Member) { + Member member = (Member) accessible; + if (Modifier.isPublic(member.getModifiers()) + && Modifier.isPublic(member.getDeclaringClass().getModifiers())) { + return accessible; + } + } + if (!accessible.isAccessible()) accessible.setAccessible(true); + return accessible; + } + + // ======= + // = 代理 = + // ======= + + /** + * 根据类, 代理创建并返回对象 + * @param proxyType 代理类 + * @param

泛型 + * @return 代理的对象 + */ + public

P proxy(final Class

proxyType) { + if (proxyType == null || mObject == null) return null; + final boolean isMap = (mObject instanceof Map); + final InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke( + Object proxy, + Method method, + Object[] args + ) { + String name = method.getName(); + try { + return reflect(mObject).method(name, args).get(); + } catch (Exception e) { + if (isMap) { + Map map = (Map) mObject; + int length = (args == null ? 0 : args.length); + if (length == 0 && name.startsWith("get")) { + return map.get(property(name.substring(3))); + } else if (length == 0 && name.startsWith("is")) { + return map.get(property(name.substring(2))); + } else if (length == 1 && name.startsWith("set")) { + map.put(property(name.substring(3)), args[0]); + return null; + } + } + JCLogUtils.eTag(TAG, e, "proxy"); + } + return null; + } + }; + return (P) Proxy.newProxyInstance( + proxyType.getClassLoader(), + new Class[]{proxyType}, handler + ); + } + + /** + * 获取实体类属性名 get/set + * @param str 属性名 + * @return 属性名字 + */ + private String property(final String str) { + int length = str.length(); + if (length == 0) { + return ""; + } else if (length == 1) { + return str.toLowerCase(); + } else { + return str.substring(0, 1).toLowerCase() + str.substring(1); + } + } + + // = + + /** + * 获取类型 + * @return {@link Class} + */ + public Class type() { + return mType; + } + + /** + * 获取类型 + * @param type {@link Class} + * @param 未知类型 + * @return {@link Class} 类所属类型 + */ + private Class wrapper(final Class type) { + if (type == null) { + return null; + } else if (type.isPrimitive()) { + if (boolean.class == type) { + return Boolean.class; + } else if (int.class == type) { + return Integer.class; + } else if (long.class == type) { + return Long.class; + } else if (short.class == type) { + return Short.class; + } else if (byte.class == type) { + return Byte.class; + } else if (double.class == type) { + return Double.class; + } else if (float.class == type) { + return Float.class; + } else if (char.class == type) { + return Character.class; + } else if (void.class == type) { + return Void.class; + } + } + return type; + } + + /** + * 获取反射想要获取的 + * @param 泛型 + * @return 反射想要获取的 + */ + public T get() { + return (T) mObject; + } + + /** + * 获取 HashCode + * @return hashCode + */ + @Override + public int hashCode() { + return this.mObject != null ? mObject.hashCode() : 0; + } + + /** + * 判断反射的两个对象是否一样 + * @param object 对象 + * @return {@code true} yes, {@code false} no + */ + @Override + public boolean equals(final Object object) { + if (this.mObject == null && object == null) { + return true; + } else { + if (this.mObject != null && object != null) { + if (object instanceof ReflectUtils) { + return this.mObject.equals(((ReflectUtils) object).get()); + } else { + return this.mObject.equals(object); + } + } + return false; + } + } + + /** + * 获取反射获取的对象 + * @return {@link Object#toString()} + */ + @Override + public String toString() { + return this.mObject != null ? mObject.toString() : null; + } + + // = + + /** + * detail: 内部标记 null + * @author Ttt + */ + private static class NULL { + } + + /** + * detail: 定义 ReflectUtils 工具异常类 + * @author Ttt + */ + public static class ReflectException + extends Exception { + + private static final long serialVersionUID = 858774075258496016L; + + public ReflectException(String message) { + super(message); + } + + public ReflectException( + String message, + Throwable cause + ) { + super(message, cause); + } + + public ReflectException(Throwable cause) { + super(cause); + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ScaleUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ScaleUtils.java new file mode 100644 index 0000000000..df1cda07a3 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ScaleUtils.java @@ -0,0 +1,689 @@ +package dev.utils.common; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 计算比例工具类 + * @author Ttt + */ +public final class ScaleUtils { + + private ScaleUtils() { + } + + // 日志 TAG + private static final String TAG = ScaleUtils.class.getSimpleName(); + + /** + * 计算比例 ( 商 ) + * @param dividend 被除数 + * @param divisor 除数 + * @return 商 + */ + public static double calcScale( + final double dividend, + final double divisor + ) { + try { + return dividend / divisor; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScale"); + } + return -1D; + } + + /** + * 计算比例 ( 被除数 ( 最大值 ) / 除数 ( 最小值 ) ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return 被除数 ( 最大值 ) / 除数 ( 最小值 ) = 商 + */ + public static double calcScaleToMath( + final double value1, + final double value2 + ) { + try { + return Math.max(value1, value2) / Math.min(value1, value2); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToMath"); + } + return -1D; + } + + // ========== + // = double = + // ========== + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcScaleToWidth( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + try { + if (currentWidth == 0D) { + return new double[]{0D, 0D}; + } + // 计算比例 + double scale = targetWidth / currentWidth; + // 计算缩放后的高度 + double scaleHeight = scale * currentHeight; + // 返回对应的数据 + return new double[]{targetWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToWidth"); + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcScaleToHeight( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + try { + if (currentHeight == 0D) { + return new double[]{0D, 0D}; + } + // 计算比例 + double scale = targetHeight / currentHeight; + // 计算缩放后的宽度 + double scaleWidth = scale * currentWidth; + // 返回对应的数据 + return new double[]{scaleWidth, targetHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcScaleToHeight"); + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcWidthHeightToScale( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + try { + // 如果宽度的比例, 大于等于高度比例 + if (widthScale >= heightScale) { // 以宽度为基准 + // 设置宽度, 以宽度为基准 + double scaleWidth = width; + // 计算宽度 + double scaleHeight = scaleWidth * (heightScale / widthScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } else { // 以高度为基准 + // 设置高度 + double scaleHeight = height; + // 同步缩放比例 + double scaleWidth = scaleHeight * (widthScale / heightScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcWidthHeightToScale"); + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcWidthToScale( + final double width, + final double widthScale, + final double heightScale + ) { + try { + // 设置宽度 + double scaleWidth = width; + // 计算高度 + double scaleHeight = scaleWidth * (heightScale / widthScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcWidthToScale"); + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return double[] { 宽度, 高度 } + */ + public static double[] calcHeightToScale( + final double height, + final double widthScale, + final double heightScale + ) { + try { + // 设置高度 + double scaleHeight = height; + // 计算宽度 + double scaleWidth = scaleHeight * (widthScale / heightScale); + // 返回对应的比例 + return new double[]{scaleWidth, scaleHeight}; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "calcHeightToScale"); + } + return null; + } + + // ======= + // = int = + // ======= + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcScaleToWidthI( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcScaleToHeightI( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcWidthHeightToScaleI( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcWidthToScaleI( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return int[] { 宽度, 高度 } + */ + public static int[] calcHeightToScaleI( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new int[]{ + Double.valueOf(result[0]).intValue(), + Double.valueOf(result[1]).intValue() + }; + } + return null; + } + + // ======== + // = long = + // ======== + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcScaleToWidthL( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcScaleToHeightL( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcWidthHeightToScaleL( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcWidthToScaleL( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return long[] { 宽度, 高度 } + */ + public static long[] calcHeightToScaleL( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new long[]{ + Double.valueOf(result[0]).longValue(), + Double.valueOf(result[1]).longValue() + }; + } + return null; + } + + // ========= + // = float = + // ========= + + /** + * 计算缩放比例 ( 根据宽度比例转换高度 ) + * @param targetWidth 需要的最终宽度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcScaleToWidthF( + final double targetWidth, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToWidth( + targetWidth, currentWidth, currentHeight + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 计算缩放比例 ( 根据高度比例转换宽度 ) + * @param targetHeight 需要的最终高度 + * @param currentWidth 当前宽度 + * @param currentHeight 当前高度 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcScaleToHeightF( + final double targetHeight, + final double currentWidth, + final double currentHeight + ) { + double[] result = calcScaleToHeight( + targetHeight, currentWidth, currentHeight + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 通过宽度、高度根据对应的比例, 转换成对应的比例宽度高度 ( 智能转换 ) + * @param width 宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcWidthHeightToScaleF( + final double width, + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthHeightToScale( + width, height, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 以宽度为基准, 转换对应比例的高度 + * @param width 宽度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcWidthToScaleF( + final double width, + final double widthScale, + final double heightScale + ) { + double[] result = calcWidthToScale( + width, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + /** + * 以高度为基准, 转换对应比例的宽度 + * @param height 高度 + * @param widthScale 宽度比例 + * @param heightScale 高度比例 + * @return float[] { 宽度, 高度 } + */ + public static float[] calcHeightToScaleF( + final double height, + final double widthScale, + final double heightScale + ) { + double[] result = calcHeightToScale( + height, widthScale, heightScale + ); + if (result != null) { + return new float[]{ + Double.valueOf(result[0]).floatValue(), + Double.valueOf(result[1]).floatValue() + }; + } + return null; + } + + // ======== + // = XY 比 = + // ======== + + /** + * 计算 XY 比 + * @param x X 值 + * @param y Y 值 + * @return XY 比实体类 + */ + public static XY calcXY( + final int x, + final int y + ) { + return calcXY(XY_LIST, x, y); + } + + /** + * 计算 XY 比 + * @param xyLists XY 比集合 + * @param x X 值 + * @param y Y 值 + * @return XY 比实体类 + */ + public static XY calcXY( + final List xyLists, + final int x, + final int y + ) { + if (xyLists != null && xyLists.size() != 0) { + List lists = new ArrayList<>(xyLists); + Collections.sort(lists); + double scale = calcScale(x, y); + for (int i = 0, len = lists.size(); i < len; i++) { + XY xy = lists.get(i); + if (scale >= xy.scale) return xy; + } + } + return null; + } + + // ======== + // = 实体类 = + // ======== + + public static final List XY_LIST; + + static { + List xys = new ArrayList<>(); + xys.add(new XY(16, 9)); + xys.add(new XY(17, 10)); + xys.add(new XY(15, 9)); + xys.add(new XY(16, 10)); + xys.add(new XY(3, 2)); + xys.add(new XY(4, 3)); + xys.add(new XY(5, 4)); + xys.add(new XY(1, 1)); + XY_LIST = Collections.unmodifiableList(xys); + } + + /** + * detail: XY 比实体类 + * @author Ttt + */ + public static class XY + implements Comparable { + + public XY( + final int x, + final int y + ) { + this(x, y, 0); + } + + public XY( + final int x, + final int y, + final int type + ) { + this.x = x; + this.y = y; + this.scale = calcScale(x, y); + this.type = type; + } + + public final int x; + public final int y; + public final double scale; + public final int type; + + public String getXYx() { + return getXY("x"); + } + + public String getXY() { + return getXY(":"); + } + + public String getXY(String delimiter) { + return x + delimiter + y; + } + + @Override + public int compareTo(XY xy) { + return Double.compare(xy.scale, this.scale); + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/StreamUtils.java b/lib/DevJava/src/main/java/dev/utils/common/StreamUtils.java new file mode 100644 index 0000000000..102d0889de --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/StreamUtils.java @@ -0,0 +1,200 @@ +package dev.utils.common; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import dev.utils.JCLogUtils; + +/** + * detail: 流操作工具类 + * @author Ttt + */ +public final class StreamUtils { + + private StreamUtils() { + } + + // 日志 TAG + private static final String TAG = StreamUtils.class.getSimpleName(); + + /** + * 输入流转输出流 + * @param inputStream {@link InputStream} + * @return {@link ByteArrayOutputStream} + */ + public static ByteArrayOutputStream inputToOutputStream(final InputStream inputStream) { + if (inputStream == null) return null; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = inputStream.read(buffer, 0, 1024)) != -1) { + baos.write(buffer, 0, len); + } + return baos; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputToOutputStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(inputStream); + } + } + + /** + * 输出流转输入流 + * @param outputStream {@link OutputStream} + * @return {@link ByteArrayInputStream} + */ + public static ByteArrayInputStream outputToInputStream(final OutputStream outputStream) { + if (outputStream == null) return null; + try { + return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputToInputStream"); + return null; + } + } + + /** + * 输入流转 byte[] + * @param inputStream {@link InputStream} + * @return byte[] + */ + public static byte[] inputStreamToBytes(final InputStream inputStream) { + if (inputStream == null) return null; + try { + return inputToOutputStream(inputStream).toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputStreamToBytes"); + return null; + } + } + + /** + * byte[] 转输出流 + * @param bytes 数据源 + * @return {@link InputStream} + */ + public static InputStream bytesToInputStream(final byte[] bytes) { + if (bytes == null || bytes.length == 0) return null; + try { + return new ByteArrayInputStream(bytes); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToInputStream"); + return null; + } + } + + /** + * 输出流转 byte[] + * @param outputStream {@link OutputStream} + * @return byte[] + */ + public static byte[] outputStreamToBytes(final OutputStream outputStream) { + if (outputStream == null) return null; + try { + return ((ByteArrayOutputStream) outputStream).toByteArray(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputStreamToBytes"); + return null; + } + } + + /** + * byte[] 转 输出流 + * @param bytes 数据源 + * @return {@link OutputStream} + */ + public static OutputStream bytesToOutputStream(final byte[] bytes) { + if (bytes == null || bytes.length == 0) return null; + ByteArrayOutputStream baos = null; + try { + baos = new ByteArrayOutputStream(); + baos.write(bytes); + return baos; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "bytesToOutputStream"); + return null; + } finally { + CloseUtils.closeIOQuietly(baos); + } + } + + /** + * 输入流转 String + * @param inputStream {@link InputStream} + * @param charsetName 编码格式 + * @return 指定编码字符串 + */ + public static String inputStreamToString( + final InputStream inputStream, + final String charsetName + ) { + if (inputStream == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new String(inputStreamToBytes(inputStream), charsetName); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "inputStreamToString"); + return null; + } + } + + /** + * String 转换输入流 + * @param str 数据源 + * @param charsetName 编码格式 + * @return {@link InputStream} + */ + public static InputStream stringToInputStream( + final String str, + final String charsetName + ) { + if (str == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new ByteArrayInputStream(str.getBytes(charsetName)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "stringToInputStream"); + return null; + } + } + + /** + * 输出流转 String + * @param outputStream {@link OutputStream} + * @param charsetName 编码格式 + * @return 指定编码字符串 + */ + public static String outputStreamToString( + final OutputStream outputStream, + final String charsetName + ) { + if (outputStream == null || StringUtils.isEmpty(charsetName)) return null; + try { + return new String(outputStreamToBytes(outputStream), charsetName); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "outputStreamToString"); + return null; + } + } + + /** + * String 转 输出流 + * @param str 数据源 + * @param charsetName 编码格式 + * @return {@link OutputStream} + */ + public static OutputStream stringToOutputStream( + final String str, + final String charsetName + ) { + if (str == null || StringUtils.isEmpty(charsetName)) return null; + try { + return bytesToOutputStream(str.getBytes(charsetName)); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "stringToOutputStream"); + return null; + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/StringUtils.java b/lib/DevJava/src/main/java/dev/utils/common/StringUtils.java new file mode 100644 index 0000000000..a6c63cb847 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/StringUtils.java @@ -0,0 +1,2191 @@ +package dev.utils.common; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; + +/** + * detail: 字符串工具类 + * @author Ttt + */ +public final class StringUtils { + + private StringUtils() { + } + + // 日志 TAG + private static final String TAG = StringUtils.class.getSimpleName(); + + // ========== + // = String = + // ========== + + /** + * 判断字符串是否为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final CharSequence str) { + return (str == null || str.length() == 0); + } + + /** + * 判断多个字符串是否存在为 null 的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmpty(final CharSequence... args) { + if (args != null && args.length != 0) { + for (CharSequence value : args) { + if (isEmpty(value)) { + return true; + } + } + return false; + } + return true; + } + + /** + * 判断字符串是否为 null ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmptyClear(final String str) { + return isEmpty(clearSpaceTabLineTrim(str)); + } + + /** + * 判断多个字符串是否存在为 null 的字符串 ( 调用 clearSpaceTabLineTrim ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmptyClear(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isEmptyClear(value)) { + return true; + } + } + return false; + } + return true; + } + + // = + + /** + * 判断字符串是否不为 null + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmpty(final CharSequence str) { + return (str != null && str.length() != 0); + } + + /** + * 判断字符串是否不为 null ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotEmptyClear(final String str) { + return isNotEmpty(clearSpaceTabLineTrim(str)); + } + + // ======== + // = Null = + // ======== + + /** + * 判断字符串是否为 "null" + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNull(final String str) { + return isEmpty(str) || DevFinal.SYMBOL.NULL.equalsIgnoreCase(str); + } + + /** + * 判断多个字符串是否存在为 "null" 的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNull(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isNull(value)) { + return true; + } + } + return false; + } + return true; + } + + /** + * 判断字符串是否为 "null" ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullClear(final String str) { + return isNull(clearSpaceTabLineTrim(str)); + } + + /** + * 判断多个字符串是否存在为 "null" 的字符串 ( 调用 clearSpaceTabLineTrim ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNullClear(final String... args) { + if (args != null && args.length != 0) { + for (String value : args) { + if (isNullClear(value)) { + return true; + } + } + return false; + } + return true; + } + + // = + + /** + * 判断字符串是否不为 "null" + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotNull(final String str) { + return !isNull(str); + } + + /** + * 判断字符串是否不为 "null" ( 调用 clearSpaceTabLineTrim ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNotNullClear(final String str) { + return isNotNull(clearSpaceTabLineTrim(str)); + } + + // ========== + // = 判断长度 = + // ========== + + /** + * 获取字符串长度 + * @param str 待校验的字符串 + * @return 字符串长度, 如果字符串为 null, 则返回 0 + */ + public static int length(final String str) { + return str == null ? 0 : str.length(); + } + + /** + * 获取字符串长度 + * @param str 待校验的字符串 + * @param defaultLength 字符串为 null 时, 返回的长度 + * @return 字符串长度, 如果字符串为 null, 则返回 defaultLength + */ + public static int length( + final String str, + final int defaultLength + ) { + return str != null ? str.length() : defaultLength; + } + + // = + + /** + * 获取字符串长度 是否等于期望长度 + * @param str 待校验的字符串 + * @param length 期望长度 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLength( + final String str, + final int length + ) { + return str != null && str.length() == length; + } + + // ========== + // = 对比判断 = + // ========== + + /** + * 判断两个值是否一样 + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equals( + final T value1, + final T value2 + ) { + return ObjectUtils.equals(value1, value2); + } + + /** + * 判断两个值是否一样 ( 非 null 判断 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @param 泛型 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsNotNull( + final T value1, + final T value2 + ) { + return value1 != null && ObjectUtils.equals(value1, value2); + } + + /** + * 判断两个值是否一样 ( 忽略大小写 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsIgnoreCase( + final String value1, + final String value2 + ) { + if (value1 != null) { + return value1.equalsIgnoreCase(value2); + } else { + return value2 == null; + } + } + + /** + * 判断两个值是否一样 ( 忽略大小写 ) + * @param value1 第一个值 + * @param value2 第二个值 + * @return {@code true} yes, {@code false} no + */ + public static boolean equalsIgnoreCaseNotNull( + final String value1, + final String value2 + ) { + return value1 != null && equalsIgnoreCase(value1, value2); + } + + // = + + /** + * 判断多个字符串是否相等, 只有全相等才返回 true ( 对比大小写 ) + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEquals(final String... args) { + return isEquals(false, args); + } + + /** + * 判断多个字符串是否相等, 只有全相等才返回 true + * @param isIgnore 是否忽略大小写 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEquals( + final boolean isIgnore, + final String... args + ) { + if (args != null) { + String last = null; + // 获取数据长度 + int len = args.length; + // 如果最多只有一个数据判断, 则直接跳过 + if (len <= 1) { + return false; + } + for (String value : args) { + // 如果等于 null, 则跳过 + if (value == null) { + return false; + } + if (last != null) { + if (isIgnore) { + if (!value.equalsIgnoreCase(last)) { + return false; + } + } else { + if (!value.equals(last)) { + return false; + } + } + } + last = value; + } + return true; + } + return false; + } + + /** + * 判断多个字符串, 只要有一个符合条件则通过 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isOrEquals( + final String str, + final String... args + ) { + return isOrEquals(false, str, args); + } + + /** + * 判断多个字符串, 只要有一个符合条件则通过 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isOrEquals( + final boolean isIgnore, + final String str, + final String... args + ) { + if (str != null && args != null && args.length != 0) { + for (String value : args) { + if (isIgnore) { + if (str.equalsIgnoreCase(value)) { + return true; + } + } else { + if (str.equals(value)) { + return true; + } + } + } + } + return false; + } + + /** + * 判断一堆值中, 是否存在符合该条件的 ( 包含 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContains( + final String str, + final String... args + ) { + return isContains(false, str, args); + } + + /** + * 判断一堆值中, 是否存在符合该条件的 ( 包含 ) + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContains( + final boolean isIgnore, + final String str, + final String... args + ) { + if (str != null && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + // 获取内容长度 + int strLength = tempString.length(); + // 遍历判断 + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value) && strLength != 0) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否包含 + if (valIgnore.contains(tempString)) { + return true; + } + } else { + // 判断是否包含 + if (value.contains(tempString)) { + return true; + } + } + } else { + if (tempString.equals(value)) { + return true; + } + } + } + } + return false; + } + + /** + * 判断内容, 是否属于特定字符串开头 ( 对比大小写 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isStartsWith( + final String str, + final String... args + ) { + return isStartsWith(false, str, args); + } + + /** + * 判断内容, 是否属于特定字符串开头 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isStartsWith( + final boolean isIgnore, + final String str, + final String... args + ) { + if (!isEmpty(str) && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value)) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否属于 val 开头 + if (tempString.startsWith(valIgnore)) { + return true; + } + } else { + // 判断是否属于 val 开头 + if (tempString.startsWith(value)) { + return true; + } + } + } + } + } + return false; + } + + /** + * 判断内容, 是否属于特定字符串结尾 ( 对比大小写 ) + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEndsWith( + final String str, + final String... args + ) { + return isEndsWith(false, str, args); + } + + /** + * 判断内容, 是否属于特定字符串结尾 + * @param isIgnore 是否忽略大小写 + * @param str 待校验的字符串 + * @param args 待校验的字符串数组 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEndsWith( + final boolean isIgnore, + final String str, + final String... args + ) { + if (!isEmpty(str) && args != null && args.length != 0) { + String tempString = str; + // 判断是否需要忽略大小写 + if (isIgnore) { + tempString = tempString.toLowerCase(); + } + for (String value : args) { + // 判断是否为 null, 或者长度为 0 + if (!isEmpty(value)) { + if (isIgnore) { + // 转换小写 + String valIgnore = value.toLowerCase(); + // 判断是否属于 val 结尾 + if (tempString.endsWith(valIgnore)) { + return true; + } + } else { + // 判断是否属于 val 结尾 + if (tempString.endsWith(value)) { + return true; + } + } + } + } + } + return false; + } + + /** + * 统计字符串匹配个数 + * @param str 待匹配字符串 + * @param keyword 匹配 key + * @return 字符串 key 匹配个数 + */ + public static int countMatches( + final String str, + final String keyword + ) { + if (isEmpty(str) || isEmpty(keyword)) return 0; + try { + int count = 0; + Matcher matcher = Pattern.compile(keyword).matcher(str); + // find() 对字符串进行匹配, 匹配到的字符串可以在任何位置 + while (matcher.find()) { + count++; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "countMatches"); + } + return -1; + } + + /** + * 统计字符串匹配个数 + * @param str 待匹配字符串 + * @param keyword 匹配 key + * @return 字符串 key 匹配个数 + */ + public static int countMatches2( + final String str, + final String keyword + ) { + if (isEmpty(str) || isEmpty(keyword)) return 0; + try { + // 获取匹配 key 长度 + int keyLength = keyword.length(); + // = + int count = 0, index = 0; + while ((index = str.indexOf(keyword, index)) != -1) { + index = index + keyLength; + count++; + } + return count; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "countMatches2"); + } + return -1; + } + + // ========== + // = 其他处理 = + // ========== + + /** + * 判断字符串是否为 null 或全为空白字符 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSpace(final CharSequence str) { + if (str == null) return true; + for (int i = 0, len = str.length(); i < len; ++i) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + /** + * 字符串 转 byte[] + * @param str 待处理字符串 + * @return byte[] + */ + public static byte[] getBytes(final String str) { + return (str != null) ? str.getBytes() : null; + } + + // = + + /** + * 清空字符串全部空格 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpace(final String str) { + return replaceAll(str, DevFinal.SYMBOL.SPACE, ""); + } + + /** + * 清空字符串全部 Tab + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearTab(final String str) { + return replaceAll(str, DevFinal.SYMBOL.TAB, ""); + } + + /** + * 清空字符串全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLine(final String str) { + return replaceAll(str, DevFinal.SYMBOL.NEW_LINE, ""); + } + + /** + * 清空字符串全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLine2(final String str) { + return replaceAll(str, DevFinal.SYMBOL.NL, ""); + } + + // = + + /** + * 清空字符串前后全部空格 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.SPACE); + } + + /** + * 清空字符串前后全部 Tab + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearTabTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.TAB); + } + + /** + * 清空字符串前后全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLineTrim(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.NEW_LINE); + } + + /** + * 清空字符串前后全部换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearLineTrim2(final String str) { + return clearSEWiths(str, DevFinal.SYMBOL.NL); + } + + /** + * 清空字符串全部空格、Tab、换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTabLine(final String str) { + if (isEmpty(str)) return str; + String value = clearSpace(str); + value = clearTab(value); + value = clearLine(value); + value = clearLine2(value); + return value; + } + + /** + * 清空字符串前后全部空格、Tab、换行符 + * @param str 待处理字符串 + * @return 处理后的字符串 + */ + public static String clearSpaceTabLineTrim(final String str) { + if (isEmpty(str)) return str; + String value = str; + while (true) { + boolean space = (value.startsWith(DevFinal.SYMBOL.SPACE) || value.endsWith(DevFinal.SYMBOL.SPACE)); + if (space) value = clearSpaceTrim(value); + + boolean tab = (value.startsWith(DevFinal.SYMBOL.TAB) || value.endsWith(DevFinal.SYMBOL.TAB)); + if (tab) value = clearTabTrim(value); + + boolean line = (value.startsWith(DevFinal.SYMBOL.NEW_LINE) || value.endsWith(DevFinal.SYMBOL.NEW_LINE)); + if (line) value = clearLineTrim(value); + + boolean line2 = (value.startsWith(DevFinal.SYMBOL.NL) || value.endsWith(DevFinal.SYMBOL.NL)); + if (line2) value = clearLineTrim2(value); + + // 都不存在则返回值 + if (!space && !tab && !line && !line2) return value; + } + } + + // = + + /** + * 追加空格 + * @param number 空格数量 + * @return 指定数量的空格字符串 + */ + public static String appendSpace(final int number) { + return forString(number, DevFinal.SYMBOL.SPACE); + } + + /** + * 追加 Tab + * @param number tab 键数量 + * @return 指定数量的 Tab 字符串 + */ + public static String appendTab(final int number) { + return forString(number, DevFinal.SYMBOL.TAB); + } + + /** + * 追加换行 + * @param number 换行数量 + * @return 指定数量的换行字符串 + */ + public static String appendLine(final int number) { + return forString(number, DevFinal.SYMBOL.NEW_LINE); + } + + /** + * 追加换行 + * @param number 换行数量 + * @return 指定数量的换行字符串 + */ + public static String appendLine2(final int number) { + return forString(number, DevFinal.SYMBOL.NL); + } + + /** + * 循环指定数量字符串 + * @param number 空格数量 + * @param str 待追加字符串 + * @return 指定数量字符串 + */ + public static String forString( + final int number, + final String str + ) { + StringBuilder builder = new StringBuilder(); + if (number > 0) { + for (int i = 0; i < number; i++) { + builder.append(str); + } + } + return builder.toString(); + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param values 待拼接对象 + * @return 拼接后的值 + */ + public static String joinArgs( + final Object delimiter, + final Object... values + ) { + if (values != null) { + int length = values.length; + if (length != 0) { + StringBuilder builder = new StringBuilder(); + builder.append(values[0]); + for (int i = 1; i < length; i++) { + builder.append(delimiter); + builder.append(values[i]); + } + return builder.toString(); + } + } + return null; + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param values 待拼接对象 + * @return 拼接后的值 + */ + public static String join( + final Object delimiter, + final Object[] values + ) { + if (values != null) { + int length = values.length; + if (length != 0) { + StringBuilder builder = new StringBuilder(); + builder.append(values[0]); + for (int i = 1; i < length; i++) { + builder.append(delimiter); + builder.append(values[i]); + } + return builder.toString(); + } + } + return null; + } + + /** + * 循环拼接 + * @param delimiter 拼接符号 + * @param iterable 待拼接对象 + * @return 拼接后的值 + */ + public static String join( + final Object delimiter, + final Iterable iterable + ) { + if (iterable != null) { + final Iterator it = iterable.iterator(); + if (!it.hasNext()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + builder.append(it.next()); + while (it.hasNext()) { + builder.append(delimiter); + builder.append(it.next()); + } + return builder.toString(); + } + return null; + } + + /** + * 冒号分割处理 + * @param str 待处理字符串 + * @return 冒号分割后的字符串 + */ + public static String colonSplit(final String str) { + if (!isEmpty(str)) { + return str.replaceAll("(?<=[0-9A-F]{2})[0-9A-F]{2}", ":$0"); + } + return str; + } + + // = + + /** + * 获取字符串 ( 判 null ) + * @param str 待校验的字符串 + * @return 校验后的字符串 + */ + public static String getString(final String str) { + return getString(str, DevFinal.SYMBOL.NULL); + } + + /** + * 获取字符串 ( 判 null ) + * @param str 待校验的字符串 + * @param defaultStr 默认字符串 + * @return 校验后的字符串 + */ + public static String getString( + final String str, + final String defaultStr + ) { + return str != null ? str : defaultStr; + } + + /** + * 获取字符串 ( 判 null ) + * @param object 待校验的对象 + * @return 校验后的字符串 + */ + public static String getString(final Object object) { + return getString(object, DevFinal.SYMBOL.NULL); + } + + /** + * 获取字符串 ( 判 null ) + * @param object 待校验的对象 + * @param defaultStr 默认字符串 + * @return 校验后的字符串 + */ + public static String getString( + final Object object, + final String defaultStr + ) { + return object != null ? object.toString() : defaultStr; + } + + /** + * 检查字符串 + * @param str 待校验字符串 + * @return 如果待校验字符串为 null, 则返回默认字符串, 如果不为 null, 则返回该字符串 + */ + public static String checkValue(final String str) { + return checkValue("", str); + } + + /** + * 检查字符串 + * @param defaultStr 默认字符串 + * @param str 待校验字符串 + * @return 如果待校验字符串为 null, 则返回 defaultStr, 如果不为 null, 则返回该字符串 + */ + public static String checkValue( + final String defaultStr, + final String str + ) { + return isEmpty(str) ? defaultStr : str; + } + + /** + * 检查字符串 ( 单独检查两个值 ) + * @param defaultStr 默认字符串 + * @param value1 第一个待校验字符串 + * @param value2 第二个待校验字符串 + * @return 两个待校验字符串中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValue( + final String defaultStr, + final String value1, + final String value2 + ) { + if (isEmpty(value1)) { + if (isEmpty(value2)) { + return defaultStr; + } else { + return value2; + } + } else { + return value1; + } + } + + /** + * 检查字符串 ( 多个值 ) + * @param defaultStr 默认字符串 + * @param args 待校验字符串数组 + * @return 字符串数组中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValues( + final String defaultStr, + final String... args + ) { + if (args != null && args.length != 0) { + for (String value : args) { + if (!isEmpty(value)) { + return value; + } + } + } + return defaultStr; + } + + /** + * 检查字符串 ( 多个值, 删除前后空格对比判断 ) + * @param defaultStr 默认字符串 + * @param args 待校验字符串数组 + * @return 字符串数组中不为 null 的字符串, 如果都为 null, 则返回 defaultStr + */ + public static String checkValuesSpace( + final String defaultStr, + final String... args + ) { + if (args != null && args.length != 0) { + for (String value : args) { + // 删除前后空格处理后, 进行返回 + String result = clearSpaceTrim(value); + if (!isEmpty(result)) { + return result; + } + } + } + return defaultStr; + } + + // =============== + // = 数据格式化处理 = + // =============== + + /** + * 字符串格式化 + * @param format 待格式化字符串 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String format( + final String format, + final Object... args + ) { + if (format == null) return null; + try { + if (args != null && args.length != 0) { + return String.format(format, args); + } else { + return format; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "format"); + } + return null; + } + + /** + * 根据可变参数数量自动格式化 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public static String argsFormat(final Object... args) { + return FormatUtils.argsOf("%s", " %s").format(args); + } + + // = + + /** + * 字符串连接, 将参数列表拼接为一个字符串 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concat(final Object... args) { + return concatSpiltWith("", args); + } + + /** + * 字符串连接, 将参数列表拼接为一个字符串 + * @param split 追加间隔 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concatSpiltWith( + final String split, + final Object... args + ) { + if (args == null) return null; + StringBuilder builder = new StringBuilder(); + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (Object value : args) { + builder.append(value).append(split); + } + } + return builder.toString(); + } + + /** + * 字符串连接, 将参数列表拼接为一个字符串 ( 最后一个不追加间隔 ) + * @param split 追加间隔 + * @param args 追加数据 + * @return 拼接后的字符串 + */ + public static String concatSpiltWithIgnoreLast( + final String split, + final Object... args + ) { + if (args == null) return null; + StringBuilder builder = new StringBuilder(); + int len = args.length; + if (len > 0) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (int i = 0; i < len - 1; i++) { + builder.append(args[i]).append(split); + } + builder.append(args[len - 1]); + } + } + return builder.toString(); + } + + /** + * StringBuilder 拼接处理 + * @param builder 拼接 Builder + * @param split 追加间隔 + * @param args 拼接数据源 + * @return {@link StringBuilder} + */ + public static StringBuilder appends( + final StringBuilder builder, + final String split, + final Object... args + ) { + if (builder != null && args != null) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (Object value : args) { + builder.append(value).append(split); + } + } + } + return builder; + } + + /** + * StringBuilder 拼接处理 ( 最后一个不追加间隔 ) + * @param builder 拼接 Builder + * @param split 追加间隔 + * @param args 拼接数据源 + * @return {@link StringBuilder} + */ + public static StringBuilder appendsIgnoreLast( + final StringBuilder builder, + final String split, + final Object... args + ) { + if (builder != null && args != null) { + int len = args.length; + if (len > 0) { + if (isEmpty(split)) { + for (Object value : args) { + builder.append(value); + } + } else { + for (int i = 0; i < len - 1; i++) { + builder.append(args[i]).append(split); + } + builder.append(args[len - 1]); + } + } + } + return builder; + } + + // ========== + // = 转换处理 = + // ========== + + /** + * 字符串进行 GBK 编码 + * @param str 待处理字符串 + * @return GBK 编码后的字符串 + */ + public static String gbkEncode(final String str) { + return strEncode(str, DevFinal.ENCODE.GBK); + } + + /** + * 字符串进行 GBK2312 编码 + * @param str 待处理字符串 + * @return GBK2312 编码后的字符串 + */ + public static String gbk2312Encode(final String str) { + return strEncode(str, DevFinal.ENCODE.GBK_2312); + } + + /** + * 字符串进行 UTF-8 编码 + * @param str 待处理字符串 + * @return UTF-8 编码后的字符串 + */ + public static String utf8Encode(final String str) { + return strEncode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行字符串编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式编码后的字符串 + */ + public static String strEncode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return new String(str.getBytes(), enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "strEncode"); + } + return str; + } + + // = + + /** + * 进行 URL 编码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 编码后的字符串 + */ + public static String urlEncode(final String str) { + return urlEncode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行 URL 编码 + * @param str 待处理字符串 + * @param enc 编码格式 + * @return 指定编码格式 URL 编码后的字符串 + */ + public static String urlEncode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return URLEncoder.encode(str, enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "urlEncode"); + } + return null; + } + + // = + + /** + * 进行 URL 解码, 默认 UTF-8 + * @param str 待处理字符串 + * @return UTF-8 编码格式 URL 解码后的字符串 + */ + public static String urlDecode(final String str) { + return urlDecode(str, DevFinal.ENCODE.UTF_8); + } + + /** + * 进行 URL 解码 + * @param str 待处理字符串 + * @param enc 解码格式 + * @return 指定编码格式 URL 解码后的字符串 + */ + public static String urlDecode( + final String str, + final String enc + ) { + if (str == null || enc == null) return null; + try { + return URLDecoder.decode(str, enc); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "urlDecode"); + } + return null; + } + + // = + + /** + * 进行 URL 解码, 默认 UTF-8 ( 循环到非 URL 编码为止 ) + * @param str 待处理字符串 + * @param threshold 解码次数阈值, 超过该次数还未完成则直接返回 + * @return UTF-8 编码格式 URL 解码后的字符串 + */ + public static String urlDecodeWhile( + final String str, + final int threshold + ) { + return urlDecodeWhile(str, DevFinal.ENCODE.UTF_8, threshold); + } + + /** + * 进行 URL 解码 ( 循环到非 URL 编码为止 ) + * @param str 待处理字符串 + * @param enc 解码格式 + * @param threshold 解码次数阈值, 超过该次数还未完成则直接返回 + * @return 指定编码格式 URL 解码后的字符串 + */ + public static String urlDecodeWhile( + final String str, + final String enc, + final int threshold + ) { + if (str == null || enc == null) return null; + int count = Math.max(threshold, 1); + int number = 0; + String result = str; + String decodeValue = StringUtils.urlDecode(str, enc); + while (true) { + // 如果相同则直接返回 + if (result.equals(decodeValue)) { + return decodeValue; + } + if (decodeValue == null) return result; + result = decodeValue; + decodeValue = StringUtils.urlDecode(result, enc); + // 判断循环次数 + number++; + if (number > count) { + if (decodeValue != null) { + return decodeValue; + } else { + return result; + } + } + } + } + + // = + + /** + * 将字符串转移为 ASCII 码 + * @param str 待处理字符串 + * @return 字符串转 ASCII 码后的字符串 + */ + public static String ascii(final String str) { + if (isEmpty(str)) return str; + try { + StringBuilder builder = new StringBuilder(); + byte[] bytes = str.getBytes(); + for (byte value : bytes) { + builder.append(Integer.toHexString(value & 0xff)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "ascii"); + } + return null; + } + + /** + * 将字符串转移为 Unicode 码 + * @param str 待处理字符串 + * @return 字符串转 Unicode 码后的字符串 + */ + public static String unicode(final String str) { + if (isEmpty(str)) return str; + try { + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + for (char value : chars) { + builder.append("\\u").append(Integer.toHexString(value)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unicode"); + } + return null; + } + + /** + * 将字符数组转移为 Unicode 码 + * @param chars char[] + * @return char[] 转 Unicode 码后的字符串 + */ + public static String unicodeString(final char[] chars) { + if (chars == null) return null; + try { + StringBuilder builder = new StringBuilder(); + for (char value : chars) { + builder.append("\\u").append(Integer.toHexString(value)); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "unicodeString"); + } + return null; + } + + /** + * 转化为半角字符 + * @param str 待处理字符串 + * @return 转换半角字符串 + */ + public static String dbc(final String str) { + if (isEmpty(str)) return str; + char[] chars = str.toCharArray(); + for (int i = 0, len = chars.length; i < len; i++) { + if (chars[i] == 12288) { + chars[i] = ' '; + } else if (65281 <= chars[i] && chars[i] <= 65374) { + chars[i] = (char) (chars[i] - 65248); + } else { + chars[i] = chars[i]; + } + } + return new String(chars); + } + + /** + * 转化为全角字符 如: a = a, A = A + * @param str 待处理字符串 + * @return 转换全角字符串 + */ + public static String sbc(final String str) { + if (isEmpty(str)) return str; + char[] chars = str.toCharArray(); + for (int i = 0, len = chars.length; i < len; i++) { + if (chars[i] == ' ') { + chars[i] = (char) 12288; + } else if (33 <= chars[i] && chars[i] <= 126) { + chars[i] = (char) (chars[i] + 65248); + } else { + chars[i] = chars[i]; + } + } + return new String(chars); + } + + // = + + /** + * 检测字符串是否全是中文 + * @param str 待校验字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkChineseToString(final String str) { + if (isEmpty(str)) return false; + boolean result = true; + char[] chars = str.toCharArray(); + for (char value : chars) { + if (!isChinese(value)) { + result = false; + break; + } + } + return result; + } + + /** + * 判断输入汉字 + * @param ch 待校验字符 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChinese(final char ch) { + Character.UnicodeBlock ub = Character.UnicodeBlock.of(ch); + return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS + || ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS + || ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A + || ub == Character.UnicodeBlock.GENERAL_PUNCTUATION + || ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION + || ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS; + } + + // =============== + // = 字符串处理方法 = + // =============== + + /** + * 首字母大写 + * @param str 待处理字符串 + * @return 首字母大写字符串 + */ + public static String upperFirstLetter(final String str) { + if (isEmpty(str) || !Character.isLowerCase(str.charAt(0))) return str; + try { + return (char) (str.charAt(0) - 32) + str.substring(1); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "upperFirstLetter"); + return str; + } + } + + /** + * 首字母小写 + * @param str 待处理字符串 + * @return 首字母小写字符串 + */ + public static String lowerFirstLetter(final String str) { + if (isEmpty(str) || !Character.isUpperCase(str.charAt(0))) return str; + try { + return (char) (str.charAt(0) + 32) + str.substring(1); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "lowerFirstLetter"); + return str; + } + } + + /** + * 反转字符串 + * @param str 待处理字符串 + * @return 反转字符串 + */ + public static String reverse(final String str) { + int len = length(str); + if (len <= 1) return str; + int mid = len >> 1; + char[] chars = str.toCharArray(); + char ch; + for (int i = 0; i < mid; ++i) { + ch = chars[i]; + chars[i] = chars[len - i - 1]; + chars[len - i - 1] = ch; + } + return new String(chars); + } + + /** + * 下划线命名转为驼峰命名 + * @param str 下划线命名格式字符串 + * @return 驼峰命名格式字符串 + */ + public static String underScoreCaseToCamelCase(final String str) { + if (isEmpty(str)) return str; + if (!str.contains("_")) return str; + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + boolean hitUnderScore = false; + builder.append(chars[0]); + for (int i = 1, len = chars.length; i < len; i++) { + char c = chars[i]; + if (c == '_') { + hitUnderScore = true; + } else { + if (hitUnderScore) { + builder.append(Character.toUpperCase(c)); + hitUnderScore = false; + } else { + builder.append(c); + } + } + } + return builder.toString(); + } + + /** + * 驼峰命名法转为下划线命名 + * @param str 驼峰命名格式字符串 + * @return 下划线命名格式字符串 + */ + public static String camelCaseToUnderScoreCase(final String str) { + if (isEmpty(str)) return str; + StringBuilder builder = new StringBuilder(); + char[] chars = str.toCharArray(); + for (char value : chars) { + if (Character.isUpperCase(value)) { + builder.append("_").append(Character.toLowerCase(value)); + } else { + builder.append(value); + } + } + return builder.toString(); + } + + /** + * 字符串数据库字符转义 + * @param str 待处理字符串 + * @return 转义处理后的字符串 + */ + public static String sqliteEscape(final String str) { + if (isEmpty(str)) return str; + String keyWord = str; + // 替换关键字 + keyWord = keyWord.replace("/", "//"); + keyWord = keyWord.replace("'", "''"); + keyWord = keyWord.replace("[", "/["); + keyWord = keyWord.replace("]", "/]"); + keyWord = keyWord.replace("%", "/%"); + keyWord = keyWord.replace("&", "/&"); + keyWord = keyWord.replace("_", "/_"); + keyWord = keyWord.replace("(", "/("); + keyWord = keyWord.replace(")", "/)"); + return keyWord; + } + + // ============ + // = 字符串处理 = + // ============ + + /** + * 转换手机号 + * @param phone 待处理字符串 + * @return 处理后的字符串 + */ + public static String convertHideMobile(final String phone) { + return convertHideMobile(phone, "*"); + } + + /** + * 转换手机号 + * @param phone 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String convertHideMobile( + final String phone, + final String symbol + ) { + return convertSymbolHide(3, phone, symbol); + } + + /** + * 转换符号处理 + * @param start 开始位置 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String convertSymbolHide( + final int start, + final String str, + final String symbol + ) { + if (!isEmpty(str)) { + if (start <= 0) { + return str; + } + // 获取数据长度 + int length = str.length(); + // 如果数据小于 start 位则直接返回 + if (length <= start) { + return str; + } else { // 大于 start 位 + StringBuilder builder = new StringBuilder(); + builder.append(str.substring(0, start)); + int len = length - start; + // 进行平分 + len /= 2; + // 进行遍历保存 + for (int i = 0; i < len; i++) { + builder.append(symbol); + } + builder.append(str.substring(start + len, length)); + return builder.toString(); + } + } + return ""; + } + + // = + + /** + * 裁剪超出的内容, 并且追加符号 ( 如 ... ) + * @param maxLength 允许最大的长度 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subEllipsize( + final int maxLength, + final String str, + final String symbol + ) { + if (maxLength >= 1) { + // 获取内容长度 + int strLength = length(str); + // 防止为不存在数据 + if (strLength != 0) { + if (maxLength >= strLength) { + return str; + } + return str.substring(0, maxLength) + checkValue(symbol); + } + } + return ""; + } + + /** + * 裁剪符号处理 + * @param start 开始位置 + * @param symbolNumber 转换数量 + * @param str 待处理字符串 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subSymbolHide( + final int start, + final int symbolNumber, + final String str, + final String symbol + ) { + if (!isEmpty(str)) { + if (start <= 0 || symbolNumber <= 0) { + return str; + } + // 获取数据长度 + int length = str.length(); + // 如果数据小于 start 位则直接返回 + if (length <= start) { + return str; + } else { // 大于 start 位 + StringBuilder builder = new StringBuilder(); + builder.append(str.substring(0, start)); + int len = length - start - symbolNumber; + // 如果超出总长度, 则进行控制 + if (len <= 0) { // 表示后面的全部转换 + len = length - start; + } else { // 需要裁剪的数量 + len = symbolNumber; + } + // 进行遍历保存 + for (int i = 0; i < len; i++) { + builder.append(symbol); + } + builder.append(str.substring(start + len, length)); + return builder.toString(); + } + } + return ""; + } + + /** + * 裁剪内容 ( 设置符号处理 ) + * @param str 待处理字符串 + * @param frontRetainLength 前面保留的长度 + * @param rearRetainLength 后面保留的长度 + * @param symbol 转换符号 + * @return 处理后的字符串 + */ + public static String subSetSymbol( + final String str, + final int frontRetainLength, + final int rearRetainLength, + final String symbol + ) { + if (str != null) { + try { + // 截取前面需保留的内容 + String startStr = str.substring(0, frontRetainLength); + // 截取后面需保留的内容 + String endStr = str.substring(str.length() - rearRetainLength); + // 特殊符号长度 + int symbolLength = str.length() - (frontRetainLength + rearRetainLength); + if (symbolLength >= 1) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < symbolLength; i++) { + builder.append(symbol); + } + return startStr + builder.toString() + endStr; + } + return startStr + endStr; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "subSetSymbol"); + } + } + return null; + } + + // =============== + // = 替换、截取操作 = + // =============== + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param endIndex 结束裁剪的位置 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int endIndex + ) { + return substring(str, 0, endIndex, true); + } + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param endIndex 结束裁剪的位置 + * @param isReturn 开始位置超过限制是否返回内容 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int endIndex, + final boolean isReturn + ) { + return substring(str, 0, endIndex, isReturn); + } + + /** + * 裁剪字符串 + * @param str 待裁剪字符串 + * @param beginIndex 开始裁剪的位置 + * @param endIndex 结束裁剪的位置 + * @param isReturn 开始位置超过限制是否返回内容 + * @return 裁剪后的字符串 + */ + public static String substring( + final String str, + final int beginIndex, + final int endIndex, + final boolean isReturn + ) { + if (!isEmpty(str) && beginIndex >= 0 && endIndex >= 0 && endIndex >= beginIndex) { + // 获取数据长度 + int len = length(str); + // 防止超过限制 + if (beginIndex > len) { + return isReturn ? str : ""; + } + // 防止超过限制 + return str.substring(beginIndex, Math.min(endIndex, len)); + } + return isReturn ? str : ""; + } + + // = + + /** + * 替换特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 ____a_a_a_a____ + * @param str 待处理字符串 + * @param suffix 替换符号字符串 + * @return 处理后的字符串 + */ + public static String replaceSEWith( + final String str, + final String suffix + ) { + return replaceSEWith(str, suffix, ""); + } + + /** + * 替换特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _, c 等于 c____a_a_a_a____c + * @param str 待处理字符串 + * @param suffix 替换匹配内容 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceSEWith( + final String str, + final String suffix, + final String replace + ) { + try { + if (isEmpty(str) || isEmpty(suffix) || replace == null || suffix.equals(replace)) { + return str; + } + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 判断是否在最头部 + if (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + // 追加内容 + builder.insert(0, replace); + } + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 判断是否在最尾部 + if (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 追加内容 + builder.insert(lastIndexOf, replace); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceSEWith"); + } + return str; + } + + // = + + /** + * 替换开头字符串 + * @param str 待处理字符串 + * @param prefix 开头匹配字符串 + * @return 处理后的字符串 + */ + public static String replaceStartsWith( + final String str, + final String prefix + ) { + return replaceStartsWith(str, prefix, ""); + } + + /** + * 替换开头字符串 + * @param str 待处理字符串 + * @param prefix 开头匹配字符串 + * @param startAppend 开头追加的内容 + * @return 处理后的字符串 + */ + public static String replaceStartsWith( + final String str, + final String prefix, + final String startAppend + ) { + if (!isEmpty(str) && !isEmpty(prefix)) { + try { + if (str.startsWith(prefix)) { + return checkValue(startAppend) + str.substring(prefix.length()); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceStartsWith"); + } + } + return str; + } + + /** + * 替换结尾字符串 + * @param str 待处理字符串 + * @param suffix 结尾匹配字符串 + * @return 处理后的字符串 + */ + public static String replaceEndsWith( + final String str, + final String suffix + ) { + return replaceEndsWith(str, suffix, ""); + } + + /** + * 替换结尾字符串 + * @param str 待处理字符串 + * @param suffix 结尾匹配字符串 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceEndsWith( + final String str, + final String suffix, + final String replace + ) { + if (!isEmpty(str) && !isEmpty(suffix)) { + try { + if (str.endsWith(suffix)) { + return str.substring(0, str.length() - suffix.length()) + replace; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceEndsWith"); + } + } + return str; + } + + // = + + /** + * 清空特定字符串开头、结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 a_a_a_a + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearSEWiths( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 进行循环判断 ( 属于最前面的, 才进行处理 ) + while (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + } + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 进行循环判断 ( 属于最后面的, 才进行处理 ) + while (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 重置数据 + lastIndexOf = builder.lastIndexOf(suffix); + bufLength = builder.length(); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearSEWiths"); + } + return str; + } + + /** + * 清空特定字符串开头的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 a_a_a_a_____ + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearStartsWith( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 进行循环判断 ( 属于最前面的, 才进行处理 ) + while (builder.indexOf(suffix) == 0) { + builder.delete(0, suffixLength); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearStartsWith"); + } + return str; + } + + /** + * 清空特定字符串结尾的字符串 + * 如 _____a_a_a_a_____ 传入 _ 等于 _____a_a_a_a + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @return 处理后的字符串 + */ + public static String clearEndsWith( + final String str, + final String suffix + ) { + if (isEmpty(str) || isEmpty(suffix)) return str; + try { + // 获取编辑内容长度 + int suffixLength = suffix.length(); + // 保存新的 Builder 中, 减少内存开销 + StringBuilder builder = new StringBuilder(str); + // 获取尾部的位置 + int lastIndexOf = builder.lastIndexOf(suffix); + // 数据长度 + int bufLength = builder.length(); + // 进行循环判断 ( 属于最后面的, 才进行处理 ) + while (lastIndexOf != -1 && lastIndexOf == (bufLength - suffixLength)) { + builder.delete(lastIndexOf, bufLength); + // 重置数据 + lastIndexOf = builder.lastIndexOf(suffix); + bufLength = builder.length(); + } + return builder.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "clearEndsWith"); + } + return str; + } + + // = + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @param replace 替换的内容 + * @return 处理后的字符串 + */ + public static String replaceAll( + final String str, + final String suffix, + final String replace + ) { + // 如果替换的内容或者判断的字符串为 null, 则直接跳过 + if (!isEmpty(str) && !isEmpty(suffix) && replace != null && !suffix.equals(replace)) { + try { + return str.replaceAll(suffix, replace); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceAll"); + } + } + return str; + } + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffix 匹配判断字符串 + * @param replace 替换的内容 + * @return 处理后的字符串, 替换失败则返回 null + */ + public static String replaceAllToNull( + final String str, + final String suffix, + final String replace + ) { + // 如果替换的内容或者判断的字符串为 null, 则直接跳过 + if (!isEmpty(str) && !isEmpty(suffix) && replace != null && !suffix.equals(replace)) { + try { + return str.replaceAll(suffix, replace); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "replaceAllToNull"); + } + } + return null; + } + + /** + * 替换字符串 + * @param str 待处理字符串 + * @param suffixArrays 匹配判断字符串数组 + * @param replaceArrays 准备替换的字符串数组 + * @return 处理后的字符串 + */ + public static String replaceAlls( + final String str, + final String[] suffixArrays, + final String[] replaceArrays + ) { + // 防止数据为 null + if (str != null && suffixArrays != null && replaceArrays != null) { + String tempString = str; + // 替换的特殊字符串长度 + int spCount = suffixArrays.length; + // 替换的内容长度 + int reCount = replaceArrays.length; + // 相同才进行处理 + if (spCount == reCount) { + // 遍历进行判断 + for (int i = 0; i < spCount; i++) { + // 进行替换字符串 + tempString = replaceAll(tempString, suffixArrays[i], replaceArrays[i]); + } + return tempString; + } + } + return null; + } + + /** + * 拆分字符串 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @return 拆分后的数组 + */ + public static String[] split( + final String str, + final String regex + ) { + if (!StringUtils.isEmpty(str) && !StringUtils.isEmpty(regex)) { + return str.split(regex); + } + return null; + } + + /** + * 拆分字符串获取指定索引 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @param index 索引 + * @return 拆分后的数组 + */ + public static String split( + final String str, + final String regex, + final int index + ) { + return split(str, regex, index, null); + } + + /** + * 拆分字符串获取指定索引 + * @param str 待处理字符串 + * @param regex 正则表达式 + * @param index 索引 + * @param defaultStr 默认字符串 + * @return 拆分后的数组 + */ + public static String split( + final String str, + final String regex, + final int index, + final String defaultStr + ) { + if (index >= 0) { + String[] arrays = split(str, regex); + if (arrays != null && arrays.length > index) { + return arrays[index]; + } + } + return defaultStr; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ThrowableUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ThrowableUtils.java new file mode 100644 index 0000000000..2a7c1adeb6 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ThrowableUtils.java @@ -0,0 +1,100 @@ +package dev.utils.common; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import dev.utils.JCLogUtils; + +/** + * detail: 异常处理工具类 + * @author Ttt + */ +public final class ThrowableUtils { + + private ThrowableUtils() { + } + + // 日志 TAG + private static final String TAG = ThrowableUtils.class.getSimpleName(); + + /** + * 获取异常信息 + * @param throwable 异常 + * @return 异常信息字符串 + */ + public static String getThrowable(final Throwable throwable) { + return getThrowable(throwable, "error(null)"); + } + + /** + * 获取异常信息 + * @param throwable 异常 + * @param errorInfo 获取失败返回字符串 + * @return 异常信息字符串 + */ + public static String getThrowable( + final Throwable throwable, + final String errorInfo + ) { + if (throwable != null) { + Throwable cause = throwable.getCause(); + if (cause != null) { + return cause.toString(); + } + return throwable.toString(); + } + return errorInfo; + } + + // ============ + // = 异常栈信息 = + // ============ + + /** + * 获取异常栈信息 + * @param throwable 异常 + * @return 异常栈信息字符串 + */ + public static String getThrowableStackTrace(final Throwable throwable) { + return getThrowableStackTrace(throwable, "error(null)"); + } + + /** + * 获取异常栈信息 + * @param throwable 异常 + * @param errorInfo 获取失败返回字符串 + * @return 异常栈信息字符串 + */ + public static String getThrowableStackTrace( + final Throwable throwable, + final String errorInfo + ) { + if (throwable != null) { + PrintWriter printWriter = null; + try { + Writer writer = new StringWriter(); + printWriter = new PrintWriter(writer); + throwable.printStackTrace(printWriter); +// // 获取错误栈信息 +// StackTraceElement[] stElement = throwable.getStackTrace(); +// // 标题, 提示属于什么异常 +// printWriter.append(throwable.toString()); +// printWriter.append(DevFinal.SYMBOL.NEW_LINE); +// // 遍历错误栈信息, 并且进行换行缩进 +// for (StackTraceElement element : stElement) { +// printWriter.append("\tat "); +// printWriter.append(element.toString()); +// printWriter.append(DevFinal.SYMBOL.NEW_LINE); +// } + return writer.toString(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getThrowableStackTrace"); + return e.toString(); + } finally { + CloseUtils.closeIOQuietly(printWriter); + } + } + return errorInfo; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/TypeUtils.java b/lib/DevJava/src/main/java/dev/utils/common/TypeUtils.java new file mode 100644 index 0000000000..2d81fed375 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/TypeUtils.java @@ -0,0 +1,185 @@ +package dev.utils.common; + +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import dev.utils.JCLogUtils; + +/** + * detail: 类型工具类 + * @author Ttt + *

+ *     Java 中与泛型相关的接口之 ParameterizedType
+ *     @see 
+ *     Java 知识总结之 Type
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class TypeUtils { + + private TypeUtils() { + } + + // 日志 TAG + private static final String TAG = TypeUtils.class.getSimpleName(); + + /** + * 获取 Array Type + * @param type Bean.class + * @return Bean[] Type + */ + public static Type getArrayType(final Type type) { + if (type == null) return null; + try { + return new GenericArrayType() { + @Override + public Type getGenericComponentType() { + return type; + } + }; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getArrayType"); + } + return null; + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + public static Type getListType(final Type type) { + if (type == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{type}, null, List.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getListType"); + } + return null; + } + + /** + * 获取 Set Type + * @param type Bean.class + * @return Set Type + */ + public static Type getSetType(final Type type) { + if (type == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{type}, null, Set.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getSetType"); + } + return null; + } + + /** + * 获取 Map Type + * @param keyType Key.class + * @param valueType Value.class + * @return Map Type + */ + public static Type getMapType( + final Type keyType, + final Type valueType + ) { + if (keyType == null || valueType == null) return null; + try { + return new ParameterizedTypeImpl( + new Type[]{keyType, valueType}, null, Map.class + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMapType"); + } + return null; + } + + /** + * 获取 Type + * @param rawType raw type + * @param typeArguments type arguments + * @return Type + */ + public static Type getType( + final Type rawType, + final Type... typeArguments + ) { + try { + return new ParameterizedTypeImpl( + typeArguments, null, rawType + ); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getType"); + } + return null; + } + + // ======== + // = Type = + // ======== + + /** + * detail: ParameterizedType 实现类 + * @author Ttt + */ + public static class ParameterizedTypeImpl + implements ParameterizedType { + + private final Type[] actualTypeArguments; + private final Type ownerType; + private final Type rawType; + + public ParameterizedTypeImpl( + Type[] actualTypeArguments, + Type ownerType, + Type rawType + ) { + this.actualTypeArguments = actualTypeArguments; + this.ownerType = ownerType; + this.rawType = rawType; + } + + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + public Type getOwnerType() { + return ownerType; + } + + public Type getRawType() { + return rawType; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + + ParameterizedTypeImpl that = (ParameterizedTypeImpl) object; + if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; + if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) { + return false; + } + return rawType != null ? rawType.equals(that.rawType) : that.rawType == null; + } + + @Override + public int hashCode() { + int result = actualTypeArguments != null ? Arrays.hashCode(actualTypeArguments) : 0; + result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); + result = 31 * result + (rawType != null ? rawType.hashCode() : 0); + return result; + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/ZipUtils.java b/lib/DevJava/src/main/java/dev/utils/common/ZipUtils.java new file mode 100644 index 0000000000..b26f8a4e55 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/ZipUtils.java @@ -0,0 +1,456 @@ +package dev.utils.common; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import dev.utils.JCLogUtils; + +/** + * detail: 压缩相关工具类 + * @author Ttt + */ +public final class ZipUtils { + + private ZipUtils() { + } + + // 日志 TAG + private static final String TAG = ZipUtils.class.getSimpleName(); + + // 缓存大小 + private static final int BUFFER_LEN = 8192; + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件路径集合 + * @param zipFilePath 压缩文件路径 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final String zipFilePath + ) + throws Exception { + return zipFiles(resFiles, zipFilePath, null); + } + + /** + * 批量压缩文件 + * @param resFilePaths 待压缩文件路径集合 + * @param zipFilePath 压缩文件路径 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFilePaths, + final String zipFilePath, + final String comment + ) + throws Exception { + if (resFilePaths == null || zipFilePath == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFilePath)); + for (String resFile : resFilePaths) { + if (!zipFile(FileUtils.getFileByPath(resFile), "", zos, comment)) { + return false; + } + } + return true; + } finally { + if (zos != null) { + zos.finish(); + CloseUtils.closeIOQuietly(zos); + } + } + } + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件集合 + * @param zipFile 压缩文件 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final File zipFile + ) + throws Exception { + return zipFiles(resFiles, zipFile, null); + } + + /** + * 批量压缩文件 + * @param resFiles 待压缩文件集合 + * @param zipFile 压缩文件 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFiles( + final Collection resFiles, + final File zipFile, + final String comment + ) + throws Exception { + if (resFiles == null || zipFile == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFile)); + for (File resFile : resFiles) { + if (!zipFile(resFile, "", zos, comment)) return false; + } + return true; + } finally { + if (zos != null) { + zos.finish(); + CloseUtils.closeIOQuietly(zos); + } + } + } + + /** + * 压缩文件 + * @param resFilePath 待压缩文件路径 + * @param zipFilePath 压缩文件路径 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final String resFilePath, + final String zipFilePath + ) + throws Exception { + return zipFile( + FileUtils.getFileByPath(resFilePath), + FileUtils.getFileByPath(zipFilePath), + null + ); + } + + /** + * 压缩文件 + * @param resFilePath 待压缩文件路径 + * @param zipFilePath 压缩文件路径 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final String resFilePath, + final String zipFilePath, + final String comment + ) + throws Exception { + return zipFile( + FileUtils.getFileByPath(resFilePath), + FileUtils.getFileByPath(zipFilePath), + comment + ); + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param zipFile 压缩文件 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final File resFile, + final File zipFile + ) + throws Exception { + return zipFile(resFile, zipFile, null); + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param zipFile 压缩文件 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + public static boolean zipFile( + final File resFile, + final File zipFile, + final String comment + ) + throws Exception { + if (resFile == null || zipFile == null) return false; + ZipOutputStream zos = null; + try { + zos = new ZipOutputStream(new FileOutputStream(zipFile)); + return zipFile(resFile, "", zos, comment); + } finally { + CloseUtils.closeIOQuietly(zos); + } + } + + /** + * 压缩文件 + * @param resFile 待压缩文件 + * @param rootPath 相对于压缩文件的路径 + * @param zos 压缩文件输出流 + * @param comment 压缩文件的注释 + * @return {@code true} 压缩成功, {@code false} 压缩失败 + * @throws Exception 异常时抛出 + */ + private static boolean zipFile( + final File resFile, + final String rootPath, + final ZipOutputStream zos, + final String comment + ) + throws Exception { + // 处理后的文件路径 + String filePath = rootPath + (StringUtils.isEmpty(rootPath) ? "" : File.separator) + resFile.getName(); + if (resFile.isDirectory()) { + File[] fileList = resFile.listFiles(); + // 如果是空文件夹那么创建它 + if (fileList == null || fileList.length == 0) { + ZipEntry entry = new ZipEntry(filePath + '/'); + entry.setComment(comment); + zos.putNextEntry(entry); + zos.closeEntry(); + } else { + for (File file : fileList) { + // 如果递归返回 false 则返回 false + if (!zipFile(file, filePath, zos, comment)) return false; + } + } + } else { + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(resFile)); + ZipEntry entry = new ZipEntry(filePath); + entry.setComment(comment); + zos.putNextEntry(entry); + byte[] buffer = new byte[BUFFER_LEN]; + int len; + while ((len = is.read(buffer, 0, BUFFER_LEN)) != -1) { + zos.write(buffer, 0, len); + } + zos.closeEntry(); + } finally { + CloseUtils.closeIOQuietly(is); + } + } + return true; + } + + /** + * 解压文件 + * @param zipFilePath 待解压文件路径 + * @param destDirPath 目标目录路径 + * @return 文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFile( + final String zipFilePath, + final String destDirPath + ) + throws Exception { + return unzipFileByKeyword(zipFilePath, destDirPath, null); + } + + /** + * 解压文件 + * @param zipFile 待解压文件 + * @param destDir 目标目录 + * @return 文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFile( + final File zipFile, + final File destDir + ) + throws Exception { + return unzipFileByKeyword(zipFile, destDir, null); + } + + /** + * 解压带有关键字的文件 + * @param zipFilePath 待解压文件路径 + * @param destDirPath 目标目录路径 + * @param keyword 关键字 + * @return 带有关键字的文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFileByKeyword( + final String zipFilePath, + final String destDirPath, + final String keyword + ) + throws Exception { + return unzipFileByKeyword( + FileUtils.getFileByPath(zipFilePath), + FileUtils.getFileByPath(destDirPath), + keyword + ); + } + + /** + * 解压带有关键字的文件 + * @param zipFile 待解压文件 + * @param destDir 目标目录 + * @param keyword 关键字 + * @return 带有关键字的文件链表 + * @throws Exception 异常时抛出 + */ + public static List unzipFileByKeyword( + final File zipFile, + final File destDir, + final String keyword + ) + throws Exception { + if (zipFile == null || destDir == null) return null; + List files = new ArrayList<>(); + ZipFile zip = new ZipFile(zipFile); + Enumeration entries = zip.entries(); + if (StringUtils.isEmpty(keyword)) { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + continue; + } + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } else { + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + String entryName = entry.getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + continue; + } + if (entryName.contains(keyword)) { + if (!unzipChildFile(destDir, files, zip, entry, entryName)) return files; + } + } + } + return files; + } + + /** + * 解压文件 + * @param destDir 目标目录 + * @param files 解压文件链表 + * @param zf 压缩文件条目 + * @param entry 压缩文件信息 + * @param entryName 文件名 + * @return {@code true} success, {@code false} fail + * @throws Exception 异常时抛出 + */ + private static boolean unzipChildFile( + final File destDir, + final List files, + final ZipFile zf, + final ZipEntry entry, + final String entryName + ) + throws Exception { + File file = new File(destDir, entryName); + files.add(file); + if (entry.isDirectory()) { + return FileUtils.createOrExistsDir(file); + } else { + if (!FileUtils.createOrExistsFile(file)) return false; + InputStream is = null; + OutputStream os = null; + try { + is = new BufferedInputStream(zf.getInputStream(entry)); + os = new BufferedOutputStream(new FileOutputStream(file)); + byte[] buffer = new byte[BUFFER_LEN]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + } finally { + CloseUtils.closeIOQuietly(is, os); + } + } + return true; + } + + /** + * 获取压缩文件中的文件路径链表 + * @param zipFilePath 压缩文件路径 + * @return 压缩文件中的文件路径链表 + * @throws Exception 异常时抛出 + */ + public static List getFilesPath(final String zipFilePath) + throws Exception { + return getFilesPath(FileUtils.getFileByPath(zipFilePath)); + } + + /** + * 获取压缩文件中的文件路径链表 + * @param zipFile 压缩文件 + * @return 压缩文件中的文件路径链表 + * @throws Exception 异常时抛出 + */ + public static List getFilesPath(final File zipFile) + throws Exception { + if (zipFile == null) return null; + List paths = new ArrayList<>(); + Enumeration entries = new ZipFile(zipFile).entries(); + while (entries.hasMoreElements()) { + String entryName = ((ZipEntry) entries.nextElement()).getName(); + if (entryName.contains("../")) { + JCLogUtils.dTag(TAG, "entryName: %s is dangerous!", entryName); + paths.add(entryName); + } else { + paths.add(entryName); + } + } + return paths; + } + + /** + * 获取压缩文件中的注释链表 + * @param zipFilePath 压缩文件路径 + * @return 压缩文件中的注释链表 + * @throws Exception 异常时抛出 + */ + public static List getComments(final String zipFilePath) + throws Exception { + return getComments(FileUtils.getFileByPath(zipFilePath)); + } + + /** + * 获取压缩文件中的注释链表 + * @param zipFile 压缩文件 + * @return 压缩文件中的注释链表 + * @throws Exception 异常时抛出 + */ + public static List getComments(final File zipFile) + throws Exception { + if (zipFile == null) return null; + List comments = new ArrayList<>(); + Enumeration entries = new ZipFile(zipFile).entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = ((ZipEntry) entries.nextElement()); + comments.add(entry.getComment()); + } + return comments; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Bindingable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Bindingable.java new file mode 100644 index 0000000000..de078e66f8 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Bindingable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Binding 接口 + * @author Ttt + *
+ *     所有通用快捷 Binding 接口定义存储类
+ *     全部接口只定义一个方法 bind() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Bindingable { + + private Bindingable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Binding { + + T bind(); + } + + // ======= + // = 有参 = + // ======= + + public interface BindingByParam { + + T bind(Param param); + } + + public interface BindingByParam2 { + + T bind( + Param param, + Param2 param2 + ); + } + + public interface BindingByParam3 { + + T bind( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface BindingByParamArgs { + + T bind(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Byable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Byable.java new file mode 100644 index 0000000000..14067b2bd0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Byable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 By 接口 + * @author Ttt + *
+ *     所有通用快捷 By 接口定义存储类
+ *     全部接口只定义一个方法 by() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Byable { + + private Byable() { + } + + // ======= + // = 无参 = + // ======= + + public interface By { + + T by(); + } + + // ======= + // = 有参 = + // ======= + + public interface ByByParam { + + T by(Param param); + } + + public interface ByByParam2 { + + T by( + Param param, + Param2 param2 + ); + } + + public interface ByByParam3 { + + T by( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ByByParamArgs { + + T by(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Calculateable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Calculateable.java new file mode 100644 index 0000000000..a59e063b46 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Calculateable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Calculate 接口 + * @author Ttt + *
+ *     所有通用快捷 Calculate 接口定义存储类
+ *     全部接口只定义一个方法 calculate() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Calculateable { + + private Calculateable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Calculate { + + T calculate(); + } + + // ======= + // = 有参 = + // ======= + + public interface CalculateByParam { + + T calculate(Param param); + } + + public interface CalculateByParam2 { + + T calculate( + Param param, + Param2 param2 + ); + } + + public interface CalculateByParam3 { + + T calculate( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CalculateByParamArgs { + + T calculate(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Callable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Callable.java new file mode 100644 index 0000000000..08ef3f7397 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Callable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Call 接口 + * @author Ttt + *
+ *     所有通用快捷 Call 接口定义存储类
+ *     全部接口只定义一个方法 callback() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Callable { + + private Callable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Call { + + T callback(); + } + + // ======= + // = 有参 = + // ======= + + public interface CallByParam { + + T callback(Param param); + } + + public interface CallByParam2 { + + T callback( + Param param, + Param2 param2 + ); + } + + public interface CallByParam3 { + + T callback( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CallByParamArgs { + + T callback(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Cloneable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Cloneable.java new file mode 100644 index 0000000000..4f4fbf62b2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Cloneable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Clone 接口 + * @author Ttt + *
+ *     所有通用快捷 Clone 接口定义存储类
+ *     全部接口只定义一个方法 cloneMethod() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Cloneable { + + private Cloneable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Clone { + + T cloneMethod(); + } + + // ======= + // = 有参 = + // ======= + + public interface CloneByParam { + + T cloneMethod(Param param); + } + + public interface CloneByParam2 { + + T cloneMethod( + Param param, + Param2 param2 + ); + } + + public interface CloneByParam3 { + + T cloneMethod( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CloneByParamArgs { + + T cloneMethod(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Closeable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Closeable.java new file mode 100644 index 0000000000..4916aa5b10 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Closeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Close 接口 + * @author Ttt + *
+ *     所有通用快捷 Close 接口定义存储类
+ *     全部接口只定义一个方法 close() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Closeable { + + private Closeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Close { + + T close(); + } + + // ======= + // = 有参 = + // ======= + + public interface CloseByParam { + + T close(Param param); + } + + public interface CloseByParam2 { + + T close( + Param param, + Param2 param2 + ); + } + + public interface CloseByParam3 { + + T close( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CloseByParamArgs { + + T close(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Commandable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Commandable.java new file mode 100644 index 0000000000..c7cccda05f --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Commandable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Command 接口 + * @author Ttt + *
+ *     所有通用快捷 Command 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Commandable { + + private Commandable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Command { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface CommandByParam { + + T execute(Param param); + } + + public interface CommandByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface CommandByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CommandByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Consumerable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Consumerable.java new file mode 100644 index 0000000000..412f9fde4a --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Consumerable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Consumer 接口 + * @author Ttt + *
+ *     所有通用快捷 Consumer 接口定义存储类
+ *     全部接口只定义一个方法 accept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Consumerable { + + private Consumerable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Consumer { + + T accept(); + } + + // ======= + // = 有参 = + // ======= + + public interface ConsumerByParam { + + T accept(Param param); + } + + public interface ConsumerByParam2 { + + T accept( + Param param, + Param2 param2 + ); + } + + public interface ConsumerByParam3 { + + T accept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ConsumerByParamArgs { + + T accept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Convertable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Convertable.java new file mode 100644 index 0000000000..9df957d175 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Convertable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Convert 接口 + * @author Ttt + *
+ *     所有通用快捷 Convert 接口定义存储类
+ *     全部接口只定义一个方法 convert() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Convertable { + + private Convertable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Convert { + + T convert(); + } + + // ======= + // = 有参 = + // ======= + + public interface ConvertByParam { + + T convert(Param param); + } + + public interface ConvertByParam2 { + + T convert( + Param param, + Param2 param2 + ); + } + + public interface ConvertByParam3 { + + T convert( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ConvertByParamArgs { + + T convert(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Copyable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Copyable.java new file mode 100644 index 0000000000..6919e5aaed --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Copyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Copy 接口 + * @author Ttt + *
+ *     所有通用快捷 Copy 接口定义存储类
+ *     全部接口只定义一个方法 copy() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Copyable { + + private Copyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Copy { + + T copy(); + } + + // ======= + // = 有参 = + // ======= + + public interface CopyByParam { + + T copy(Param param); + } + + public interface CopyByParam2 { + + T copy( + Param param, + Param2 param2 + ); + } + + public interface CopyByParam3 { + + T copy( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CopyByParamArgs { + + T copy(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Correctable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Correctable.java new file mode 100644 index 0000000000..6e97f3a4bb --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Correctable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Correct 接口 + * @author Ttt + *
+ *     所有通用快捷 Correct 接口定义存储类
+ *     全部接口只定义一个方法 correct() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Correctable { + + private Correctable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Correct { + + T correct(); + } + + // ======= + // = 有参 = + // ======= + + public interface CorrectByParam { + + T correct(Param param); + } + + public interface CorrectByParam2 { + + T correct( + Param param, + Param2 param2 + ); + } + + public interface CorrectByParam3 { + + T correct( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface CorrectByParamArgs { + + T correct(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Decodeable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Decodeable.java new file mode 100644 index 0000000000..2d8a4729aa --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Decodeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Decode 接口 + * @author Ttt + *
+ *     所有通用快捷 Decode 接口定义存储类
+ *     全部接口只定义一个方法 decode() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Decodeable { + + private Decodeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Decode { + + T decode(); + } + + // ======= + // = 有参 = + // ======= + + public interface DecodeByParam { + + T decode(Param param); + } + + public interface DecodeByParam2 { + + T decode( + Param param, + Param2 param2 + ); + } + + public interface DecodeByParam3 { + + T decode( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface DecodeByParamArgs { + + T decode(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Decryptable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Decryptable.java new file mode 100644 index 0000000000..7b631c7742 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Decryptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Decrypt 接口 + * @author Ttt + *
+ *     所有通用快捷 Decrypt 接口定义存储类
+ *     全部接口只定义一个方法 decrypt() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Decryptable { + + private Decryptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Decrypt { + + T decrypt(); + } + + // ======= + // = 有参 = + // ======= + + public interface DecryptByParam { + + T decrypt(Param param); + } + + public interface DecryptByParam2 { + + T decrypt( + Param param, + Param2 param2 + ); + } + + public interface DecryptByParam3 { + + T decrypt( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface DecryptByParamArgs { + + T decrypt(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Editorable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Editorable.java new file mode 100644 index 0000000000..abc4224900 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Editorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Editor 接口 + * @author Ttt + *
+ *     所有通用快捷 Editor 接口定义存储类
+ *     全部接口只定义一个方法 edit() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Editorable { + + private Editorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Editor { + + T edit(); + } + + // ======= + // = 有参 = + // ======= + + public interface EditorByParam { + + T edit(Param param); + } + + public interface EditorByParam2 { + + T edit( + Param param, + Param2 param2 + ); + } + + public interface EditorByParam3 { + + T edit( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EditorByParamArgs { + + T edit(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Encodeable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Encodeable.java new file mode 100644 index 0000000000..848c1d20bb --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Encodeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Encode 接口 + * @author Ttt + *
+ *     所有通用快捷 Encode 接口定义存储类
+ *     全部接口只定义一个方法 encode() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Encodeable { + + private Encodeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Encode { + + T encode(); + } + + // ======= + // = 有参 = + // ======= + + public interface EncodeByParam { + + T encode(Param param); + } + + public interface EncodeByParam2 { + + T encode( + Param param, + Param2 param2 + ); + } + + public interface EncodeByParam3 { + + T encode( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EncodeByParamArgs { + + T encode(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Encryptable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Encryptable.java new file mode 100644 index 0000000000..4ac57e06aa --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Encryptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Encrypt 接口 + * @author Ttt + *
+ *     所有通用快捷 Encrypt 接口定义存储类
+ *     全部接口只定义一个方法 encrypt() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Encryptable { + + private Encryptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Encrypt { + + T encrypt(); + } + + // ======= + // = 有参 = + // ======= + + public interface EncryptByParam { + + T encrypt(Param param); + } + + public interface EncryptByParam2 { + + T encrypt( + Param param, + Param2 param2 + ); + } + + public interface EncryptByParam3 { + + T encrypt( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface EncryptByParamArgs { + + T encrypt(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Errorable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Errorable.java new file mode 100644 index 0000000000..9f97696404 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Errorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Error 接口 + * @author Ttt + *
+ *     所有通用快捷 Error 接口定义存储类
+ *     全部接口只定义一个方法 tryCatch() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Errorable { + + private Errorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Error { + + T tryCatch(); + } + + // ======= + // = 有参 = + // ======= + + public interface ErrorByParam { + + T tryCatch(Param param); + } + + public interface ErrorByParam2 { + + T tryCatch( + Param param, + Param2 param2 + ); + } + + public interface ErrorByParam3 { + + T tryCatch( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ErrorByParamArgs { + + T tryCatch(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Filterable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Filterable.java new file mode 100644 index 0000000000..6a9eff782a --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Filterable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Filter 接口 + * @author Ttt + *
+ *     所有通用快捷 Filter 接口定义存储类
+ *     全部接口只定义一个方法 accept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Filterable { + + private Filterable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Filter { + + T accept(); + } + + // ======= + // = 有参 = + // ======= + + public interface FilterByParam { + + T accept(Param param); + } + + public interface FilterByParam2 { + + T accept( + Param param, + Param2 param2 + ); + } + + public interface FilterByParam3 { + + T accept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FilterByParamArgs { + + T accept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Findable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Findable.java new file mode 100644 index 0000000000..721e424511 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Findable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Find 接口 + * @author Ttt + *
+ *     所有通用快捷 Find 接口定义存储类
+ *     全部接口只定义一个方法 find() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Findable { + + private Findable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Find { + + T find(); + } + + // ======= + // = 有参 = + // ======= + + public interface FindByParam { + + T find(Param param); + } + + public interface FindByParam2 { + + T find( + Param param, + Param2 param2 + ); + } + + public interface FindByParam3 { + + T find( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FindByParamArgs { + + T find(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Flowable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Flowable.java new file mode 100644 index 0000000000..6bb46d4bb6 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Flowable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Flow 接口 + * @author Ttt + *
+ *     所有通用快捷 Flow 接口定义存储类
+ *     全部接口只定义一个方法 flow() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Flowable { + + private Flowable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Flow { + + T flow(); + } + + // ======= + // = 有参 = + // ======= + + public interface FlowByParam { + + T flow(Param param); + } + + public interface FlowByParam2 { + + T flow( + Param param, + Param2 param2 + ); + } + + public interface FlowByParam3 { + + T flow( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FlowByParamArgs { + + T flow(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Functionable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Functionable.java new file mode 100644 index 0000000000..487b6f229f --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Functionable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Function 接口 + * @author Ttt + *
+ *     所有通用快捷 Function 接口定义存储类
+ *     全部接口只定义一个方法 apply() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Functionable { + + private Functionable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Function { + + T apply(); + } + + // ======= + // = 有参 = + // ======= + + public interface FunctionByParam { + + T apply(Param param); + } + + public interface FunctionByParam2 { + + T apply( + Param param, + Param2 param2 + ); + } + + public interface FunctionByParam3 { + + T apply( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface FunctionByParamArgs { + + T apply(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Getable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Getable.java new file mode 100644 index 0000000000..79d7d7fda1 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Getable.java @@ -0,0 +1,63 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Get 接口 + * @author Ttt + *
+ *     所有通用快捷 Get 接口定义存储类
+ *     全部接口只定义一个方法 get() 且返回值一致
+ *     唯一差异就是参数不同
+ *     

+ * 其他类全部都是 copy {@link Getable} 代码完全一致, 只有方法名、接口名不同 + * 不通用 {@link Getable} 接口是为了区分功能, 对常用的功能定义对应类 + * 如 Readable、Writeable 读写较为常用, 所以专门定义对应接口类 + *

+ * 正常如 {@link Writeable} write() 方法需要返回写入结果, 可自行传入 泛型为 Boolean + * 也能自行决定需要返回什么类型值 + *
+ */ +public final class Getable { + + private Getable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Get { + + T get(); + } + + // ======= + // = 有参 = + // ======= + + public interface GetByParam { + + T get(Param param); + } + + public interface GetByParam2 { + + T get( + Param param, + Param2 param2 + ); + } + + public interface GetByParam3 { + + T get( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface GetByParamArgs { + + T get(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/IOable.java b/lib/DevJava/src/main/java/dev/utils/common/able/IOable.java new file mode 100644 index 0000000000..86c44003be --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/IOable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 IO 接口 + * @author Ttt + *
+ *     所有通用快捷 IO 接口定义存储类
+ *     全部接口只定义一个方法 io() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class IOable { + + private IOable() { + } + + // ======= + // = 无参 = + // ======= + + public interface IO { + + T io(); + } + + // ======= + // = 有参 = + // ======= + + public interface IOByParam { + + T io(Param param); + } + + public interface IOByParam2 { + + T io( + Param param, + Param2 param2 + ); + } + + public interface IOByParam3 { + + T io( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface IOByParamArgs { + + T io(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Inputable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Inputable.java new file mode 100644 index 0000000000..c58f6e563a --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Inputable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Input 接口 + * @author Ttt + *
+ *     所有通用快捷 Input 接口定义存储类
+ *     全部接口只定义一个方法 input() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Inputable { + + private Inputable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Input { + + T input(); + } + + // ======= + // = 有参 = + // ======= + + public interface InputByParam { + + T input(Param param); + } + + public interface InputByParam2 { + + T input( + Param param, + Param2 param2 + ); + } + + public interface InputByParam3 { + + T input( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface InputByParamArgs { + + T input(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Interceptable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Interceptable.java new file mode 100644 index 0000000000..f9dce34c76 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Interceptable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Intercept 接口 + * @author Ttt + *
+ *     所有通用快捷 Intercept 接口定义存储类
+ *     全部接口只定义一个方法 intercept() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Interceptable { + + private Interceptable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Intercept { + + T intercept(); + } + + // ======= + // = 有参 = + // ======= + + public interface InterceptByParam { + + T intercept(Param param); + } + + public interface InterceptByParam2 { + + T intercept( + Param param, + Param2 param2 + ); + } + + public interface InterceptByParam3 { + + T intercept( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface InterceptByParamArgs { + + T intercept(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Methodable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Methodable.java new file mode 100644 index 0000000000..d2ddc8bf54 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Methodable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Method 接口 + * @author Ttt + *
+ *     所有通用快捷 Method 接口定义存储类
+ *     全部接口只定义一个方法 invoke() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Methodable { + + private Methodable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Method { + + T invoke(); + } + + // ======= + // = 有参 = + // ======= + + public interface MethodByParam { + + T invoke(Param param); + } + + public interface MethodByParam2 { + + T invoke( + Param param, + Param2 param2 + ); + } + + public interface MethodByParam3 { + + T invoke( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface MethodByParamArgs { + + T invoke(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Modifyable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Modifyable.java new file mode 100644 index 0000000000..d8cb9f84bd --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Modifyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Modify 接口 + * @author Ttt + *
+ *     所有通用快捷 Modify 接口定义存储类
+ *     全部接口只定义一个方法 modify() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Modifyable { + + private Modifyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Modify { + + T modify(); + } + + // ======= + // = 有参 = + // ======= + + public interface ModifyByParam { + + T modify(Param param); + } + + public interface ModifyByParam2 { + + T modify( + Param param, + Param2 param2 + ); + } + + public interface ModifyByParam3 { + + T modify( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ModifyByParamArgs { + + T modify(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Notifyable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Notifyable.java new file mode 100644 index 0000000000..dc85aa9009 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Notifyable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Notify 接口 + * @author Ttt + *
+ *     所有通用快捷 Notify 接口定义存储类
+ *     全部接口只定义一个方法 notifyMethod() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Notifyable { + + private Notifyable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Notify { + + T notifyMethod(); + } + + // ======= + // = 有参 = + // ======= + + public interface NotifyByParam { + + T notifyMethod(Param param); + } + + public interface NotifyByParam2 { + + T notifyMethod( + Param param, + Param2 param2 + ); + } + + public interface NotifyByParam3 { + + T notifyMethod( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface NotifyByParamArgs { + + T notifyMethod(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Ofable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Ofable.java new file mode 100644 index 0000000000..7a689fb8a7 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Ofable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Of 接口 + * @author Ttt + *
+ *     所有通用快捷 Of 接口定义存储类
+ *     全部接口只定义一个方法 of() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Ofable { + + private Ofable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Of { + + T of(); + } + + // ======= + // = 有参 = + // ======= + + public interface OfByParam { + + T of(Param param); + } + + public interface OfByParam2 { + + T of( + Param param, + Param2 param2 + ); + } + + public interface OfByParam3 { + + T of( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OfByParamArgs { + + T of(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Operatorable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Operatorable.java new file mode 100644 index 0000000000..464cbcf837 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Operatorable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Operator 接口 + * @author Ttt + *
+ *     所有通用快捷 Operator 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Operatorable { + + private Operatorable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Operator { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface OperatorByParam { + + T execute(Param param); + } + + public interface OperatorByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface OperatorByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OperatorByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Outputable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Outputable.java new file mode 100644 index 0000000000..1ab98cccea --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Outputable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Output 接口 + * @author Ttt + *
+ *     所有通用快捷 Output 接口定义存储类
+ *     全部接口只定义一个方法 output() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Outputable { + + private Outputable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Output { + + T output(); + } + + // ======= + // = 有参 = + // ======= + + public interface OutputByParam { + + T output(Param param); + } + + public interface OutputByParam2 { + + T output( + Param param, + Param2 param2 + ); + } + + public interface OutputByParam3 { + + T output( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface OutputByParamArgs { + + T output(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Parseable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Parseable.java new file mode 100644 index 0000000000..90f780a9f5 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Parseable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Parse 接口 + * @author Ttt + *
+ *     所有通用快捷 Parse 接口定义存储类
+ *     全部接口只定义一个方法 parse() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Parseable { + + private Parseable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Parse { + + T parse(); + } + + // ======= + // = 有参 = + // ======= + + public interface ParseByParam { + + T parse(Param param); + } + + public interface ParseByParam2 { + + T parse( + Param param, + Param2 param2 + ); + } + + public interface ParseByParam3 { + + T parse( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ParseByParamArgs { + + T parse(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Pasteable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Pasteable.java new file mode 100644 index 0000000000..9c68593550 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Pasteable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Paste 接口 + * @author Ttt + *
+ *     所有通用快捷 Paste 接口定义存储类
+ *     全部接口只定义一个方法 paste() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Pasteable { + + private Pasteable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Paste { + + T paste(); + } + + // ======= + // = 有参 = + // ======= + + public interface PasteByParam { + + T paste(Param param); + } + + public interface PasteByParam2 { + + T paste( + Param param, + Param2 param2 + ); + } + + public interface PasteByParam3 { + + T paste( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface PasteByParamArgs { + + T paste(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Queryable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Queryable.java new file mode 100644 index 0000000000..567b109ded --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Queryable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Query 接口 + * @author Ttt + *
+ *     所有通用快捷 Query 接口定义存储类
+ *     全部接口只定义一个方法 query() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Queryable { + + private Queryable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Query { + + T query(); + } + + // ======= + // = 有参 = + // ======= + + public interface QueryByParam { + + T query(Param param); + } + + public interface QueryByParam2 { + + T query( + Param param, + Param2 param2 + ); + } + + public interface QueryByParam3 { + + T query( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface QueryByParamArgs { + + T query(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Readable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Readable.java new file mode 100644 index 0000000000..d90c26d3e7 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Readable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Read 接口 + * @author Ttt + *
+ *     所有通用快捷 Read 接口定义存储类
+ *     全部接口只定义一个方法 read() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Readable { + + private Readable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Read { + + T read(); + } + + // ======= + // = 有参 = + // ======= + + public interface ReadByParam { + + T read(Param param); + } + + public interface ReadByParam2 { + + T read( + Param param, + Param2 param2 + ); + } + + public interface ReadByParam3 { + + T read( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ReadByParamArgs { + + T read(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Refreshable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Refreshable.java new file mode 100644 index 0000000000..95d2ad68ec --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Refreshable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Refresh 接口 + * @author Ttt + *
+ *     所有通用快捷 Refresh 接口定义存储类
+ *     全部接口只定义一个方法 refresh() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Refreshable { + + private Refreshable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Refresh { + + T refresh(); + } + + // ======= + // = 有参 = + // ======= + + public interface RefreshByParam { + + T refresh(Param param); + } + + public interface RefreshByParam2 { + + T refresh( + Param param, + Param2 param2 + ); + } + + public interface RefreshByParam3 { + + T refresh( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface RefreshByParamArgs { + + T refresh(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Replaceable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Replaceable.java new file mode 100644 index 0000000000..be736d50be --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Replaceable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Replace 接口 + * @author Ttt + *
+ *     所有通用快捷 Replace 接口定义存储类
+ *     全部接口只定义一个方法 replace() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Replaceable { + + private Replaceable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Replace { + + T replace(); + } + + // ======= + // = 有参 = + // ======= + + public interface ReplaceByParam { + + T replace(Param param); + } + + public interface ReplaceByParam2 { + + T replace( + Param param, + Param2 param2 + ); + } + + public interface ReplaceByParam3 { + + T replace( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ReplaceByParamArgs { + + T replace(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Routerable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Routerable.java new file mode 100644 index 0000000000..5f800b4148 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Routerable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Router 接口 + * @author Ttt + *
+ *     所有通用快捷 Router 接口定义存储类
+ *     全部接口只定义一个方法 router() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Routerable { + + private Routerable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Router { + + T router(); + } + + // ======= + // = 有参 = + // ======= + + public interface RouterByParam { + + T router(Param param); + } + + public interface RouterByParam2 { + + T router( + Param param, + Param2 param2 + ); + } + + public interface RouterByParam3 { + + T router( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface RouterByParamArgs { + + T router(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Searchable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Searchable.java new file mode 100644 index 0000000000..4bc32376a3 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Searchable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Search 接口 + * @author Ttt + *
+ *     所有通用快捷 Search 接口定义存储类
+ *     全部接口只定义一个方法 search() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Searchable { + + private Searchable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Search { + + T search(); + } + + // ======= + // = 有参 = + // ======= + + public interface SearchByParam { + + T search(Param param); + } + + public interface SearchByParam2 { + + T search( + Param param, + Param2 param2 + ); + } + + public interface SearchByParam3 { + + T search( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SearchByParamArgs { + + T search(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Sendable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Sendable.java new file mode 100644 index 0000000000..79e47eb5ea --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Sendable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Send 接口 + * @author Ttt + *
+ *     所有通用快捷 Send 接口定义存储类
+ *     全部接口只定义一个方法 post() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Sendable { + + private Sendable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Send { + + T post(); + } + + // ======= + // = 有参 = + // ======= + + public interface SendByParam { + + T post(Param param); + } + + public interface SendByParam2 { + + T post( + Param param, + Param2 param2 + ); + } + + public interface SendByParam3 { + + T post( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SendByParamArgs { + + T post(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Sortable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Sortable.java new file mode 100644 index 0000000000..a19114b586 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Sortable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Sort 接口 + * @author Ttt + *
+ *     所有通用快捷 Sort 接口定义存储类
+ *     全部接口只定义一个方法 sort() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Sortable { + + private Sortable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Sort { + + T sort(); + } + + // ======= + // = 有参 = + // ======= + + public interface SortByParam { + + T sort(Param param); + } + + public interface SortByParam2 { + + T sort( + Param param, + Param2 param2 + ); + } + + public interface SortByParam3 { + + T sort( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SortByParamArgs { + + T sort(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Splitable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Splitable.java new file mode 100644 index 0000000000..a31f8c66c7 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Splitable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Split 接口 + * @author Ttt + *
+ *     所有通用快捷 Split 接口定义存储类
+ *     全部接口只定义一个方法 split() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Splitable { + + private Splitable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Split { + + T split(); + } + + // ======= + // = 有参 = + // ======= + + public interface SplitByParam { + + T split(Param param); + } + + public interface SplitByParam2 { + + T split( + Param param, + Param2 param2 + ); + } + + public interface SplitByParam3 { + + T split( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SplitByParamArgs { + + T split(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Supplierable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Supplierable.java new file mode 100644 index 0000000000..8c0ffd7860 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Supplierable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Supplier 接口 + * @author Ttt + *
+ *     所有通用快捷 Supplier 接口定义存储类
+ *     全部接口只定义一个方法 getAs() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Supplierable { + + private Supplierable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Supplier { + + T getAs(); + } + + // ======= + // = 有参 = + // ======= + + public interface SupplierByParam { + + T getAs(Param param); + } + + public interface SupplierByParam2 { + + T getAs( + Param param, + Param2 param2 + ); + } + + public interface SupplierByParam3 { + + T getAs( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface SupplierByParamArgs { + + T getAs(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Taskable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Taskable.java new file mode 100644 index 0000000000..672393f339 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Taskable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Task 接口 + * @author Ttt + *
+ *     所有通用快捷 Task 接口定义存储类
+ *     全部接口只定义一个方法 result() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Taskable { + + private Taskable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Task { + + T result(); + } + + // ======= + // = 有参 = + // ======= + + public interface TaskByParam { + + T result(Param param); + } + + public interface TaskByParam2 { + + T result( + Param param, + Param2 param2 + ); + } + + public interface TaskByParam3 { + + T result( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface TaskByParamArgs { + + T result(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Threadable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Threadable.java new file mode 100644 index 0000000000..ad53d1dafc --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Threadable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Thread 接口 + * @author Ttt + *
+ *     所有通用快捷 Thread 接口定义存储类
+ *     全部接口只定义一个方法 execute() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Threadable { + + private Threadable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Thread { + + T execute(); + } + + // ======= + // = 有参 = + // ======= + + public interface ThreadByParam { + + T execute(Param param); + } + + public interface ThreadByParam2 { + + T execute( + Param param, + Param2 param2 + ); + } + + public interface ThreadByParam3 { + + T execute( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ThreadByParamArgs { + + T execute(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Toable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Toable.java new file mode 100644 index 0000000000..f077d9023b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Toable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 To 接口 + * @author Ttt + *
+ *     所有通用快捷 To 接口定义存储类
+ *     全部接口只定义一个方法 to() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Toable { + + private Toable() { + } + + // ======= + // = 无参 = + // ======= + + public interface To { + + T to(); + } + + // ======= + // = 有参 = + // ======= + + public interface ToByParam { + + T to(Param param); + } + + public interface ToByParam2 { + + T to( + Param param, + Param2 param2 + ); + } + + public interface ToByParam3 { + + T to( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface ToByParamArgs { + + T to(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/UnBinderable.java b/lib/DevJava/src/main/java/dev/utils/common/able/UnBinderable.java new file mode 100644 index 0000000000..b57c0aa186 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/UnBinderable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 UnBinder 接口 + * @author Ttt + *
+ *     所有通用快捷 UnBinder 接口定义存储类
+ *     全部接口只定义一个方法 unbind() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class UnBinderable { + + private UnBinderable() { + } + + // ======= + // = 无参 = + // ======= + + public interface UnBinder { + + T unbind(); + } + + // ======= + // = 有参 = + // ======= + + public interface UnBinderByParam { + + T unbind(Param param); + } + + public interface UnBinderByParam2 { + + T unbind( + Param param, + Param2 param2 + ); + } + + public interface UnBinderByParam3 { + + T unbind( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface UnBinderByParamArgs { + + T unbind(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/able/Writeable.java b/lib/DevJava/src/main/java/dev/utils/common/able/Writeable.java new file mode 100644 index 0000000000..82a311ad03 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/able/Writeable.java @@ -0,0 +1,56 @@ +package dev.utils.common.able; + +/** + * detail: 通用 Write 接口 + * @author Ttt + *
+ *     所有通用快捷 Write 接口定义存储类
+ *     全部接口只定义一个方法 write() 且返回值一致
+ *     唯一差异就是参数不同
+ * 
+ */ +public final class Writeable { + + private Writeable() { + } + + // ======= + // = 无参 = + // ======= + + public interface Write { + + T write(); + } + + // ======= + // = 有参 = + // ======= + + public interface WriteByParam { + + T write(Param param); + } + + public interface WriteByParam2 { + + T write( + Param param, + Param2 param2 + ); + } + + public interface WriteByParam3 { + + T write( + Param param, + Param2 param2, + Param3 param3 + ); + } + + public interface WriteByParamArgs { + + T write(Param... args); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/Averager.java b/lib/DevJava/src/main/java/dev/utils/common/assist/Averager.java new file mode 100644 index 0000000000..3988977b50 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/Averager.java @@ -0,0 +1,64 @@ +package dev.utils.common.assist; + +import java.util.ArrayList; +import java.util.List; + +/** + * detail: 均值计算 ( 用以统计平均数 ) 辅助类 + * @author Ttt + */ +public class Averager { + + private final List mNumLists = new ArrayList<>(); + + /** + * 添加一个数字 + * @param number Number + * @return {@link Averager} + */ + public synchronized Averager add(final Number number) { + mNumLists.add(number); + return this; + } + + /** + * 清除全部 + * @return {@link Averager} + */ + public Averager clear() { + mNumLists.clear(); + return this; + } + + /** + * 获取参与均值计算的数字个数 + * @return 参与均值计算的数字个数 + */ + public Number size() { + return mNumLists.size(); + } + + /** + * 获取平均数 + * @return 全部数字平均数 + */ + public Number getAverage() { + if (mNumLists.size() == 0) { + return 0; + } else { + float sum = 0F; + for (int i = 0, len = mNumLists.size(); i < len; i++) { + sum = sum + mNumLists.get(i).floatValue(); + } + return sum / mNumLists.size(); + } + } + + /** + * 输出参与均值计算的数字 + * @return 参与均值计算的数字 + */ + public String print() { + return "printList(" + size() + "): " + mNumLists; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/FlagsValue.java b/lib/DevJava/src/main/java/dev/utils/common/assist/FlagsValue.java new file mode 100644 index 0000000000..9f1d600813 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/FlagsValue.java @@ -0,0 +1,78 @@ +package dev.utils.common.assist; + +/** + * detail: 标记值计算存储 ( 位运算符 ) + * @author Ttt + */ +public final class FlagsValue { + + public FlagsValue() { + } + + public FlagsValue(final int flags) { + this.mFlags = flags; + } + + // ============= + // = 对外公开方法 = + // ============= + + // flags value + private int mFlags; + + /** + * 获取 flags value + * @return flags value + */ + public int getFlags() { + return mFlags; + } + + /** + * 设置 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue setFlags(final int flags) { + this.mFlags = flags; + return this; + } + + /** + * 添加 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue addFlags(final int flags) { + this.mFlags |= flags; + return this; + } + + /** + * 移除 flags value + * @param flags flags value + * @return {@link FlagsValue} + */ + public FlagsValue clearFlags(final int flags) { + this.mFlags &= ~flags; + return this; + } + + /** + * 是否存在 flags value + * @param flags flags value + * @return {@code true} yes, {@code false} no + */ + public boolean hasFlags(final int flags) { + return (mFlags & flags) == flags; + } + + /** + * 是否不存在 flags value + * @param flags flags value + * @return {@code true} yes, {@code false} no + */ + public boolean notHasFlags(final int flags) { + return (mFlags & flags) != flags; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/TimeAverager.java b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeAverager.java new file mode 100644 index 0000000000..01b76b5438 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeAverager.java @@ -0,0 +1,66 @@ +package dev.utils.common.assist; + +/** + * detail: 时间均值计算辅助类 + * @author Ttt + */ +public class TimeAverager { + + // 计时器 + private final TimeCounter mTimeCounter = new TimeCounter(); + // 均值器 + private final Averager mAverager = new Averager(); + + /** + * 开始计时 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long start() { + return mTimeCounter.start(); + } + + /** + * 结束计时 ( 毫秒 ) + * @return 结束时间 ( 毫秒 ) + */ + public long end() { + long time = mTimeCounter.duration(); + mAverager.add(time); + return time; + } + + /** + * 结束计时, 并重新启动新的计时 + * @return 距离上次计时的时间差 ( 毫秒 ) + */ + public long endAndRestart() { + long time = mTimeCounter.durationRestart(); + mAverager.add(time); + return time; + } + + /** + * 求全部计时均值 + * @return 全部计时时间均值 + */ + public Number average() { + return mAverager.getAverage(); + } + + /** + * 输出全部时间值 + * @return 计时信息 + */ + public String print() { + return mAverager.print(); + } + + /** + * 清除计时数据 + * @return {@link TimeAverager} + */ + public TimeAverager clear() { + mAverager.clear(); + return this; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/TimeCounter.java b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeCounter.java new file mode 100644 index 0000000000..60e287d77c --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeCounter.java @@ -0,0 +1,59 @@ +package dev.utils.common.assist; + +/** + * detail: 时间计时辅助类 + * @author Ttt + */ +public class TimeCounter { + + // 开始时间 + private long mStart; + + public TimeCounter() { + this(true); + } + + /** + * 构造函数 + * @param isStart 是否开始计时 + */ + public TimeCounter(final boolean isStart) { + if (isStart) start(); + } + + /** + * 开始计时 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long start() { + mStart = System.currentTimeMillis(); + return mStart; + } + + /** + * 获取持续的时间并重新启动 ( 毫秒 ) + * @return 距离上次开始时间的时间差 ( 毫秒 ) + */ + public long durationRestart() { + long now = System.currentTimeMillis(); + long diff = now - mStart; + mStart = now; + return diff; + } + + /** + * 获取持续的时间 ( 毫秒 ) + * @return 距离开始时间的时间差 ( 毫秒 ) + */ + public long duration() { + return System.currentTimeMillis() - mStart; + } + + /** + * 获取开始时间 ( 毫秒 ) + * @return 开始时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStart; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/TimeKeeper.java b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeKeeper.java new file mode 100644 index 0000000000..1419779390 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/TimeKeeper.java @@ -0,0 +1,71 @@ +package dev.utils.common.assist; + +/** + * detail: 堵塞时间辅助类 + * @author Ttt + */ +public class TimeKeeper { + + /** + * 设置等待一段时间后, 通知方法 ( 异步 ) + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param callback 结束回调通知 + */ + public void waitForEndAsync( + final long keepTimeMillis, + final OnEndCallback callback + ) { + if (keepTimeMillis > 0L) { + new Thread(() -> waitForEnd(keepTimeMillis, callback)).start(); + } + } + + /** + * 设置等待一段时间后, 通知方法 ( 同步 ) + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param callback 结束回调通知 + */ + public void waitForEnd( + final long keepTimeMillis, + final OnEndCallback callback + ) { + if (keepTimeMillis > 0L) { + // 开始堵塞时间 + long startTime = System.currentTimeMillis(); + try { + // 进行堵塞 + Thread.sleep(keepTimeMillis); + // 触发回调 + if (callback != null) { + callback.onEnd(keepTimeMillis, startTime, System.currentTimeMillis(), false); + } + } catch (Exception e) { + // 触发回调 + if (callback != null) { + callback.onEnd(keepTimeMillis, startTime, System.currentTimeMillis(), true); + } + } + } + } + + /** + * detail: 结束通知回调 + * @author Ttt + */ + public interface OnEndCallback { + + /** + * 结束触发通知方法 + * @param keepTimeMillis 堵塞时间 ( 毫秒 ) + * @param startTimeMillis 开始堵塞时间 ( 毫秒 ) + * @param endTimeMillis 结束时间 ( 毫秒 ) + * @param isError 是否发生异常 + */ + void onEnd( + long keepTimeMillis, + long startTimeMillis, + long endTimeMillis, + boolean isError + ); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java b/lib/DevJava/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java new file mode 100644 index 0000000000..0e6dc29944 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/WeakReferenceAssist.java @@ -0,0 +1,153 @@ +package dev.utils.common.assist; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * detail: 弱引用辅助类 + * @author Ttt + */ +public final class WeakReferenceAssist { + + // 日志 TAG + private final String TAG = WeakReferenceAssist.class.getSimpleName(); + + // 弱引用存储 + private final Map> mWeakMaps = new HashMap<>(); + + // ============= + // = 对外公开方法 = + // ============= + + // ========== + // = Single = + // ========== + + /** + * 获取单个弱引用对象 + * @return 单个弱引用对象 + */ + public WeakReference getSingleWeak() { + return getWeak(TAG); + } + + /** + * 获取单个弱引用对象值 + * @return 单个弱引用对象值 + */ + public T getSingleWeakValue() { + return getWeakValue(TAG); + } + + /** + * 获取单个弱引用对象值 + * @param defaultValue 默认值 + * @return 单个弱引用对象值 + */ + public T getSingleWeakValue(final T defaultValue) { + return getWeakValue(TAG, defaultValue); + } + + // = + + /** + * 保存单个弱引用对象值 + * @param value 待存储值 + * @return {@code true} success, {@code false} fail + */ + public boolean setSingleWeakValue(final T value) { + return setWeakValue(TAG, value); + } + + /** + * 移除单个弱引用持有对象 + * @return {@code true} success, {@code false} fail + */ + public boolean removeSingleWeak() { + return removeWeak(TAG); + } + + // =========== + // = Map Key = + // =========== + + /** + * 获取弱引用对象 + * @param key Key + * @return 弱引用对象 + */ + public WeakReference getWeak(final String key) { + return mWeakMaps.get(key); + } + + /** + * 获取弱引用对象值 + * @param key Key + * @return 弱引用对象值 + */ + public T getWeakValue(final String key) { + return getWeakValue(key, null); + } + + /** + * 获取弱引用对象值 + * @param key Key + * @param defaultValue 默认值 + * @return 弱引用对象值 + */ + public T getWeakValue( + final String key, + final T defaultValue + ) { + WeakReference weak = mWeakMaps.get(key); + if (weak == null) return defaultValue; + T value = weak.get(); + if (value != null) return value; + return defaultValue; + } + + // = + + /** + * 保存弱引用对象值 + * @param key Key + * @param value 待存储值 + * @return {@code true} success, {@code false} fail + */ + public boolean setWeakValue( + final String key, + final T value + ) { + if (key == null || value == null) return false; + mWeakMaps.put(key, new WeakReference<>(value)); + return true; + } + + /** + * 移除指定弱引用持有对象 + * @param key Key + * @return {@code true} success, {@code false} fail + */ + public boolean removeWeak(final String key) { + if (key == null) return false; + WeakReference weak = mWeakMaps.remove(key); + if (weak == null) return false; + weak.clear(); + return true; + } + + /** + * 清空全部弱引用持有对象 + */ + public void clear() { + List> lists = new ArrayList<>(mWeakMaps.values()); + mWeakMaps.clear(); + for (WeakReference weak : lists) { + if (weak != null) weak.clear(); + } + lists.clear(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java b/lib/DevJava/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java new file mode 100644 index 0000000000..187b2e2894 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/record/FileRecordUtils.java @@ -0,0 +1,306 @@ +package dev.utils.common.assist.record; + +import java.io.File; + +import dev.utils.DevFinal; +import dev.utils.common.ConvertUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.ThrowableUtils; + +/** + * detail: 文件记录分析工具类 + * @author Ttt + */ +public final class FileRecordUtils { + + private FileRecordUtils() { + } + + // 成功常量字符串 + private static final String RECORD_SUCCESS = "record successful"; + // 是否处理记录 + private static boolean sHandler = true; + // 日志记录插入信息 + private static RecordInsert sRecordInsert = null; + // 文件记录回调 + private static Callback sCallback = null; + + // ========== + // = 接口回调 = + // ========== + + /** + * detail: 文件记录回调 + * @author Ttt + */ + public interface Callback { + + /** + * 记录结果回调 + * @param result 保存结果 + * @param config 日志记录配置信息 + * @param filePath 存储路径 + * @param fileName 文件名 ( 含后缀 ) + * @param logContent 日志信息 + * @param logs 原始日志内容数组 + */ + void callback( + boolean result, + RecordConfig config, + String filePath, + String fileName, + String logContent, + Object... logs + ); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 拼接插入日志 + * @param recordInsert 日志记录插入信息 + * @param logContent 日志内容 + * @return 最终日志信息 + */ + private static String concatInsertLog( + final RecordInsert recordInsert, + final String logContent + ) { + if (recordInsert == null) { + return logContent; + } + StringBuilder builder = new StringBuilder(); + // 追加头部信息 + if (StringUtils.isNotEmpty(recordInsert.getLogHeader())) { + builder.append(recordInsert.getLogHeader()); + } + // 加入日志内容 + builder.append(logContent); + // 追加尾部信息 + if (StringUtils.isNotEmpty(recordInsert.getLogTail())) { + builder.append(recordInsert.getLogTail()); + } + return builder.toString(); + } + + /** + * 拼接日志 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 拼接后的日志内容 + */ + private static String concatLog( + final RecordConfig config, + final Object... logs + ) { + if (logs == null || logs.length == 0) return null; + // 是否插入头数据 ( time =>、logs[] ) + boolean headerData = (config == null || config.isInsertHeaderData()); + + StringBuilder builder = new StringBuilder(); + if (headerData) { + builder.append(DevFinal.SYMBOL.NEW_LINE_X2) + // 获取当前时间 + .append(DateUtils.getDateNow()) + // 追加边距、换行 + .append(" =>"); + } + // 循环追加内容 + for (int i = 0, len = logs.length; i < len; i++) { + // 追加存储内容 + builder.append(DevFinal.SYMBOL.NEW_LINE_X2); + if (headerData) { + builder.append("logs[").append(i).append("]: ") + .append(DevFinal.SYMBOL.NEW_LINE); + } + Object object = logs[i]; + if (object instanceof Throwable) { + String errorLog = ThrowableUtils.getThrowableStackTrace((Throwable) object); + builder.append(errorLog); + } else { + builder.append(ConvertUtils.toString(object)); + } + } + return builder.toString(); + } + + /** + * 最终记录方法 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 记录结果提示 + */ + private static String finalRecord( + final RecordConfig config, + final Object... logs + ) { + // 判断全局是否处理 + if (!sHandler) return "global do not handle"; + // 判断配置是否为 null + if (config == null) return "config is null"; + // 判断配置是否处理 + if (!config.isHandler()) return "config do not handle"; + // 判断是否存在日志内容 + if (logs == null || logs.length == 0) return "no data record"; + + // 文件路径 + String filePath = config.getFinalPath(); + // 文件名 + String fileName = config.getFileName(); + // 文件路径、文件名为 null 则不处理 + if (StringUtils.isEmpty(filePath, fileName)) return "filePath is null"; + + // 日志记录插入信息 + RecordInsert recordInsert = config.getRecordInsert(sRecordInsert); + // 日志内容 + String logContent = concatLog(config, logs); + // 拼接最终内容 + String finalLogContent = concatInsertLog(recordInsert, logContent); + + // 获取存储文件 + File file = FileUtils.getFile(filePath, fileName); + // 文件不存在则进行追加文件信息 + if (!FileUtils.isFileExists(file)) { + if (recordInsert != null) { + String fileInfo = recordInsert.getFileInfo(); + if (fileInfo != null) { + // 文件信息 ( 一个文件只会添加一次文件信息, 且在最顶部 ) + FileUtils.saveFile(file, StringUtils.getBytes(fileInfo)); + } + } + } + // 追加日志内容 + boolean result = FileUtils.appendFile(file, StringUtils.getBytes(finalLogContent)); + + if (sCallback != null) { + sCallback.callback(result, config, filePath, fileName, finalLogContent, logs); + } + return result ? RECORD_SUCCESS : "record failed"; + } + + // ============= + // = 对外公开方法 = + // ============= + + // =========== + // = get/set = + // =========== + + /** + * 校验记录方法返回字符串是否成功 + * @param value 待校验值 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSuccessful(final String value) { + return RECORD_SUCCESS.equals(value); + } + + /** + * 是否处理记录 + * @return {@code true} yes, {@code false} no + */ + public static boolean isHandler() { + return sHandler; + } + + /** + * 设置是否处理记录 + * @param handler 是否处理记录 + */ + public static void setHandler(final boolean handler) { + FileRecordUtils.sHandler = handler; + } + + /** + * 获取日志记录插入信息 + * @return 日志记录插入信息 + */ + public static RecordInsert getRecordInsert() { + return sRecordInsert; + } + + /** + * 设置日志记录插入信息 + * @param recordInsert 日志记录插入信息 + */ + public static void setRecordInsert(final RecordInsert recordInsert) { + FileRecordUtils.sRecordInsert = recordInsert; + } + + /** + * 设置文件记录回调 + * @param callback 文件记录回调 + */ + public static void setCallback(final Callback callback) { + FileRecordUtils.sCallback = callback; + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取日志内容 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordConfig config, + final Object... logs + ) { + if (config != null) { + return getLogContent(config, config.getRecordInsert(sRecordInsert), logs); + } + return getLogContent(null, sRecordInsert, logs); + } + + /** + * 获取日志内容 + * @param recordInsert 日志记录插入信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordInsert recordInsert, + final Object... logs + ) { + return getLogContent(null, recordInsert, logs); + } + + /** + * 获取日志内容 + * @param config 日志记录配置信息 + * @param recordInsert 日志记录插入信息 + * @param logs 日志内容数组 + * @return 日志内容 + */ + public static String getLogContent( + final RecordConfig config, + final RecordInsert recordInsert, + final Object... logs + ) { + String logContent = concatLog(config, logs); + if (StringUtils.isEmpty(logContent)) return null; + return concatInsertLog(recordInsert, logContent); + } + + // = + + /** + * 记录方法 + * @param config 日志记录配置信息 + * @param logs 日志内容数组 + * @return 记录结果提示 + */ + public static String record( + final RecordConfig config, + final Object... logs + ) { + return finalRecord(config, logs); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordConfig.java b/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordConfig.java new file mode 100644 index 0000000000..d3812a6aac --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordConfig.java @@ -0,0 +1,311 @@ +package dev.utils.common.assist.record; + +import java.io.File; +import java.util.Date; + +import dev.utils.DevFinal; +import dev.utils.common.DateUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 日志记录配置信息 + * @author Ttt + */ +public final class RecordConfig { + + // 存储路径 + private final String mStoragePath; + // 文件名 ( 固定 ) + private final String mFileName = "log_record.txt"; + // 文件夹名 ( 模块名 ) + private final String mFolderName; + // 文件记录间隔时间 如: HH + private final TIME mFileIntervalTime; + // 是否处理记录 + private boolean mHandler; + // 是否插入头数据 ( time =>、logs[] ) + private boolean mInsertHeaderData; + // 日志记录插入信息 + private RecordInsert mRecordInsert; + + // ======= + // = 枚举 = + // ======= + + /** + * detail: 文件记录间隔时间枚举 + * @author Ttt + */ + public enum TIME { + + // DEFAULT ( 默认天, 在根目录下 ) + DEFAULT, + + // 小时 + HH, + + // 分钟 + MM, + + // 秒 + SS + } + + // ========== + // = 构造函数 = + // ========== + + /** + * 构造函数 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @param insertHeaderData 是否插入头数据 + */ + private RecordConfig( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler, + final boolean insertHeaderData + ) { + this.mStoragePath = storagePath; + this.mFolderName = folderName; + this.mFileIntervalTime = fileIntervalTime; + this.mHandler = handler; + this.mInsertHeaderData = insertHeaderData; + } + + // = + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName + ) { + return get(storagePath, folderName, TIME.DEFAULT, true, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime + ) { + return get(storagePath, folderName, fileIntervalTime, true, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler + ) { + return get(storagePath, folderName, fileIntervalTime, handler, true); + } + + /** + * 获取配置信息 + * @param storagePath 存储路径 + * @param folderName 文件夹名 ( 模块名 ) + * @param fileIntervalTime 文件记录间隔时间 + * @param handler 是否处理记录 + * @param insertHeaderData 是否插入头数据 + * @return {@link RecordConfig} + */ + public static RecordConfig get( + final String storagePath, + final String folderName, + final TIME fileIntervalTime, + final boolean handler, + final boolean insertHeaderData + ) { + if (StringUtils.isEmpty(storagePath, folderName)) return null; + return new RecordConfig( + storagePath, folderName, + (fileIntervalTime != null ? fileIntervalTime : TIME.DEFAULT), + handler, insertHeaderData + ); + } + + // =========== + // = get/set = + // =========== + + /** + * 获取存储路径 + * @return 存储路径 + */ + public String getStoragePath() { + return mStoragePath; + } + + /** + * 获取文件名 ( 固定 ) + * @return 文件名 ( 固定 ) + */ + public String getFileName() { + return mFileName; + } + + /** + * 获取文件夹名 ( 模块名 ) + * @return 文件夹名 ( 模块名 ) + */ + public String getFolderName() { + return mFolderName; + } + + /** + * 获取文件记录间隔时间 + * @return 文件记录间隔时间 + */ + public TIME getFileIntervalTime() { + return mFileIntervalTime; + } + + /** + * 是否处理记录 + * @return {@code true} yes, {@code false} no + */ + public boolean isHandler() { + return mHandler; + } + + /** + * 设置是否处理记录 + * @param handler 是否处理记录 + * @return {@link RecordConfig} + */ + public RecordConfig setHandler(final boolean handler) { + this.mHandler = handler; + return this; + } + + /** + * 是否插入头数据 + * @return {@code true} yes, {@code false} no + */ + public boolean isInsertHeaderData() { + return mInsertHeaderData; + } + + /** + * 设置是否插入头数据 + * @param insertHeaderData 是否插入头数据 + * @return {@link RecordConfig} + */ + public RecordConfig setInsertHeaderData(final boolean insertHeaderData) { + this.mInsertHeaderData = insertHeaderData; + return this; + } + + /** + * 获取日志记录插入信息 + * @return 日志记录插入信息 + */ + public RecordInsert getRecordInsert() { + return mRecordInsert; + } + + /** + * 获取日志记录插入信息 + * @param defaultValue 默认值 + * @return 日志记录插入信息 + */ + public RecordInsert getRecordInsert(final RecordInsert defaultValue) { + if (mRecordInsert != null) return mRecordInsert; + return defaultValue; + } + + /** + * 设置日志记录插入信息 + * @param recordInsert 日志记录插入信息 + * @return {@link RecordConfig} + */ + public RecordConfig setRecordInsert(final RecordInsert recordInsert) { + this.mRecordInsert = recordInsert; + return this; + } + + // = + + /** + * 获取文件地址 + * @return 文件地址 + */ + public String getFinalPath() { + File file = FileUtils.getFile(mStoragePath, getIntervalTimeFolder()); + // 创建文件夹 + FileUtils.createFolder(file); + return FileUtils.getAbsolutePath(file); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取时间间隔所属的文件夹 + * @return 时间间隔所属的文件夹 + */ + private String getIntervalTimeFolder() { + // 文件夹 + String folder = String.format( + "FileRecord/%s/%s/", + DateUtils.getDateNow(DevFinal.TIME.yyyyMMdd_UNDERSCORE), + mFolderName + ); + // 进行判断 + switch (mFileIntervalTime) { + case DEFAULT: + return folder; + case HH: + case MM: + case SS: + Date date = new Date(); + // 小时格式 ( 24 ) + String hh_Format = DateUtils.timeAddZero(DateUtils.get24Hour(date)); + // 判断属于小时格式 + if (mFileIntervalTime == TIME.HH) { + // folder/HH_number/ + return folder + "HH_" + hh_Format + File.separator; + } else { + // 分钟格式 + String mm_Format = DateUtils.timeAddZero(DateUtils.getMinute(date)); + // 判断是否属于分钟 + if (mFileIntervalTime == TIME.MM) { + // folder/HH_number/MM_number/ + return folder + "HH_" + hh_Format + "/MM_" + mm_Format + File.separator; + } else { // 属于秒 + // 秒格式 + String ss_Format = DateUtils.timeAddZero(DateUtils.getSecond(date)); + // folder/HH_number/MM_number/SS_number/ + return folder + "HH_" + hh_Format + "/MM_" + mm_Format + "/SS_" + ss_Format + File.separator; + } + } + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordInsert.java b/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordInsert.java new file mode 100644 index 0000000000..696584c439 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/record/RecordInsert.java @@ -0,0 +1,66 @@ +package dev.utils.common.assist.record; + +/** + * detail: 日志记录插入信息 + * @author Ttt + */ +public class RecordInsert { + + // 文件信息 ( 一个文件只会添加一次文件信息, 且在最顶部 ) + protected String mFileInfo; + + // 每条日志头部信息 + protected String mLogHeader; + + // 每条日志尾部信息 + protected String mLogTail; + + // ========== + // = 构造函数 = + // ========== + + public RecordInsert(final String fileInfo) { + this.mFileInfo = fileInfo; + } + + public RecordInsert( + final String fileInfo, + final String logHeader, + final String logTail + ) { + this.mFileInfo = fileInfo; + this.mLogHeader = logHeader; + this.mLogTail = logTail; + } + + // =========== + // = get/set = + // =========== + + public String getFileInfo() { + return mFileInfo; + } + + public RecordInsert setFileInfo(final String fileInfo) { + this.mFileInfo = fileInfo; + return this; + } + + public String getLogHeader() { + return mLogHeader; + } + + public RecordInsert setLogHeader(final String logHeader) { + this.mLogHeader = logHeader; + return this; + } + + public String getLogTail() { + return mLogTail; + } + + public RecordInsert setLogTail(final String logTail) { + this.mLogTail = logTail; + return this; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java b/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java new file mode 100644 index 0000000000..1c705ebc4b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileBreadthFirstSearchUtils.java @@ -0,0 +1,414 @@ +package dev.utils.common.assist.search; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +import dev.utils.JCLogUtils; + +/** + * detail: 文件广度优先搜索算法 ( 多线程 + 队列, 搜索某个目录下的全部文件 ) + * @author Ttt + */ +public final class FileBreadthFirstSearchUtils { + + // 日志 TAG + private static final String TAG = FileBreadthFirstSearchUtils.class.getSimpleName(); + + /** + * 构造函数 + */ + public FileBreadthFirstSearchUtils() { + } + + /** + * 构造函数 + * @param searchHandler 搜索处理接口 + */ + public FileBreadthFirstSearchUtils(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + } + + /** + * detail: 文件信息 Item + * @author Ttt + */ + public static final class FileItem { + + public FileItem(final File file) { + this.file = file; + } + + // 文件 + public File file; + + // 子文件夹目录 + public Map mapChilds; + + /** + * 保存子文件信息 + * @param file 文件 + * @return 文件信息 {@link FileItem} + */ + private synchronized FileItem put(final File file) { + if (mapChilds == null) { + mapChilds = new HashMap<>(); + } + if (file != null) { + FileItem fileItem = new FileItem(file); + mapChilds.put(file.getAbsolutePath(), fileItem); + return fileItem; + } + return null; + } + } + + /** + * detail: 文件队列 + * @author Ttt + */ + private static class FileQueue { + + FileQueue( + File file, + FileItem fileItem + ) { + this.file = file; + this.fileItem = fileItem; + } + + // 当前准备处理文件夹 + private final File file; + + // 上一级目录对象 + private final FileItem fileItem; + } + + /** + * detail: 搜索处理接口 + * @author Ttt + */ + public interface SearchHandler { + + /** + * 判断是否处理该文件 + * @param file 文件 + * @return {@code true} 处理该文件, {@code false} 跳过该文件不处理 + */ + boolean isHandlerFile(File file); + + /** + * 是否添加到集合 + * @param file 文件 + * @return {@code true} 添加, {@code false} 不添加 + */ + boolean isAddToList(File file); + + /** + * 搜索结束监听 + * @param rootFileItem 根文件信息 {@link FileItem} + * @param startTime 开始扫描时间 + * @param endTime 扫描结束时间 + */ + void onEndListener( + FileItem rootFileItem, + long startTime, + long endTime + ); + } + + // 搜索处理接口 + private SearchHandler mSearchHandler; + + // 内部实现接口 + private final SearchHandler mInnerHandler = new SearchHandler() { + @Override + public boolean isHandlerFile(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isHandlerFile(file); + } + return true; + } + + @Override + public boolean isAddToList(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isAddToList(file); + } + return true; + } + + @Override + public void onEndListener( + FileItem rootFileItem, + long startTime, + long endTime + ) { + // 表示非搜索中 + mRunning = false; + // 触发回调 + if (mSearchHandler != null) { + mSearchHandler.onEndListener(rootFileItem, startTime, endTime); + } + } + }; + + /** + * 设置搜索处理接口 + * @param searchHandler 搜索处理接口 + * @return {@link FileBreadthFirstSearchUtils} + */ + public FileBreadthFirstSearchUtils setSearchHandler(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + return this; + } + + /** + * 获取任务队列同时进行数量 + * @return 队列数量 + */ + public int getQueueSameTimeNumber() { + return mQueueSameTimeNumber; + } + + /** + * 任务队列同时进行数量 + * @param queueSameTimeNumber 同一时间线程队列数量 + * @return {@link FileBreadthFirstSearchUtils} + */ + public synchronized FileBreadthFirstSearchUtils setQueueSameTimeNumber(final int queueSameTimeNumber) { + if (mRunning) { + return this; + } + this.mQueueSameTimeNumber = queueSameTimeNumber; + return this; + } + + /** + * 是否搜索中 + * @return {@code true} 搜索 / 运行中, {@code false} 非搜索 / 运行中 + */ + public boolean isRunning() { + return mRunning; + } + + /** + * 停止搜索 + */ + public void stop() { + mStop = true; + } + + /** + * 是否停止搜索 + * @return {@code true} 已停止搜索, {@code false} 搜索中 + */ + public boolean isStop() { + return mStop; + } + + /** + * 获取开始搜索时间 ( 毫秒 ) + * @return 开始搜索时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStartTime; + } + + /** + * 获取结束搜索时间 ( 毫秒 ) + * @return 结束搜索时间 ( 毫秒 ) + */ + public long getEndTime() { + return mEndTime; + } + + /** + * 获取延迟校验时间 ( 毫秒 ) + * @return 延迟线程校验时间 ( 毫秒 ) + */ + public long getDelayTime() { + return mDelayTime; + } + + /** + * 设置延迟校验时间 ( 毫秒 ) + * @param delayTimeMillis 延迟校验时间 ( 毫秒 ) + * @return {@link FileBreadthFirstSearchUtils} + */ + public FileBreadthFirstSearchUtils setDelayTime(final long delayTimeMillis) { + this.mDelayTime = delayTimeMillis; + return this; + } + + // = + + // 根目录对象 + private FileItem mRootFileItem; + // 判断是否运行中 + private boolean mRunning = false; + // 是否停止搜索 + private boolean mStop = false; + // 开始搜索时间 + private long mStartTime = 0L; + // 结束搜索时间 + private long mEndTime = 0L; + // 延迟时间 + private long mDelayTime = 50L; + // 任务队列同时进行数量 + private int mQueueSameTimeNumber = 5; + // 线程池 + private final ExecutorService mExecutor = Executors.newCachedThreadPool(); + // 任务队列 + private final LinkedBlockingQueue mTaskQueue = new LinkedBlockingQueue<>(); + + /** + * 搜索目录 + * @param path 根目录路径 + */ + public synchronized void query(final String path) { + if (mRunning) { + return; + } else if (path == null || path.trim().length() == 0) { + // 触发结束回调 + mInnerHandler.onEndListener(null, -1, -1); + return; + } + // 表示运行中 + mRunning = true; + mStop = false; + // 设置开始搜索时间 + mStartTime = System.currentTimeMillis(); + try { + // 获取根目录 File + File file = new File(path); + // 初始化根目录 + mRootFileItem = new FileItem(file); + // 判断是否文件 + if (file.isFile()) { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + return; + } + // 获取文件夹全部子文件 + String[] fileArrays = file.list(); + // 获取文件总数 + if (fileArrays != null && fileArrays.length != 0) { + new Thread(() -> { + // 查询文件 + queryFile(mRootFileItem.file, mRootFileItem); + // 循环队列 + whileQueue(); + }).start(); + } else { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "query"); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } + } + + /** + * 搜索文件 + * @param file 文件 + * @param fileItem 所在文件夹信息对象 ( 上一级目录 ) + */ + private void queryFile( + final File file, + final FileItem fileItem + ) { + try { + if (mStop) { + return; + } + if (file != null && file.exists()) { + // 判断是否处理 + if (mInnerHandler.isHandlerFile(file)) { + // 如果属于文件夹 + if (file.isDirectory()) { + // 获取文件夹全部子文件 + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File queryFile : files) { + // 属于文件夹 + if (queryFile.isDirectory()) { + if (mStop) { + return; + } + FileItem subFileItem = fileItem.put(queryFile); + // 添加任务 + mTaskQueue.offer(new FileQueue(queryFile, subFileItem)); + } else { // 属于文件 + if (!mStop && mInnerHandler.isAddToList(queryFile)) { + // 属于文件则直接保存 + fileItem.put(queryFile); + } + } + } + } else { // 属于文件 + if (!mStop && mInnerHandler.isAddToList(file)) { + // 属于文件则直接保存 + fileItem.put(file); + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "queryFile"); + } + } + + // ============ + // = 线程池处理 = + // ============ + + /** + * 循环队列 + */ + private void whileQueue() { + // 判断是否为 null + boolean isEmpty = mTaskQueue.isEmpty(); + // 循环则不处理 + while (!isEmpty) { + if (mStop) break; + // 获取线程活动数量 + int threadCount = ((ThreadPoolExecutor) mExecutor).getActiveCount(); + // 判断是否超过 + if (threadCount > mQueueSameTimeNumber) { + continue; + } + // 获取文件对象 + final FileQueue fileQueue = mTaskQueue.poll(); + // 判断是否为 null + if (fileQueue != null) { + mExecutor.execute(() -> queryFile(fileQueue.file, fileQueue.fileItem)); + } + + // 判断是否存在队列数据 + isEmpty = (mTaskQueue.isEmpty() && threadCount == 0); + if (isEmpty) { // 如果不存在, 防止搜索过快, 延迟再次判断 + if (mStop) break; + try { + Thread.sleep(mDelayTime); + } catch (Exception ignored) { + } + isEmpty = mTaskQueue.isEmpty(); + } + } + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(mRootFileItem, mStartTime, mEndTime); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java b/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java new file mode 100644 index 0000000000..5db82f5f4d --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/search/FileDepthFirstSearchUtils.java @@ -0,0 +1,295 @@ +package dev.utils.common.assist.search; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.JCLogUtils; + +/** + * detail: 文件深度优先搜索算法 ( 递归搜索某个目录下的全部文件 ) + * @author Ttt + */ +public final class FileDepthFirstSearchUtils { + + // 日志 TAG + private static final String TAG = FileDepthFirstSearchUtils.class.getSimpleName(); + + /** + * 构造函数 + */ + public FileDepthFirstSearchUtils() { + } + + /** + * 构造函数 + * @param searchHandler 搜索处理接口 + */ + public FileDepthFirstSearchUtils(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + } + + /** + * detail: 文件信息 Item + * @author Ttt + */ + public static final class FileItem { + + public FileItem(final File file) { + this.file = file; + } + + // 文件 + public File file; + + // 子文件夹目录 + public List listChilds = null; + } + + /** + * detail: 搜索处理接口 + * @author Ttt + */ + public interface SearchHandler { + + /** + * 判断是否处理该文件 + * @param file 文件 + * @return {@code true} 处理该文件, {@code false} 跳过该文件不处理 + */ + boolean isHandlerFile(File file); + + /** + * 是否添加到集合 + * @param file 文件 + * @return {@code true} 添加, {@code false} 不添加 + */ + boolean isAddToList(File file); + + /** + * 搜索结束监听 + * @param lists 根目录的子文件目录集合 + * @param startTime 开始扫描时间 + * @param endTime 扫描结束时间 + */ + void onEndListener( + List lists, + long startTime, + long endTime + ); + } + + // 搜索处理接口 + private SearchHandler mSearchHandler; + + // 内部实现接口 + private final SearchHandler mInnerHandler = new SearchHandler() { + @Override + public boolean isHandlerFile(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isHandlerFile(file); + } + return true; + } + + @Override + public boolean isAddToList(File file) { + if (mSearchHandler != null) { + return mSearchHandler.isAddToList(file); + } + return true; + } + + @Override + public void onEndListener( + List lists, + long startTime, + long endTime + ) { + // 表示非搜索中 + mRunning = false; + // 触发回调 + if (mSearchHandler != null) { + mSearchHandler.onEndListener(lists, startTime, endTime); + } + } + }; + + /** + * 设置搜索处理接口 + * @param searchHandler 搜索处理接口 + * @return {@link FileDepthFirstSearchUtils} + */ + public FileDepthFirstSearchUtils setSearchHandler(final SearchHandler searchHandler) { + this.mSearchHandler = searchHandler; + return this; + } + + /** + * 是否搜索中 + * @return {@code true} 搜索 / 运行中, {@code false} 非搜索 / 运行中 + */ + public boolean isRunning() { + return mRunning; + } + + /** + * 停止搜索 + */ + public void stop() { + mStop = true; + } + + /** + * 是否停止搜索 + * @return {@code true} 已停止搜索, {@code false} 搜索中 + */ + public boolean isStop() { + return mStop; + } + + /** + * 获取开始搜索时间 ( 毫秒 ) + * @return 开始搜索时间 ( 毫秒 ) + */ + public long getStartTime() { + return mStartTime; + } + + /** + * 获取结束搜索时间 ( 毫秒 ) + * @return 结束搜索时间 ( 毫秒 ) + */ + public long getEndTime() { + return mEndTime; + } + + // = + + // 判断是否运行中 + private boolean mRunning = false; + // 是否停止搜索 + private boolean mStop = false; + // 开始搜索时间 + private long mStartTime = 0L; + // 结束搜索时间 + private long mEndTime = 0L; + + /** + * 搜索目录 + * @param path 根目录路径 + * @param isRelation 是否关联到 Child List + */ + public synchronized void query( + final String path, + final boolean isRelation + ) { + if (mRunning) { + return; + } else if (path == null || path.trim().length() == 0) { + // 触发结束回调 + mInnerHandler.onEndListener(null, -1, -1); + return; + } + // 表示运行中 + mRunning = true; + mStop = false; + // 设置开始搜索时间 + mStartTime = System.currentTimeMillis(); + try { + // 获取根目录 File + final File file = new File(path); + // 判断是否文件 + if (file.isFile()) { + List lists = new ArrayList<>(); + lists.add(new FileItem(file)); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(lists, mStartTime, mEndTime); + return; + } + // 获取文件夹全部子文件 + String[] fileArrays = file.list(); + // 获取文件总数 + if (fileArrays != null && fileArrays.length != 0) { + new Thread(() -> { + List lists = new ArrayList<>(); + // 查询文件 + queryFile(file, lists, isRelation); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(lists, mStartTime, mEndTime); + }).start(); + } else { + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(null, mStartTime, mEndTime); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "query"); + // 触发结束回调 + mEndTime = System.currentTimeMillis(); + mInnerHandler.onEndListener(null, mStartTime, mEndTime); + } + } + + /** + * 搜索文件 + * @param file 文件 + * @param lists 保存数据源 + * @param isRelation 是否关联到 Child List + */ + private void queryFile( + final File file, + final List lists, + final boolean isRelation + ) { + try { + if (mStop) { + return; + } + if (file != null && file.exists()) { + // 判断是否处理 + if (mInnerHandler.isHandlerFile(file)) { + // 如果属于文件夹 + if (file.isDirectory()) { + // 获取文件夹全部子文件 + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File queryFile : files) { + if (isRelation) { + if (queryFile.isDirectory()) { + List childs = new ArrayList<>(); + // 查找文件 + queryFile(queryFile, childs, isRelation); + // 保存数据 + FileItem fileItem = new FileItem(queryFile); + fileItem.listChilds = childs; + lists.add(fileItem); + } else { + // 属于文件 + if (mInnerHandler.isAddToList(queryFile)) { + // 属于文件则直接保存 + lists.add(new FileItem(queryFile)); + } + } + } else { + // 查找文件 + queryFile(queryFile, lists, isRelation); + } + } + } else { // 属于文件 + if (mInnerHandler.isAddToList(file)) { + // 属于文件则直接保存 + lists.add(new FileItem(file)); + } + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "queryFile"); + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java b/lib/DevJava/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java new file mode 100644 index 0000000000..2ebad80411 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/url/DevJavaUrlParser.java @@ -0,0 +1,123 @@ +package dev.utils.common.assist.url; + +import java.util.LinkedHashMap; +import java.util.Map; + +import dev.utils.common.HttpParamsUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Dev 库 Java 通用 Url 解析器 + * @author Ttt + *
+ *     不依赖 android api
+ * 
+ */ +public class DevJavaUrlParser + implements UrlExtras.Parser { + + // 完整 Url + private String mUrl; + // Url 前缀 ( 去除参数部分 ) + private String mUrlPrefix; + // Url 参数部分字符串 + private String mUrlParams; + // Url Params Map + private Map mUrlParamsMap; + // Url Params Map ( 参数值进行 UrlDecode ) + private Map mUrlParamsDecodeMap; + // 是否解析、转换 Param Map + private boolean mConvertMap = true; + + // ==================== + // = UrlExtras.Parser = + // ==================== + + @Override + public UrlExtras.Parser reset(final String url) { + return new DevJavaUrlParser().setUrl(url); + } + + @Override + public UrlExtras.Parser setUrl(final String url) { + initialize(url); + return this; + } + + @Override + public String getUrl() { + return this.mUrl; + } + + @Override + public String getUrlByPrefix() { + return this.mUrlPrefix; + } + + @Override + public String getUrlByParams() { + return this.mUrlParams; + } + + @Override + public Map getUrlParams() { + return this.mUrlParamsMap; + } + + @Override + public Map getUrlParamsDecode() { + return this.mUrlParamsDecodeMap; + } + + @Override + public boolean isConvertMap() { + return this.mConvertMap; + } + + @Override + public UrlExtras.Parser setConvertMap(final boolean convertMap) { + this.mConvertMap = convertMap; + return this; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化方法 + *
+     *     会清空 url 字符串全部空格、Tab、换行符, 如有特殊符号需提前自行转义
+     * 
+ * @param url 待处理完整 Url + */ + private void initialize(final String url) { + this.mUrl = StringUtils.clearSpaceTabLine(url); + this.mUrlPrefix = null; + this.mUrlParams = null; + this.mUrlParamsMap = null; + this.mUrlParamsDecodeMap = null; + + if (StringUtils.isNotEmpty(mUrl)) { + String[] array = HttpParamsUtils.getUrlParamsArray(mUrl); + this.mUrlPrefix = array[0]; + this.mUrlParams = array[1]; + + if (mConvertMap && StringUtils.isNotEmpty(mUrlParams)) { + this.mUrlParamsMap = HttpParamsUtils.splitParams( + mUrlParams, false + ); + this.mUrlParamsDecodeMap = new LinkedHashMap<>(); + for (Map.Entry entry : mUrlParamsMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + + String decode = StringUtils.urlDecodeWhile(value, 10); + this.mUrlParamsDecodeMap.put( + key, StringUtils.checkValue(value, decode) + ); + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/assist/url/UrlExtras.java b/lib/DevJava/src/main/java/dev/utils/common/assist/url/UrlExtras.java new file mode 100644 index 0000000000..3c715bb53b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/assist/url/UrlExtras.java @@ -0,0 +1,168 @@ +package dev.utils.common.assist.url; + +import java.util.Map; + +/** + * detail: Url 携带信息解析 + * @author Ttt + */ +public class UrlExtras { + + // 完整 Url + private String mUrl; + // Url 解析器 + private Parser mParser; + + // ========== + // = 构造函数 = + // ========== + + public UrlExtras(final String url) { + this(url, new DevJavaUrlParser()); + } + + public UrlExtras( + final String url, + final Parser parser + ) { + this.mUrl = url; + setParser(parser); + } + + // ======== + // = 解析器 = + // ======== + + /** + * detail: Url 解析器 + * @author Ttt + */ + public interface Parser { + + /** + * 重置并返回一个新的解析器 + * @param url 完整 Url + * @return Parser + */ + Parser reset(String url); + + /** + * 设置完整 Url + * @param url Url + * @return Parser + */ + Parser setUrl(String url); + + /** + * 获取完整 Url + * @return Url + */ + String getUrl(); + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @return Url 前缀 + */ + String getUrlByPrefix(); + + /** + * 获取 Url 参数部分字符串 + * @return Url 参数部分字符串 + */ + String getUrlByParams(); + + /** + * 获取 Url Params Map + * @return Url Params Map + */ + Map getUrlParams(); + + /** + * 获取 Url Params Map ( 参数值进行 UrlDecode ) + * @return Url Params Map + */ + Map getUrlParamsDecode(); + + // = + + /** + * 是否解析、转换 Param Map + * @return {@code true} yes, {@code false} no + */ + boolean isConvertMap(); + + /** + * 设置是否解析、转换 Param Map + * @param convertMap {@code true} yes, {@code false} no + * @return Parser + */ + Parser setConvertMap(boolean convertMap); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取完整 Url + * @return Url + */ + public String getUrl() { + return mUrl; + } + + /** + * 获取 Url 前缀 ( 去除参数部分 ) + * @return Url 前缀 + */ + public String getUrlByPrefix() { + return (mParser != null) ? mParser.getUrlByPrefix() : null; + } + + /** + * 获取 Url 参数部分字符串 + * @return Url 参数部分字符串 + */ + public String getUrlByParams() { + return (mParser != null) ? mParser.getUrlByParams() : null; + } + + /** + * 获取 Url Params Map + * @return Url Params Map + */ + public Map getUrlParams() { + return (mParser != null) ? mParser.getUrlParams() : null; + } + + /** + * 获取 Url Params Map ( 参数值进行 UrlDecode ) + * @return Url Params Map + */ + public Map getUrlParamsDecode() { + return (mParser != null) ? mParser.getUrlParamsDecode() : null; + } + + // = + + /** + * 获取 Url 解析器 + * @return Parser + */ + public Parser getParser() { + return mParser; + } + + /** + * 设置 Url 解析器 + * @param parser Parser + * @return UrlExtras + */ + public UrlExtras setParser(final Parser parser) { + if (parser != null) { + parser.setUrl(mUrl); + } + this.mParser = parser; + return this; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64.java new file mode 100644 index 0000000000..2ad842f93b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64.java @@ -0,0 +1,781 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.utils.common.cipher; + +import java.io.UnsupportedEncodingException; + +/** + * detail: Base64 工具类 + * @author Android + */ +public final class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + + /** + * Flag to pass to Base64OutputStream to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + + /* package */ static abstract class Coder { + public byte[] output; + public int op; + + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process( + byte[] input, + int offset, + int len, + boolean finish + ); + + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + String str, + int flags + ) { + return decode(str.getBytes(), flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + byte[] input, + int flags + ) { + return decode(input, 0, input.length, flags); + } + + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + *

+ * The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode( + byte[] input, + int offset, + int len, + int flags + ) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len * 3 / 4]); + + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + + /* package */ static class Decoder + extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int[] DECODE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int[] DECODE_WEBSAFE = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + + /** + * Non-data values in the DECODE arrays. + */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + + final private int[] alphabet; + + public Decoder( + int flags, + byte[] output + ) { + this.output = output; + + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3 / 4 + 10; + } + + /** + * Decode another block of input data. + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process( + byte[] input, + int offset, + int len, + boolean finish + ) { + if (this.state == 6) return false; + + int p = offset; + len += offset; + + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p + 4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p + 1] & 0xff] << 12) | + (alphabet[input[p + 2] & 0xff] << 6) | + (alphabet[input[p + 3] & 0xff]))) >= 0) { + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + + // The fast path isn't available either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + + int d = alphabet[input[p++] & 0xff]; + + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op + 2] = (byte) value; + output[op + 1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op + 1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + + this.state = state; + this.op = op; + return true; + } + } + + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString( + byte[] input, + int flags + ) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString( + byte[] input, + int offset, + int len, + int flags + ) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode( + byte[] input, + int flags + ) { + return encode(input, 0, input.length, flags); + } + + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode( + byte[] input, + int offset, + int len, + int flags + ) { + Encoder encoder = new Encoder(flags, null); + + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: + break; + case 1: + output_len += 2; + break; + case 2: + output_len += 3; + break; + } + } + + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len - 1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + + assert encoder.op == output_len; + + return encoder.output; + } + + /* package */ static class Encoder + extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte[] ENCODE_WEBSAFE = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + + final private byte[] tail; + /* package */ int tailLen; + private int count; + + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + + public Encoder( + int flags, + byte[] output + ) { + this.output = output; + + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + + tail = new byte[2]; + tailLen = 0; + + count = do_newline ? LINE_GROUPS : -1; + } + + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8 / 5 + 10; + } + + public boolean process( + byte[] input, + int offset, + int len, + boolean finish + ) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + + int p = offset; + len += offset; + int v = -1; + + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + + switch (tailLen) { + case 0: + // There was no tail. + break; + + case 1: + if (p + 2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + ; + break; + + case 2: + if (p + 1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p + 3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p + 1] & 0xff) << 8) | + (input[p + 2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op + 1] = alphabet[(v >> 12) & 0x3f]; + output[op + 2] = alphabet[(v >> 6) & 0x3f]; + output[op + 3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + + if (p - tailLen == len - 1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p - tailLen == len - 2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + + if (p == len - 1) { + tail[tailLen++] = input[p]; + } else if (p == len - 2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p + 1]; + } + } + + this.op = op; + this.count = count; + + return true; + } + } + + private Base64() { + } // don't instantiate +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64Cipher.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64Cipher.java new file mode 100644 index 0000000000..5021a0966f --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/Base64Cipher.java @@ -0,0 +1,79 @@ +package dev.utils.common.cipher; + +/** + * detail: Base64 编解码 ( 并进行 ) 加解密 + * @author Ttt + */ +public class Base64Cipher + implements Cipher { + + // 中间加密层 + private Cipher mCipher; + // Base64 编解码 flags + private int mFlags = Base64.DEFAULT; + + /** + * 构造函数 + * @param flags Base64 编解码 flags + */ + public Base64Cipher(final int flags) { + this.mFlags = flags; + } + + /** + * 构造函数 + * @param cipher 加解密中间层 + */ + public Base64Cipher(final Cipher cipher) { + this.mCipher = cipher; + } + + /** + * 构造函数 + * @param cipher 加解密中间层 + * @param flags Base64 编解码 flags + */ + public Base64Cipher( + final Cipher cipher, + final int flags + ) { + this.mCipher = cipher; + this.mFlags = flags; + } + + // = + + /** + * 解码 + * @param data 待解码数据 + * @return 解码后的 byte[] + */ + @Override + public byte[] decrypt(byte[] data) { + if (data == null) return null; + // 先解码 + data = Base64.decode(data, mFlags); + // 再解密 + if (mCipher != null) { + data = mCipher.decrypt(data); + } + return data; + } + + /** + * 编码 + * @param data 待编码数据 + * @return 编码后的 byte[] + */ + @Override + public byte[] encrypt(byte[] data) { + if (data == null) return null; + // 先加密 + if (mCipher != null) { + data = mCipher.encrypt(data); + } + if (data == null) return null; + // 再编码 + return Base64.encode(data, mFlags); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/Cipher.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/Cipher.java new file mode 100644 index 0000000000..c54f4bc69d --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/Cipher.java @@ -0,0 +1,10 @@ +package dev.utils.common.cipher; + +/** + * detail: 通用加解密中间层 + * @author Ttt + */ +public interface Cipher + extends Decrypt, + Encrypt { +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/CipherUtils.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/CipherUtils.java new file mode 100644 index 0000000000..056d412067 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/CipherUtils.java @@ -0,0 +1,65 @@ +package dev.utils.common.cipher; + +import dev.utils.common.ConvertUtils; + +/** + * detail: 加密工具类 + * @author Ttt + */ +public final class CipherUtils { + + private CipherUtils() { + } + + /** + * 加密方法 + * @param object 待加密对象 + * @return 加密后的十六进制字符串 + */ + public static String encrypt(final Object object) { + return encrypt(object, null); + } + + /** + * 加密方法 + * @param object 待加密对象 + * @param cipher 加解密中间层 + * @return 加密后的十六进制字符串 + */ + public static String encrypt( + final Object object, + final Cipher cipher + ) { + if (object == null) return null; + byte[] bytes = ConvertUtils.objectToBytes(object); + if (cipher != null) bytes = cipher.encrypt(bytes); + return ConvertUtils.toHexString(bytes); + } + + // = + + /** + * 解密方法 + * @param hex 十六进制字符串 + * @return 解密后的对象 + */ + public static Object decrypt(final String hex) { + return decrypt(hex, null); + } + + /** + * 解密方法 + * @param hex 十六进制字符串 + * @param cipher 加解密中间层 + * @return 解密后的对象 + */ + public static Object decrypt( + final String hex, + final Cipher cipher + ) { + if (hex == null) return null; + byte[] bytes = ConvertUtils.decodeHex(hex.toCharArray()); + if (cipher != null) bytes = cipher.decrypt(bytes); + return ConvertUtils.bytesToObject(bytes); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/Decrypt.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/Decrypt.java new file mode 100644 index 0000000000..ab696494a6 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/Decrypt.java @@ -0,0 +1,15 @@ +package dev.utils.common.cipher; + +/** + * detail: 解密 ( 解码 ) 接口 + * @author Ttt + */ +public interface Decrypt { + + /** + * 解密 ( 解码 ) 方法 + * @param data 待解码数据 + * @return 解码后的 byte[] + */ + byte[] decrypt(byte[] data); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/cipher/Encrypt.java b/lib/DevJava/src/main/java/dev/utils/common/cipher/Encrypt.java new file mode 100644 index 0000000000..d802d33e72 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/cipher/Encrypt.java @@ -0,0 +1,15 @@ +package dev.utils.common.cipher; + +/** + * detail: 加密 ( 编码 ) 接口 + * @author Ttt + */ +public interface Encrypt { + + /** + * 加密 ( 编码 ) 方法 + * @param data 待编码数据 + * @return 编码后的 byte[] + */ + byte[] encrypt(byte[] data); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/ComparatorUtils.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/ComparatorUtils.java new file mode 100644 index 0000000000..a4347090c9 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/ComparatorUtils.java @@ -0,0 +1,479 @@ +package dev.utils.common.comparator; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import dev.utils.common.CollectionUtils; +import dev.utils.common.FileUtils; +import dev.utils.common.comparator.sort.DateSort; +import dev.utils.common.comparator.sort.DateSortAsc; +import dev.utils.common.comparator.sort.DateSortDesc; +import dev.utils.common.comparator.sort.DoubleSort; +import dev.utils.common.comparator.sort.DoubleSortAsc; +import dev.utils.common.comparator.sort.DoubleSortDesc; +import dev.utils.common.comparator.sort.FileLastModifiedSortAsc; +import dev.utils.common.comparator.sort.FileLastModifiedSortDesc; +import dev.utils.common.comparator.sort.FileLengthSortAsc; +import dev.utils.common.comparator.sort.FileLengthSortDesc; +import dev.utils.common.comparator.sort.FileNameSortAsc; +import dev.utils.common.comparator.sort.FileNameSortDesc; +import dev.utils.common.comparator.sort.FileSortAsc; +import dev.utils.common.comparator.sort.FileSortDesc; +import dev.utils.common.comparator.sort.FloatSort; +import dev.utils.common.comparator.sort.FloatSortAsc; +import dev.utils.common.comparator.sort.FloatSortDesc; +import dev.utils.common.comparator.sort.IntSort; +import dev.utils.common.comparator.sort.IntSortAsc; +import dev.utils.common.comparator.sort.IntSortDesc; +import dev.utils.common.comparator.sort.LongSort; +import dev.utils.common.comparator.sort.LongSortAsc; +import dev.utils.common.comparator.sort.LongSortDesc; +import dev.utils.common.comparator.sort.StringSort; +import dev.utils.common.comparator.sort.StringSortAsc; +import dev.utils.common.comparator.sort.StringSortDesc; +import dev.utils.common.comparator.sort.StringSortWindowsSimple; +import dev.utils.common.comparator.sort.StringSortWindowsSimple2; +import dev.utils.common.comparator.sort.WindowsExplorerFileSimpleComparator; +import dev.utils.common.comparator.sort.WindowsExplorerFileSimpleComparator2; +import dev.utils.common.comparator.sort.WindowsExplorerStringSimpleComparator; +import dev.utils.common.comparator.sort.WindowsExplorerStringSimpleComparator2; + +/** + * detail: 排序比较器工具类 + * @author Ttt + *

+ *     使用以下方法要求 List 中不能存在 null 数据
+ *     {@link #sort(List, Comparator)}
+ *     {@link #sortAsc(List)}
+ *     {@link #sortDesc(List)}
+ *     视情况可用以下方法清空 null 数据
+ *     {@link CollectionUtils#clearNull(Collection)}
+ *     

+ * File 排序可直接使用以下方法获取 List + * {@link FileUtils#listOrEmpty(File)} + * {@link FileUtils#listFilesOrEmpty(File)} + *
+ */ +public final class ComparatorUtils { + + private ComparatorUtils() { + } + + /** + * List 反转处理 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean reverse(final List list) { + if (list != null) { + Collections.reverse(list); + return true; + } + return false; + } + + /** + * List 排序处理 + * @param list 集合 + * @param comparator 排序比较器 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sort( + final List list, + final Comparator comparator + ) { + if (list != null) { + Collections.sort(list, comparator); + return true; + } + return false; + } + + /** + * List 升序处理 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static > boolean sortAsc(final List list) { + if (list != null) { + Collections.sort(list); + return true; + } + return false; + } + + /** + * List 降序处理 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDesc(final List list) { + return sort(list, Collections.reverseOrder()); + } + + // ======== + // = File = + // ======== + + /** + * 文件修改时间升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLastModifiedAsc(final List list) { + return sort(list, new FileLastModifiedSortAsc()); + } + + /** + * 文件修改时间降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLastModifiedDesc(final List list) { + return sort(list, new FileLastModifiedSortDesc()); + } + + /** + * 文件大小升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLengthAsc(final List list) { + return sort(list, new FileLengthSortAsc()); + } + + /** + * 文件大小降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileLengthDesc(final List list) { + return sort(list, new FileLengthSortDesc()); + } + + /** + * 文件名升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileNameAsc(final List list) { + return sort(list, new FileNameSortAsc()); + } + + /** + * 文件名降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileNameDesc(final List list) { + return sort(list, new FileNameSortDesc()); + } + + /** + * 文件升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileAsc(final List list) { + return sort(list, new FileSortAsc()); + } + + /** + * 文件降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFileDesc(final List list) { + return sort(list, new FileSortDesc()); + } + + // ======== + // = Date = + // ======== + + /** + * Date 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDateAsc(final List list) { + return sort(list, new DateSortAsc<>()); + } + + /** + * Date 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDateDesc(final List list) { + return sort(list, new DateSortDesc<>()); + } + + // ========= + // = Double = + // ========= + + /** + * Double 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDoubleAsc(final List list) { + return sort(list, new DoubleSortAsc<>()); + } + + /** + * Double 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortDoubleDesc(final List list) { + return sort(list, new DoubleSortDesc<>()); + } + + // ========= + // = Float = + // ========= + + /** + * Float 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFloatAsc(final List list) { + return sort(list, new FloatSortAsc<>()); + } + + /** + * Float 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortFloatDesc(final List list) { + return sort(list, new FloatSortDesc<>()); + } + + // ======= + // = Int = + // ======= + + /** + * Int 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortIntAsc(final List list) { + return sort(list, new IntSortAsc<>()); + } + + /** + * Int 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortIntDesc(final List list) { + return sort(list, new IntSortDesc<>()); + } + + // ======== + // = Long = + // ======== + + /** + * Long 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortLongAsc(final List list) { + return sort(list, new LongSortAsc<>()); + } + + /** + * Long 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortLongDesc(final List list) { + return sort(list, new LongSortDesc<>()); + } + + // ========= + // = String = + // ========= + + /** + * String 升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringAsc(final List list) { + return sort(list, new StringSortAsc<>()); + } + + /** + * String 降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringDesc(final List list) { + return sort(list, new StringSortDesc<>()); + } + + // = + + /** + * String Windows 排序比较器简单实现升序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimpleAsc(final List list) { + return sort(list, new StringSortWindowsSimple<>()); + } + + /** + * String Windows 排序比较器简单实现降序排序 + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimpleDesc(final List list) { + boolean result = sortStringWindowsSimpleAsc(list); + if (result) reverse(list); + return result; + } + + /** + * String Windows 排序比较器简单实现升序排序 ( 实现方式二 ) + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimple2Asc(final List list) { + return sort(list, new StringSortWindowsSimple2<>()); + } + + /** + * String Windows 排序比较器简单实现降序排序 ( 实现方式二 ) + * @param list 集合 + * @param 泛型 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortStringWindowsSimple2Desc(final List list) { + boolean result = sortStringWindowsSimple2Asc(list); + if (result) reverse(list); + return result; + } + + // ==================== + // = Windows Explorer = + // ==================== + + /** + * Windows 目录资源文件升序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparatorAsc(final List list) { + return sort(list, new WindowsExplorerFileSimpleComparator()); + } + + /** + * Windows 目录资源文件降序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparatorDesc(final List list) { + boolean result = sortWindowsExplorerFileSimpleComparatorAsc(list); + if (result) reverse(list); + return result; + } + + /** + * Windows 目录资源文件升序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparator2Asc(final List list) { + return sort(list, new WindowsExplorerFileSimpleComparator2()); + } + + /** + * Windows 目录资源文件降序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerFileSimpleComparator2Desc(final List list) { + boolean result = sortWindowsExplorerFileSimpleComparator2Asc(list); + if (result) reverse(list); + return result; + } + + // = + + /** + * Windows 目录资源文件名升序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparatorAsc(final List list) { + return sort(list, new WindowsExplorerStringSimpleComparator()); + } + + /** + * Windows 目录资源文件名降序排序 + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparatorDesc(final List list) { + boolean result = sortWindowsExplorerStringSimpleComparatorAsc(list); + if (result) reverse(list); + return result; + } + + /** + * Windows 目录资源文件名升序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparator2Asc(final List list) { + return sort(list, new WindowsExplorerStringSimpleComparator2()); + } + + /** + * Windows 目录资源文件名降序排序 ( 实现方式二 ) + * @param list 集合 + * @return {@code true} success, {@code false} fail + */ + public static boolean sortWindowsExplorerStringSimpleComparator2Desc(final List list) { + boolean result = sortWindowsExplorerStringSimpleComparator2Asc(list); + if (result) reverse(list); + return result; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSort.java new file mode 100644 index 0000000000..59c01a2658 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSort.java @@ -0,0 +1,12 @@ +package dev.utils.common.comparator.sort; + +import java.util.Date; + +/** + * detail: Date 排序值 + * @author Ttt + */ +public interface DateSort { + + Date getDateSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java new file mode 100644 index 0000000000..78a5c09fac --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortAsc.java @@ -0,0 +1,24 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.Date; + +/** + * detail: Date 升序排序 + * @author Ttt + */ +public class DateSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + Date date1 = (t != null) ? t.getDateSortValue() : null; + Date date2 = (t1 != null) ? t1.getDateSortValue() : null; + long value1 = (date1 != null) ? date1.getTime() : 0L; + long value2 = (date2 != null) ? date2.getTime() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java new file mode 100644 index 0000000000..7249586f47 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DateSortDesc.java @@ -0,0 +1,24 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.Date; + +/** + * detail: Date 降序排序 + * @author Ttt + */ +public class DateSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + Date date1 = (t != null) ? t.getDateSortValue() : null; + Date date2 = (t1 != null) ? t1.getDateSortValue() : null; + long value1 = (date1 != null) ? date1.getTime() : 0L; + long value2 = (date2 != null) ? date2.getTime() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java new file mode 100644 index 0000000000..642ae94f0d --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Double 排序值 + * @author Ttt + */ +public interface DoubleSort { + + double getDoubleSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java new file mode 100644 index 0000000000..c5a3355eba --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Double 升序排序 + * @author Ttt + */ +public class DoubleSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + double value1 = (t != null) ? t.getDoubleSortValue() : 0D; + double value2 = (t1 != null) ? t1.getDoubleSortValue() : 0D; + return Double.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java new file mode 100644 index 0000000000..ec2d2a3e9a --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/DoubleSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Double 降序排序 + * @author Ttt + */ +public class DoubleSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + double value1 = (t != null) ? t.getDoubleSortValue() : 0D; + double value2 = (t1 != null) ? t1.getDoubleSortValue() : 0D; + return Double.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java new file mode 100644 index 0000000000..d149b80729 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortAsc.java @@ -0,0 +1,22 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件修改时间升序排序 + * @author Ttt + */ +public class FileLastModifiedSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.lastModified() : 0L; + long value2 = (f1 != null) ? f1.lastModified() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java new file mode 100644 index 0000000000..be86ac298e --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLastModifiedSortDesc.java @@ -0,0 +1,22 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件修改时间降序排序 + * @author Ttt + */ +public class FileLastModifiedSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.lastModified() : 0L; + long value2 = (f1 != null) ? f1.lastModified() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java new file mode 100644 index 0000000000..ac69d9fed9 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortAsc.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件大小升序排序 + * @author Ttt + *
+ *     不考虑文件夹内部文件大小, 文件夹大小根据 API length() 进行比较
+ * 
+ */ +public class FileLengthSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.length() : 0L; + long value2 = (f1 != null) ? f1.length() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java new file mode 100644 index 0000000000..a6bd181246 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileLengthSortDesc.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件大小降序排序 + * @author Ttt + *
+ *     不考虑文件夹内部文件大小, 文件夹大小根据 API length() 进行比较
+ * 
+ */ +public class FileLengthSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + long value1 = (f != null) ? f.length() : 0L; + long value2 = (f1 != null) ? f1.length() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java new file mode 100644 index 0000000000..c1ca6a6052 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortAsc.java @@ -0,0 +1,29 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件名升序排序 + * @author Ttt + */ +public class FileNameSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + if (f.isDirectory() && f1.isFile()) { + return -1; + } + if (f.isFile() && f1.isDirectory()) { + return 1; + } + return f.getName().compareTo(f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java new file mode 100644 index 0000000000..7dd63a5338 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileNameSortDesc.java @@ -0,0 +1,29 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件名降序排序 + * @author Ttt + */ +public class FileNameSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return 0; + } + if (f.isDirectory() && f1.isFile()) { + return 1; + } + if (f.isFile() && f1.isDirectory()) { + return -1; + } + return f1.getName().compareTo(f.getName()); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java new file mode 100644 index 0000000000..d4e8fd6ca0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortAsc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件升序排序 + * @author Ttt + */ +public class FileSortAsc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return f.compareTo(f1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java new file mode 100644 index 0000000000..ebe96504b3 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FileSortDesc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: 文件降序排序 + * @author Ttt + */ +public class FileSortDesc + implements Comparator { + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return 0; + } + return f1.compareTo(f); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSort.java new file mode 100644 index 0000000000..ee56a1d948 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Float 排序值 + * @author Ttt + */ +public interface FloatSort { + + float getFloatSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java new file mode 100644 index 0000000000..44006fd6e0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Float 升序排序 + * @author Ttt + */ +public class FloatSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + float value1 = (t != null) ? t.getFloatSortValue() : 0F; + float value2 = (t1 != null) ? t1.getFloatSortValue() : 0F; + return Float.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java new file mode 100644 index 0000000000..ebd933634c --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/FloatSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Float 降序排序 + * @author Ttt + */ +public class FloatSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + float value1 = (t != null) ? t.getFloatSortValue() : 0F; + float value2 = (t1 != null) ? t1.getFloatSortValue() : 0F; + return Float.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSort.java new file mode 100644 index 0000000000..14bd095622 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Int 排序值 + * @author Ttt + */ +public interface IntSort { + + int getIntSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java new file mode 100644 index 0000000000..c6a8dd1fe7 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Int 升序排序 + * @author Ttt + */ +public class IntSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + int value1 = (t != null) ? t.getIntSortValue() : 0; + int value2 = (t1 != null) ? t1.getIntSortValue() : 0; + return Integer.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java new file mode 100644 index 0000000000..ae4488636d --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/IntSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Int 降序排序 + * @author Ttt + */ +public class IntSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + int value1 = (t != null) ? t.getIntSortValue() : 0; + int value2 = (t1 != null) ? t1.getIntSortValue() : 0; + return Integer.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSort.java new file mode 100644 index 0000000000..9a5e00ade0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: Long 排序值 + * @author Ttt + */ +public interface LongSort { + + long getLongSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java new file mode 100644 index 0000000000..a4f6982480 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortAsc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Long 升序排序 + * @author Ttt + */ +public class LongSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + long value1 = (t != null) ? t.getLongSortValue() : 0L; + long value2 = (t1 != null) ? t1.getLongSortValue() : 0L; + return Long.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java new file mode 100644 index 0000000000..30c204f2e8 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/LongSortDesc.java @@ -0,0 +1,21 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: Long 降序排序 + * @author Ttt + */ +public class LongSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + long value1 = (t != null) ? t.getLongSortValue() : 0L; + long value2 = (t1 != null) ? t1.getLongSortValue() : 0L; + return Long.compare(value2, value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSort.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSort.java new file mode 100644 index 0000000000..d05e7eef46 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSort.java @@ -0,0 +1,10 @@ +package dev.utils.common.comparator.sort; + +/** + * detail: String 排序值 + * @author Ttt + */ +public interface StringSort { + + String getStringSortValue(); +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java new file mode 100644 index 0000000000..4150baaf91 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortAsc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String 升序排序 + * @author Ttt + */ +public class StringSortAsc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return value1.compareTo(value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java new file mode 100644 index 0000000000..6ee185a4d0 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortDesc.java @@ -0,0 +1,23 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String 降序排序 + * @author Ttt + */ +public class StringSortDesc + implements Comparator { + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return value2.compareTo(value1); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java new file mode 100644 index 0000000000..003812e62d --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String Windows 排序比较器简单实现 + * @author Ttt + */ +public class StringSortWindowsSimple + implements Comparator { + + private final WindowsExplorerStringSimpleComparator COMPARATOR = new WindowsExplorerStringSimpleComparator(); + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return COMPARATOR.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java new file mode 100644 index 0000000000..bc87069fc9 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/StringSortWindowsSimple2.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; + +/** + * detail: String Windows 排序比较器简单实现 + * @author Ttt + */ +public class StringSortWindowsSimple2 + implements Comparator { + + private final WindowsExplorerStringSimpleComparator2 COMPARATOR = new WindowsExplorerStringSimpleComparator2(); + + @Override + public int compare( + T t, + T t1 + ) { + String value1 = (t != null) ? t.getStringSortValue() : null; + String value2 = (t1 != null) ? t1.getStringSortValue() : null; + if (value1 == null) value1 = ""; + if (value2 == null) value2 = ""; + return COMPARATOR.compare(value1, value2); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java new file mode 100644 index 0000000000..94067eeaf2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: Windows 目录资源文件排序比较器 + * @author Ttt + */ +public class WindowsExplorerFileSimpleComparator + implements Comparator { + + private final WindowsExplorerStringSimpleComparator COMPARATOR = new WindowsExplorerStringSimpleComparator(); + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return COMPARATOR.compare(f.getName(), f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java new file mode 100644 index 0000000000..644cc9833f --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerFileSimpleComparator2.java @@ -0,0 +1,25 @@ +package dev.utils.common.comparator.sort; + +import java.io.File; +import java.util.Comparator; + +/** + * detail: Windows 目录资源文件排序比较器 + * @author Ttt + */ +public class WindowsExplorerFileSimpleComparator2 + implements Comparator { + + private final WindowsExplorerStringSimpleComparator2 COMPARATOR = new WindowsExplorerStringSimpleComparator2(); + + @Override + public int compare( + File f, + File f1 + ) { + if (f == null || f1 == null) { + return -1; + } + return COMPARATOR.compare(f.getName(), f1.getName()); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java new file mode 100644 index 0000000000..0a30585b91 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator.java @@ -0,0 +1,93 @@ +package dev.utils.common.comparator.sort; + +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: Windows 目录资源文件名排序比较器 + * @author Ttt + *
+ *     非完全符合 Windows 目录页排序结果 ( 一定程度上相似 )
+ *     用于目录页对比排序, 而非全部子目录完整路径对比
+ *     

+ * 代码来源 + * @see + *
+ */ +public class WindowsExplorerStringSimpleComparator + implements Comparator { + + @Override + public int compare( + String o1, + String o2 + ) { + if (o1 == null || o2 == null) { + return -1; + } + return innerCompare(o1, o2); + } + + // ========== + // = 具体实现 = + // ========== + + private final Pattern splitPattern = Pattern.compile("^(.*?)(\\d*)(?:\\.([^.]*))?$"); + + private int innerCompare( + String str1, + String str2 + ) { + SplitFileName data1 = getSplitFileName(str1); + SplitFileName data2 = getSplitFileName(str2); + + // Compare the name part case insensitive. + int result = data1.name.compareToIgnoreCase(data2.name); + // If name is equal, then compare by number + if (result == 0) { + result = data1.number.compareTo(data2.number); + } + // If numbers are equal then compare by length text of number. This + // is valid because it differs only by heading zeros. Longer comes first. + if (result == 0) { + result = -Integer.compare(data1.numberText.length(), data2.numberText.length()); + } + // If all above is equal, compare by ext. + if (result == 0) { + result = data1.ext.compareTo(data2.ext); + } + return result; + } + + private SplitFileName getSplitFileName(String fileName) { + Matcher matcher = splitPattern.matcher(fileName); + if (matcher.matches()) { + return new SplitFileName(matcher.group(1), matcher.group(2), matcher.group(3)); + } else { + return new SplitFileName(fileName, "", ""); + } + } + + private static class SplitFileName { + + String name; + Long number; + String numberText; + String ext; + + public SplitFileName( + String name, + String numberText, + String ext + ) { + this.name = StringUtils.checkValue(name); + this.number = ConvertUtils.toLong(numberText, -1L); + this.numberText = StringUtils.checkValue(numberText); + this.ext = StringUtils.checkValue(ext); + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java new file mode 100644 index 0000000000..fac24f0601 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/comparator/sort/WindowsExplorerStringSimpleComparator2.java @@ -0,0 +1,94 @@ +package dev.utils.common.comparator.sort; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * detail: Windows 目录资源文件名排序比较器 + * @author Ttt + *
+ *     非完全符合 Windows 目录页排序结果 ( 一定程度上相似 )
+ *     用于目录页对比排序, 而非全部子目录完整路径对比
+ *     

+ * 代码来源 + * @see
+ *
+ */ +public class WindowsExplorerStringSimpleComparator2 + implements Comparator { + + @Override + public int compare( + String o1, + String o2 + ) { + if (o1 == null || o2 == null) { + return -1; + } + return innerCompare(o1, o2); + } + + // ========== + // = 具体实现 = + // ========== + + private final Pattern splitPattern = Pattern.compile("\\d+|\\.|\\s"); + + private int innerCompare( + String str1, + String str2 + ) { + Iterator i1 = splitStringPreserveDelimiter(str1).iterator(); + Iterator i2 = splitStringPreserveDelimiter(str2).iterator(); + while (true) { + // Til here all is equal. + if (!i1.hasNext() && !i2.hasNext()) { + return 0; + } + // first has no more parts -> comes first + if (!i1.hasNext() && i2.hasNext()) { + return -1; + } + // first has more parts than i2 -> comes after + if (i1.hasNext() && !i2.hasNext()) { + return 1; + } + + String data1 = i1.next(); + String data2 = i2.next(); + int result; + try { + // If both datas are numbers, then compare numbers + result = Long.compare(Long.parseLong(data1), Long.parseLong(data2)); + // If numbers are equal than longer comes first + if (result == 0) { + result = -Integer.compare(data1.length(), data2.length()); + } + } catch (NumberFormatException ex) { + // compare text case insensitive + result = data1.compareToIgnoreCase(data2); + } + + if (result != 0) { + return result; + } + } + } + + private List splitStringPreserveDelimiter(String str) { + Matcher matcher = splitPattern.matcher(str); + List list = new ArrayList<>(); + int pos = 0; + while (matcher.find()) { + list.add(str.substring(pos, matcher.start())); + list.add(matcher.group()); + pos = matcher.end(); + } + list.add(str.substring(pos)); + return list; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/AESUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/AESUtils.java new file mode 100644 index 0000000000..6c2b904cb2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/AESUtils.java @@ -0,0 +1,85 @@ +package dev.utils.common.encrypt; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: AES 对称加密工具类 + * @author Ttt + *
+ *     Advanced Encryption Standard 高级数据加密标准 ( 对称加密算法 )
+ *     AES 算法可以有效抵制针对 DES 的攻击算法
+ * 
+ */ +public final class AESUtils { + + private AESUtils() { + } + + // 日志 TAG + private static final String TAG = AESUtils.class.getSimpleName(); + + /** + * 生成密钥 + * @return 密钥 byte[] + */ + public static byte[] initKey() { + try { + KeyGenerator keyGen = KeyGenerator.getInstance("AES"); + keyGen.init(256); // 192 256 + SecretKey secretKey = keyGen.generateKey(); + return secretKey.getEncoded(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "initKey"); + } + return null; + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/CRCUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/CRCUtils.java new file mode 100644 index 0000000000..7b73d0e306 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/CRCUtils.java @@ -0,0 +1,84 @@ +package dev.utils.common.encrypt; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.zip.CRC32; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; + +/** + * detail: CRC 工具类 + * @author Ttt + *
+ *     Cyclic Redundancy Check 循环冗余校验
+ *     CRC 是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数
+ * 
+ */ +public final class CRCUtils { + + private CRCUtils() { + } + + // 日志 TAG + private static final String TAG = CRCUtils.class.getSimpleName(); + + /** + * 获取 CRC32 值 + * @param data 字符串数据 + * @return CRC32 long 值 + */ + public static long getCRC32(final String data) { + if (data == null) return -1L; + try { + CRC32 crc32 = new CRC32(); + crc32.update(data.getBytes()); + return crc32.getValue(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getCRC32"); + } + return -1L; + } + + /** + * 获取 CRC32 值 + * @param data 字符串数据 + * @return CRC32 字符串 + */ + public static String getCRC32ToHexString(final String data) { + if (data == null) return null; + try { + CRC32 crc32 = new CRC32(); + crc32.update(data.getBytes()); + return Long.toHexString(crc32.getValue()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getCRC32ToHexString"); + } + return null; + } + + /** + * 获取文件 CRC32 值 + * @param filePath 文件路径 + * @return 文件 CRC32 值 + */ + public static String getFileCRC32(final String filePath) { + if (filePath == null) return null; + InputStream is = null; + try { + is = new FileInputStream(filePath); + byte[] buffer = new byte[1024]; + CRC32 crc32 = new CRC32(); + int numRead; + while ((numRead = is.read(buffer)) > 0) { + crc32.update(buffer, 0, numRead); + } + return Long.toHexString(crc32.getValue()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileCRC32"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/DESUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/DESUtils.java new file mode 100644 index 0000000000..d024c8451c --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/DESUtils.java @@ -0,0 +1,88 @@ +package dev.utils.common.encrypt; + +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: DES 对称加密工具类 + * @author Ttt + *
+ *     Data Encryption Standard 数据加密标准 ( 对称加密算法 )
+ * 
+ */ +public final class DESUtils { + + private DESUtils() { + } + + // 日志 TAG + private static final String TAG = DESUtils.class.getSimpleName(); + + /** + * 获取可逆算法 DES 的密钥 + * @param key 前八个字节将被用来生成密钥 + * @return 可逆算法 DES 的密钥 + */ + public static Key getDESKey(final byte[] key) { + if (key == null) return null; + try { + DESKeySpec desKey = new DESKeySpec(key); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); + return keyFactory.generateSecret(desKey); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDESKey"); + } + return null; + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DES"); + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DES"); + Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/EncryptUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/EncryptUtils.java new file mode 100644 index 0000000000..124f62d551 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/EncryptUtils.java @@ -0,0 +1,1188 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.security.DigestInputStream; +import java.security.Key; +import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.Cipher; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; +import dev.utils.common.ArrayUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; +import dev.utils.common.cipher.Base64; + +/** + * detail: 加解密通用工具类 + * @author Blankj + * @author Ttt + */ +public final class EncryptUtils { + + private EncryptUtils() { + } + + // 日志 TAG + private static final String TAG = EncryptUtils.class.getSimpleName(); + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的数据 + */ + public static byte[] encryptMD2(final byte[] data) { + return hashTemplate(data, "MD2"); + } + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的十六进制字符串 + */ + public static String encryptMD2ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptMD2ToHexString(data.getBytes()); + } + + /** + * MD2 加密 + * @param data 待加密数据 + * @return MD2 加密后的十六进制字符串 + */ + public static String encryptMD2ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptMD2(data)); + } + + // = + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的数据 + */ + public static byte[] encryptMD5(final byte[] data) { + return hashTemplate(data, "MD5"); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptMD5ToHexString(data.getBytes()); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @param salt salt + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString( + final String data, + final String salt + ) { + if (data == null && salt == null) return null; + if (salt == null) return ConvertUtils.toHexString(encryptMD5(data.getBytes())); + if (data == null) return ConvertUtils.toHexString(encryptMD5(salt.getBytes())); + return ConvertUtils.toHexString(encryptMD5((data + salt).getBytes())); + } + + // = + + /** + * MD5 加密 + * @param data 待加密数据 + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptMD5(data)); + } + + /** + * MD5 加密 + * @param data 待加密数据 + * @param salt salt + * @return MD5 加密后的十六进制字符串 + */ + public static String encryptMD5ToHexString( + final byte[] data, + final byte[] salt + ) { + if (data == null && salt == null) return null; + if (salt == null) return ConvertUtils.toHexString(encryptMD5(data)); + if (data == null) return ConvertUtils.toHexString(encryptMD5(salt)); + // 拼接数据 + byte[] bytes = ArrayUtils.arrayCopy(data, salt); + return ConvertUtils.toHexString(encryptMD5(bytes)); + } + + // = + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] encryptMD5File(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return encryptMD5File(file); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String encryptMD5FileToHexString(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return encryptMD5FileToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String encryptMD5FileToHexString(final File file) { + return ConvertUtils.toHexString(encryptMD5File(file)); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] encryptMD5File(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, digest); + byte[] buffer = new byte[256 * 1024]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + digest = dis.getMessageDigest(); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encryptMD5File"); + return null; + } finally { + CloseUtils.closeIOQuietly(dis); + } + } + + // = + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据 + */ + public static byte[] encryptSHA1(final byte[] data) { + return hashTemplate(data, "SHA-1"); + } + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据转十六进制字符串 + */ + public static String encryptSHA1ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA1ToHexString(data.getBytes()); + } + + /** + * SHA1 加密 + * @param data 待加密数据 + * @return SHA1 加密后的数据转十六进制字符串 + */ + public static String encryptSHA1ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA1(data)); + } + + // = + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据 + */ + public static byte[] encryptSHA224(final byte[] data) { + return hashTemplate(data, "SHA224"); + } + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据转十六进制字符串 + */ + public static String encryptSHA224ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA224ToHexString(data.getBytes()); + } + + /** + * SHA224 加密 + * @param data 待加密数据 + * @return SHA224 加密后的数据转十六进制字符串 + */ + public static String encryptSHA224ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA224(data)); + } + + // = + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据 + */ + public static byte[] encryptSHA256(final byte[] data) { + return hashTemplate(data, "SHA-256"); + } + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据转十六进制字符串 + */ + public static String encryptSHA256ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA256ToHexString(data.getBytes()); + } + + /** + * SHA256 加密 + * @param data 待加密数据 + * @return SHA256 加密后的数据转十六进制 + */ + public static String encryptSHA256ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA256(data)); + } + + // = + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据 + */ + public static byte[] encryptSHA384(final byte[] data) { + return hashTemplate(data, "SHA-384"); + } + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据转十六进制 + */ + public static String encryptSHA384ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA384ToHexString(data.getBytes()); + } + + /** + * SHA384 加密 + * @param data 待加密数据 + * @return SHA384 加密后的数据转十六进制 + */ + public static String encryptSHA384ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA384(data)); + } + + // = + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据 + */ + public static byte[] encryptSHA512(final byte[] data) { + return hashTemplate(data, "SHA-512"); + } + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据转十六进制 + */ + public static String encryptSHA512ToHexString(final String data) { + if (data == null || data.length() == 0) return null; + return encryptSHA512ToHexString(data.getBytes()); + } + + /** + * SHA512 加密 + * @param data 待加密数据 + * @return SHA512 加密后的数据转十六进制 + */ + public static String encryptSHA512ToHexString(final byte[] data) { + return ConvertUtils.toHexString(encryptSHA512(data)); + } + + /** + * Hash 加密模版方法 + * @param data 待加密数据 + * @param algorithm 算法 + * @return 指定加密算法加密后的数据 + */ + public static byte[] hashTemplate( + final byte[] data, + final String algorithm + ) { + if (data == null || data.length == 0) return null; + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + digest.update(data); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hashTemplate"); + return null; + } + } + + // = + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据 + */ + public static byte[] encryptHmacMD5( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacMD5"); + } + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据转十六进制 + */ + public static String encryptHmacMD5ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacMD5ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacMD5 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacMD5 加密后的数据转十六进制 + */ + public static String encryptHmacMD5ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacMD5(data, key)); + } + + // = + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据 + */ + public static byte[] encryptHmacSHA1( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA1"); + } + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据转十六进制 + */ + public static String encryptHmacSHA1ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA1ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA1 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA1 加密后的数据转十六进制 + */ + public static String encryptHmacSHA1ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA1(data, key)); + } + + // = + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据 + */ + public static byte[] encryptHmacSHA224( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA224"); + } + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据转十六进制 + */ + public static String encryptHmacSHA224ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA224ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA224 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA224 加密后的数据转十六进制 + */ + public static String encryptHmacSHA224ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA224(data, key)); + } + + // = + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据 + */ + public static byte[] encryptHmacSHA256( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA256"); + } + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据转十六进制 + */ + public static String encryptHmacSHA256ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA256ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA256 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA256 加密后的数据转十六进制 + */ + public static String encryptHmacSHA256ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA256(data, key)); + } + + // = + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据 + */ + public static byte[] encryptHmacSHA384( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA384"); + } + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据转十六进制 + */ + public static String encryptHmacSHA384ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA384ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA384 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA384 加密后的数据转十六进制 + */ + public static String encryptHmacSHA384ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA384(data, key)); + } + + // = + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据 + */ + public static byte[] encryptHmacSHA512( + final byte[] data, + final byte[] key + ) { + return hmacTemplate(data, key, "HmacSHA512"); + } + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据转十六进制 + */ + public static String encryptHmacSHA512ToHexString( + final String data, + final String key + ) { + if (data == null || data.length() == 0 || key == null || key.length() == 0) return null; + return encryptHmacSHA512ToHexString(data.getBytes(), key.getBytes()); + } + + /** + * HmacSHA512 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return HmacSHA512 加密后的数据转十六进制 + */ + public static String encryptHmacSHA512ToHexString( + final byte[] data, + final byte[] key + ) { + return ConvertUtils.toHexString(encryptHmacSHA512(data, key)); + } + + /** + * Hmac 加密模版方法 + * @param data 待加密数据 + * @param key 密钥 + * @param algorithm 算法 + * @return 指定加密算法和密钥, 加密后的数据 + */ + public static byte[] hmacTemplate( + final byte[] data, + final byte[] key, + final String algorithm + ) { + if (data == null || data.length == 0 || key == null || key.length == 0) return null; + try { + SecretKeySpec secretKey = new SecretKeySpec(key, algorithm); + Mac mac = Mac.getInstance(algorithm); + mac.init(secretKey); + return mac.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "hmacTemplate"); + return null; + } + } + + // = + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据 + */ + public static byte[] encryptDES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DES", transformation, iv, true); + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据转 Base64 + */ + public static byte[] encryptDESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encryptDES(data, key, transformation, iv)); + } + + /** + * DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 加密后的数据转十六进制 + */ + public static String encryptDESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encryptDES(data, key, transformation, iv)); + } + + // = + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return DES 解密后的数据 + */ + public static byte[] decryptDES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DES", transformation, iv, false); + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 DES 解密后的数据 + */ + public static byte[] decryptDESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptDES(base64Decode(data), key, transformation, iv); + } + + /** + * DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 DES 解密后的数据 + */ + public static byte[] decryptDESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptDES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + // = + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据 + */ + public static byte[] encrypt3DES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DESede", transformation, iv, true); + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据转 Base64 + */ + public static byte[] encrypt3DESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encrypt3DES(data, key, transformation, iv)); + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 加密后的数据转十六进制 + */ + public static String encrypt3DESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encrypt3DES(data, key, transformation, iv)); + } + + // = + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 3DES 解密后的数据 + */ + public static byte[] decrypt3DES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "DESede", transformation, iv, false); + } + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 3DES 解密后的数据 + */ + public static byte[] decrypt3DESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decrypt3DES(base64Decode(data), key, transformation, iv); + } + + /** + * 3DES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 3DES 解密后的数据 + */ + public static byte[] decrypt3DESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decrypt3DES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + // = + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据 + */ + public static byte[] encryptAES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "AES", transformation, iv, true); + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据转 Base64 + */ + public static byte[] encryptAESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return base64Encode(encryptAES(data, key, transformation, iv)); + } + + /** + * AES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 加密后的数据转十六进制 + */ + public static String encryptAESToHexString( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return ConvertUtils.toHexString(encryptAES(data, key, transformation, iv)); + } + + // = + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return AES 解密后的数据 + */ + public static byte[] decryptAES( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return symmetricTemplate(data, key, "AES", transformation, iv, false); + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return Base64 解码后, 在进行 AES 解密后的数据 + */ + public static byte[] decryptAESToBase64( + final byte[] data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptAES(base64Decode(data), key, transformation, iv); + } + + /** + * AES 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @return 十六进制转换后, 在进行 AES 解密后的数据 + */ + public static byte[] decryptAESToHexString( + final String data, + final byte[] key, + final String transformation, + final byte[] iv + ) { + return decryptAES(ConvertUtils.decodeHex(data), key, transformation, iv); + } + + /** + * 对称加密模版方法 + * @param data 待加解密数据 + * @param key 密钥 + * @param algorithm 算法 + * @param transformation {@link Cipher#getInstance} transformation + * @param iv 算法参数 {@link AlgorithmParameterSpec} + * @param isEncrypt 是否加密处理 + * @return 指定加密算法, 加解密后的数据 + */ + public static byte[] symmetricTemplate( + final byte[] data, + final byte[] key, + final String algorithm, + final String transformation, + final byte[] iv, + final boolean isEncrypt + ) { + if (data == null || data.length == 0 || key == null || key.length == 0) return null; + try { + SecretKey secretKey; + if ("DES".equals(algorithm)) { + DESKeySpec desKey = new DESKeySpec(key); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(algorithm); + secretKey = keyFactory.generateSecret(desKey); + } else { + secretKey = new SecretKeySpec(key, algorithm); + } + Cipher cipher = Cipher.getInstance(transformation); + if (iv == null || iv.length == 0) { + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey); + } else { + AlgorithmParameterSpec params = new IvParameterSpec(iv); + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, params); + } + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "symmetricTemplate"); + return null; + } + } + + // = + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据 + */ + public static byte[] encryptRSA( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return rsaTemplate(data, key, isPublicKey, transformation, true); + } + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据转 Base64 + */ + public static byte[] encryptRSAToBase64( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return base64Encode(encryptRSA(data, key, isPublicKey, transformation)); + } + + /** + * RSA 加密 + * @param data 待加密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 加密后的数据转十六进制 + */ + public static String encryptRSAToHexString( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return ConvertUtils.toHexString(encryptRSA(data, key, isPublicKey, transformation)); + } + + // = + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return RSA 解密后的数据 + */ + public static byte[] decryptRSA( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return rsaTemplate(data, key, isPublicKey, transformation, false); + } + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return Base64 解码后, 在进行 RSA 解密后的数据 + */ + public static byte[] decryptRSAToBase64( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return decryptRSA(base64Decode(data), key, isPublicKey, transformation); + } + + /** + * RSA 解密 + * @param data 待解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @return 十六进制转换后, 在进行 RSA 解密后的数据 + */ + public static byte[] decryptRSAToHexString( + final String data, + final byte[] key, + final boolean isPublicKey, + final String transformation + ) { + return decryptRSA(ConvertUtils.decodeHex(data), key, isPublicKey, transformation); + } + + /** + * RSA 加解密模版方法 + * @param data 待加解密数据 + * @param key 密钥 + * @param isPublicKey {@code true} {@link X509EncodedKeySpec}, {@code false} {@link PKCS8EncodedKeySpec} + * @param transformation {@link Cipher#getInstance} transformation + * @param isEncrypt 是否加密处理 + * @return 指定加密算法, 加解密后的数据 + */ + public static byte[] rsaTemplate( + final byte[] data, + final byte[] key, + final boolean isPublicKey, + final String transformation, + final boolean isEncrypt + ) { + if (data == null || key == null) return null; + try { + int dataLength = data.length; + int keyLength = key.length; + if (dataLength == 0 || keyLength == 0) return null; + + Key rsaKey; + if (isPublicKey) { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(key); + rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec); + } else { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(key); + rsaKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); + } + if (rsaKey == null) return null; + Cipher cipher = Cipher.getInstance(transformation); + cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey); + int maxLen = isEncrypt ? 117 : 128; + int count = dataLength / maxLen; + if (count > 0) { + byte[] ret = new byte[0]; + byte[] buffer = new byte[maxLen]; + int index = 0; + for (int i = 0; i < count; i++) { + System.arraycopy(data, index, buffer, 0, maxLen); + ret = ArrayUtils.arrayCopy(ret, cipher.doFinal(buffer)); + index += maxLen; + } + if (index != dataLength) { + int restLen = dataLength - index; + buffer = new byte[restLen]; + System.arraycopy(data, index, buffer, 0, restLen); + ret = ArrayUtils.arrayCopy(ret, cipher.doFinal(buffer)); + } + return ret; + } else { + return cipher.doFinal(data); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "rsaTemplate"); + } + return null; + } + + // ========== + // = 私有方法 = + // ========== + + /** + * Base64 编码 + * @param input 待编码数据 + * @return Base64 编码后的 byte[] + */ + private static byte[] base64Encode(final byte[] input) { + if (input == null) return null; + return Base64.encode(input, Base64.NO_WRAP); + } + + /** + * Base64 解码 + * @param input 待解码数据 + * @return Base64 解码后的 byte[] + */ + private static byte[] base64Decode(final byte[] input) { + if (input == null) return null; + return Base64.decode(input, Base64.NO_WRAP); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/EscapeUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/EscapeUtils.java new file mode 100644 index 0000000000..27b40240b1 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/EscapeUtils.java @@ -0,0 +1,148 @@ +package dev.utils.common.encrypt; + +/** + * detail: 字符串 ( 编解码 ) 工具类 + * @author Ttt + */ +public final class EscapeUtils { + + private EscapeUtils() { + } + + /** + * 编码 + * @param data 待编码数据 + * @return 编码后的字符串 + */ + public static String escape(final String data) { + if (data == null) return null; + StringBuilder builder = new StringBuilder(); + for (int i = 0, len = data.length(); i < len; i++) { + int ch = data.charAt(i); + if ('A' <= ch && ch <= 'Z') { + builder.append((char) ch); + } else if ('a' <= ch && ch <= 'z') { + builder.append((char) ch); + } else if ('0' <= ch && ch <= '9') { + builder.append((char) ch); + } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' + || ch == '~' || ch == '*' || ch == '\'' || ch == '(' + || ch == ')') { + builder.append((char) ch); + } else if (ch <= 0x007F) { + builder.append('%'); + builder.append(HEX[ch]); + } else { + builder.append('%'); + builder.append('u'); + builder.append(HEX[(ch >>> 8)]); + builder.append(HEX[(0x00FF & ch)]); + } + } + return builder.toString(); + } + + /** + * 解码 + *
+     *     本方法不论参数 data 是否经过 escape() 编码, 均能获取正确的 ( 解码 ) 结果
+     * 
+ * @param data 待解码数据 + * @return 解码后的字符串 + */ + public static String unescape(final String data) { + if (data == null) return null; + StringBuilder builder = new StringBuilder(); + int i = 0; + int len = data.length(); + while (i < len) { + int ch = data.charAt(i); + if ('A' <= ch && ch <= 'Z') { + builder.append((char) ch); + } else if ('a' <= ch && ch <= 'z') { + builder.append((char) ch); + } else if ('0' <= ch && ch <= '9') { + builder.append((char) ch); + } else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' + || ch == '~' || ch == '*' || ch == '\'' || ch == '(' + || ch == ')') { + builder.append((char) ch); + } else if (ch == '%') { + int cint = 0; + if ('u' != data.charAt(i + 1)) { + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 1)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 2)]; + i += 2; + } else { + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 2)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 3)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 4)]; + cint = (cint << 4) | BYTE_VALUES[data.charAt(i + 5)]; + i += 5; + } + builder.append((char) cint); + } else { + builder.append((char) ch); + } + i++; + } + return builder.toString(); + } + + // = + + // 十六进制 0-255 + private static final String[] HEX = { + "00", "01", "02", "03", "04", "05", + "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", + "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26", + "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", + "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", + "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", + "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", + "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", + "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", + "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", + "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", + "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", + "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", + "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5", + "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0", + "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", + "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6", + "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1", + "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", + "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", + "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF" + }; + + private static final byte[] BYTE_VALUES = { + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F + }; +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/MD5Utils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/MD5Utils.java new file mode 100644 index 0000000000..97e7b5fa73 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/MD5Utils.java @@ -0,0 +1,154 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.security.DigestInputStream; +import java.security.MessageDigest; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: MD5 加密工具类 + * @author Ttt + *
+ *     Message Digest 消息摘要算法
+ * 
+ */ +public final class MD5Utils { + + private MD5Utils() { + } + + // 日志 TAG + private static final String TAG = MD5Utils.class.getSimpleName(); + + /** + * 加密内容 ( 32 位小写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5(final String data) { + if (data == null) return null; + try { + return md5(data.getBytes()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5"); + } + return null; + } + + /** + * 加密内容 ( 32 位小写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5(final byte[] data) { + if (data == null) return null; + try { + // 获取 MD5 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance("MD5"); + // 使用指定的字节更新摘要 + digest.update(data); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5"); + } + return null; + } + + /** + * 加密内容 ( 32 位大写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5Upper(final String data) { + if (data == null) return null; + try { + return md5Upper(data.getBytes()); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5Upper"); + } + return null; + } + + /** + * 加密内容 ( 32 位大写 MD5 ) + * @param data 待加密数据 + * @return MD5 加密后的字符串 + */ + public static String md5Upper(final byte[] data) { + if (data == null) return null; + try { + // 获取 MD5 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance("MD5"); + // 使用指定的字节更新摘要 + digest.update(data); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), false); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "md5Upper"); + } + return null; + } + + // = + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值 + */ + public static byte[] getFileMD5(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileMD5(file); + } + + /** + * 获取文件 MD5 值 + * @param filePath 文件路径 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileMD5ToHexString(file); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值转十六进制字符串 + */ + public static String getFileMD5ToHexString(final File file) { + return ConvertUtils.toHexString(getFileMD5(file)); + } + + /** + * 获取文件 MD5 值 + * @param file 文件 + * @return 文件 MD5 值 byte[] + */ + public static byte[] getFileMD5(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, digest); + byte[] buffer = new byte[256 * 1024]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + digest = dis.getMessageDigest(); + return digest.digest(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileMD5"); + return null; + } finally { + CloseUtils.closeIOQuietly(dis); + } + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/SHAUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/SHAUtils.java new file mode 100644 index 0000000000..abb7d8f086 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/SHAUtils.java @@ -0,0 +1,165 @@ +package dev.utils.common.encrypt; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.MessageDigest; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.ConvertUtils; +import dev.utils.common.StringUtils; + +/** + * detail: SHA 加密工具类 + * @author Ttt + */ +public final class SHAUtils { + + private SHAUtils() { + } + + // 日志 TAG + private static final String TAG = SHAUtils.class.getSimpleName(); + + /** + * 加密内容 SHA1 + * @param data 待加密数据 + * @return SHA1 加密后的字符串 + */ + public static String sha1(final String data) { + return shaHex(data, "SHA-1"); + } + + /** + * 加密内容 SHA224 + * @param data 待加密数据 + * @return SHA224 加密后的字符串 + */ + public static String sha224(final String data) { + return shaHex(data, "SHA-224"); + } + + /** + * 加密内容 SHA256 + * @param data 待加密数据 + * @return SHA256 加密后的字符串 + */ + public static String sha256(final String data) { + return shaHex(data, "SHA-256"); + } + + /** + * 加密内容 SHA384 + * @param data 待加密数据 + * @return SHA384 加密后的字符串 + */ + public static String sha384(final String data) { + return shaHex(data, "SHA-384"); + } + + /** + * 加密内容 SHA512 + * @param data 待加密数据 + * @return SHA512 加密后的字符串 + */ + public static String sha512(final String data) { + return shaHex(data, "SHA-512"); + } + + // = + + /** + * 获取文件 SHA1 值 + * @param filePath 文件路径 + * @return 文件 SHA1 字符串信息 + */ + public static String getFileSHA1(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileSHA(file, "SHA-1"); + } + + /** + * 获取文件 SHA1 值 + * @param file 文件 + * @return 文件 SHA1 字符串信息 + */ + public static String getFileSHA1(final File file) { + return getFileSHA(file, "SHA-1"); + } + + /** + * 获取文件 SHA256 值 + * @param filePath 文件路径 + * @return 文件 SHA256 字符串信息 + */ + public static String getFileSHA256(final String filePath) { + File file = StringUtils.isEmpty(filePath) ? null : new File(filePath); + return getFileSHA(file, "SHA-256"); + } + + /** + * 获取文件 SHA256 值 + * @param file 文件 + * @return 文件 SHA256 字符串信息 + */ + public static String getFileSHA256(final File file) { + return getFileSHA(file, "SHA-256"); + } + + // = + + /** + * 加密内容 SHA 模板 + * @param data 待加密数据 + * @param algorithm 算法 + * @return SHA 算法加密后的字符串 + */ + public static String shaHex( + final String data, + final String algorithm + ) { + if (data == null || algorithm == null) return null; + try { + byte[] bytes = data.getBytes(); + // 获取 SHA-1 摘要算法的 MessageDigest 对象 + MessageDigest digest = MessageDigest.getInstance(algorithm); + // 使用指定的字节更新摘要 + digest.update(bytes); + // 获取密文 + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "shaHex"); + } + return null; + } + + /** + * 获取文件 SHA 值 + * @param file 文件 + * @param algorithm 算法 + * @return 文件指定 SHA 字符串信息 + */ + public static String getFileSHA( + final File file, + final String algorithm + ) { + if (file == null || algorithm == null) return null; + InputStream is = null; + try { + is = new FileInputStream(file); + byte[] buffer = new byte[1024]; + MessageDigest digest = MessageDigest.getInstance(algorithm); + int numRead; + while ((numRead = is.read(buffer)) > 0) { + digest.update(buffer, 0, numRead); + } + return ConvertUtils.toHexString(digest.digest(), true); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getFileSHA"); + } finally { + CloseUtils.closeIOQuietly(is); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java new file mode 100644 index 0000000000..77ae64dc2e --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/TripleDESUtils.java @@ -0,0 +1,84 @@ +package dev.utils.common.encrypt; + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; + +import dev.utils.JCLogUtils; + +/** + * detail: 3DES 对称加密工具类 + * @author Ttt + *
+ *     Triple DES、DESede 进行了三重 DES 加密的算法 ( 对称加密算法 )
+ * 
+ */ +public final class TripleDESUtils { + + private TripleDESUtils() { + } + + // 日志 TAG + private static final String TAG = TripleDESUtils.class.getSimpleName(); + + /** + * 生成密钥 + * @return 密钥 byte[] + */ + public static byte[] initKey() { + try { + KeyGenerator keyGen = KeyGenerator.getInstance("DESede"); + keyGen.init(168); // 112 168 + SecretKey secretKey = keyGen.generateKey(); + return secretKey.getEncoded(); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "initKey"); + } + return null; + } + + /** + * 3DES 加密 + * @param data 待加密数据 + * @param key 密钥 + * @return 加密后的 byte[] + */ + public static byte[] encrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DESede"); + Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "encrypt"); + } + return null; + } + + /** + * 3DES 解密 + * @param data 待加密数据 + * @param key 密钥 + * @return 解密后的 byte[] + */ + public static byte[] decrypt( + final byte[] data, + final byte[] key + ) { + if (data == null || key == null) return null; + try { + SecretKey secretKey = new SecretKeySpec(key, "DESede"); + Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return cipher.doFinal(data); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "decrypt"); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/encrypt/XorUtils.java b/lib/DevJava/src/main/java/dev/utils/common/encrypt/XorUtils.java new file mode 100644 index 0000000000..5940b03e32 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/encrypt/XorUtils.java @@ -0,0 +1,95 @@ +package dev.utils.common.encrypt; + +/** + * detail: 异或工具类 + * @author Ttt + *
+ *     位运算可以实现很多高级、高效的运算
+ *     可用于 IM 二进制数据包加密
+ *     1. 能够实现加密
+ *     2. 采用异或加密算法不会改变二进制数据的长度这对二进制数据包封包起到不小的好处
+ *     也可用于记事本等场景
+ *     

+ * 参考链接 + * @see
+ *
+ */ +public final class XorUtils { + + private XorUtils() { + } + + /** + * 加解密 ( 固定 Key 方式 ) 这种方式 加解密 方法共用 + *
+     *     加密: byte[] bytes = encryptAsFix("123".getBytes());
+     *     解密: String str = new String(encryptAsFix(bytes));
+     * 
+ * @param data 待加解密数据 + * @return 加解密后的数据 byte[] + */ + public static byte[] encryptAsFix(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = 0; i < len; i++) { + data[i] ^= key; + } + return data; + } + + // = + + /** + * 加密 ( 非固定 Key 方式 ) + * @param data 待加密数据 + * @return 加密后的数据 byte[] + */ + public static byte[] encrypt(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = 0; i < len; i++) { + data[i] = (byte) (data[i] ^ key); + key = data[i]; + } + return data; + } + + /** + * 解密 ( 非固定 Key 方式 ) + * @param data 待解密数据 + * @return 解密后的数据 byte[] + */ + public static byte[] decrypt(final byte[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + int key = 0x12; + for (int i = len - 1; i > 0; i--) { + data[i] = (byte) (data[i] ^ data[i - 1]); + } + data[0] = (byte) (data[0] ^ key); + return data; + } + + // = + + /** + * 数据异或校验位计算 + * @param data 待计算数据 + * @return 校验位值 + */ + public static byte xorChecksum(final byte[] data) { + if (data == null) return 0; + int len = data.length; + if (len == 0) return 0; + byte value = data[0]; + for (int i = 1; i < len; i++) { + value ^= data[i]; + } + return value; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/file/FilePartAssist.java b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartAssist.java new file mode 100644 index 0000000000..ed7037e2bf --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartAssist.java @@ -0,0 +1,155 @@ +package dev.utils.common.file; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.common.CollectionUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 文件分片辅助类 + * @author Ttt + */ +public final class FilePartAssist { + + // 文件路径 + private final File file; + // 文件分片信息集合 + private final List filePartItems = new ArrayList<>(); + + /** + * 构造函数 + * @param file 文件 + * @param filePartItems 文件分片信息集合 + */ + public FilePartAssist( + final File file, + final List filePartItems + ) { + this.file = file; + if (filePartItems != null) { + this.filePartItems.addAll(filePartItems); + } + } + + /** + * 构造函数 + * @param filePath 文件路径 + * @param partCount 分片总数 + */ + public FilePartAssist( + final String filePath, + final int partCount + ) { + this(FileUtils.getFile(filePath), partCount); + } + + /** + * 构造函数 + * @param file 文件 + * @param partCount 分片总数 + */ + public FilePartAssist( + final File file, + final int partCount + ) { + this.file = file; + if (file != null && file.exists() && partCount > 0) { + // 原始文件总字节 + long fileByteLength = file.length(); + // 分片总字节 + long partByteLength = fileByteLength / partCount; + // 余数 ( 全部加到最后一个分片 ) + long remainder = fileByteLength - partByteLength * partCount; + // 当前分片字节累加总数 + long total = 0; + if (partCount > 1) { // 如果分片大于 1 个, 则处理前面的数量 + for (int i = 0, len = partCount - 1; i < len; i++) { + FilePartItem item = new FilePartItem( + i, partCount, partByteLength, fileByteLength, + total, total + partByteLength + ); + total += partByteLength; + filePartItems.add(item); + } + } + // 最后一个分片片段 + FilePartItem item = new FilePartItem( + partCount - 1, partCount, partByteLength, fileByteLength, + total, total + partByteLength + remainder + ); + filePartItems.add(item); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取文件 + * @return {@link File} + */ + public File getFile() { + return file; + } + + /** + * 获取文件名 + * @return 文件名 + */ + public String getFileName() { + return FileUtils.getFileName(file); + } + + /** + * 获取文件分片信息集合 + * @return {@link List} + */ + public List getFilePartItems() { + return filePartItems; + } + + /** + * 获取指定索引文件分片信息 + * @param partIndex 分片索引 + * @return {@link FilePartItem} + */ + public FilePartItem getFilePartItem(final int partIndex) { + return CollectionUtils.get(filePartItems, partIndex); + } + + /** + * 获取分片总数 + * @return 分片总数 + */ + public int getPartCount() { + return filePartItems.size(); + } + + /** + * 是否存在分片 + * @return {@code true} yes, {@code false} no + */ + public boolean existsPart() { + return getPartCount() != 0; + } + + /** + * 是否只有一个分片 + * @return {@code true} yes, {@code false} no + */ + public boolean isOnlyOne() { + return getPartCount() == 1; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public String getPartName(final int partIndex) { + return FilePartUtils.getPartName(getFileName(), partIndex); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/file/FilePartItem.java b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartItem.java new file mode 100644 index 0000000000..2a6d533330 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartItem.java @@ -0,0 +1,82 @@ +package dev.utils.common.file; + +/** + * detail: 文件分片信息 Item + * @author Ttt + */ +public class FilePartItem { + + // 分片索引 + public final int partIndex; + // 分片总数 + public final int partCount; + // 分片总字节 + public final long partByteLength; + // 原始文件总字节 + public final long fileByteLength; + // 分片字节开始索引 + public final long start; + // 分片字节结束索引 + public final long end; + + public FilePartItem( + int partIndex, + int partCount, + long partByteLength, + long fileByteLength, + long start, + long end + ) { + this.partIndex = partIndex; + this.partCount = partCount; + this.partByteLength = partByteLength; + this.fileByteLength = fileByteLength; + this.start = start; + this.end = end; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断是否 First Item + * @return {@code true} yes, {@code false} no + */ + public boolean isFirstItem() { + return partIndex == 0; + } + + /** + * 判断是否 Last Item + * @return {@code true} yes, {@code false} no + */ + public boolean isLastItem() { + return partIndex + 1 == partCount; + } + + /** + * 是否存在分片 + * @return {@code true} yes, {@code false} no + */ + public boolean existsPart() { + return partCount != 0; + } + + /** + * 是否只有一个分片 + * @return {@code true} yes, {@code false} no + */ + public boolean isOnlyOne() { + return partCount == 1; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param fileName 原始文件名 + * @return 分片文件名 + */ + public String getPartName(final String fileName) { + return FilePartUtils.getPartName(fileName, partIndex); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/file/FilePartUtils.java b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartUtils.java new file mode 100644 index 0000000000..bb3aea266b --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/file/FilePartUtils.java @@ -0,0 +1,765 @@ +package dev.utils.common.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import dev.utils.JCLogUtils; +import dev.utils.common.CloseUtils; +import dev.utils.common.FileUtils; + +/** + * detail: 文件分片工具类 + * @author Ttt + *
+ *     当下载大文件时, 如果网络不稳定或者程序异常退出, 会导致下载失败, 甚至重试多次仍无法完成下载
+ *     可以使用断点续传下载:
+ *     断点续传下载将需要下载的文件分成若干个分片分别下载, 所有分片都下载完成后, 将所有分片合并成完整的文件
+ *     也可以用于断点续传上传 ( 分片续传 )
+ *     

+ * RandomAccessFile 简介与使用 + * @see
+ *

+ * 可用 {@link FileUtils#getFileMD5(File)} 进行校验分片合并后与源文件 MD5 值是否一致 + *
+ */ +public final class FilePartUtils { + + private FilePartUtils() { + } + + // 日志 TAG + private static final String TAG = FilePartUtils.class.getSimpleName(); + // 分片文件后缀 + public static final String PART_SUFFIX = "_part_"; + // 分片数量 + public static final int PART_COUNT = 10; + // 分片片段允许最小值 byte ( 默认 1mb ) + public static final long MIN_LENGTH = 1048576L; + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param item {@link FilePartItem} + * @param fileName 原始文件名 + * @return 分片文件名 + */ + public static String getPartName( + final FilePartItem item, + final String fileName + ) { + return item != null ? item.getPartName(fileName) : null; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public static String getPartName( + final FilePartAssist assist, + final int partIndex + ) { + return assist != null ? assist.getPartName(partIndex) : null; + } + + /** + * 获取分片文件名 ( 后缀索引拼接 ) + * @param fileName 原始文件名 + * @param partIndex 分片索引 + * @return 分片文件名 + */ + public static String getPartName( + final String fileName, + final int partIndex + ) { + return String.format("%s%s%s", fileName, PART_SUFFIX, partIndex); + } + + // = + + /** + * 获取文件分片辅助类 + * @param filePath 文件路径 + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist(final String filePath) { + return getFilePartAssist(FileUtils.getFile(filePath), PART_COUNT, MIN_LENGTH); + } + + /** + * 获取文件分片辅助类 + * @param filePath 文件路径 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist( + final String filePath, + final int partCount, + final long minLength + ) { + return getFilePartAssist(FileUtils.getFile(filePath), partCount, minLength); + } + + /** + * 获取文件分片辅助类 + * @param file 文件 + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist(final File file) { + return getFilePartAssist(file, PART_COUNT, MIN_LENGTH); + } + + /** + * 获取文件分片辅助类 + * @param file 文件 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@link FilePartAssist} + */ + public static FilePartAssist getFilePartAssist( + final File file, + final int partCount, + final long minLength + ) { + boolean filePart = isFilePart(file, partCount, minLength); + return new FilePartAssist(file, filePart ? partCount : 1); + } + + // = + + /** + * 是否符合文件分片条件 + * @param filePath 文件路径 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart(final String filePath) { + return isFilePart(FileUtils.getFile(filePath), PART_COUNT, MIN_LENGTH); + } + + /** + * 是否符合文件分片条件 + * @param filePath 文件路径 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart( + final String filePath, + final int partCount, + final long minLength + ) { + return isFilePart(FileUtils.getFile(filePath), partCount, minLength); + } + + /** + * 是否符合文件分片条件 + * @param file 文件 + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart(final File file) { + return isFilePart(file, PART_COUNT, MIN_LENGTH); + } + + /** + * 是否符合文件分片条件 + * @param file 文件 + * @param partCount 分片总数 + * @param minLength 分片片段允许最小值 byte + * @return {@code true} yes, {@code false} no + */ + public static boolean isFilePart( + final File file, + final int partCount, + final long minLength + ) { + // 原始文件总字节 + long fileByteLength = FileUtils.getFileLength(file); + // 分片总字节 + long partByteLength = fileByteLength / partCount; + return partByteLength >= minLength; + } + + // ========== + // = 文件拆分 = + // ========== + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final long start, + final long end + ) { + return fileSplit(FileUtils.getFile(filePath), start, end); + } + + /** + * 文件拆分 + *
+     *     慎用, 防止内存溢出
+     * 
+ * @param file 文件 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final long start, + final long end + ) { + if (file != null && file.exists() && start >= 0 && end > start) { + RandomAccessFile raf = null; + try { + raf = new RandomAccessFile(file, "r"); + if (end > raf.length()) return null; + raf.seek(start); + byte[] bytes = new byte[(int) (end - start)]; + raf.read(bytes); + return bytes; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplit"); + } finally { + CloseUtils.closeIOQuietly(raf); + } + } + return null; + } + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final FilePartItem item + ) { + return fileSplit(FileUtils.getFile(filePath), item); + } + + /** + * 文件拆分 + * @param file 文件 + * @param item {@link FilePartItem} + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final FilePartItem item + ) { + if (file == null || item == null) return null; + return fileSplit(file, item.start, item.end); + } + + /** + * 文件拆分 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final String filePath, + final FilePartAssist assist, + final int partIndex + ) { + return fileSplit(FileUtils.getFile(filePath), assist, partIndex); + } + + /** + * 文件拆分 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param partIndex 分片索引 + * @return 指定位置数据 + */ + public static byte[] fileSplit( + final File file, + final FilePartAssist assist, + final int partIndex + ) { + if (file == null || assist == null) return null; + return fileSplit(file, assist.getFilePartItem(partIndex)); + } + + // = + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @param destFolderPath 存储目标文件夹地址 + * @param partName 分片文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final long start, + final long end, + final String destFolderPath, + final String partName + ) { + return fileSplitSave(FileUtils.getFile(filePath), start, end, destFolderPath, partName); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param start 分片字节开始索引 + * @param end 分片字节结束索引 + * @param destFolderPath 存储目标文件夹地址 + * @param partName 分片文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final long start, + final long end, + final String destFolderPath, + final String partName + ) { + if (file != null && file.exists() && start >= 0 && end > start) { + FileInputStream fis = null; + FileChannel inputChannel = null; + FileOutputStream fos = null; + FileChannel outputChannel = null; + try { + fis = new FileInputStream(file); + if (end > file.length()) return false; + inputChannel = fis.getChannel(); + fos = new FileOutputStream(new File(destFolderPath, partName)); + outputChannel = fos.getChannel(); + inputChannel.transferTo(start, (int) (end - start), outputChannel); + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitSave"); + } finally { + CloseUtils.closeIOQuietly(outputChannel, fos, inputChannel, fis); + } + } + return false; + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final FilePartItem item, + final String destFolderPath + ) { + return fileSplitSave(FileUtils.getFile(filePath), item, destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param item {@link FilePartItem} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final FilePartItem item, + final String destFolderPath + ) { + if (file == null || item == null) return false; + return fileSplitSave(file, item.start, item.end, destFolderPath, + item.getPartName(FileUtils.getFileName(file)) + ); + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + return fileSplitSave(FileUtils.getFile(filePath), assist, destFolderPath, partIndex); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSave( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + if (file == null || assist == null) return false; + return fileSplitSave(file, assist.getFilePartItem(partIndex), destFolderPath); + } + + // = + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final String filePath, + final String destFolderPath + ) { + return fileSplitSaves(FileUtils.getFile(filePath), destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final File file, + final String destFolderPath + ) { + return fileSplitSaves(file, getFilePartAssist(file), destFolderPath); + } + + /** + * 文件拆分并存储 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final String filePath, + final FilePartAssist assist, + final String destFolderPath + ) { + return fileSplitSaves(FileUtils.getFile(filePath), assist, destFolderPath); + } + + /** + * 文件拆分并存储 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 存储目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitSaves( + final File file, + final FilePartAssist assist, + final String destFolderPath + ) { + if (file == null || assist == null) return false; + if (!assist.existsPart()) return false; + String fileName = FileUtils.getFileName(file); + if (fileName == null) return false; + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + FilePartItem item = assist.getFilePartItem(i); + if (item != null) { + boolean result = fileSplitSave(file, item.start, item.end, destFolderPath, + item.getPartName(fileName) + ); + if (!result) return false; + } else { + return false; + } + } + return true; + } + + // ========== + // = 分片删除 = + // ========== + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param item {@link FilePartItem} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final String filePath, + final FilePartItem item, + final String destFolderPath + ) { + return fileSplitDelete(FileUtils.getFile(filePath), item, destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param item {@link FilePartItem} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final File file, + final FilePartItem item, + final String destFolderPath + ) { + if (file == null || item == null) return false; + return FileUtils.deleteFile( + FileUtils.getFile(destFolderPath, item.getPartName(FileUtils.getFileName(file))) + ); + } + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + return fileSplitDelete(FileUtils.getFile(filePath), assist, destFolderPath, partIndex); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @param partIndex 分片索引 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDelete( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final int partIndex + ) { + if (file == null || assist == null) return false; + return fileSplitDelete(file, assist.getFilePartItem(partIndex), destFolderPath); + } + + // = + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final String filePath, + final String destFolderPath + ) { + return fileSplitDeletes(FileUtils.getFile(filePath), destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final File file, + final String destFolderPath + ) { + return fileSplitDeletes(file, getFilePartAssist(file), destFolderPath); + } + + /** + * 删除拆分文件 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final String filePath, + final FilePartAssist assist, + final String destFolderPath + ) { + return fileSplitDeletes(FileUtils.getFile(filePath), assist, destFolderPath); + } + + /** + * 删除拆分文件 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 待删除目标文件夹地址 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitDeletes( + final File file, + final FilePartAssist assist, + final String destFolderPath + ) { + if (file == null || assist == null) return false; + if (!assist.existsPart()) return false; + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + fileSplitDelete(file, assist.getFilePartItem(i), destFolderPath); + } + return true; + } + + // ========== + // = 分片合并 = + // ========== + + /** + * 分片合并 + * @param filePath 文件路径 + * @param paths 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergePaths( + final String filePath, + final List paths + ) { + return fileSplitMergePaths(FileUtils.getFile(filePath), paths); + } + + /** + * 分片合并 + * @param file 文件 + * @param paths 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergePaths( + final File file, + final List paths + ) { + if (file == null || paths == null) return false; + return fileSplitMergeFiles(file, FileUtils.convertFiles(paths)); + } + + /** + * 分片合并 + * @param filePath 文件路径 + * @param files 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergeFiles( + final String filePath, + final List files + ) { + return fileSplitMergeFiles(FileUtils.getFile(filePath), files); + } + + /** + * 分片合并 + * @param file 文件 + * @param files 待合并文件 ( 按顺序 ) + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMergeFiles( + final File file, + final List files + ) { + if (file == null || files == null) return false; + if (files.isEmpty()) return false; + FileUtils.deleteFile(file); + RandomAccessFile raf = null; + RandomAccessFile reader = null; + try { + raf = new RandomAccessFile(file, "rw"); + for (int i = 0, len = files.size(); i < len; i++) { + reader = new RandomAccessFile(files.get(i), "r"); + byte[] buffer = new byte[1024]; + int readLen; // 读取的字节数 + while ((readLen = reader.read(buffer)) != -1) { + raf.write(buffer, 0, readLen); + } + CloseUtils.closeIOQuietly(reader); + } + return true; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitMergeFiles"); + FileUtils.deleteFile(file); + } finally { + CloseUtils.closeIOQuietly(reader, raf); + } + return false; + } + + /** + * 分片合并 + * @param filePath 文件路径 + * @param assist {@link FilePartAssist} + * @param destFolderPath 分片所在文件夹地址 + * @param fileName 原文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMerge( + final String filePath, + final FilePartAssist assist, + final String destFolderPath, + final String fileName + ) { + return fileSplitMerge(FileUtils.getFile(filePath), assist, destFolderPath, fileName); + } + + /** + * 分片合并 + * @param file 文件 + * @param assist {@link FilePartAssist} + * @param destFolderPath 分片所在文件夹地址 + * @param fileName 原文件名 + * @return {@code true} success, {@code false} fail + */ + public static boolean fileSplitMerge( + final File file, + final FilePartAssist assist, + final String destFolderPath, + final String fileName + ) { + if (file == null || assist == null || destFolderPath == null || fileName == null) { + return false; + } + if (!assist.existsPart()) return false; + List files = new ArrayList<>(); + try { + for (int i = 0, len = assist.getPartCount(); i < len; i++) { + FilePartItem item = assist.getFilePartItems().get(i); + File partFile = new File(destFolderPath, item.getPartName(fileName)); + files.add(partFile); + } + return fileSplitMergeFiles(file, files); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "fileSplitMerge"); + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/format/ArgsFormatter.java b/lib/DevJava/src/main/java/dev/utils/common/format/ArgsFormatter.java new file mode 100644 index 0000000000..188ab16f53 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/format/ArgsFormatter.java @@ -0,0 +1,204 @@ +package dev.utils.common.format; + +import dev.utils.JCLogUtils; + +/** + * detail: 可变数组格式化 + * @author Ttt + */ +public class ArgsFormatter { + + // 日志 TAG + private final String TAG = ArgsFormatter.class.getSimpleName(); + + // 开始占位说明符 + private final String startSpecifier; + // 中间占位说明符 + private final String middleSpecifier; + // 结尾占位说明符 + private final String endSpecifier; + // 是否抛出异常 + private final boolean throwError; + // 格式化异常默认值 + private final String defaultValue; + + /** + * 构造函数 + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + */ + public ArgsFormatter( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + this.startSpecifier = startSpecifier; + this.middleSpecifier = middleSpecifier; + this.endSpecifier = endSpecifier; + this.throwError = throwError; + this.defaultValue = defaultValue; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, middleSpecifier, + false, null + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, endSpecifier, + false, null + ); + } + + /** + * 获取 ArgsFormatter + * @param startSpecifier 开始占位说明符 + * @param middleSpecifier 中间占位说明符 + * @param endSpecifier 结尾占位说明符 + * @param throwError 是否抛出异常 + * @param defaultValue 格式化异常默认值 + * @return {@link ArgsFormatter} + */ + public static ArgsFormatter get( + final String startSpecifier, + final String middleSpecifier, + final String endSpecifier, + final boolean throwError, + final String defaultValue + ) { + return new ArgsFormatter( + startSpecifier, middleSpecifier, endSpecifier, + throwError, defaultValue + ); + } + + // ======= + // = get = + // ======= + + /** + * 获取开始占位说明符 + * @return 开始占位说明符 + */ + public String getStartSpecifier() { + return startSpecifier; + } + + /** + * 获取中间占位说明符 + * @return 中间占位说明符 + */ + public String getMiddleSpecifier() { + return middleSpecifier; + } + + /** + * 获取结尾占位说明符 + * @return 结尾占位说明符 + */ + public String getEndSpecifier() { + return endSpecifier; + } + + /** + * 是否抛出异常 + * @return {@code true} yes, {@code false} no + */ + public boolean isThrowError() { + return throwError; + } + + /** + * 获取格式化异常默认值 + * @return 格式化异常默认值 + */ + public String getDefaultValue() { + return defaultValue; + } + + // ============ + // = 格式化方法 = + // ============ + + /** + * 根据可变参数数量自动格式化 + * @param args 格式化参数 + * @return 格式化后的字符串 + */ + public String format(final Object... args) { + return formatByArray(args); + } + + /** + * 根据可变参数数量自动格式化 + * @param objects 格式化参数 + * @return 格式化后的字符串 + */ + public String formatByArray(final Object[] objects) { + if (objects != null && objects.length != 0) { + String format = createFormatString(objects); + try { + return String.format(format, objects); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "formatByArray"); + if (throwError) throw e; + } + } + return defaultValue; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 创建格式化占位说明符字符串 + * @param objects 格式化参数 + * @return 格式化占位说明符字符串 + */ + private String createFormatString(final Object[] objects) { + StringBuilder builder = new StringBuilder(); + builder.append(startSpecifier); + int length = objects.length; + for (int i = 1; i < length; i++) { + if (i == length - 1) { + builder.append(endSpecifier); + } else { + builder.append(middleSpecifier); + } + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/format/UnitSpanFormatter.java b/lib/DevJava/src/main/java/dev/utils/common/format/UnitSpanFormatter.java new file mode 100644 index 0000000000..631aabf275 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/format/UnitSpanFormatter.java @@ -0,0 +1,435 @@ +package dev.utils.common.format; + +import dev.utils.common.BigDecimalUtils; +import dev.utils.common.NumberUtils; + +/** + * detail: 单位数组范围格式化 + * @author Ttt + */ +public class UnitSpanFormatter { + + // 单位格式化精度 + private final int precision; + // 是否自动补 0 ( 只有 int、long 有效 ) + private final boolean appendZero; + // 格式化异常默认值 + private final String defaultValue; + + /** + * 构造函数 + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + */ + public UnitSpanFormatter( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + this.precision = precision; + this.appendZero = appendZero; + this.defaultValue = defaultValue; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision + ) { + return new UnitSpanFormatter(precision, false, null); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final String defaultValue + ) { + return new UnitSpanFormatter(precision, false, defaultValue); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final boolean appendZero + ) { + return new UnitSpanFormatter(precision, appendZero, null); + } + + /** + * 获取 UnitSpanFormatter + * @param precision 单位格式化精度 + * @param appendZero 是否自动补 0 ( 只有 int、long 有效 ) + * @param defaultValue 格式化异常默认值 + * @return {@link UnitSpanFormatter} + */ + public static UnitSpanFormatter get( + final int precision, + final boolean appendZero, + final String defaultValue + ) { + return new UnitSpanFormatter(precision, appendZero, defaultValue); + } + + // ======= + // = get = + // ======= + + /** + * 获取单位格式化精度 + * @return 单位格式化精度 + */ + public int getPrecision() { + return precision; + } + + /** + * 是否自动补 0 + * @return {@code true} yes, {@code false} no + */ + public boolean isAppendZero() { + return appendZero; + } + + /** + * 获取格式化异常默认值 + * @return 格式化异常默认值 + */ + public String getDefaultValue() { + return defaultValue; + } + + // ============ + // = 格式化方法 = + // ============ + + // ========== + // = double = + // ========== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final double[] values, + final String[] units + ) { + return format(values, units, null); + } + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String format( + final double[] values, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + BigDecimalUtils.Operation op = null; + if (operation != null) op = operation.clone(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + if (op != null) { + String value = op.setBigDecimal(values[i]).round() + .toPlainString(); + builder.append(value); + } else { + builder.append(values[i]); + } + builder.append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ========= + // = float = + // ========= + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final float[] values, + final String[] units + ) { + return format(values, units, null); + } + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String format( + final float[] values, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + BigDecimalUtils.Operation op = null; + if (operation != null) op = operation.clone(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + if (op != null) { + String value = op.setBigDecimal(values[i]).round() + .toPlainString(); + builder.append(value); + } else { + builder.append(values[i]); + } + builder.append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ======== + // = long = + // ======== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final long[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + long value = values[i]; + builder.append(NumberUtils.addZero(value, appendZero)) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ======= + // = int = + // ======= + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final int[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + long value = values[i]; + builder.append(NumberUtils.addZero(value, appendZero)) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // =========== + // = Generic = + // =========== + + /** + * 格式化 + * @param values 待格式化值 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String format( + final Object[] values, + final String[] units + ) { + if (precision > 0 && values != null && units != null) { + if (precision <= values.length && precision <= units.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < precision; i++) { + builder.append(values[i]) + .append(units[i]); + } + return builder.toString(); + } + } + return defaultValue; + } + + // ============= + // = Unit Span = + // ============= + + // ========== + // = double = + // ========== + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final double value, + final double[] unitSpans, + final String[] units + ) { + return formatBySpan(value, unitSpans, units, null); + } + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final double value, + final double[] unitSpans, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (unitSpans != null && units != null) { + double[] values = NumberUtils.calculateUnitD(value, unitSpans); + return format(values, units, operation); + } + return defaultValue; + } + + // ========= + // = float = + // ========= + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final float value, + final float[] unitSpans, + final String[] units + ) { + return formatBySpan(value, unitSpans, units, null); + } + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @param operation BigDecimal 操作包装类 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final float value, + final float[] unitSpans, + final String[] units, + final BigDecimalUtils.Operation operation + ) { + if (unitSpans != null && units != null) { + float[] values = NumberUtils.calculateUnitF(value, unitSpans); + return format(values, units, operation); + } + return defaultValue; + } + + // ======== + // = long = + // ======== + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final long value, + final long[] unitSpans, + final String[] units + ) { + if (unitSpans != null && units != null) { + long[] values = NumberUtils.calculateUnitL(value, unitSpans); + return format(values, units); + } + return defaultValue; + } + + // ======= + // = int = + // ======= + + /** + * 计算指定单位倍数格式化 + * @param value 待格式化值 + * @param unitSpans 对应单位范围 + * @param units 对应值单位 + * @return 单位数组范围格式化字符串 + */ + public String formatBySpan( + final int value, + final int[] unitSpans, + final String[] units + ) { + if (unitSpans != null && units != null) { + int[] values = NumberUtils.calculateUnitI(value, unitSpans); + return format(values, units); + } + return defaultValue; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/random/AliasMethod.java b/lib/DevJava/src/main/java/dev/utils/common/random/AliasMethod.java new file mode 100644 index 0000000000..0e7eb2b332 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/random/AliasMethod.java @@ -0,0 +1,179 @@ +package dev.utils.common.random; + +/** + * File: AliasMethod.java + * Author: Keith Schwarz (htiek@cs.stanford.edu) + * An implementation of the alias method implemented using Vose's algorithm. + * The alias method allows for efficient sampling of random values from a + * discrete probability distribution (i.e. rolling a loaded die) in O(1) time + * each after O(n) preprocessing time. + * For a complete writeup on the alias method, including the intuition and + * important proofs, please see the article "Darts, Dice, and Coins: Smpling + * from a Discrete Distribution" at + * http://www.keithschwarz.com/darts-dice-coins/ + */ + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Random; + +/** + * detail: 随机概率采样算法 + * @author Keith Schwarz + *
+ *     @see 
+ *     @see 
+ * 
+ */ +public final class AliasMethod { + + /* The random number generator used to sample from the distribution. */ + private final Random random; + + /* The probability and alias tables. */ + private final int[] alias; + private final double[] probability; + + /** + * Constructs a new AliasMethod to sample from a discrete distribution and + * hand back outcomes based on the probability distribution. + *

+ * Given as input a list of probabilities corresponding to outcomes 0, 1, + * ..., n - 1, this constructor creates the probability and alias tables + * needed to efficiently sample from this distribution. + * @param probabilities The list of probabilities. + */ + public AliasMethod(List probabilities) { + this(probabilities, new Random()); + } + + /** + * Constructs a new AliasMethod to sample from a discrete distribution and + * hand back outcomes based on the probability distribution. + *

+ * Given as input a list of probabilities corresponding to outcomes 0, 1, + * ..., n - 1, along with the random number generator that should be used + * as the underlying generator, this constructor creates the probability + * and alias tables needed to efficiently sample from this distribution. + * @param probabilities The list of probabilities. + * @param random The random number generator + */ + public AliasMethod( + List probabilities, + Random random + ) { + /* Begin by doing basic structural checks on the inputs. */ + if (probabilities == null || random == null) { + throw new NullPointerException(); + } + if (probabilities.size() == 0) { + throw new IllegalArgumentException("Probability vector must be nonempty."); + } + + /* Allocate space for the probability and alias tables. */ + probability = new double[probabilities.size()]; + alias = new int[probabilities.size()]; + + /* Store the underlying generator. */ + this.random = random; + + /* Compute the average probability and cache it for later use. */ + final double average = 1.0 / probabilities.size(); + + /* Make a copy of the probabilities list, since we will be making + * changes to it. + */ + probabilities = new ArrayList(probabilities); + + /* Create two stacks to act as worklists as we populate the tables. */ + Deque small = new ArrayDeque<>(); + Deque large = new ArrayDeque<>(); + + /* Populate the stacks with the input probabilities. */ + for (int i = 0; i < probabilities.size(); ++i) { + /* If the probability is below the average probability, then we add + * it to the small list; otherwise we add it to the large list. + */ + if (probabilities.get(i) >= average) { + large.add(i); + } else { + small.add(i); + } + } + + /* As a note: in the mathematical specification of the algorithm, we + * will always exhaust the small list before the big list. However, + * due to floating point inaccuracies, this is not necessarily true. + * Consequently, this inner loop (which tries to pair small and large + * elements) will have to check that both lists aren't empty. + */ + while (!small.isEmpty() && !large.isEmpty()) { + /* Get the index of the small and the large probabilities. */ + int less = small.removeLast(); + int more = large.removeLast(); + + /* These probabilities have not yet been scaled up to be such that + * 1/n is given weight 1.0. We do this here instead. + */ + probability[less] = probabilities.get(less) * probabilities.size(); + alias[less] = more; + + /* Decrease the probability of the larger one by the appropriate + * amount. + */ + probabilities.set( + more, + (probabilities.get(more) + probabilities.get(less)) - average + ); + + /* If the new probability is less than the average, add it into the + * small list; otherwise add it to the large list. + */ + if (probabilities.get(more) >= 1.0 / probabilities.size()) { + large.add(more); + } else { + small.add(more); + } + } + + /* At this point, everything is in one list, which means that the + * remaining probabilities should all be 1/n. Based on this, set them + * appropriately. Due to numerical issues, we can't be sure which + * stack will hold the entries, so we empty both. + */ + while (!small.isEmpty()) { + probability[small.removeLast()] = 1.0; + } + while (!large.isEmpty()) { + probability[large.removeLast()] = 1.0; + } + } + + /** + * 获取随机索引 ( 对应几率索引 ) + * Samples a value from the underlying distribution. + * @return A random value sampled from the underlying distribution. + */ + public int next() { + /* Generate a fair die roll to determine which column to inspect. */ + int column = random.nextInt(probability.length); + + /* Generate a biased coin toss to determine which option to pick. */ + boolean coinToss = random.nextDouble() < probability[column]; + + /* Based on the outcome, return either the column or its alias. */ + return coinToss ? column : alias[column]; + } + +// public static void main(String[] args) { +// // 使用方法 +// List lists = new ArrayList<>(); +// lists.add(0.15); // 0.15 几率 +// lists.add(0.85); // 0.85 几率 +// AliasMethod aliasMethod = new AliasMethod(lists); +// int result = aliasMethod.next(); +// // result 0 = 0.15, 1 = 0.85 +// } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadManager.java b/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadManager.java new file mode 100644 index 0000000000..31bb29bb6e --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadManager.java @@ -0,0 +1,103 @@ +package dev.utils.common.thread; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * detail: 线程池管理工具类 + * @author Ttt + */ +public final class DevThreadManager { + + private DevThreadManager() { + } + + // 默认通用线程池 ( 通过 CPU 自动处理 ) + private static final DevThreadPool sDevThreadPool = new DevThreadPool(DevThreadPool.DevThreadPoolType.CALC_CPU); + // 线程池数据 + private static final Map sThreadMaps = new LinkedHashMap<>(); + // 配置数据 + private static final Map sConfigMaps = new HashMap<>(); + + /** + * 获取 DevThreadManager 实例 + * @param threadNumber 线程数量 + * @return {@link DevThreadPool} + */ + public static synchronized DevThreadPool getInstance(final int threadNumber) { + // 初始化 key + String key = "n_" + threadNumber; + // 如果不为 null, 则直接返回 + DevThreadPool devThreadPool = sThreadMaps.get(key); + if (devThreadPool != null) { + return devThreadPool; + } + devThreadPool = new DevThreadPool(threadNumber); + sThreadMaps.put(key, devThreadPool); + return devThreadPool; + } + + /** + * 获取 DevThreadManager 实例 + * @param key 线程配置 key {@link DevThreadPool.DevThreadPoolType} or int-Integer + * @return {@link DevThreadPool} + */ + public static synchronized DevThreadPool getInstance(final String key) { + // 如果不为 null, 则直接返回 + DevThreadPool devThreadPool = sThreadMaps.get(key); + if (devThreadPool != null) { + return devThreadPool; + } + Object object = sConfigMaps.get(key); + if (object != null) { + try { + // 判断是否属于线程池类型 + if (object instanceof DevThreadPool.DevThreadPoolType) { + devThreadPool = new DevThreadPool((DevThreadPool.DevThreadPoolType) object); + } else if (object instanceof Integer) { + devThreadPool = new DevThreadPool((Integer) object); + } else { // 其他类型, 统一转换 Integer + devThreadPool = new DevThreadPool(Integer.parseInt((String) object)); + } + sThreadMaps.put(key, devThreadPool); + return devThreadPool; + } catch (Exception e) { + return sDevThreadPool; + } + } + return sDevThreadPool; + } + + // = + + /** + * 初始化配置信息 + * @param mapConfigs 线程配置信息 Map + */ + public static void initConfig(final Map mapConfigs) { + if (mapConfigs != null) { + sConfigMaps.putAll(mapConfigs); + } + } + + /** + * 添加配置信息 + * @param key 线程配置 key + * @param value 线程配置 value + */ + public static void putConfig( + final String key, + final Object value + ) { + sConfigMaps.put(key, value); + } + + /** + * 移除配置信息 + * @param key 线程配置 key + */ + public static void removeConfig(final String key) { + sConfigMaps.remove(key); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadPool.java b/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadPool.java new file mode 100644 index 0000000000..eaa6bad6a8 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/thread/DevThreadPool.java @@ -0,0 +1,489 @@ +package dev.utils.common.thread; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * detail: 线程池 ( 构建类 ) + * @author Ttt + *

+ *     @see 
+ *     

+ * 创建线程池 ( 参数 ) + * 1. 线程池里面管理多少个线程 + * 2. 如果排队满了, 额外的开的线程数 + * 3. 如果线程池没有要执行的任务存活多久 + * 4. 时间的单位 + * 5. 如果 线程池里管理的线程都已经用了, 剩下的任务临时存到 LinkedBlockingQueue 对象中排队 + * public ThreadPoolExecutor(int corePoolSize, + * int maximumPoolSize, + * long keepAliveTime, + * TimeUnit unit, + * BlockingQueue workQueue) { + * this (corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, + * Executors.defaultThreadFactory(), defaultHandler); + * } + *
+ */ +public final class DevThreadPool { + + // 线程池对象 + private final ExecutorService mThreadPool; + // 定时任务线程池 + private ScheduledExecutorService mScheduleExec; + + /** + * 构造函数 + * @param threadNumber 线程数量 + */ + public DevThreadPool(int threadNumber) { + // 如果小于等于 0, 则默认使用 1 + if (threadNumber <= 0) { + threadNumber = 1; + } + this.mThreadPool = Executors.newFixedThreadPool(threadNumber); + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(threadNumber); + } + + /** + * 构造函数 + * @param threadPool {@link ExecutorService} + */ + public DevThreadPool(final ExecutorService threadPool) { + this.mThreadPool = threadPool; + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(getThreads()); + } + + /** + * 构造函数 + * @param devThreadPoolType 线程初始化类型 {@link DevThreadPoolType} + */ + public DevThreadPool(final DevThreadPoolType devThreadPoolType) { + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(getThreads()); + // = + if (devThreadPoolType != null) { + switch (devThreadPoolType) { + case SINGLE: + mThreadPool = Executors.newSingleThreadExecutor(); + // 初始化定时器任务 + this.mScheduleExec = Executors.newScheduledThreadPool(1); + break; +// case AUTO_CPU: +// mThreadPool = Executors.newWorkStealingPool(); +// break; + case CALC_CPU: + mThreadPool = Executors.newFixedThreadPool(getThreads()); + break; + case CACHE: + mThreadPool = Executors.newCachedThreadPool(); + break; + default: + mThreadPool = Executors.newFixedThreadPool(getThreads()); + break; + } + } else { + mThreadPool = Executors.newFixedThreadPool(getThreads()); + } + } + + /** + * detail: 线程池初始化枚举类型 + * @author Ttt + *
+     *     @see 
+     *     @see 
+     * 
+ */ + public enum DevThreadPoolType { + + // 如果当前线程意外终止, 会创建一个新线程继续执行任务, 这和我们直接创建线程不同, 也和 newFixedThreadPool(1) 不同 + SINGLE, // newSingleThreadExecutor 获取的是一个单个的线程, 这个线程会保证你的任务执行完成 + + AUTO_CPU, // 根据 CPU 来创建 ( 自定义创建 ) + + CALC_CPU, // 手动计算 CPU 来创建 + + CACHE, // 可缓存线程池 + +// 1 newCachedThreadPool: 创建一个可缓存线程池, 如果线程池长度超过处理需要, 可灵活回收空闲线程, 若无可回收, 则新建线程 +// 2 newFixedThreadPool: 创建一个固定数目的、可重用的线程池 +// 3 newScheduledThreadPool: 创建一个定长线程池, 支持定时及周期性任务执行 +// 4 newSingleThreadExecutor: 创建一个单线程化的线程池, 它只会用唯一的工作线程来执行任务, 保证所有任务按照指定顺序 (FIFO、LIFO) 优先级执行 +// 5 newSingleThreadScheduledExcutor: 创建一个单例线程池, 定期或延时执行任务 +// 6 newWorkStealingPool: 创建持有足够线程的线程池来支持给定的并行级别, 并通过使用多个队列, 减少竞争 +// 它需要穿一个并行级别的参数, 如果不传, 则被设定为默认的 CPU 数量 +// 7 ForkJoinPool: 支持大任务分解成小任务的线程池, 这是 Java8 新增线程池, 通常配合 ForkJoinTask 接口的子类 RecursiveAction 或 RecursiveTask 使用 + } + + // = + + /** + * 获取线程数 + * @return {@link DevThreadPool#getCalcThreads()} + */ + public static int getThreads() { + return getCalcThreads(); + } + + /** + * 获取线程数 + * @return 自动计算 CPU 核心数 + */ + public static int getCalcThreads() { + // 获取 CPU 核心数 + int cpuNumber = Runtime.getRuntime().availableProcessors(); + // 如果小于等于 5, 则返回 5 + if (cpuNumber <= 5) { + return 5; + } else { // 大于 5 的情况 + // 防止线程数量过大, 当大于 10 的时候, 返回 10 + // 不大于 10 的时候, 默认返回 支持的数量 * 2 + 1 + return Math.min(cpuNumber * 2 + 1, 10); + } + } + + // = + + /** + * 加入到线程池任务队列 + * @param runnable 线程 + */ + public void execute(final Runnable runnable) { + if (mThreadPool != null && runnable != null) { + mThreadPool.execute(runnable); + } + } + + /** + * 加入到线程池任务队列 + * @param runnables 线程集合 + */ + public void execute(final List runnables) { + if (mThreadPool != null && runnables != null) { + for (Runnable command : runnables) { + if (command != null) { + mThreadPool.execute(command); + } + } + } + } + + /** + * 通过反射, 调用某个类的方法 + * @param method 方法 + * @param object 对象 + */ + public void execute( + final Method method, + final Object object + ) { + if (mThreadPool != null && method != null && object != null) { + mThreadPool.execute(() -> { + try { + method.invoke(object); + } catch (Exception ignore) { + } + }); + } + } + + // = + + /** + * shutdown 会等待所有提交的任务执行完成, 不管是正在执行还是保存在任务队列中的已提交任务 + * 待以前提交的任务执行完毕后关闭线程池 + * 启动一次顺序关闭, 执行以前提交的任务, 但不接受新任务 + * 如果已经关闭, 则调用没有作用 + */ + public void shutdown() { + if (mThreadPool != null) { + mThreadPool.shutdown(); + } + } + + /** + * shutdownNow 会尝试中断正在执行的任务 ( 其主要是中断一些指定方法如 sleep 方法 ) , 并且停止执行等待队列中提交的任务 + * 试图停止所有正在执行的活动任务, 暂停处理正在等待的任务, 并返回等待执行的任务列表 + * 无法保证能够停止正在处理的活动执行任务, 但是会尽力尝试 + * @return {@link List} + */ + public List shutdownNow() { + if (mThreadPool != null) { + return mThreadPool.shutdownNow(); + } + return null; + } + + /** + * 判断线程池是否已关闭 ( isShutDown 当调用 shutdown() 方法后返回为 true ) + * @return {@code true} yes, {@code false} no + */ + public boolean isShutdown() { + if (mThreadPool != null) { + return mThreadPool.isShutdown(); + } + return false; + } + + /** + * 若关闭后所有任务都已完成, 则返回 true + * 注意除非首先调用 shutdown 或 shutdownNow, 否则 isTerminated 永不为 true + * isTerminated 当调用 shutdown() 方法后, 并且所有提交的任务完成后返回为 true + * @return {@code true} yes, {@code false} no + */ + public boolean isTerminated() { + if (mThreadPool != null) { + return mThreadPool.isTerminated(); + } + return false; + } + + /** + * 请求关闭、发生超时或者当前线程中断 + * 无论哪一个首先发生之后, 都将导致阻塞, 直到所有任务完成执行 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @return {@code true} 请求成功, {@code false} 请求超时 + * @throws InterruptedException 终端异常 + */ + public boolean awaitTermination( + final long timeout, + final TimeUnit unit + ) + throws InterruptedException { + if (mThreadPool != null && unit != null) { + return mThreadPool.awaitTermination(timeout, unit); + } + return false; + } + + /** + * 提交一个 Callable 任务用于执行 + * 如果想立即阻塞任务的等待, 则可以使用 {@code result = threadPool.submit(aCallable).get();} 形式的构造 + * @param task 任务 + * @param 泛型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回该任务的结果 + */ + public Future submit(final Callable task) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task); + } + return null; + } + + /** + * 提交一个 Runnable 任务用于执行 + * @param task 任务 + * @param result 返回的结果 + * @param 泛型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回该任务的结果 + */ + public Future submit( + final Runnable task, + final T result + ) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task, result); + } + return null; + } + + /** + * 提交一个 Runnable 任务用于执行 + * @param task 任务 + * @param 未知类型 + * @return 表示任务等待完成的 Future, 该 Future 的 {@code get} 方法在成功完成时将会返回 null 结果 + */ + public Future submit(final Runnable task) { + if (mThreadPool != null && task != null) { + return mThreadPool.submit(task); + } + return null; + } + + /** + * 执行给定的任务 + * 当所有任务完成时, 返回保持任务状态和结果的 Future 列表 + * 返回列表的所有元素的 {@link Future#isDone} 为 {@code true} + * 注意, 可以正常地或通过抛出异常来终止已完成任务 + * 如果正在进行此操作时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param 泛型 + * @return 表示任务的 Future 列表, 列表顺序与给定任务列表的迭代器所生成的顺序相同, 每个任务都已完成 + * @throws InterruptedException 如果等待时发生中断, 在这种情况下取消尚未完成的任务 + */ + public List> invokeAll(final Collection> tasks) + throws InterruptedException { + if (mThreadPool != null && tasks != null) { + return mThreadPool.invokeAll(tasks); + } + return null; + } + + /** + * 执行给定的任务 + * 当所有任务完成或超时期满时 ( 无论哪个首先发生 ), 返回保持任务状态和结果的 Future 列表 + * 返回列表的所有元素的 {@link Future#isDone} 为 {@code true} + * 一旦返回后, 即取消尚未完成的任务 + * 注意, 可以正常地或通过抛出异常来终止已完成任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @param 泛型 + * @return 表示任务的 Future 列表, 列表顺序与给定任务列表的迭代器所生成的顺序相同 + * 如果操作未超时, 则已完成所有任务, 如果确实超时了, 则某些任务尚未完成 + * @throws InterruptedException 如果等待时发生中断, 在这种情况下取消尚未完成的任务 + */ + public List> invokeAll( + final Collection> tasks, + final long timeout, + final TimeUnit unit + ) + throws InterruptedException { + if (mThreadPool != null && tasks != null && unit != null) { + return mThreadPool.invokeAll(tasks, timeout, unit); + } + return null; + } + + /** + * 执行给定的任务 + * 如果某个任务已成功完成 ( 也就是未抛出异常 ), 则返回其结果 + * 一旦正常或异常返回后, 则取消尚未完成的任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param 泛型 + * @return 某个任务返回的结果 + * @throws InterruptedException 如果等待时发生中断 + * @throws ExecutionException 如果没有任务成功完成 + */ + public T invokeAny(final Collection> tasks) + throws InterruptedException, ExecutionException { + if (mThreadPool != null && tasks != null) { + return mThreadPool.invokeAny(tasks); + } + return null; + } + + /** + * 执行给定的任务 + * 如果在给定的超时期满前某个任务已成功完成 ( 也就是未抛出异常 ), 则返回其结果 + * 一旦正常或异常返回后, 则取消尚未完成的任务 + * 如果此操作正在进行时修改了给定的 collection, 则此方法的结果是不确定的 + * @param tasks 任务集合 + * @param timeout 最长等待时间 + * @param unit 时间单位 + * @param 泛型 + * @return 某个任务返回的结果 + * @throws InterruptedException 如果等待时发生中断 + * @throws ExecutionException 如果没有任务成功完成 + * @throws TimeoutException 如果在所有任务成功完成之前给定的超时期满 + */ + public T invokeAny( + final Collection> tasks, + final long timeout, + final TimeUnit unit + ) + throws InterruptedException, ExecutionException, TimeoutException { + if (mThreadPool != null && tasks != null && unit != null) { + return mThreadPool.invokeAny(tasks, timeout, unit); + } + return null; + } + + // = + + /** + * 延迟执行 Runnable 命令 + * @param command 命令 + * @param delay 延迟时间 + * @param unit 单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在完成后将返回 {@code null} + */ + public ScheduledFuture schedule( + final Runnable command, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.schedule(command, delay, unit); + } + return null; + } + + /** + * 延迟执行 Callable 命令 + * @param callable 命令 + * @param delay 延迟时间 + * @param unit 时间单位 + * @param 泛型 + * @return 可用于提取结果或取消的 ScheduledFuture + */ + public ScheduledFuture schedule( + final Callable callable, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && callable != null && unit != null) { + return mScheduleExec.schedule(callable, delay, unit); + } + return null; + } + + /** + * 延迟并循环执行命令 + * @param command 命令 + * @param initialDelay 首次执行的延迟时间 + * @param period 连续执行之间的周期 + * @param unit 时间单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在取消后将抛出异常 + */ + public ScheduledFuture scheduleWithFixedRate( + final Runnable command, + final long initialDelay, + final long period, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.scheduleAtFixedRate(command, initialDelay, period, unit); + } + return null; + } + + /** + * 延迟并以固定休息时间循环执行命令 + * @param command 命令 + * @param initialDelay 首次执行的延迟时间 + * @param delay 每一次执行终止和下一次执行开始之间的延迟 + * @param unit 时间单位 + * @param 未知类型 + * @return 表示挂起任务完成的 ScheduledFuture, 并且其 {@code get()} 方法在取消后将抛出异常 + */ + public ScheduledFuture scheduleWithFixedDelay( + final Runnable command, + final long initialDelay, + final long delay, + final TimeUnit unit + ) { + if (mScheduleExec != null && command != null && unit != null) { + return mScheduleExec.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } + return null; + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/validator/BankCheckUtils.java b/lib/DevJava/src/main/java/dev/utils/common/validator/BankCheckUtils.java new file mode 100644 index 0000000000..02683179e6 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/validator/BankCheckUtils.java @@ -0,0 +1,745 @@ +package dev.utils.common.validator; + +import dev.utils.JCLogUtils; + +/** + * detail: 银行卡管理工具类 + * @author AbrahamCaiJin + * @author Ttt + *
+ *     当你输入信用卡号码的时候, 有没有担心输错了而造成损失呢, 其实可以不必这么担心
+ *     因为并不是一个随便的信用卡号码都是合法的, 它必须通过 Luhn 算法来验证通过
+ *     

+ * 该校验的过程: + * 1、从卡号最后一位数字开始, 逆向将奇数位 (1、3、5 等等 ) 相加 + * 2、从卡号最后一位数字开始, 逆向将偶数位数字, 先乘以 2 ( 如果乘积为两位数, 则将其减去 9), 再求和 + * 3、将奇数位总和加上偶数位总和, 结果应该可以被 10 整除 + *

+ * 例如, 卡号是: 5432123456788881 + * 则奇数、偶数位 ( 用红色标出 ) 分布: 5432123456788881 + * 奇数位和 = 35 + * 偶数位乘以 2 ( 有些要减去 9) 的结果: 1 6 2 6 1 5 7 7, 求和 = 35 + * 最后 35 + 35 = 70 可以被 10 整除, 认定校验通过 + *
+ */ +public final class BankCheckUtils { + + private BankCheckUtils() { + } + + // 日志 TAG + private static final String TAG = BankCheckUtils.class.getSimpleName(); + + /** + * 校验银行卡卡号是否合法 + * @param cardId 待校验银行卡号 + * @return {@code true} yes, {@code false} no + */ + public static boolean checkBankCard(final String cardId) { + if (cardId == null || cardId.trim().length() == 0) return false; + try { + char bit = getBankCardCheckCode(cardId.substring(0, cardId.length() - 1)); + if (bit == 'N') return false; + return (cardId.charAt(cardId.length() - 1) == bit); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "checkBankCard"); + return false; + } + } + + /** + * 从不含校验位的银行卡卡号采用 Luhn 校验算法获取校验位 + * @param nonCheckCodeCardId 待校验银行卡号 + * @return 银行卡校验位 + */ + public static char getBankCardCheckCode(final String nonCheckCodeCardId) { + try { + if (nonCheckCodeCardId == null + || nonCheckCodeCardId.trim().length() == 0 + || !nonCheckCodeCardId.matches("\\d+")) { + // 如果传的不是数据返回 N + return 'N'; + } + char[] chs = nonCheckCodeCardId.trim().toCharArray(); + int luhnSum = 0; + int len = chs.length; + for (int i = len - 1, j = 0; i >= 0; i--, j++) { + int k = chs[i] - '0'; + if (j % 2 == 0) { + k *= 2; + k = k / 10 + k % 10; + } + luhnSum += k; + } + return (luhnSum % 10 == 0) ? '0' : (char) ((10 - luhnSum % 10) + '0'); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBankCardCheckCode"); + return 'N'; + } + } + + /** + * 通过银行卡的 前六位确定 判断银行开户行及卡种 + * @param cardBin 待校验银行卡号 + * @return 银行开户行及卡种 + */ + public static String getNameOfBank(final String cardBin) { + if (cardBin == null || cardBin.trim().length() < 6) return ""; + try { + // 通过银行卡的前六位确定 + String cardBin6 = cardBin.trim().substring(0, 6); + int index = -1; + for (int i = 0, len = BANK_BIN.length; i < len; i++) { + if (cardBin6.equals(BANK_BIN[i])) { + index = i; + } + } + if (index == -1) { + return ""; + } + return BANK_NAME[index]; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getNameOfBank"); + return ""; + } + } + + // = + + /* + * 银行卡是由「发卡行标识代码 + 自定义 + 校验码」等部分组成的 BIN 号 银联标准卡与以往发行的银行卡最直接的区别就是其卡号前六位数字的不同 + * 银行卡卡号的前六位是用来表示发卡银行或机构的, 称为「发卡行识别码」(Bank Identification Number, 缩写为「BIN」) + * 银联标准卡是由国内各家商业银行 ( 含邮储、信用社 ) 共同发行、符合银联业务规范和技术标准、卡正面右下角带有「银联」标识 + * ( 目前新发行的银联标准卡一定带有国际化的银联新标识, 新发的非银联标准卡使用旧的联网通用银联标识 )、卡号前六位为 622126 至 622925 之一的银行卡 + * 是中国银行卡产业共有的民族品牌 + */ + + // BIN 号 + private static final String[] BANK_BIN = { + "621098", "622150", "622151", + "622181", "622188", "955100", "621095", "620062", "621285", + "621798", "621799", "621797", "620529", "622199", "621096", + "621622", "623219", "621674", "623218", "621599", "370246", + "370248", "370249", "427010", "427018", "427019", "427020", + "427029", "427030", "427039", "370247", "438125", "438126", + "451804", "451810", "451811", "458071", "489734", "489735", + "489736", "510529", "427062", "524091", "427064", "530970", + "530990", "558360", "620200", "620302", "620402", "620403", + "620404", "524047", "620406", "620407", "525498", "620409", + "620410", "620411", "620412", "620502", "620503", "620405", + "620408", "620512", "620602", "620604", "620607", "620611", + "620612", "620704", "620706", "620707", "620708", "620709", + "620710", "620609", "620712", "620713", "620714", "620802", + "620711", "620904", "620905", "621001", "620902", "621103", + "621105", "621106", "621107", "621102", "621203", "621204", + "621205", "621206", "621207", "621208", "621209", "621210", + "621302", "621303", "621202", "621305", "621306", "621307", + "621309", "621311", "621313", "621211", "621315", "621304", + "621402", "621404", "621405", "621406", "621407", "621408", + "621409", "621410", "621502", "621317", "621511", "621602", + "621603", "621604", "621605", "621608", "621609", "621610", + "621611", "621612", "621613", "621614", "621615", "621616", + "621617", "621607", "621606", "621804", "621807", "621813", + "621814", "621817", "621901", "621904", "621905", "621906", + "621907", "621908", "621909", "621910", "621911", "621912", + "621913", "621915", "622002", "621903", "622004", "622005", + "622006", "622007", "622008", "622010", "622011", "622012", + "621914", "622015", "622016", "622003", "622018", "622019", + "622020", "622102", "622103", "622104", "622105", "622013", + "622111", "622114", "622200", "622017", "622202", "622203", + "622208", "622210", "622211", "622212", "622213", "622214", + "622110", "622220", "622223", "622225", "622229", "622230", + "622231", "622232", "622233", "622234", "622235", "622237", + "622215", "622239", "622240", "622245", "622224", "622303", + "622304", "622305", "622306", "622307", "622308", "622309", + "622238", "622314", "622315", "622317", "622302", "622402", + "622403", "622404", "622313", "622504", "622505", "622509", + "622513", "622517", "622502", "622604", "622605", "622606", + "622510", "622703", "622715", "622806", "622902", "622903", + "622706", "623002", "623006", "623008", "623011", "623012", + "622904", "623015", "623100", "623202", "623301", "623400", + "623500", "623602", "623803", "623901", "623014", "624100", + "624200", "624301", "624402", "62451804", "62451810", "62451811", + "62458071", "623700", "628288", "624000", "628286", "622206", + "621225", "526836", "513685", "543098", "458441", "620058", + "621281", "622246", "900000", "544210", "548943", "370267", + "621558", "621559", "621722", "621723", "620086", "621226", + "402791", "427028", "427038", "548259", "356879", "356880", + "356881", "356882", "528856", "621618", "620516", "621227", + "621721", "900010", "625330", "625331", "625332", "623062", + "622236", "621670", "524374", "550213", "374738", "374739", + "621288", "625708", "625709", "622597", "622599", "360883", + "360884", "625865", "625866", "625899", "621376", "620054", + "620142", "621428", "625939", "621434", "625987", "621761", + "621749", "620184", "621300", "621378", "625114", "622159", + "621720", "625021", "625022", "621379", "620114", "620146", + "621724", "625918", "621371", "620143", "620149", "621414", + "625914", "621375", "620187", "621433", "625986", "621370", + "625925", "622926", "622927", "622928", "622929", "622930", + "622931", "620124", "620183", "620561", "625116", "622227", + "621372", "621464", "625942", "622158", "625917", "621765", + "620094", "620186", "621719", "621719", "621750", "621377", + "620148", "620185", "621374", "621731", "621781", "552599", + "623206", "621671", "620059", "403361", "404117", "404118", + "404119", "404120", "404121", "463758", "514027", "519412", + "519413", "520082", "520083", "558730", "621282", "621336", + "621619", "622821", "622822", "622823", "622824", "622825", + "622826", "622827", "622828", "622836", "622837", "622840", + "622841", "622843", "622844", "622845", "622846", "622847", + "622848", "622849", "623018", "625996", "625997", "625998", + "628268", "625826", "625827", "548478", "544243", "622820", + "622830", "622838", "625336", "628269", "620501", "621660", + "621661", "621662", "621663", "621665", "621667", "621668", + "621669", "621666", "625908", "625910", "625909", "356833", + "356835", "409665", "409666", "409668", "409669", "409670", + "409671", "409672", "456351", "512315", "512316", "512411", + "512412", "514957", "409667", "518378", "518379", "518474", + "518475", "518476", "438088", "524865", "525745", "525746", + "547766", "552742", "553131", "558868", "514958", "622752", + "622753", "622755", "524864", "622757", "622758", "622759", + "622760", "622761", "622762", "622763", "601382", "622756", + "628388", "621256", "621212", "620514", "622754", "622764", + "518377", "622765", "622788", "621283", "620061", "621725", + "620040", "558869", "621330", "621331", "621332", "621333", + "621297", "377677", "621568", "621569", "625905", "625906", + "625907", "628313", "625333", "628312", "623208", "621620", + "621756", "621757", "621758", "621759", "621785", "621786", + "621787", "621788", "621789", "621790", "621672", "625337", + "625338", "625568", "621648", "621248", "621249", "622750", + "622751", "622771", "622772", "622770", "625145", "620531", + "620210", "620211", "622479", "622480", "622273", "622274", + "621231", "621638", "621334", "625140", "621395", "622725", + "622728", "621284", "421349", "434061", "434062", "436728", + "436742", "453242", "491031", "524094", "526410", "544033", + "552245", "589970", "620060", "621080", "621081", "621466", + "621467", "621488", "621499", "621598", "621621", "621700", + "622280", "622700", "622707", "622966", "622988", "625955", + "625956", "553242", "621082", "621673", "623211", "356896", + "356899", "356895", "436718", "436738", "436745", "436748", + "489592", "531693", "532450", "532458", "544887", "552801", + "557080", "558895", "559051", "622166", "622168", "622708", + "625964", "625965", "625966", "628266", "628366", "625362", + "625363", "628316", "628317", "620021", "620521", "405512", + "601428", "405512", "434910", "458123", "458124", "520169", + "522964", "552853", "601428", "622250", "622251", "521899", + "622254", "622255", "622256", "622257", "622258", "622259", + "622253", "622261", "622284", "622656", "628216", "622252", + "66405512", "622260", "66601428", "955590", "955591", "955592", + "955593", "628218", "622262", "621069", "620013", "625028", + "625029", "621436", "621002", "621335", "433670", "433680", + "442729", "442730", "620082", "622690", "622691", "622692", + "622696", "622698", "622998", "622999", "433671", "968807", + "968808", "968809", "621771", "621767", "621768", "621770", + "621772", "621773", "620527", "356837", "356838", "486497", + "622660", "622662", "622663", "622664", "622665", "622666", + "622667", "622669", "622670", "622671", "622672", "622668", + "622661", "622674", "622673", "620518", "621489", "621492", + "620535", "623156", "621490", "621491", "620085", "623155", + "623157", "623158", "623159", "999999", "621222", "623020", + "623021", "623022", "623023", "622630", "622631", "622632", + "622633", "622615", "622616", "622618", "622622", "622617", + "622619", "415599", "421393", "421865", "427570", "427571", + "472067", "472068", "622620", "621691", "545392", "545393", + "545431", "545447", "356859", "356857", "407405", "421869", + "421870", "421871", "512466", "356856", "528948", "552288", + "622600", "622601", "622602", "517636", "622621", "628258", + "556610", "622603", "464580", "464581", "523952", "545217", + "553161", "356858", "622623", "625911", "377152", "377153", + "377158", "377155", "625912", "625913", "356885", "356886", + "356887", "356888", "356890", "402658", "410062", "439188", + "439227", "468203", "479228", "479229", "512425", "521302", + "524011", "356889", "545620", "545621", "545947", "545948", + "552534", "552587", "622575", "622576", "622577", "622579", + "622580", "545619", "622581", "622582", "622588", "622598", + "622609", "690755", "690755", "545623", "621286", "620520", + "621483", "621485", "621486", "628290", "622578", "370285", + "370286", "370287", "370289", "439225", "518710", "518718", + "628362", "439226", "628262", "625802", "625803", "621299", + "966666", "622909", "622908", "438588", "438589", "461982", + "486493", "486494", "486861", "523036", "451289", "527414", + "528057", "622901", "622902", "622922", "628212", "451290", + "524070", "625084", "625085", "625086", "625087", "548738", + "549633", "552398", "625082", "625083", "625960", "625961", + "625962", "625963", "356851", "356852", "404738", "404739", + "456418", "498451", "515672", "356850", "517650", "525998", + "622177", "622277", "622516", "622517", "622518", "622520", + "622521", "622522", "622523", "628222", "628221", "984301", + "984303", "622176", "622276", "622228", "621352", "621351", + "621390", "621792", "625957", "625958", "621791", "620530", + "625993", "622519", "621793", "621795", "621796", "622500", + "623078", "622384", "940034", "940015", "622886", "622391", + "940072", "622359", "940066", "622857", "940065", "621019", + "622309", "621268", "622884", "621453", "622684", "621016", + "621015", "622950", "622951", "621072", "623183", "623185", + "621005", "622172", "622985", "622987", "622267", "622278", + "622279", "622468", "622892", "940021", "621050", "620522", + "356827", "356828", "356830", "402673", "402674", "438600", + "486466", "519498", "520131", "524031", "548838", "622148", + "622149", "622268", "356829", "622300", "628230", "622269", + "625099", "625953", "625350", "625351", "625352", "519961", + "625839", "421317", "602969", "621030", "621420", "621468", + "623111", "422160", "422161", "622865", "940012", "623131", + "622178", "622179", "628358", "622394", "940025", "621279", + "622281", "622316", "940022", "621418", "512431", "520194", + "621626", "623058", "602907", "622986", "622989", "622298", + "622338", "940032", "623205", "621977", "990027", "622325", + "623029", "623105", "621244", "623081", "623108", "566666", + "622455", "940039", "622466", "628285", "622420", "940041", + "623118", "603708", "622993", "623070", "623069", "623172", + "623173", "622383", "622385", "628299", "603506", "603367", + "622878", "623061", "623209", "628242", "622595", "622303", + "622305", "621259", "622596", "622333", "940050", "621439", + "623010", "621751", "628278", "625502", "625503", "625135", + "622476", "621754", "622143", "940001", "623026", "623086", + "628291", "621532", "621482", "622135", "622152", "622153", + "622154", "622996", "622997", "940027", "623099", "623007", + "940055", "622397", "622398", "940054", "622331", "622426", + "625995", "621452", "628205", "628214", "625529", "622428", + "621529", "622429", "621417", "623089", "623200", "940057", + "622311", "623119", "622877", "622879", "621775", "623203", + "603601", "622137", "622327", "622340", "622366", "622134", + "940018", "623016", "623096", "940049", "622425", "622425", + "621577", "622485", "623098", "628329", "621538", "940006", + "621269", "622275", "621216", "622465", "940031", "621252", + "622146", "940061", "621419", "623170", "622440", "940047", + "940017", "622418", "623077", "622413", "940002", "623188", + "622310", "940068", "622321", "625001", "622427", "940069", + "623039", "628273", "622370", "683970", "940074", "621437", + "628319", "990871", "622308", "621415", "623166", "622132", + "621340", "621341", "622140", "623073", "622147", "621633", + "622301", "623171", "621422", "622335", "622336", "622165", + "622315", "628295", "625950", "621760", "622337", "622411", + "623102", "622342", "623048", "622367", "622392", "623085", + "622395", "622441", "622448", "621413", "622856", "621037", + "621097", "621588", "623032", "622644", "623518", "622870", + "622866", "623072", "622897", "628279", "622864", "621403", + "622561", "622562", "622563", "622167", "622777", "621497", + "622868", "622899", "628255", "625988", "622566", "622567", + "622625", "622626", "625946", "628200", "621076", "504923", + "622173", "622422", "622447", "622131", "940076", "621579", + "622876", "622873", "622962", "622936", "623060", "622937", + "623101", "621460", "622939", "622960", "623523", "621591", + "622961", "628210", "622283", "625902", "621010", "622980", + "623135", "621726", "621088", "620517", "622740", "625036", + "621014", "621004", "622972", "623196", "621028", "623083", + "628250", "623121", "621070", "628253", "622979", "621035", + "621038", "621086", "621498", "621296", "621448", "622945", + "621755", "622940", "623120", "628355", "621089", "623161", + "628339", "621074", "621515", "623030", "621345", "621090", + "623178", "621091", "623168", "621057", "623199", "621075", + "623037", "628303", "621233", "621235", "621223", "621780", + "621221", "623138", "628389", "621239", "623068", "621271", + "628315", "621272", "621738", "621273", "623079", "621263", + "621325", "623084", "621327", "621753", "628331", "623160", + "621366", "621388", "621348", "621359", "621360", "621217", + "622959", "621270", "622396", "622511", "623076", "621391", + "621339", "621469", "621625", "623688", "623113", "621601", + "621655", "621636", "623182", "623087", "621696", "622955", + "622478", "940013", "621495", "621688", "623162", "622462", + "628272", "625101", "622323", "623071", "603694", "622128", + "622129", "623035", "623186", "621522", "622271", "940037", + "940038", "985262", "622322", "628381", "622481", "622341", + "940058", "623115", "621258", "621465", "621528", "622328", + "940062", "625288", "623038", "625888", "622332", "940063", + "623123", "622138", "621066", "621560", "621068", "620088", + "621067", "622531", "622329", "623103", "622339", "620500", + "621024", "622289", "622389", "628300", "625516", "621516", + "622859", "622869", "623075", "622895", "623125", "622947", + "621561", "623095", "621073", "623109", "621361", "623033", + "623207", "622891", "621363", "623189", "623510", "622995", + "621053", "621230", "621229", "622218", "628267", "621392", + "621481", "621310", "621396", "623251", "628351" + }; + + // 发卡行·卡种名称 + private static final String[] BANK_NAME = { + "邮储银行·绿卡通", "邮储银行·绿卡银联标准卡", + "邮储银行·绿卡银联标准卡", "邮储银行·绿卡专用卡", "邮储银行·绿卡银联标准卡", "邮储银行·绿卡 (银联卡)", + "邮储银行·绿卡 VIP 卡", "邮储银行·银联标准卡", "邮储银行·中职学生资助卡", "邮政储蓄银行·IC 绿卡通 VIP 卡", + "邮政储蓄银行·IC 绿卡通", "邮政储蓄银行·IC 联名卡", "邮政储蓄银行·IC 预付费卡", "邮储银行·绿卡银联标准卡", + "邮储银行·绿卡通", "邮政储蓄银行·武警军人保障卡", "邮政储蓄银行·中国旅游卡 (金卡)", + "邮政储蓄银行·普通高中学生资助卡", "邮政储蓄银行·中国旅游卡 (普卡)", "邮政储蓄银行·福农卡", + "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", "工商银行·牡丹运通卡金卡", + "工商银行·牡丹 VISA 卡 (单位卡)", "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 卡 (单位卡)", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹 VISA 信用卡", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹运通卡普通卡", "工商银行·牡丹 VISA 信用卡", + "工商银行·牡丹 VISA 白金卡", "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹贷记卡 (银联卡)", + "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹贷记卡 (银联卡)", "工商银行·牡丹欧元卡", "工商银行·牡丹欧元卡", + "工商银行·牡丹欧元卡", "工商银行·牡丹万事达国际借记卡", "工商银行·牡丹 VISA 信用卡", "工商银行·海航信用卡", + "工商银行·牡丹 VISA 信用卡", "工商银行·牡丹万事达信用卡", "工商银行·牡丹万事达信用卡", + "工商银行·牡丹万事达信用卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹万事达白金卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·海航信用卡个人普卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·灵通卡", "工商银行·牡丹灵通卡", "工商银行·E 时代卡", "工商银行·E 时代卡", + "工商银行·理财金卡", "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", + "工商银行·准贷记卡 (个普)", "工商银行·准贷记卡 (个普)", "工商银行·牡丹灵通卡", "工商银行·准贷记卡 (商普)", + "工商银行·牡丹卡 (商务卡)", "工商银行·准贷记卡 (商金)", "工商银行·牡丹卡 (商务卡)", "工商银行·贷记卡 (个普)", + "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", "工商银行·牡丹卡 (个人卡)", + "工商银行·贷记卡 (个金)", "工商银行·牡丹交通卡", "工商银行·准贷记卡 (个金)", "工商银行·牡丹交通卡", + "工商银行·贷记卡 (商普)", "工商银行·贷记卡 (商金)", "工商银行·牡丹卡 (商务卡)", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹交通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", + "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹灵通卡", "工商银行·牡丹贷记卡", + "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹贷记卡", "工商银行·牡丹灵通卡", + "工商银行·中央预算单位公务卡", "工商银行·牡丹灵通卡", "工商银行·财政预算单位公务卡", "工商银行·牡丹卡白金卡", + "工商银行·牡丹卡普卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", "工商银行·国航知音牡丹信用卡", + "工商银行·国航知音牡丹信用卡", "工商银行·银联标准卡", "工商银行·中职学生资助卡", "工商银行·专用信用消费卡", + "工商银行·牡丹社会保障卡", "中国工商银行·牡丹东航联名卡", "中国工商银行·牡丹东航联名卡", + "中国工商银行·牡丹运通白金卡", "中国工商银行·福农灵通卡", "中国工商银行·福农灵通卡", "工商银行·灵通卡", + "工商银行·灵通卡", "中国工商银行·中国旅行卡", "工商银行·牡丹卡普卡", "工商银行·国际借记卡", + "工商银行·国际借记卡", "工商银行·国际借记卡", "工商银行·国际借记卡", "中国工商银行·牡丹 JCB 信用卡", + "中国工商银行·牡丹 JCB 信用卡", "中国工商银行·牡丹 JCB 信用卡", "中国工商银行·牡丹 JCB 信用卡", + "中国工商银行·牡丹多币种卡", "中国工商银行·武警军人保障卡", "工商银行·预付芯片卡", "工商银行·理财金账户金卡", + "工商银行·灵通卡", "工商银行·牡丹宁波市民卡", "中国工商银行·中国旅游卡", "中国工商银行·中国旅游卡", + "中国工商银行·中国旅游卡", "中国工商银行·借记卡", "中国工商银行·借贷合一卡", "中国工商银行·普通高中学生资助卡", + "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹多币种卡", "中国工商银行·牡丹百夫长信用卡", + "中国工商银行·牡丹百夫长信用卡", "工商银行·工银财富卡", "中国工商银行·中小商户采购卡", + "中国工商银行·中小商户采购卡", "中国工商银行·环球旅行金卡", "中国工商银行·环球旅行白金卡", + "中国工商银行·牡丹工银大来卡", "中国工商银行·牡丹工银大莱卡", "中国工商银行·IC 金卡", "中国工商银行·IC 白金卡", + "中国工商银行·工行 IC 卡 (红卡)", "中国工商银行布鲁塞尔分行·借记卡", "中国工商银行布鲁塞尔分行·预付卡", + "中国工商银行布鲁塞尔分行·预付卡", "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", + "中国工商银行金边分行·借记卡", "中国工商银行金边分行·信用卡", "中国工商银行加拿大分行·借记卡", + "中国工商银行加拿大分行·借记卡", "中国工商银行加拿大分行·预付卡", "中国工商银行巴黎分行·借记卡", + "中国工商银行巴黎分行·借记卡", "中国工商银行巴黎分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", + "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·贷记卡", "中国工商银行法兰克福分行·贷记卡", + "中国工商银行法兰克福分行·借记卡", "中国工商银行法兰克福分行·预付卡", "中国工商银行法兰克福分行·预付卡", + "中国工商银行印尼分行·借记卡", "中国工商银行印尼分行·信用卡", "中国工商银行米兰分行·借记卡", + "中国工商银行米兰分行·预付卡", "中国工商银行米兰分行·预付卡", "中国工商银行阿拉木图子行·借记卡", + "中国工商银行阿拉木图子行·贷记卡", "中国工商银行阿拉木图子行·借记卡", "中国工商银行阿拉木图子行·预付卡", + "中国工商银行万象分行·借记卡", "中国工商银行万象分行·贷记卡", "中国工商银行卢森堡分行·借记卡", + "中国工商银行卢森堡分行·贷记卡", "中国工商银行澳门分行·E 时代卡", "中国工商银行澳门分行·E 时代卡", + "中国工商银行澳门分行·E 时代卡", "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·理财金账户", + "中国工商银行澳门分行·理财金账户", "中国工商银行澳门分行·预付卡", "中国工商银行澳门分行·预付卡", + "中国工商银行澳门分行·工银闪付预付卡", "中国工商银行澳门分行·工银银联公司卡", "中国工商银行澳门分行·Diamond", + "中国工商银行阿姆斯特丹·借记卡", "中国工商银行卡拉奇分行·借记卡", "中国工商银行卡拉奇分行·贷记卡", + "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·贷记卡", "中国工商银行新加坡分行·借记卡", + "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·预付卡", "中国工商银行新加坡分行·借记卡", + "中国工商银行新加坡分行·借记卡", "中国工商银行马德里分行·借记卡", "中国工商银行马德里分行·借记卡", + "中国工商银行马德里分行·预付卡", "中国工商银行马德里分行·预付卡", "中国工商银行伦敦子行·借记卡", + "中国工商银行伦敦子行·工银伦敦借记卡", "中国工商银行伦敦子行·借记卡", "农业银行·金穗贷记卡", "农业银行·中国旅游卡", + "农业银行·普通高中学生资助卡", "农业银行·银联标准卡", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·VISA 白金卡", + "农业银行·万事达白金卡", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", + "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡 (银联卡)", "农业银行·金穗贷记卡", "农业银行·中职学生资助卡", + "农业银行·专用惠农卡", "农业银行·武警军人保障卡", "农业银行·金穗校园卡 (银联卡)", "农业银行·金穗星座卡 (银联卡)", + "农业银行·金穗社保卡 (银联卡)", "农业银行·金穗旅游卡 (银联卡)", "农业银行·金穗青年卡 (银联卡)", + "农业银行·复合介质金穗通宝卡", "农业银行·金穗海通卡", "农业银行·退役金卡", "农业银行·金穗贷记卡", + "农业银行·金穗贷记卡", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗惠农卡", "农业银行·金穗通宝银卡", + "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡", + "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝卡 (银联卡)", "农业银行·金穗通宝钻石卡", "农业银行·掌尚钱包", + "农业银行·银联 IC 卡金卡", "农业银行·银联预算单位公务卡金卡", "农业银行·银联 IC 卡白金卡", "农业银行·金穗公务卡", + "中国农业银行贷记卡·IC 普卡", "中国农业银行贷记卡·IC 金卡", "中国农业银行贷记卡·澳元卡", + "中国农业银行贷记卡·欧元卡", "中国农业银行贷记卡·金穗通商卡", "中国农业银行贷记卡·金穗通商卡", + "中国农业银行贷记卡·银联白金卡", "中国农业银行贷记卡·中国旅游卡", "中国农业银行贷记卡·银联 IC 公务卡", + "宁波市农业银行·市民卡 B 卡", "中国银行·联名卡", "中国银行·个人普卡", "中国银行·个人金卡", "中国银行·员工普卡", + "中国银行·员工金卡", "中国银行·理财普卡", "中国银行·理财金卡", "中国银行·理财银卡", "中国银行·理财白金卡", + "中国银行·中行金融 IC 卡白金卡", "中国银行·中行金融 IC 卡普卡", "中国银行·中行金融 IC 卡金卡", + "中国银行·中银 JCB 卡金卡", "中国银行·中银 JCB 卡普卡", "中国银行·员工普卡", "中国银行·个人普卡", + "中国银行·中银威士信用卡员", "中国银行·中银威士信用卡员", "中国银行·个人白金卡", "中国银行·中银威士信用卡", + "中国银行·长城公务卡", "中国银行·长城电子借记卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", + "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·中银威士信用卡员", + "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", "中国银行·长城万事达信用卡", + "中国银行·长城万事达信用卡", "中国银行·中银奥运信用卡", "中国银行·长城信用卡", "中国银行·长城信用卡", + "中国银行·长城信用卡", "中国银行·长城万事达信用卡", "中国银行·长城公务卡", "中国银行·长城公务卡", + "中国银行·中银万事达信用卡", "中国银行·中银万事达信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", + "中国银行·长城人民币信用卡", "中国银行·长城信用卡", "中国银行·长城人民币信用卡", "中国银行·长城人民币信用卡", + "中国银行·长城信用卡", "中国银行·银联单币贷记卡", "中国银行·长城信用卡", "中国银行·长城信用卡", + "中国银行·长城信用卡", "中国银行·长城电子借记卡", "中国银行·长城人民币信用卡", "中国银行·银联标准公务卡", + "中国银行·一卡双账户普卡", "中国银行·财互通卡", "中国银行·电子现金卡", "中国银行·长城人民币信用卡", + "中国银行·长城单位信用卡普卡", "中国银行·中银女性主题信用卡", "中国银行·长城单位信用卡金卡", "中国银行·白金卡", + "中国银行·中职学生资助卡", "中国银行·银联标准卡", "中国银行·金融 IC 卡", "中国银行·长城社会保障卡", + "中国银行·世界卡", "中国银行·社保联名卡", "中国银行·社保联名卡", "中国银行·医保联名卡", "中国银行·医保联名卡", + "中国银行·公司借记卡", "中国银行·银联美运顶级卡", "中国银行·长城福农借记卡金卡", "中国银行·长城福农借记卡普卡", + "中国银行·中行金融 IC 卡普卡", "中国银行·中行金融 IC 卡金卡", "中国银行·中行金融 IC 卡白金卡", + "中国银行·长城银联公务 IC 卡白金卡", "中国银行·中银旅游信用卡", "中国银行·长城银联公务 IC 卡金卡", + "中国银行·中国旅游卡", "中国银行·武警军人保障卡", "中国银行·社保联名借记 IC 卡", "中国银行·社保联名借记 IC 卡", + "中国银行·医保联名借记 IC 卡", "中国银行·医保联名借记 IC 卡", "中国银行·借记 IC 个人普卡", + "中国银行·借记 IC 个人金卡", "中国银行·借记 IC 个人普卡", "中国银行·借记 IC 白金卡", "中国银行·借记 IC 钻石卡", + "中国银行·借记 IC 联名卡", "中国银行·普通高中学生资助卡", "中国银行·长城环球通港澳台旅游金卡", + "中国银行·长城环球通港澳台旅游白金卡", "中国银行·中银福农信用卡", "中国银行金边分行·借记卡", + "中国银行雅加达分行·借记卡", "中国银行首尔分行·借记卡", "中国银行澳门分行·人民币信用卡", + "中国银行澳门分行·人民币信用卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", "中国银行澳门分行·中银卡", + "中国银行澳门分行·中银银联双币商务卡", "中国银行澳门分行·预付卡", "中国银行澳门分行·澳门中国银行银联预付卡", + "中国银行澳门分行·澳门中国银行银联预付卡", "中国银行澳门分行·熊猫卡", "中国银行澳门分行·财富卡", + "中国银行澳门分行·银联港币卡", "中国银行澳门分行·银联澳门币卡", "中国银行马尼拉分行·双币种借记卡", + "中国银行胡志明分行·借记卡", "中国银行曼谷分行·借记卡", "中国银行曼谷分行·长城信用卡环球通", + "中国银行曼谷分行·借记卡", "建设银行·龙卡准贷记卡", "建设银行·龙卡准贷记卡金卡", "建设银行·中职学生资助卡", + "建设银行·乐当家银卡 VISA", "建设银行·乐当家金卡 VISA", "建设银行·乐当家白金卡", + "建设银行·龙卡普通卡 VISA", "建设银行·龙卡储蓄卡", "建设银行·VISA 准贷记卡 (银联卡)", + "建设银行·VISA 准贷记金卡", "建设银行·乐当家", "建设银行·乐当家", "建设银行·准贷记金卡", + "建设银行·乐当家白金卡", "建设银行·金融复合 IC 卡", "建设银行·银联标准卡", "建设银行·银联理财钻石卡", + "建设银行·金融 IC 卡", "建设银行·理财白金卡", "建设银行·社保 IC 卡", "建设银行·财富卡私人银行卡", + "建设银行·理财金卡", "建设银行·福农卡", "建设银行·武警军人保障卡", "建设银行·龙卡通", "建设银行·银联储蓄卡", + "建设银行·龙卡储蓄卡 (银联卡)", "建设银行·准贷记卡", "建设银行·理财白金卡", "建设银行·理财金卡", + "建设银行·准贷记卡普卡", "建设银行·准贷记卡金卡", "建设银行·龙卡信用卡", "建设银行·建行陆港通龙卡", + "中国建设银行·普通高中学生资助卡", "中国建设银行·中国旅游卡", "中国建设银行·龙卡 JCB 金卡", + "中国建设银行·龙卡 JCB 白金卡", "中国建设银行·龙卡 JCB 普卡", "中国建设银行·龙卡贷记卡公司卡", + "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡国际普通卡 VISA", "中国建设银行·龙卡国际金卡 VISA", + "中国建设银行·VISA 白金信用卡", "中国建设银行·龙卡国际白金卡", "中国建设银行·龙卡国际普通卡 MASTER", + "中国建设银行·龙卡国际金卡 MASTER", "中国建设银行·龙卡万事达金卡", "中国建设银行·龙卡贷记卡", + "中国建设银行·龙卡万事达白金卡", "中国建设银行·龙卡贷记卡", "中国建设银行·龙卡万事达信用卡", + "中国建设银行·龙卡人民币信用卡", "中国建设银行·龙卡人民币信用金卡", "中国建设银行·龙卡人民币白金卡", + "中国建设银行·龙卡 IC 信用卡普卡", "中国建设银行·龙卡 IC 信用卡金卡", "中国建设银行·龙卡 IC 信用卡白金卡", + "中国建设银行·龙卡银联公务卡普卡", "中国建设银行·龙卡银联公务卡金卡", "中国建设银行·中国旅游卡", + "中国建设银行·中国旅游卡", "中国建设银行·龙卡 IC 公务卡", "中国建设银行·龙卡 IC 公务卡", "交通银行·交行预付卡", + "交通银行·世博预付 IC 卡", "交通银行·太平洋互连卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋互连卡 (银联卡)", + "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋双币贷记卡", + "交通银行·太平洋白金信用卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋万事顺卡", "交通银行·太平洋人民币贷记卡", + "交通银行·太平洋人民币贷记卡", "交通银行·太平洋双币贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", + "交通银行·太平洋准贷记卡", "交通银行·太平洋准贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋借记卡", + "交通银行·太平洋人民币贷记卡", "交通银行·太平洋借记卡", "交通银行·太平洋 MORE 卡", "交通银行·白金卡", + "交通银行·交通银行公务卡普卡", "交通银行·太平洋人民币贷记卡", "交通银行·太平洋互连卡", "交通银行·太平洋借记卡", + "交通银行·太平洋万事顺卡", "交通银行·太平洋贷记卡 (银联卡)", "交通银行·太平洋贷记卡 (银联卡)", + "交通银行·太平洋贷记卡 (银联卡)", "交通银行·太平洋贷记卡 (银联卡)", "交通银行·交通银行公务卡金卡", + "交通银行·交银 IC 卡", "交通银行香港分行·交通银行港币借记卡", "交通银行香港分行·港币礼物卡", + "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币种信用卡", "交通银行香港分行·双币卡", + "交通银行香港分行·银联人民币卡", "交通银行澳门分行·银联借记卡", "中信银行·中信借记卡", "中信银行·中信借记卡", + "中信银行·中信国际借记卡", "中信银行·中信国际借记卡", "中信银行·中国旅行卡", "中信银行·中信借记卡 (银联卡)", + "中信银行·中信借记卡 (银联卡)", "中信银行·中信贵宾卡 (银联卡)", "中信银行·中信理财宝金卡", + "中信银行·中信理财宝白金卡", "中信银行·中信钻石卡", "中信银行·中信钻石卡", "中信银行·中信借记卡", + "中信银行·中信理财宝 (银联卡)", "中信银行·中信理财宝 (银联卡)", "中信银行·中信理财宝 (银联卡)", + "中信银行·借记卡", "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", + "中信银行·理财宝 IC 卡", "中信银行·理财宝 IC 卡", "中信银行·主账户复合电子现金卡", "光大银行·阳光商旅信用卡", + "光大银行·阳光商旅信用卡", "光大银行·阳光商旅信用卡", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", "光大银行·阳光卡 (银联卡)", + "光大银行·阳光卡 (银联卡)", "光大银行·借记卡普卡", "光大银行·社会保障 IC 卡", "光大银行·IC 借记卡普卡", + "光大银行·手机支付卡", "光大银行·联名 IC 卡普卡", "光大银行·借记 IC 卡白金卡", "光大银行·借记 IC 卡金卡", + "光大银行·阳光旅行卡", "光大银行·借记 IC 卡钻石卡", "光大银行·联名 IC 卡金卡", "光大银行·联名 IC 卡白金卡", + "光大银行·联名 IC 卡钻石卡", "华夏银行·华夏卡 (银联卡)", "华夏银行·华夏白金卡", "华夏银行·华夏普卡", + "华夏银行·华夏金卡", "华夏银行·华夏白金卡", "华夏银行·华夏钻石卡", "华夏银行·华夏卡 (银联卡)", + "华夏银行·华夏至尊金卡 (银联卡)", "华夏银行·华夏丽人卡 (银联卡)", "华夏银行·华夏万通卡", + "民生银行·民生借记卡 (银联卡)", "民生银行·民生银联借记卡-金卡", "民生银行·钻石卡", + "民生银行·民生借记卡 (银联卡)", "民生银行·民生借记卡 (银联卡)", "民生银行·民生借记卡 (银联卡)", + "民生银行·民生借记卡", "民生银行·民生国际卡", "民生银行·民生国际卡 (银卡)", "民生银行·民生国际卡 (欧元卡)", + "民生银行·民生国际卡 (澳元卡)", "民生银行·民生国际卡", "民生银行·民生国际卡", "民生银行·薪资理财卡", + "民生银行·借记卡普卡", "民生银行·民生 MasterCard", "民生银行·民生 MasterCard", + "民生银行·民生 MasterCard", "民生银行·民生 MasterCard", "民生银行·民生 JCB 信用卡", + "民生银行·民生 JCB 金卡", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生 JCB 普卡", "民生银行·民生贷记卡 (银联卡)", "民生银行·民生贷记卡 (银联卡)", + "民生银行·民生信用卡 (银联卡)", "民生银行·民生信用卡 (银联卡)", "民生银行·民生银联白金信用卡", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生银联个人白金卡", "民生银行·公务卡金卡", + "民生银行·民生贷记卡 (银联卡)", "民生银行·民生银联商务信用卡", "民生银行·民 VISA 无限卡", + "民生银行·民生 VISA 商务白金卡", "民生银行·民生万事达钛金卡", "民生银行·民生万事达世界卡", + "民生银行·民生万事达白金公务卡", "民生银行·民生 JCB 白金卡", "民生银行·银联标准金卡", "民生银行·银联芯片普卡", + "民生银行·民生运通双币信用卡普卡", "民生银行·民生运通双币信用卡金卡", "民生银行·民生运通双币信用卡钻石卡", + "民生银行·民生运通双币标准信用卡白金卡", "民生银行·银联芯片金卡", "民生银行·银联芯片白金卡", + "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·两地一卡通", "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", + "招商银行·VISA 商务信用卡", "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·招行国际卡 (银联卡)", "招商银行·世纪金花联名信用卡", + "招商银行·招行国际卡 (银联卡)", "招商银行·招商银行信用卡", "招商银行·万事达信用卡", "招商银行·万事达信用卡", + "招商银行·万事达信用卡", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·一卡通 (银联卡)", "招商银行·万事达信用卡", "招商银行·招商银行信用卡", "招商银行·招商银行信用卡", + "招商银行·一卡通 (银联卡)", "招商银行·公司卡 (银联卡)", "招商银行·金卡", "招商银行·招行一卡通", + "招商银行·招行一卡通", "招商银行·万事达信用卡", "招商银行·金葵花卡", "招商银行·电子现金卡", + "招商银行·银联 IC 普卡", "招商银行·银联 IC 金卡", "招商银行·银联金葵花 IC 卡", "招商银行·IC 公务卡", + "招商银行·招商银行信用卡", "招商银行信用卡中心·美国运通绿卡", "招商银行信用卡中心·美国运通金卡", + "招商银行信用卡中心·美国运通商务绿卡", "招商银行信用卡中心·美国运通商务金卡", "招商银行信用卡中心·VISA 信用卡", + "招商银行信用卡中心·MASTER 信用卡", "招商银行信用卡中心·MASTER 信用金卡", + "招商银行信用卡中心·银联标准公务卡 (金卡)", "招商银行信用卡中心·VISA 信用卡", + "招商银行信用卡中心·银联标准财政公务卡", "招商银行信用卡中心·芯片 IC 信用卡", "招商银行信用卡中心·芯片 IC 信用卡", + "招商银行香港分行·香港一卡通", "兴业银行·兴业卡 (银联卡)", "兴业银行·兴业卡 (银联标准卡)", + "兴业银行·兴业自然人生理财卡", "兴业银行·兴业智能卡 (银联卡)", "兴业银行·兴业智能卡", + "兴业银行·visa 标准双币个人普卡", "兴业银行·VISA 商务普卡", "兴业银行·VISA 商务金卡", + "兴业银行·VISA 运动白金信用卡", "兴业银行·万事达信用卡 (银联卡)", "兴业银行·VISA 信用卡 (银联卡)", + "兴业银行·加菲猫信用卡", "兴业银行·个人白金卡", "兴业银行·银联信用卡 (银联卡)", "兴业银行·银联信用卡 (银联卡)", + "兴业银行·银联白金信用卡", "兴业银行·银联标准公务卡", "兴业银行·VISA 信用卡 (银联卡)", + "兴业银行·万事达信用卡 (银联卡)", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记金卡", + "兴业银行·银联标准贷记金卡", "兴业银行·银联标准贷记金卡", "兴业银行·兴业信用卡", "兴业银行·兴业信用卡", + "兴业银行·兴业信用卡", "兴业银行·银联标准贷记普卡", "兴业银行·银联标准贷记普卡", "兴业银行·兴业芯片普卡", + "兴业银行·兴业芯片金卡", "兴业银行·兴业芯片白金卡", "兴业银行·兴业芯片钻石卡", "浦东发展银行·浦发 JCB 金卡", + "浦东发展银行·浦发 JCB 白金卡", "浦东发展银行·信用卡 VISA 普通", "浦东发展银行·信用卡 VISA 金卡", + "浦东发展银行·浦发银行 VISA 年青卡", "浦东发展银行·VISA 白金信用卡", "浦东发展银行·浦发万事达白金卡", + "浦东发展银行·浦发 JCB 普卡", "浦东发展银行·浦发万事达金卡", "浦东发展银行·浦发万事达普卡", + "浦东发展银行·浦发单币卡", "浦东发展银行·浦发银联单币麦兜普卡", "浦东发展银行·东方轻松理财卡", + "浦东发展银行·东方 - 轻松理财卡普卡", "浦东发展银行·东方轻松理财卡", "浦东发展银行·东方轻松理财智业金卡", + "浦东发展银行·东方卡 (银联卡)", "浦东发展银行·东方卡 (银联卡)", "浦东发展银行·东方卡 (银联卡)", + "浦东发展银行·公务卡金卡", "浦东发展银行·公务卡普卡", "浦东发展银行·东方卡", "浦东发展银行·东方卡", + "浦东发展银行·浦发单币卡", "浦东发展银行·浦发联名信用卡", "浦东发展银行·浦发银联白金卡", + "浦东发展银行·轻松理财普卡", "浦东发展银行·移动联名卡", "浦东发展银行·轻松理财消贷易卡", + "浦东发展银行·轻松理财普卡 (复合卡)", "浦东发展银行·贷记卡", "浦东发展银行·贷记卡", + "浦东发展银行·东方借记卡 (复合卡)", "浦东发展银行·电子现金卡 (IC 卡)", "浦东发展银行·移动浦发联名卡", + "浦东发展银行·东方 - 标准准贷记卡", "浦东发展银行·轻松理财金卡 (复合卡)", "浦东发展银行·轻松理财白金卡 (复合卡)", + "浦东发展银行·轻松理财钻石卡 (复合卡)", "浦东发展银行·东方卡", "恒丰银行·九州 IC 卡", + "恒丰银行·九州借记卡 (银联卡)", "恒丰银行·九州借记卡 (银联卡)", "天津市商业银行·银联卡 (银联卡)", + "烟台商业银行·金通卡", "潍坊银行·鸢都卡 (银联卡)", "潍坊银行·鸳都卡 (银联卡)", "临沂商业银行·沂蒙卡 (银联卡)", + "临沂商业银行·沂蒙卡 (银联卡)", "日照市商业银行·黄海卡", "日照市商业银行·黄海卡 (银联卡)", "浙商银行·商卡", + "浙商银行·商卡", "渤海银行·浩瀚金卡", "渤海银行·渤海银行借记卡", "渤海银行·金融 IC 卡", + "渤海银行·渤海银行公司借记卡", "星展银行·星展银行借记卡", "星展银行·星展银行借记卡", "恒生银行·恒生通财卡", + "恒生银行·恒生优越通财卡", "新韩银行·新韩卡", "上海银行·慧通钻石卡", "上海银行·慧通金卡", + "上海银行·私人银行卡", "上海银行·综合保险卡", "上海银行·申卡社保副卡 (有折)", "上海银行·申卡社保副卡 (无折)", + "上海银行·白金 IC 借记卡", "上海银行·慧通白金卡 (配折)", "上海银行·慧通白金卡 (不配折)", + "上海银行·申卡 (银联卡)", "上海银行·申卡借记卡", "上海银行·银联申卡 (银联卡)", "上海银行·单位借记卡", + "上海银行·首发纪念版 IC 卡", "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·J 分期付款信用卡", + "上海银行·申卡贷记卡", "上海银行·申卡贷记卡", "上海银行·上海申卡 IC", "上海银行·申卡贷记卡", + "上海银行·申卡贷记卡普通卡", "上海银行·申卡贷记卡金卡", "上海银行·万事达白金卡", "上海银行·万事达星运卡", + "上海银行·申卡贷记卡金卡", "上海银行·申卡贷记卡普通卡", "上海银行·安融卡", "上海银行·分期付款信用卡", + "上海银行·信用卡", "上海银行·个人公务卡", "上海银行·安融卡", "上海银行·上海银行银联白金卡", + "上海银行·贷记 IC 卡", "上海银行·中国旅游卡 (IC 普卡)", "上海银行·中国旅游卡 (IC 金卡)", + "上海银行·中国旅游卡 (IC 白金卡)", "上海银行·万事达钻石卡", "上海银行·淘宝 IC 普卡", "北京银行·京卡借记卡", + "北京银行·京卡 (银联卡)", "北京银行·京卡借记卡", "北京银行·京卡", "北京银行·京卡", "北京银行·借记 IC 卡", + "北京银行·京卡贵宾金卡", "北京银行·京卡贵宾白金卡", "吉林银行·君子兰一卡通 (银联卡)", + "吉林银行·君子兰卡 (银联卡)", "吉林银行·长白山金融 IC 卡", "吉林银行·信用卡", "吉林银行·信用卡", + "吉林银行·公务卡", "镇江市商业银行·金山灵通卡 (银联卡)", "镇江市商业银行·金山灵通卡 (银联卡)", + "宁波银行·银联标准卡", "宁波银行·汇通借记卡", "宁波银行·汇通卡 (银联卡)", "宁波银行·明州卡", + "宁波银行·汇通借记卡", "宁波银行·汇通国际卡银联双币卡", "宁波银行·汇通国际卡银联双币卡", "平安银行·新磁条借记卡", + "平安银行·平安银行 IC 借记卡", "平安银行·万事顺卡", "平安银行·平安银行借记卡", "平安银行·平安银行借记卡", + "平安银行·万事顺借记卡", "焦作市商业银行·月季借记卡 (银联卡)", "焦作市商业银行·月季城市通 (银联卡)", + "焦作市商业银行·中国旅游卡", "温州银行·金鹿卡", "汉口银行·九通卡 (银联卡)", "汉口银行·九通卡", + "汉口银行·借记卡", "汉口银行·借记卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰 IC 卡", "盛京银行·玫瑰 IC 卡", + "盛京银行·玫瑰卡", "盛京银行·玫瑰卡", "盛京银行·玫瑰卡 (银联卡)", "盛京银行·玫瑰卡 (银联卡)", + "盛京银行·盛京银行公务卡", "洛阳银行·都市一卡通 (银联卡)", "洛阳银行·都市一卡通 (银联卡)", "洛阳银行·--", + "大连银行·北方明珠卡", "大连银行·人民币借记卡", "大连银行·金融 IC 借记卡", "大连银行·大连市社会保障卡", + "大连银行·借记 IC 卡", "大连银行·借记 IC 卡", "大连银行·大连市商业银行贷记卡", "大连银行·大连市商业银行贷记卡", + "大连银行·银联标准公务卡", "苏州市商业银行·姑苏卡", "杭州商业银行·西湖卡", "杭州商业银行·西湖卡", + "杭州商业银行·借记 IC 卡", "杭州商业银行·", "南京银行·梅花信用卡公务卡", "南京银行·梅花信用卡商务卡", + "南京银行·梅花贷记卡 (银联卡)", "南京银行·梅花借记卡 (银联卡)", "南京银行·白金卡", "南京银行·商务卡", + "东莞市商业银行·万顺通卡 (银联卡)", "东莞市商业银行·万顺通卡 (银联卡)", "东莞市商业银行·万顺通借记卡", + "东莞市商业银行·社会保障卡", "乌鲁木齐市商业银行·雪莲借记 IC 卡", "乌鲁木齐市商业银行·乌鲁木齐市公务卡", + "乌鲁木齐市商业银行·福农卡贷记卡", "乌鲁木齐市商业银行·福农卡准贷记卡", "乌鲁木齐市商业银行·雪莲准贷记卡", + "乌鲁木齐市商业银行·雪莲贷记卡 (银联卡)", "乌鲁木齐市商业银行·雪莲借记 IC 卡", + "乌鲁木齐市商业银行·雪莲借记卡 (银联卡)", "乌鲁木齐市商业银行·雪莲卡 (银联卡)", "绍兴银行·兰花 IC 借记卡", + "绍兴银行·社保 IC 借记卡", "绍兴银行·兰花公务卡", "成都商业银行·芙蓉锦程福农卡", "成都商业银行·芙蓉锦程天府通卡", + "成都商业银行·锦程卡 (银联卡)", "成都商业银行·锦程卡金卡", "成都商业银行·锦程卡定活一卡通金卡", + "成都商业银行·锦程卡定活一卡通", "成都商业银行·锦程力诚联名卡", "成都商业银行·锦程力诚联名卡", + "成都商业银行·锦程卡 (银联卡)", "抚顺银行·借记 IC 卡", "临商银行·借记卡", "宜昌市商业银行·三峡卡 (银联卡)", + "宜昌市商业银行·信用卡 (银联卡)", "葫芦岛市商业银行·一通卡", "葫芦岛市商业银行·一卡通 (银联卡)", + "天津市商业银行·津卡", "天津市商业银行·津卡贷记卡 (银联卡)", "天津市商业银行·贷记 IC 卡", "天津市商业银行·--", + "天津银行·商务卡", "宁夏银行·宁夏银行公务卡", "宁夏银行·宁夏银行福农贷记卡", "宁夏银行·如意卡 (银联卡)", + "宁夏银行·宁夏银行福农借记卡", "宁夏银行·如意借记卡", "宁夏银行·如意 IC 卡", "宁夏银行·宁夏银行如意借记卡", + "宁夏银行·中国旅游卡", "齐商银行·金达卡 (银联卡)", "齐商银行·金达借记卡 (银联卡)", "齐商银行·金达 IC 卡", + "徽商银行·黄山卡", "徽商银行·黄山卡", "徽商银行·借记卡", "徽商银行·徽商银行中国旅游卡 (安徽)", + "徽商银行合肥分行·黄山卡", "徽商银行芜湖分行·黄山卡 (银联卡)", "徽商银行马鞍山分行·黄山卡 (银联卡)", + "徽商银行淮北分行·黄山卡 (银联卡)", "徽商银行安庆分行·黄山卡 (银联卡)", "重庆银行·长江卡 (银联卡)", + "重庆银行·长江卡 (银联卡)", "重庆银行·长江卡", "重庆银行·借记 IC 卡", "哈尔滨银行·丁香一卡通 (银联卡)", + "哈尔滨银行·丁香借记卡 (银联卡)", "哈尔滨银行·丁香卡", "哈尔滨银行·福农借记卡", + "无锡市商业银行·太湖金保卡 (银联卡)", "丹东银行·借记 IC 卡", "丹东银行·丹东银行公务卡", "兰州银行·敦煌卡", + "南昌银行·金瑞卡 (银联卡)", "南昌银行·南昌银行借记卡", "南昌银行·金瑞卡", "晋商银行·晋龙一卡通", + "晋商银行·晋龙一卡通", "晋商银行·晋龙卡 (银联卡)", "青岛银行·金桥通卡", "青岛银行·金桥卡 (银联卡)", + "青岛银行·金桥卡 (银联卡)", "青岛银行·金桥卡", "青岛银行·借记 IC 卡", "吉林银行·雾凇卡 (银联卡)", + "吉林银行·雾凇卡 (银联卡)", "南通商业银行·金桥卡 (银联卡)", "南通商业银行·金桥卡 (银联卡)", + "日照银行·黄海卡、财富卡借记卡", "鞍山银行·千山卡 (银联卡)", "鞍山银行·千山卡 (银联卡)", "鞍山银行·千山卡", + "青海银行·三江银行卡 (银联卡)", "青海银行·三江卡", "台州银行·大唐贷记卡", "台州银行·大唐准贷记卡", + "台州银行·大唐卡 (银联卡)", "台州银行·大唐卡", "台州银行·借记卡", "台州银行·公务卡", + "泉州银行·海峡银联卡 (银联卡)", "泉州银行·海峡储蓄卡", "泉州银行·海峡银联卡 (银联卡)", "泉州银行·海峡卡", + "泉州银行·公务卡", "昆明商业银行·春城卡 (银联卡)", "昆明商业银行·春城卡 (银联卡)", + "昆明商业银行·富滇 IC 卡 (复合卡)", "阜新银行·借记 IC 卡", "嘉兴银行·南湖借记卡 (银联卡)", "廊坊银行·白金卡", + "廊坊银行·金卡", "廊坊银行·银星卡 (银联卡)", "廊坊银行·龙凤呈祥卡", "内蒙古银行·百灵卡 (银联卡)", + "内蒙古银行·成吉思汗卡", "湖州市商业银行·百合卡", "湖州市商业银行·", "沧州银行·狮城卡", + "南宁市商业银行·桂花卡 (银联卡)", "包商银行·雄鹰卡 (银联卡)", "包商银行·包头市商业银行借记卡", + "包商银行·雄鹰贷记卡", "包商银行·包商银行内蒙古自治区公务卡", "包商银行·贷记卡", "包商银行·借记卡", + "连云港市商业银行·金猴神通借记卡", "威海商业银行·通达卡 (银联卡)", "威海市商业银行·通达借记 IC 卡", + "攀枝花市商业银行·攀枝花卡 (银联卡)", "攀枝花市商业银行·攀枝花卡", "绵阳市商业银行·科技城卡 (银联卡)", + "泸州市商业银行·酒城卡 (银联卡)", "泸州市商业银行·酒城 IC 卡", "大同市商业银行·云冈卡 (银联卡)", + "三门峡银行·天鹅卡 (银联卡)", "广东南粤银行·南珠卡 (银联卡)", "张家口市商业银行·好运 IC 借记卡", + "桂林市商业银行·漓江卡 (银联卡)", "龙江银行·福农借记卡", "龙江银行·联名借记卡", "龙江银行·福农借记卡", + "龙江银行·龙江 IC 卡", "龙江银行·社会保障卡", "龙江银行·--", "江苏长江商业银行·长江卡", + "徐州市商业银行·彭城借记卡 (银联卡)", "南充市商业银行·借记 IC 卡", "南充市商业银行·熊猫团团卡", + "莱商银行·银联标准卡", "莱芜银行·金凤卡", "莱商银行·借记 IC 卡", "德阳银行·锦程卡定活一卡通", + "德阳银行·锦程卡定活一卡通金卡", "德阳银行·锦程卡定活一卡通", "唐山市商业银行·唐山市城通卡", + "曲靖市商业银行·珠江源卡", "曲靖市商业银行·珠江源 IC 卡", "温州银行·金鹿信用卡", "温州银行·金鹿信用卡", + "温州银行·金鹿公务卡", "温州银行·贷记 IC 卡", "汉口银行·汉口银行贷记卡", "汉口银行·汉口银行贷记卡", + "汉口银行·九通香港旅游贷记普卡", "汉口银行·九通香港旅游贷记金卡", "汉口银行·贷记卡", "汉口银行·九通公务卡", + "江苏银行·聚宝借记卡", "江苏银行·月季卡", "江苏银行·紫金卡", "江苏银行·绿扬卡 (银联卡)", + "江苏银行·月季卡 (银联卡)", "江苏银行·九州借记卡 (银联卡)", "江苏银行·月季卡 (银联卡)", + "江苏银行·聚宝惠民福农卡", "江苏银行·江苏银行聚宝 IC 借记卡", "江苏银行·聚宝 IC 借记卡 VIP 卡", + "长治市商业银行·长治商行银联晋龙卡", "承德市商业银行·热河卡", "承德银行·借记 IC 卡", "德州银行·长河借记卡", + "德州银行·--", "遵义市商业银行·社保卡", "遵义市商业银行·尊卡", "邯郸市商业银行·邯银卡", + "邯郸市商业银行·邯郸银行贵宾 IC 借记卡", "安顺市商业银行·黄果树福农卡", "安顺市商业银行·黄果树借记卡", + "江苏银行·紫金信用卡 (公务卡)", "江苏银行·紫金信用卡", "江苏银行·天翼联名信用卡", "平凉市商业银行·广成卡", + "玉溪市商业银行·红塔卡", "玉溪市商业银行·红塔卡", "浙江民泰商业银行·金融 IC 卡", "浙江民泰商业银行·民泰借记卡", + "浙江民泰商业银行·金融 IC 卡 C 卡", "浙江民泰商业银行·银联标准普卡金卡", "浙江民泰商业银行·商惠通", + "上饶市商业银行·三清山卡", "东营银行·胜利卡", "泰安市商业银行·岱宗卡", "泰安市商业银行·市民一卡通", + "浙江稠州商业银行·义卡", "浙江稠州商业银行·义卡借记 IC 卡", "浙江稠州商业银行·公务卡", "自贡市商业银行·借记 IC 卡", + "自贡市商业银行·锦程卡", "鄂尔多斯银行·天骄公务卡", "鹤壁银行·鹤卡", "许昌银行·连城卡", "铁岭银行·龙凤卡", + "乐山市商业银行·大福卡", "乐山市商业银行·--", "长安银行·长长卡", "长安银行·借记 IC 卡", + "重庆三峡银行·财富人生卡", "重庆三峡银行·借记卡", "石嘴山银行·麒麟借记卡", "石嘴山银行·麒麟借记卡", + "石嘴山银行·麒麟公务卡", "盘锦市商业银行·鹤卡", "盘锦市商业银行·盘锦市商业银行鹤卡", "平顶山银行·平顶山银行公务卡", + "朝阳银行·鑫鑫通卡", "朝阳银行·朝阳银行福农卡", "朝阳银行·红山卡", "宁波东海银行·绿叶卡", + "遂宁市商业银行·锦程卡", "遂宁是商业银行·金荷卡", "保定银行·直隶卡", "保定银行·直隶卡", + "凉山州商业银行·锦程卡", "凉山州商业银行·金凉山卡", "漯河银行·福卡", "漯河银行·福源卡", "漯河银行·福源公务卡", + "达州市商业银行·锦程卡", "新乡市商业银行·新卡", "晋中银行·九州方圆借记卡", "晋中银行·九州方圆卡", + "驻马店银行·驿站卡", "驻马店银行·驿站卡", "驻马店银行·公务卡", "衡水银行·金鼎卡", "衡水银行·借记 IC 卡", + "周口银行·如愿卡", "周口银行·公务卡", "阳泉市商业银行·金鼎卡", "阳泉市商业银行·金鼎卡", + "宜宾市商业银行·锦程卡", "宜宾市商业银行·借记 IC 卡", "库尔勒市商业银行·孔雀胡杨卡", "雅安市商业银行·锦城卡", + "雅安市商业银行·--", "安阳银行·安鼎卡", "信阳银行·信阳卡", "信阳银行·公务卡", "信阳银行·信阳卡", + "华融湘江银行·华融卡", "华融湘江银行·华融卡", "营口沿海银行·祥云借记卡", "景德镇商业银行·瓷都卡", + "哈密市商业银行·瓜香借记卡", "湖北银行·金牛卡", "湖北银行·汉江卡", "湖北银行·借记卡", "湖北银行·三峡卡", + "湖北银行·至尊卡", "湖北银行·金融 IC 卡", "西藏银行·借记 IC 卡", "新疆汇和银行·汇和卡", "广东华兴银行·借记卡", + "广东华兴银行·华兴银联公司卡", "广东华兴银行·华兴联名 IC 卡", "广东华兴银行·华兴金融 IC 借记卡", "濮阳银行·龙翔卡", + "宁波通商银行·借记卡", "甘肃银行·神舟兴陇借记卡", "甘肃银行·甘肃银行神州兴陇 IC 卡", "枣庄银行·借记 IC 卡", + "本溪市商业银行·借记卡", "盛京银行·医保卡", "上海农商银行·如意卡 (银联卡)", "上海农商银行·如意卡 (银联卡)", + "上海农商银行·鑫通卡", "上海农商银行·国际如意卡", "上海农商银行·借记 IC 卡", + "常熟市农村商业银行·粒金贷记卡 (银联卡)", "常熟市农村商业银行·公务卡", "常熟市农村商业银行·粒金准贷卡", + "常熟农村商业银行·粒金借记卡 (银联卡)", "常熟农村商业银行·粒金 IC 卡", "常熟农村商业银行·粒金卡", + "深圳农村商业银行·信通卡 (银联卡)", "深圳农村商业银行·信通商务卡 (银联卡)", "深圳农村商业银行·信通卡", + "深圳农村商业银行·信通商务卡", "广州农村商业银行·福农太阳卡", "广东南海农村商业银行·盛通卡", + "广东南海农村商业银行·盛通卡 (银联卡)", "佛山顺德农村商业银行·恒通卡 (银联卡)", "佛山顺德农村商业银行·恒通卡", + "佛山顺德农村商业银行·恒通卡 (银联卡)", "江阴农村商业银行·暨阳公务卡", "江阴市农村商业银行·合作贷记卡 (银联卡)", + "江阴农村商业银行·合作借记卡", "江阴农村商业银行·合作卡 (银联卡)", "江阴农村商业银行·暨阳卡", + "重庆农村商业银行·江渝借记卡 VIP 卡", "重庆农村商业银行·江渝 IC 借记卡", "重庆农村商业银行·江渝乡情福农卡", + "东莞农村商业银行·信通卡 (银联卡)", "东莞农村商业银行·信通卡 (银联卡)", "东莞农村商业银行·信通信用卡", + "东莞农村商业银行·信通借记卡", "东莞农村商业银行·贷记 IC 卡", "张家港农村商业银行·一卡通 (银联卡)", + "张家港农村商业银行·一卡通 (银联卡)", "张家港农村商业银行·", "北京农村商业银行·信通卡", "北京农村商业银行·惠通卡", + "北京农村商业银行·凤凰福农卡", "北京农村商业银行·惠通卡", "北京农村商业银行·中国旅行卡", "北京农村商业银行·凤凰卡", + "天津农村商业银行·吉祥商联 IC 卡", "天津农村商业银行·信通借记卡 (银联卡)", "天津农村商业银行·借记 IC 卡", + "鄞州农村合作银行·蜜蜂借记卡 (银联卡)", "宁波鄞州农村合作银行·蜜蜂电子钱包 (IC)", + "宁波鄞州农村合作银行·蜜蜂 IC 借记卡", "宁波鄞州农村合作银行·蜜蜂贷记 IC 卡", "宁波鄞州农村合作银行·蜜蜂贷记卡", + "宁波鄞州农村合作银行·公务卡", "成都农村商业银行·福农卡", "成都农村商业银行·福农卡", + "珠海农村商业银行·信通卡 (银联卡)", "太仓农村商业银行·郑和卡 (银联卡)", "太仓农村商业银行·郑和 IC 借记卡", + "无锡农村商业银行·金阿福", "无锡农村商业银行·借记 IC 卡", "黄河农村商业银行·黄河卡", + "黄河农村商业银行·黄河富农卡福农卡", "黄河农村商业银行·借记 IC 卡", "天津滨海农村商业银行·四海通卡", + "天津滨海农村商业银行·四海通 e 芯卡", "武汉农村商业银行·汉卡", "武汉农村商业银行·汉卡", + "武汉农村商业银行·中国旅游卡", "江南农村商业银行·阳湖卡 (银联卡)", "江南农村商业银行·天天红火卡", + "江南农村商业银行·借记 IC 卡", "海口联合农村商业银行·海口联合农村商业银行合卡", "湖北嘉鱼吴江村镇银行·垂虹卡", + "福建建瓯石狮村镇银行·玉竹卡", "浙江平湖工银村镇银行·金平卡", "重庆璧山工银村镇银行·翡翠卡", + "重庆农村商业银行·银联标准贷记卡", "重庆农村商业银行·公务卡", "南阳村镇银行·玉都卡", + "晋中市榆次融信村镇银行·魏榆卡", "三水珠江村镇银行·珠江太阳卡", "东营莱商村镇银行·绿洲卡", "建设银行·单位结算卡", + "玉溪市商业银行·红塔卡" + }; +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/validator/IDCardUtils.java b/lib/DevJava/src/main/java/dev/utils/common/validator/IDCardUtils.java new file mode 100644 index 0000000000..6138f78085 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/validator/IDCardUtils.java @@ -0,0 +1,650 @@ +package dev.utils.common.validator; + +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.DateUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 居民身份证工具类 + * @author AbrahamCaiJin + * @author Ttt + */ +public final class IDCardUtils { + + private IDCardUtils() { + } + + // 日志 TAG + private static final String TAG = IDCardUtils.class.getSimpleName(); + + // 加权因子 + private static final int[] POWER = { + 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 + }; + // 身份证最少位数 + public static final int CHINA_ID_MIN_LENGTH = 15; + // 身份证最大位数 + public static final int CHINA_ID_MAX_LENGTH = 18; + // 省份编码 + private static final Map sCityCodeMaps = new HashMap<>(); + // 台湾身份首字母对应数字 + private static final Map sTWFirstCodeMaps = new HashMap<>(); + // 香港身份首字母对应数字 + private static final Map sHKFirstCodeMaps = new HashMap<>(); + + static { + sCityCodeMaps.put("11", "北京"); + sCityCodeMaps.put("12", "天津"); + sCityCodeMaps.put("13", "河北"); + sCityCodeMaps.put("14", "山西"); + sCityCodeMaps.put("15", "内蒙古"); + sCityCodeMaps.put("21", "辽宁"); + sCityCodeMaps.put("22", "吉林"); + sCityCodeMaps.put("23", "黑龙江"); + sCityCodeMaps.put("31", "上海"); + sCityCodeMaps.put("32", "江苏"); + sCityCodeMaps.put("33", "浙江"); + sCityCodeMaps.put("34", "安徽"); + sCityCodeMaps.put("35", "福建"); + sCityCodeMaps.put("36", "江西"); + sCityCodeMaps.put("37", "山东"); + sCityCodeMaps.put("41", "河南"); + sCityCodeMaps.put("42", "湖北"); + sCityCodeMaps.put("43", "湖南"); + sCityCodeMaps.put("44", "广东"); + sCityCodeMaps.put("45", "广西"); + sCityCodeMaps.put("46", "海南"); + sCityCodeMaps.put("50", "重庆"); + sCityCodeMaps.put("51", "四川"); + sCityCodeMaps.put("52", "贵州"); + sCityCodeMaps.put("53", "云南"); + sCityCodeMaps.put("54", "西藏"); + sCityCodeMaps.put("61", "陕西"); + sCityCodeMaps.put("62", "甘肃"); + sCityCodeMaps.put("63", "青海"); + sCityCodeMaps.put("64", "宁夏"); + sCityCodeMaps.put("65", "新疆"); + sCityCodeMaps.put("71", "台湾"); + sCityCodeMaps.put("81", "香港"); + sCityCodeMaps.put("82", "澳门"); + sCityCodeMaps.put("83", "台湾"); + sCityCodeMaps.put("91", "国外"); + sTWFirstCodeMaps.put("A", 10); + sTWFirstCodeMaps.put("B", 11); + sTWFirstCodeMaps.put("C", 12); + sTWFirstCodeMaps.put("D", 13); + sTWFirstCodeMaps.put("E", 14); + sTWFirstCodeMaps.put("F", 15); + sTWFirstCodeMaps.put("G", 16); + sTWFirstCodeMaps.put("H", 17); + sTWFirstCodeMaps.put("J", 18); + sTWFirstCodeMaps.put("K", 19); + sTWFirstCodeMaps.put("L", 20); + sTWFirstCodeMaps.put("M", 21); + sTWFirstCodeMaps.put("N", 22); + sTWFirstCodeMaps.put("P", 23); + sTWFirstCodeMaps.put("Q", 24); + sTWFirstCodeMaps.put("R", 25); + sTWFirstCodeMaps.put("S", 26); + sTWFirstCodeMaps.put("T", 27); + sTWFirstCodeMaps.put("U", 28); + sTWFirstCodeMaps.put("V", 29); + sTWFirstCodeMaps.put("X", 30); + sTWFirstCodeMaps.put("Y", 31); + sTWFirstCodeMaps.put("W", 32); + sTWFirstCodeMaps.put("Z", 33); + sTWFirstCodeMaps.put("I", 34); + sTWFirstCodeMaps.put("O", 35); + sHKFirstCodeMaps.put("A", 1); + sHKFirstCodeMaps.put("B", 2); + sHKFirstCodeMaps.put("C", 3); + sHKFirstCodeMaps.put("R", 18); + sHKFirstCodeMaps.put("U", 21); + sHKFirstCodeMaps.put("Z", 26); + sHKFirstCodeMaps.put("X", 24); + sHKFirstCodeMaps.put("W", 23); + sHKFirstCodeMaps.put("O", 15); + sHKFirstCodeMaps.put("N", 14); + } + + /** + * 身份证校验规则, 验证 15 位身份编码是否合法 + * @param idCard 待验证身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateIdCard15(final String idCard) { + // 属于数字, 并且长度为 15 位数 + if (isNumber(idCard) && idCard.length() == CHINA_ID_MIN_LENGTH) { + // 获取省份编码 + String provinceCode = idCard.substring(0, 2); + if (sCityCodeMaps.get(provinceCode) == null) return false; + // 获取出生日期 + String birthCode = idCard.substring(6, 12); + Date birthDate = null; + try { + birthDate = DateUtils.getSafeDateFormat(DevFinal.TIME.yy).parse(birthCode.substring(0, 2)); + } catch (ParseException e) { + JCLogUtils.eTag(TAG, e, "validateIdCard15"); + } + Calendar calendar = Calendar.getInstance(); + if (birthDate != null) calendar.setTime(birthDate); + // 判断是否有效日期 + return validateDateSmallerThenNow( + calendar.get(Calendar.YEAR), + Integer.parseInt(birthCode.substring(2, 4)), + Integer.parseInt(birthCode.substring(4, 6)) + ); + } + return false; + } + + /** + * 身份证校验规则, 验证 18 位身份编码是否合法 + * @param idCard 待验证身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateIdCard18(final String idCard) { + if (idCard != null && idCard.length() == CHINA_ID_MAX_LENGTH) { + // 前 17 位 + String code17 = idCard.substring(0, 17); + // 第 18 位 + String code18 = idCard.substring(17, CHINA_ID_MAX_LENGTH); + // 判断前 17 位是否数字 + if (isNumber(code17)) { + try { + int[] cardArrays = convertCharToInt(code17.toCharArray()); + int sum17 = getPowerSum(cardArrays); + // 获取校验位 + String str = getCheckCode18(sum17); + // 判断最后一位是否一样 + if (str.length() > 0 && str.equalsIgnoreCase(code18)) { + return true; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateIdCard18"); + } + } + } + return false; + } + + /** + * 将 15 位身份证号码转换为 18 位 + * @param idCard 15 位身份编码 + * @return 18 位身份编码 + */ + public static String convert15CardTo18(final String idCard) { + // 属于数字, 并且长度为 15 位数 + if (isNumber(idCard) && idCard.length() == CHINA_ID_MIN_LENGTH) { + String idCard18; + Date birthDate = null; + // 获取出生日期 + String birthday = idCard.substring(6, 12); + try { + birthDate = DateUtils.getSafeDateFormat(DevFinal.TIME.yyMMdd_HYPHEN).parse(birthday); + } catch (ParseException e) { + JCLogUtils.eTag(TAG, e, "convert15CardTo18"); + } + Calendar calendar = Calendar.getInstance(); + if (birthDate != null) calendar.setTime(birthDate); + try { + // 获取出生年 ( 完全表现形式, 如: 2010) + String year = String.valueOf(calendar.get(Calendar.YEAR)); + // 保存省市区信息 + 年 + 月日 + 后续信息 ( 顺序位、性别等 ) + idCard18 = idCard.substring(0, 6) + year + idCard.substring(8); + // 转换字符数组 + int[] cardArrays = convertCharToInt(idCard18.toCharArray()); + int sum17 = getPowerSum(cardArrays); + // 获取校验位 + String str = getCheckCode18(sum17); + // 判断长度, 拼接校验位 + return (str.length() > 0) ? (idCard18 + str) : null; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convert15CardTo18"); + } + } + return null; + } + + /** + * 验证台湾身份证号码 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateTWCard(final String idCard) { + // 台湾身份证 10 位 + if (idCard == null || idCard.length() != 10) return false; + try { + // 第一位英文 不同县市 + String start = idCard.substring(0, 1); + String mid = idCard.substring(1, 9); + String end = idCard.substring(9, 10); + int intStart = sTWFirstCodeMaps.get(start); + int sum = intStart / 10 + (intStart % 10) * 9; + char[] chars = mid.toCharArray(); + int iflag = 8; + for (char value : chars) { + sum = sum + Integer.parseInt(String.valueOf(value)) * iflag; + iflag--; + } + return (sum % 10 == 0 ? 0 : 10 - sum % 10) == Integer.parseInt(end); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateTWCard"); + } + return false; + } + + /** + * 验证香港身份证号码 ( 部份特殊身份证无法检查 ) + * 身份证前 2 位为英文字符, 如果只出现一个英文字符则表示第一位是空格, 对应数字 58 前 2 位英文字符 A-Z 分别对应数字 10-35 + * 最后一位校验码为 0-9 的数字加上字符 "A", "A" 代表 10 + * 将身份证号码全部转换为数字, 分别对应乘 9-1 相加的总和, 整除 11 则证件号码有效 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateHKCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return false; + try { + String card = idCard.replaceAll("[\\(|\\)]", ""); + int sum; + if (card.length() == 9) { + sum = ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 9 + ((int) card.substring(1, 2).toUpperCase().toCharArray()[0] - 55) * 8; + card = card.substring(1, 9); + } else { + sum = 522 + ((int) card.substring(0, 1).toUpperCase().toCharArray()[0] - 55) * 8; + } + String mid = card.substring(1, 7); + String end = card.substring(7, 8); + char[] chars = mid.toCharArray(); + int iflag = 7; + for (char value : chars) { + sum = sum + Integer.parseInt(String.valueOf(value)) * iflag; + iflag--; + } + if (end.equalsIgnoreCase("A")) { + sum = sum + 10; + } else { + sum = sum + Integer.parseInt(end); + } + return (sum % 11 == 0); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateHKCard"); + } + return false; + } + + /** + * 判断 10 位数的身份证号, 是否合法 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static String[] validateIdCard10(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + String[] info = new String[3]; + info[0] = "N"; // 默认未知地区 + info[1] = "N"; // 默认未知性别 + info[2] = "false"; // 默认非法 + try { + // 属于 8, 9, 10 长度范围内 + if (idCard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 + info[0] = "台湾"; + String char2 = idCard.substring(1, 2); + if (char2.equals("1")) { + info[1] = "M"; + } else if (char2.equals("2")) { + info[1] = "F"; + } else { + info[1] = "N"; + info[2] = "false"; + return info; + } + info[2] = validateTWCard(idCard) ? "true" : "false"; + } else if (idCard.matches("^[1|5|7][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 + info[0] = "澳门"; + info[1] = "N"; + // TODO + } else if (idCard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 + info[0] = "香港"; + info[1] = "N"; + info[2] = validateHKCard(idCard) ? "true" : "false"; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "validateIdCard10"); + } + return info; + } + + /** + * 验证身份证是否合法 + * @param idCard 身份证号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean validateCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return false; + String card = idCard.trim(); + if (validateIdCard18(card)) return true; + if (validateIdCard15(card)) return true; + String[] cardArrays = validateIdCard10(card); + return (cardArrays != null && "true".equals(cardArrays[2])); + } + + /** + * 根据身份编号获取年龄 + * @param idCard 身份编号 + * @return 年龄 + */ + public static int getAgeByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return 0; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + String year = idCardStr.substring(6, 10); + // 获取当前年份 + int currentYear = Calendar.getInstance().get(Calendar.YEAR); + // 当前年份 ( 出生年份 ) + return currentYear - Integer.parseInt(year); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getAgeByIdCard"); + } + return 0; + } + + /** + * 根据身份编号获取生日 + * @param idCard 身份编号 + * @return 生日 (yyyyMMdd) + */ + public static String getBirthByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + return idCardStr.substring(6, 14); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBirthByIdCard"); + } + return null; + } + + /** + * 根据身份编号获取生日 + * @param idCard 身份编号 + * @return 生日 (yyyyMMdd) + */ + public static String getBirthdayByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.replaceAll("(\\d{4})(\\d{2})(\\d{2})", "$1-$2-$3"); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getBirthdayByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 年份 ) + * @param idCard 身份编号 + * @return 生日 (yyyy) + */ + public static String getYearByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(0, 4); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getYearByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 月份 ) + * @param idCard 身份编号 + * @return 生日 (MM) + */ + public static String getMonthByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(4, 6); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getMonthByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取生日 ( 天数 ) + * @param idCard 身份编号 + * @return 生日 (dd) + */ + public static String getDateByIdCard(final String idCard) { + // 获取生日 + String birth = getBirthByIdCard(idCard); + // 进行处理 + if (birth != null) { + try { + return birth.substring(6, 8); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getDateByIdCard"); + } + } + return null; + } + + /** + * 根据身份编号获取性别 + * @param idCard 身份编号 + * @return 性别 男 (M)、女 (F)、未知 (N) + */ + public static String getGenderByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + String idCardStr = idCard; + // 属于 15 位身份证, 则转换为 18 位 + if (idCardStr.length() == CHINA_ID_MIN_LENGTH) { + idCardStr = convert15CardTo18(idCard); + } + // 属于 18 位身份证才处理 + if (idCardStr != null && idCardStr.length() == CHINA_ID_MAX_LENGTH) { + // 获取第 17 位性别信息 + String cardNumber = idCardStr.substring(16, 17); + // 奇数为男, 偶数为女 + return (Integer.parseInt(cardNumber) % 2 == 0) ? "F" : "M"; + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getGenderByIdCard"); + } + // 默认未知 + return "N"; + } + + /** + * 根据身份编号获取户籍省份 + * @param idCard 身份编码 + * @return 省级编码 + */ + public static String getProvinceByIdCard(final String idCard) { + if (StringUtils.isEmpty(idCard)) return null; + try { + // 身份证长度 + int idCardLength = idCard.length(); + // 属于 15 位身份证、或 18 位身份证 + if (idCardLength == CHINA_ID_MIN_LENGTH || idCardLength == CHINA_ID_MAX_LENGTH) { + return sCityCodeMaps.get(idCard.substring(0, 2)); + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "getProvinceByIdCard"); + } + return null; + } + + /** + * 将身份证的每位和对应位的加权因子相乘之后, 再获取和值 + * @param data byte[] 数据 + * @return 身份证编码, 加权引子 + */ + public static int getPowerSum(final int[] data) { + if (data == null) return 0; + int len = data.length; + if (len == 0) return 0; + int powerLength = POWER.length; + int sum = 0; + if (powerLength == len) { + for (int i = 0; i < len; i++) { + for (int j = 0; j < powerLength; j++) { + if (i == j) { + sum = sum + data[i] * POWER[j]; + } + } + } + } + return sum; + } + + /** + * 将 POWER 和值与 11 取模获取余数进行校验码判断 + * @param sum {@link IDCardUtils#getPowerSum} + * @return 校验位 + */ + public static String getCheckCode18(final int sum) { + String code = ""; + switch (sum % 11) { + case 10: + code = "2"; + break; + case 9: + code = "3"; + break; + case 8: + code = "4"; + break; + case 7: + code = "5"; + break; + case 6: + code = "6"; + break; + case 5: + code = "7"; + break; + case 4: + code = "8"; + break; + case 3: + code = "9"; + break; + case 2: + code = "x"; + break; + case 1: + code = "0"; + break; + case 0: + code = "1"; + break; + } + return code; + } + + // ========== + // = 私有方法 = + // ========== + + /** + * 将字符数组转换成数字数组 + * @param data char[] + * @return int[] + */ + private static int[] convertCharToInt(final char[] data) { + if (data == null) return null; + int len = data.length; + if (len == 0) return null; + try { + int[] arrays = new int[len]; + for (int i = 0; i < len; i++) { + arrays[i] = Integer.parseInt(String.valueOf(data[i])); + } + return arrays; + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "convertCharToInt"); + } + return null; + } + + /** + * 验证小于当前日期 是否有效 + * @param yearData 待校验的日期 ( 年 ) + * @param monthData 待校验的日期 ( 月 1-12) + * @param dayData 待校验的日期 ( 日 ) + * @return {@code true} yes, {@code false} no + */ + private static boolean validateDateSmallerThenNow( + final int yearData, + final int monthData, + final int dayData + ) { + int year = Calendar.getInstance().get(Calendar.YEAR); + int datePerMonth; + int MIN = 1930; + if (yearData < MIN || yearData >= year) { + return false; + } + if (monthData < 1 || monthData > 12) { + return false; + } + switch (monthData) { + case 4: + case 6: + case 9: + case 11: + datePerMonth = 30; + break; + case 2: + boolean dm = (yearData % 4 == 0 && yearData % 100 != 0 || yearData % 400 == 0) && yearData > MIN; + datePerMonth = dm ? 29 : 28; + break; + default: + datePerMonth = 31; + } + return (dayData >= 1) && (dayData <= datePerMonth); + } + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + private static boolean isNumber(final String str) { + return !StringUtils.isEmpty(str) && str.matches("^[0-9]*$"); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java b/lib/DevJava/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java new file mode 100644 index 0000000000..9f46ef93c2 --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/validator/ValiToPhoneUtils.java @@ -0,0 +1,248 @@ +package dev.utils.common.validator; + +/** + * detail: 检验联系 ( 手机号码、座机 ) 工具类 + * @author Ttt + *
+ *     @see 
+ *     

+ * 验证手机号码是否正确 + * 移动: 134 135 136 137 138 139 147 148 150 151 152 157 158 159 172 178 182 183 184 187 188 195 198 + * 联通: 130 131 132 145 146 155 156 166 167 171 175 176 185 186 196 + * 电信: 133 149 153 173 174 177 180 181 189 190 191 193 199 + * 广电: 192 + * 虚拟运营商: 162 165 167 170 171 + *
+ */ +public final class ValiToPhoneUtils { + + private ValiToPhoneUtils() { + } + + /** + * 中国手机号码格式验证 ( 简单手机号码校验 ) + *
+     *     在输入可以调用该方法, 点击发送验证码, 使用 isPhone
+     *     简单手机号码校验 校验手机号码的长度和 1 开头 ( 是否 11 位 )
+     * 
+ * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneSimple(final String phone) { + return ValidatorUtils.match(CHAIN_PHONE_SIMPLE, phone); + } + + /** + * 是否中国手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhone(final String phone) { + return ValidatorUtils.match(CHINA_PHONE_PATTERN, phone); + } + + /** + * 是否中国移动手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaMobile(final String phone) { + return ValidatorUtils.match(CHINA_MOBILE_PATTERN, phone); + } + + /** + * 是否中国联通手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaUnicom(final String phone) { + return ValidatorUtils.match(CHINA_UNICOM_PATTERN, phone); + } + + /** + * 是否中国电信手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaTelecom(final String phone) { + return ValidatorUtils.match(CHINA_TELECOM_PATTERN, phone); + } + + /** + * 是否中国广电手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaBroadcast(final String phone) { + return ValidatorUtils.match(CHINA_BROADCAST_PATTERN, phone); + } + + /** + * 是否中国虚拟运营商手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaVirtual(final String phone) { + return ValidatorUtils.match(CHINA_VIRTUAL_PATTERN, phone); + } + + // = + + /** + * 是否中国香港手机号码 + * @param phone 待校验的手机号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneToChinaHkMobile(final String phone) { + return ValidatorUtils.match(CHINA_HK_PHONE_PATTERN, phone); + } + + /** + * 验证电话号码的格式 + * @param phone 待校验的号码 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPhoneCallNum(final String phone) { + return ValidatorUtils.match(PHONE_CALL_PATTERN, phone); + } + + // ============== + // = 手机号码判断 = + // ============== + + // 简单手机号码校验 校验手机号码的长度和 1 开头 ( 是否 11 位 ) + public static final String CHAIN_PHONE_SIMPLE = "^(?:\\+86)?1\\d{10}$"; + + // 中国手机号码正则 + public static final String CHINA_PHONE_PATTERN; + + // 中国移动号码正则 + public static final String CHINA_MOBILE_PATTERN; + + // 中国联通号码正则 + public static final String CHINA_UNICOM_PATTERN; + + // 中国电信号码正则 + public static final String CHINA_TELECOM_PATTERN; + + // 中国广电号码正则 + public static final String CHINA_BROADCAST_PATTERN; + + // 中国虚拟运营商号码正则 + public static final String CHINA_VIRTUAL_PATTERN; + + // 中国香港手机号码正则 香港手机号码 8 位数, 5|6|8|9 开头 + 7 位任意数 + public static final String CHINA_HK_PHONE_PATTERN = "^(5|6|8|9)\\d{7}$"; + + // ========== + // = 座机判断 = + // ========== + + // 座机电话格式验证 + public static final String PHONE_CALL_PATTERN = "^(?:\\(\\d{3,4}\\)|\\d{3,4}-)?\\d{7,8}(?:-\\d{1,4})?$"; + + static { + + StringBuilder builder; + + // ========== + // = 中国移动 = + // ========== + + // 中国移动: 134 135 136 137 138 139 147 148 150 151 152 157 158 159 172 178 182 183 184 187 188 195 198 + builder = new StringBuilder(); + builder.append("^13[4,5,6,7,8,9]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[7,8]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[0,1,2,7,8,9]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^17[2,8]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[2,3,4,7,8]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[5,8]{1}\\d{8}$"); // 19 开头 + CHINA_MOBILE_PATTERN = builder.toString(); + + // ========== + // = 中国联通 = + // ========== + + // 中国联通: 130 131 132 145 146 155 156 166 167 171 175 176 185 186 196 + builder = new StringBuilder(); + builder.append("^13[0,1,2]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[5,6]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[5,6]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^16[6,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[1,5,6]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[5,6]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[6]{1}\\d{8}$"); // 19 开头 + CHINA_UNICOM_PATTERN = builder.toString(); + + // ========== + // = 中国电信 = + // ========== + + // 中国电信: 133 149 153 173 174 177 180 181 189 190 191 193 199 + builder = new StringBuilder(); + builder.append("^13[3]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[9]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[3]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^17[3,4,7]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[0,1,9]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[0,1,3,9]{1}\\d{8}$"); // 19 开头 + CHINA_TELECOM_PATTERN = builder.toString(); + + // ========== + // = 中国广电 = + // ========== + + // 中国广电: 192 + builder = new StringBuilder(); + builder.append("^19[2]{1}\\d{8}$"); // 19 开头 + CHINA_BROADCAST_PATTERN = builder.toString(); + + // ============ + // = 虚拟运营商 = + // ============ + + // 虚拟运营商: 162 165 167 170 171 + builder = new StringBuilder(); + builder.append("^16[2,5,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[0,1]{1}\\d{8}$"); // 17 开头 + CHINA_VIRTUAL_PATTERN = builder.toString(); + + // ========== + // = 中国手机 = + // ========== + + // 中国手机: 130 131 132 133 134 135 136 137 138 139 145 146 147 148 149 150 151 152 153 155 156 157 158 159 162 165 166 167 167 170 171 171 172 173 174 175 176 177 178 180 181 182 183 184 185 186 187 188 189 190 191 192 193 195 196 198 199 + builder = new StringBuilder(); + builder.append("^13[0,1,2,3,4,5,6,7,8,9]{1}\\d{8}$"); // 13 开头 + builder.append("|"); + builder.append("^14[5,6,7,8,9]{1}\\d{8}$"); // 14 开头 + builder.append("|"); + builder.append("^15[0,1,2,3,5,6,7,8,9]{1}\\d{8}$"); // 15 开头 + builder.append("|"); + builder.append("^16[2,5,6,7,7]{1}\\d{8}$"); // 16 开头 + builder.append("|"); + builder.append("^17[0,1,1,2,3,4,5,6,7,8]{1}\\d{8}$"); // 17 开头 + builder.append("|"); + builder.append("^18[0,1,2,3,4,5,6,7,8,9]{1}\\d{8}$"); // 18 开头 + builder.append("|"); + builder.append("^19[0,1,2,3,5,6,8,9]{1}\\d{8}$"); // 19 开头 + CHINA_PHONE_PATTERN = builder.toString(); + } +} \ No newline at end of file diff --git a/lib/DevJava/src/main/java/dev/utils/common/validator/ValidatorUtils.java b/lib/DevJava/src/main/java/dev/utils/common/validator/ValidatorUtils.java new file mode 100644 index 0000000000..352a0eb85a --- /dev/null +++ b/lib/DevJava/src/main/java/dev/utils/common/validator/ValidatorUtils.java @@ -0,0 +1,214 @@ +package dev.utils.common.validator; + +import java.util.regex.Pattern; + +import dev.utils.DevFinal; +import dev.utils.JCLogUtils; +import dev.utils.common.StringUtils; + +/** + * detail: 校验工具类 + * @author Ttt + */ +public final class ValidatorUtils { + + private ValidatorUtils() { + } + + // 日志 TAG + private static final String TAG = ValidatorUtils.class.getSimpleName(); + + /** + * 通用匹配函数 + * @param regex 正则表达式 + * @param input 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean match( + final String regex, + final String input + ) { + if (!StringUtils.isEmpty(input)) { + try { + return Pattern.matches(regex, input); + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "match"); + } + } + return false; + } + + // = + + /** + * 检验数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumber(final String str) { + return match(DevFinal.REGEX.NUMBER, str); + } + + /** + * 检验数字或包含小数点 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberDecimal(final String str) { + return match(DevFinal.REGEX.NUMBER_OR_DECIMAL, str); + } + + /** + * 判断字符串是不是全是字母 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isLetter(final String str) { + return match(DevFinal.REGEX.LETTER, str); + } + + /** + * 判断字符串是不是包含数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContainNumber(final String str) { + return match(DevFinal.REGEX.CONTAIN_NUMBER, str); + } + + /** + * 判断字符串是不是只含字母和数字 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNumberLetter(final String str) { + return match(DevFinal.REGEX.NUMBER_OR_LETTER, str); + } + + /** + * 检验特殊符号 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isSpec(final String str) { + return match(DevFinal.REGEX.SPECIAL, str); + } + + /** + * 检验微信号 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isWx(final String str) { + return match(DevFinal.REGEX.WX, str); + } + + /** + * 检验真实姓名 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRealName(final String str) { + return match(DevFinal.REGEX.REALNAME, str); + } + + /** + * 校验昵称 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isNickName(final String str) { + return match(DevFinal.REGEX.NICKNAME, str); + } + + /** + * 校验用户名 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUserName(final String str) { + return match(DevFinal.REGEX.USERNAME, str); + } + + /** + * 校验密码 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPassword(final String str) { + return match(DevFinal.REGEX.PASSWORD, str); + } + + /** + * 校验邮箱 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isEmail(final String str) { + return match(DevFinal.REGEX.EMAIL, str); + } + + /** + * 校验 URL + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isUrl(final String str) { + if (!StringUtils.isEmpty(str)) { + return match(DevFinal.REGEX.URL, str.toLowerCase()); + } + return false; + } + + /** + * 校验 IP 地址 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isIPAddress(final String str) { + return match(DevFinal.REGEX.IP_ADDRESS, str); + } + + /** + * 校验汉字 ( 无符号, 纯汉字 ) + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChinese(final String str) { + return match(DevFinal.REGEX.CHINESE, str); + } + + /** + * 判断字符串是不是全是中文 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isChineseAll(final String str) { + return match(DevFinal.REGEX.CHINESE_ALL, str); + } + + /** + * 判断字符串中包含中文、包括中文字符标点等 + * @param str 待校验的字符串 + * @return {@code true} yes, {@code false} no + */ + public static boolean isContainChinese(final String str) { + if (!StringUtils.isEmpty(str)) { + try { + int length = str.length(); + if (length != 0) { + char[] dChar = str.toCharArray(); + for (int i = 0; i < length; i++) { + boolean flag = String.valueOf(dChar[i]).matches(DevFinal.REGEX.CHINESE_ALL2); + if (flag) { + return true; + } + } + } + } catch (Exception e) { + JCLogUtils.eTag(TAG, e, "isContainChinese"); + } + } + return false; + } +} \ No newline at end of file diff --git a/lib/DevMVVM/.gitignore b/lib/DevMVVM/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevMVVM/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevMVVM/CHANGELOG.md b/lib/DevMVVM/CHANGELOG.md new file mode 100644 index 0000000000..670a47b4f7 --- /dev/null +++ b/lib/DevMVVM/CHANGELOG.md @@ -0,0 +1,7 @@ +Change Log +========== + +Version 1.0.0 *(2022-09-20)* +---------------------------- + +* Initial release diff --git a/lib/DevMVVM/README.md b/lib/DevMVVM/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/DevMVVM/build.gradle b/lib/DevMVVM/build.gradle new file mode 100644 index 0000000000..c2cb917cbb --- /dev/null +++ b/lib/DevMVVM/build.gradle @@ -0,0 +1,46 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply from: rootProject.file(files.unified_use_view_data_binding_gradle) +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' + +android.defaultConfig { + versionCode versions.dev_mvvm_versionCode + versionName versions.dev_mvvm_versionName + // DevMVVM Module Version + buildConfigField "int", "DevMVVM_VersionCode", "${versions.dev_mvvm_versionCode}" + buildConfigField "String", "DevMVVM_Version", "\"${versions.dev_mvvm_versionName}\"" + // DevAssist Module Version + buildConfigField "int", "DevAssist_VersionCode", "${versions.dev_assist_versionCode}" + buildConfigField "String", "DevAssist_Version", "\"${versions.dev_assist_versionName}\"" + // DevBaseMVVM Module Version + buildConfigField "int", "DevBaseMVVM_VersionCode", "${versions.dev_base_mvvm_versionCode}" + buildConfigField "String", "DevBaseMVVM_Version", "\"${versions.dev_base_mvvm_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_assist + api deps.dev.dev_base_mvvm + api deps.dev.dev_engine + api deps.dev.dev_widget + } else { + // 编译时使用 + api project(':DevAssist') + api project(':DevBaseMVVM') + api project(':DevEngine') + api project(':DevWidget') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevMVVM/proguard-rules.pro b/lib/DevMVVM/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/DevMVVM/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/DevMVVM/project.properties b/lib/DevMVVM/project.properties new file mode 100644 index 0000000000..dc4049a0bc --- /dev/null +++ b/lib/DevMVVM/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevMVVM +project.groupId=io.github.afkt +project.artifactId=DevMVVM +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevMVVM \ No newline at end of file diff --git a/lib/DevMVVM/src/main/AndroidManifest.xml b/lib/DevMVVM/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c51b589cf7 --- /dev/null +++ b/lib/DevMVVM/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/DevMVVM.kt b/lib/DevMVVM/src/main/java/dev/mvvm/DevMVVM.kt new file mode 100644 index 0000000000..1964058745 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/DevMVVM.kt @@ -0,0 +1,122 @@ +package dev.mvvm + +import dev.base.DevVariableExt +import dev.engine.image.ImageConfig +import dev.mvvm.base.Config +import dev.mvvm.utils.image.AppImageConfig + +/** + * detail: DevMVVM + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevMVVM { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevMVVM 版本号 + * @return DevMVVM versionCode + */ + fun getDevMVVMVersionCode(): Int { + return BuildConfig.DevMVVM_VersionCode + } + + /** + * 获取 DevMVVM 版本 + * @return DevMVVM versionName + */ + fun getDevMVVMVersion(): String { + return BuildConfig.DevMVVM_Version + } + + /** + * 获取 DevAssist 版本号 + * @return DevAssist versionCode + */ + fun getDevAssistVersionCode(): Int { + return BuildConfig.DevAssist_VersionCode + } + + /** + * 获取 DevAssist 版本 + * @return DevAssist versionName + */ + fun getDevAssistVersion(): String { + return BuildConfig.DevAssist_Version + } + + /** + * 获取 DevBaseMVVM 版本号 + * @return DevBaseMVVM versionCode + */ + fun getDevBaseMVVMVersionCode(): Int { + return BuildConfig.DevBaseMVVM_VersionCode + } + + /** + * 获取 DevBaseMVVM 版本 + * @return DevBaseMVVM versionName + */ + fun getDevBaseMVVMVersion(): String { + return BuildConfig.DevBaseMVVM_Version + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 开启日志开关 + */ + fun openLog(): DevMVVM { + Config.openLog() + return this + } + + /** + * 设置默认点击时间间隔 + * @param intervalTime 双击时间间隔 + */ + fun setIntervalTime(intervalTime: Long): DevMVVM { + Config.setIntervalTime(intervalTime) + return this + } + + /** + * 设置 ImageConfig 创建器 + * @param creator Creator + */ + fun setImageCreator(creator: DevVariableExt.Creator): DevMVVM { + AppImageConfig.setCreator(creator) + return this + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/Config.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/Config.kt new file mode 100644 index 0000000000..4950b6671d --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/Config.kt @@ -0,0 +1,66 @@ +package dev.mvvm.base + +import dev.expand.engine.log.log_isPrintLog +import dev.utils.app.ClickUtils + +/** + * detail: DevMVVM 通用配置方法 + * @author Ttt + */ +object Config { + + // 是否打印日志 ( 用于控制 MVVM 模块 ) + private var JUDGE_PRINT_LOG = false + + // 双击间隔时间 + private var INTERVAL_TIME = ClickUtils.INTERVAL_TIME + + /** + * 开启日志开关 + */ + internal fun openLog() { + JUDGE_PRINT_LOG = true + } + + /** + * 设置默认点击时间间隔 + * @param intervalTime 双击时间间隔 + */ + internal fun setIntervalTime(intervalTime: Long) { + INTERVAL_TIME = intervalTime + } + + // ============== + // = 对外公开方法 = + // ============== + + /** + * 是否打印日志 + * @return `true` yes, `false` no + */ + fun printLog(): Boolean { + return JUDGE_PRINT_LOG + } + + /** + * 是否打印日志 + * @param engine String? + * @return `true` yes, `false` no + */ + fun printLog(engine: String?): Boolean { + return JUDGE_PRINT_LOG && log_isPrintLog(engine) + } + + /** + * 获取双击间隔时间 + * @param intervalTime 原始间隔时间 + * @return 双击间隔时间 + */ + fun intervalTime(intervalTime: Long): Long { + return if (intervalTime == 0L) { + INTERVAL_TIME + } else { + intervalTime + } + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapter.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapter.kt new file mode 100644 index 0000000000..6ee04ff393 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapter.kt @@ -0,0 +1,49 @@ +package dev.mvvm.base.adapter + +import android.view.ViewGroup +import androidx.databinding.ViewDataBinding +import dev.adapter.DevDataAdapter +import dev.base.adapter.DevBaseViewDataBindingVH +import dev.base.adapter.newDataBindingViewHolder +import dev.mvvm.base.adapter.item.ItemBinding +import dev.mvvm.base.adapter.item.ItemLifecycle + +/** + * detail: 通用 DataBinding Data Adapter + * @author Ttt + */ +abstract class BaseDataAdapter( + val itemBinding: ItemBinding, + var itemLifecycle: ItemLifecycle = ItemLifecycle.of() +) : DevDataAdapter>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewDataBindingVH { + return newDataBindingViewHolder(parent, itemBinding.layoutRes) + } + + override fun onBindViewHolder( + holder: DevBaseViewDataBindingVH, + position: Int + ) { + itemLifecycle.tryGetLifecycleOwner(holder.itemView) + onItemBinding(holder.binding, position, getDataItem(position)) + itemLifecycle.getLifecycleOwner()?.let { + holder.binding.lifecycleOwner = it + } + } + + /** + * Item DataBinding + * @param binding VDB + * @param position Int + * @param item 对应索引实体类 + */ + abstract fun onItemBinding( + binding: VDB, + position: Int, + item: T + ) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt.kt new file mode 100644 index 0000000000..abc71bb11f --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt.kt @@ -0,0 +1,43 @@ +package dev.mvvm.base.adapter + +import android.view.ViewGroup +import androidx.databinding.ViewDataBinding +import dev.adapter.DevDataAdapterExt +import dev.base.adapter.DevBaseViewDataBindingVH +import dev.base.adapter.newDataBindingViewHolder +import dev.mvvm.base.adapter.item.ItemBinding + +/** + * detail: 通用 DataBinding Data AdapterExt + * @author Ttt + */ +abstract class BaseDataAdapterExt( + val itemBinding: ItemBinding +) : DevDataAdapterExt>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewDataBindingVH { + return newDataBindingViewHolder(parent, itemBinding.layoutRes) + } + + override fun onBindViewHolder( + holder: DevBaseViewDataBindingVH, + position: Int + ) { + onItemBinding(holder.binding, position, getDataItem(position)) + } + + /** + * Item DataBinding + * @param binding VDB + * @param position Int + * @param item 对应索引实体类 + */ + abstract fun onItemBinding( + binding: VDB, + position: Int, + item: T + ) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt2.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt2.kt new file mode 100644 index 0000000000..e1c51f5368 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/BaseDataAdapterExt2.kt @@ -0,0 +1,43 @@ +package dev.mvvm.base.adapter + +import android.view.ViewGroup +import androidx.databinding.ViewDataBinding +import dev.adapter.DevDataAdapterExt2 +import dev.base.adapter.DevBaseViewDataBindingVH +import dev.base.adapter.newDataBindingViewHolder +import dev.mvvm.base.adapter.item.ItemBinding + +/** + * detail: 通用 DataBinding Data AdapterExt2 + * @author Ttt + */ +abstract class BaseDataAdapterExt2( + val itemBinding: ItemBinding +) : DevDataAdapterExt2>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DevBaseViewDataBindingVH { + return newDataBindingViewHolder(parent, itemBinding.layoutRes) + } + + override fun onBindViewHolder( + holder: DevBaseViewDataBindingVH, + position: Int + ) { + onItemBinding(holder.binding, position, getDataItem(position)) + } + + /** + * Item DataBinding + * @param binding VDB + * @param position Int + * @param item 对应索引实体类 + */ + abstract fun onItemBinding( + binding: VDB, + position: Int, + item: T + ) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemBinding.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemBinding.kt new file mode 100644 index 0000000000..6f8f113977 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemBinding.kt @@ -0,0 +1,29 @@ +package dev.mvvm.base.adapter.item + +import android.util.SparseArray +import androidx.annotation.LayoutRes + +/** + * detail: Item Binding 信息类 + * @author Ttt + */ +class ItemBinding( + // Item 绑定实体类 + val variableId: Int, + // DataBinding 绑定布局 + @LayoutRes val layoutRes: Int +) { + + companion object { + + fun of( + variableId: Int, + @LayoutRes layoutRes: Int + ): ItemBinding { + return ItemBinding(variableId, layoutRes) + } + } + + // 额外绑定参数 + private val extraBindings: SparseArray? = null +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemLifecycle.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemLifecycle.kt new file mode 100644 index 0000000000..5ae1d0444f --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/adapter/item/ItemLifecycle.kt @@ -0,0 +1,99 @@ +package dev.mvvm.base.adapter.item + +import android.view.View +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import dev.mvvm.command.BindingGet + +/** + * detail: Item Binding Lifecycle + * @author Ttt + */ +class ItemLifecycle private constructor() { + + companion object { + + fun of(): ItemLifecycle { + return ItemLifecycle() + } + } + + // 使用 LiveData 绑定使用 + private var lifecycleOwner: LifecycleOwner? = null + + // LifecycleOwner 通用获取接口 + private var lifecycleGet: BindingGet? = null + + // ============= + // = 对外公开方法 = + // ============= + + fun getLifecycleOwner(): LifecycleOwner? { + return lifecycleOwner + } + + fun setLifecycleOwner(owner: LifecycleOwner?): ItemLifecycle { + lifecycleOwner = owner + return this + } + + fun getLifecycleIMPL(): BindingGet? { + return lifecycleGet + } + + fun setLifecycleIMPL(implGET: BindingGet?): ItemLifecycle { + lifecycleGet = implGET + return this + } + + // ========== + // = 核心方法 = + // ========== + + /** + * LifecycleOwner 是否有效 + * @return `true` yes, `false` no + */ + fun isValidLifecycleOwner(): Boolean { + return !isInvalidLifecycleOwner() + } + + /** + * LifecycleOwner 是否失效 + * @return `true` yes, `false` no + */ + fun isInvalidLifecycleOwner(): Boolean { + if (lifecycleOwner == null) return true + return (lifecycleOwner!!.lifecycle.currentState == Lifecycle.State.DESTROYED) + } + + /** + * 尝试获取 LifecycleOwner + * @param view View + */ + fun tryGetLifecycleOwner(view: View?) { + if (isValidLifecycleOwner()) return + lifecycleOwner = lifecycleGet?.get() + if (isValidLifecycleOwner()) return + view?.let { lifecycleOwner = findLifecycleOwner(it) } + } + + /** + * 从给定的 View 中获取 LifecycleOwner + * @param view View + * @return LifecycleOwner? + * 如果 View 没有关联 ViewDataBinding 则通过 view.context 获取 + */ + fun findLifecycleOwner(view: View): LifecycleOwner? { + val binding = DataBindingUtil.findBinding(view) + var lifecycleOwner: LifecycleOwner? = binding?.lifecycleOwner + val ctx = view.context + if (lifecycleOwner == null && ctx is LifecycleOwner) { + lifecycleOwner = ctx + } + + return lifecycleOwner + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Margins.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Margins.kt new file mode 100644 index 0000000000..d4fac80352 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Margins.kt @@ -0,0 +1,68 @@ +package dev.mvvm.base.attribute + +/** + * detail: 通用 Margin 类 + * @author Ttt + */ +class Margins( + var left: Int = 0, + var top: Int = 0, + var right: Int = 0, + var bottom: Int = 0 +) { + + constructor() : this(0, 0, 0, 0) + + constructor(value: Int) : this(value, value, value, value) + + constructor( + leftRight: Int = 0, + topBottom: Int = 0 + ) : this(leftRight, topBottom, leftRight, topBottom) + + // = + + companion object { + val NO_MARGINS get() = Margins() + } + + fun clone(): Margins { + return Margins(left, top, right, bottom) + } + + // ============ + // = override = + // ============ + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Margins + if (left != other.left) return false + if (top != other.top) return false + if (right != other.right) return false + if (bottom != other.bottom) return false + + return true + } + + override fun hashCode(): Int { + var result = left + result = 31 * result + top + result = 31 * result + right + result = 31 * result + bottom + return result + } + + override fun toString(): String { + val builder = StringBuilder() + builder.append("Margins{") + builder.append("left: ").append(left) + builder.append(", top: ").append(top) + builder.append(", right: ").append(right) + builder.append(", bottom: ").append(bottom) + builder.append("}") + return builder.toString() + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Paddings.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Paddings.kt new file mode 100644 index 0000000000..a39b665733 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/attribute/Paddings.kt @@ -0,0 +1,68 @@ +package dev.mvvm.base.attribute + +/** + * detail: 通用 Padding 类 + * @author Ttt + */ +class Paddings( + var left: Int = 0, + var top: Int = 0, + var right: Int = 0, + var bottom: Int = 0 +) { + + constructor() : this(0, 0, 0, 0) + + constructor(value: Int) : this(value, value, value, value) + + constructor( + leftRight: Int = 0, + topBottom: Int = 0 + ) : this(leftRight, topBottom, leftRight, topBottom) + + // = + + companion object { + val NO_PADDINGS get() = Paddings() + } + + fun clone(): Paddings { + return Paddings(left, top, right, bottom) + } + + // ============ + // = override = + // ============ + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Paddings + if (left != other.left) return false + if (top != other.top) return false + if (right != other.right) return false + if (bottom != other.bottom) return false + + return true + } + + override fun hashCode(): Int { + var result = left + result = 31 * result + top + result = 31 * result + right + result = 31 * result + bottom + return result + } + + override fun toString(): String { + val builder = StringBuilder() + builder.append("Paddings{") + builder.append("left: ").append(left) + builder.append(", top: ").append(top) + builder.append(", right: ").append(right) + builder.append(", bottom: ").append(bottom) + builder.append("}") + return builder.toString() + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/base/viewmodel/LifecycleViewModel.kt b/lib/DevMVVM/src/main/java/dev/mvvm/base/viewmodel/LifecycleViewModel.kt new file mode 100644 index 0000000000..82ab08ceb9 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/base/viewmodel/LifecycleViewModel.kt @@ -0,0 +1,11 @@ +package dev.mvvm.base.viewmodel + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.ViewModel + +/** + * detail: 监听生命周期 ViewModel + * @author Ttt + */ +open class LifecycleViewModel : ViewModel(), + DefaultLifecycleObserver \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/animation/Animation.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/animation/Animation.kt new file mode 100644 index 0000000000..037fa5fb6d --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/animation/Animation.kt @@ -0,0 +1,26 @@ +package dev.mvvm.binding.animation + +import android.view.View +import android.view.animation.Animation +import androidx.databinding.BindingAdapter +import dev.utils.app.anim.ViewAnimationUtils + +// ================================= +// = View Animation BindingAdapter = +// ================================= + +@BindingAdapter( + value = ["binding_invisibleViewByAlpha", "binding_anim_banClick", "binding_anim_listener"], + requireAll = false +) +fun View.bindingInvisibleViewByAlpha( + durationMillis: Long, + isBanClick: Boolean, + listener: Animation.AnimationListener? +) { + if (durationMillis > 0L) { + ViewAnimationUtils.invisibleViewByAlpha( + this, durationMillis, isBanClick, listener + ) + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/listener/Listener.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/listener/Listener.kt new file mode 100644 index 0000000000..e60536602a --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/listener/Listener.kt @@ -0,0 +1,50 @@ +package dev.mvvm.binding.listener + +import android.view.View +import androidx.databinding.BindingAdapter +import dev.expand.engine.log.log_dTag +import dev.mvvm.base.Config +import dev.mvvm.command.BindingConsumer +import dev.utils.app.ClickUtils + + +// ======================= +// = View BindingAdapter = +// ======================= + +private const val TAG = "View_BindingAdapter" + +// ========= +// = Click = +// ========= + +@BindingAdapter( + value = ["binding_click", "binding_click_interval"], + requireAll = false +) +fun View.bindingViewClick( + listener: BindingConsumer?, + intervalTime: Long +) { + if (listener == null) return + val interval = Config.intervalTime(intervalTime) + + if (Config.printLog()) { + TAG.log_dTag( + message = "bindingViewClick\nintervalTime: %s", + args = arrayOf(interval) + ) + } + this.setOnClickListener { + if (interval > 0L && ClickUtils.isFastDoubleClick(this, interval)) { + if (Config.printLog()) { + TAG.log_dTag( + message = "bindingViewClick\nintervalTime: %s\nisFastDoubleClick: true", + args = arrayOf(interval) + ) + } + return@setOnClickListener + } + listener.accept(it) + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ConstraintLayout.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ConstraintLayout.kt new file mode 100644 index 0000000000..775b90620e --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ConstraintLayout.kt @@ -0,0 +1,13 @@ +package dev.mvvm.binding.view + +import androidx.constraintlayout.widget.Guideline +import androidx.databinding.BindingAdapter + +// =================================== +// = ConstraintLayout BindingAdapter = +// =================================== + +@BindingAdapter("binding_layoutConstraintGuideBegin") +fun Guideline.bindingLayoutConstraintGuideBegin(margin: Int) { + this.setGuidelineBegin(margin) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ImageView.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ImageView.kt new file mode 100644 index 0000000000..cf6bff3d79 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ImageView.kt @@ -0,0 +1,80 @@ +package dev.mvvm.binding.view + +import android.graphics.Bitmap +import android.widget.ImageView +import androidx.databinding.BindingAdapter +import dev.base.DevSource +import dev.engine.image.listener.LoadListener +import dev.expand.engine.image.display +import dev.expand.engine.log.log_dTag +import dev.mvvm.base.Config +import dev.mvvm.utils.letNull +import dev.mvvm.utils.toSource + +// ======================== +// = Image BindingAdapter = +// ======================== + +private const val TAG = "Image_BindingAdapter" + +//, "binding_placeholderRes" + +@BindingAdapter( + value = ["binding_url", "binding_engine", "binding_listener"], + requireAll = false +) +fun ImageView.bindingImageUrl( + url: String, + engine: String?, + listener: LoadListener? +) { + if (Config.printLog(engine)) { + TAG.log_dTag( + engine = engine, + message = "bindingImageUrl\nurl: %s\nengine: %s\nlistener: %s", + args = arrayOf(url, engine, listener) + ) + } + listener.letNull({ + this.display( + engine = engine, + source = url.toSource(), + listener = it + ) + }, { + this.display( + engine = engine, + source = url.toSource() + ) + }) +} + +@BindingAdapter( + value = ["binding_source", "binding_engine", "binding_listener"], + requireAll = false +) +fun ImageView.bindingImageSource( + source: DevSource, + engine: String?, + listener: LoadListener? +) { + if (Config.printLog(engine)) { + TAG.log_dTag( + engine = engine, + message = "bindingImageSource\nsource: %s\nengine: %s\nlistener: %s", + args = arrayOf(source, engine, listener) + ) + } + listener.letNull({ + this.display( + engine = engine, + source = source, + listener = it + ) + }, { + this.display( + engine = engine, + source = source + ) + }) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/TextView.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/TextView.kt new file mode 100644 index 0000000000..867a8326f5 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/TextView.kt @@ -0,0 +1,18 @@ +package dev.mvvm.binding.view + +import android.widget.TextView +import androidx.databinding.BindingAdapter +import dev.utils.app.TextViewUtils + +// =========================== +// = TextView BindingAdapter = +// =========================== + +// ============= +// = textStyle = +// ============= + +@BindingAdapter("binding_textBold") +fun TextView.bindingTextBold(bold: Boolean) { + TextViewUtils.setBold(this, bold) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/View.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/View.kt new file mode 100644 index 0000000000..3994f3d5ba --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/View.kt @@ -0,0 +1,27 @@ +package dev.mvvm.binding.view + +import android.view.View +import androidx.databinding.BindingAdapter + +// ======================= +// = View BindingAdapter = +// ======================= + +// =========== +// = visible = +// =========== + +@BindingAdapter("binding_visibility") +fun View.bindingVisibility(visibility: Int) { + this.visibility = visibility +} + +@BindingAdapter("binding_visibleOrGone") +fun View.bindingVisibleOrGone(visible: Boolean) { + this.visibility = if (visible) View.VISIBLE else View.GONE +} + +@BindingAdapter("binding_visibleOrInVisible") +fun View.bindingVisibleOrInVisible(visible: Boolean) { + this.visibility = if (visible) View.VISIBLE else View.INVISIBLE +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewMargin.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewMargin.kt new file mode 100644 index 0000000000..da0dad6d16 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewMargin.kt @@ -0,0 +1,68 @@ +package dev.mvvm.binding.view + +import android.view.View +import androidx.databinding.BindingAdapter +import dev.mvvm.base.attribute.Margins +import dev.utils.app.ViewUtils + +// ============================== +// = View Margin BindingAdapter = +// ============================== + +@BindingAdapter("binding_marginAttr") +fun View.bindingMarginAttr(margins: Margins?) { + margins?.let { + ViewUtils.setMargin(this, it.left, it.top, it.right, it.bottom) + } +} + +// = + +@BindingAdapter("binding_margin") +fun View.bindingMargin(margin: Int) { + ViewUtils.setMargin(this, margin) +} + +@BindingAdapter( + value = ["binding_marginLeft", "binding_marginReset"], + requireAll = false +) +fun View.bindingMarginLeft( + margin: Int, + reset: Boolean +) { + ViewUtils.setMarginLeft(this, margin, reset) +} + +@BindingAdapter( + value = ["binding_marginTop", "binding_marginReset"], + requireAll = false +) +fun View.bindingMarginTop( + margin: Int, + reset: Boolean +) { + ViewUtils.setMarginTop(this, margin, reset) +} + +@BindingAdapter( + value = ["binding_marginRight", "binding_marginReset"], + requireAll = false +) +fun View.bindingMarginRight( + margin: Int, + reset: Boolean +) { + ViewUtils.setMarginRight(this, margin, reset) +} + +@BindingAdapter( + value = ["binding_marginBottom", "binding_marginReset"], + requireAll = false +) +fun View.bindingMarginBottom( + margin: Int, + reset: Boolean +) { + ViewUtils.setMarginBottom(this, margin, reset) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewPadding.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewPadding.kt new file mode 100644 index 0000000000..bdc6d547aa --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/view/ViewPadding.kt @@ -0,0 +1,68 @@ +package dev.mvvm.binding.view + +import android.view.View +import androidx.databinding.BindingAdapter +import dev.mvvm.base.attribute.Paddings +import dev.utils.app.ViewUtils + +// ============================== +// = View Padding BindingAdapter = +// ============================== + +@BindingAdapter("binding_paddingAttr") +fun View.bindingPaddingAttr(paddings: Paddings?) { + paddings?.let { + ViewUtils.setPadding(this, it.left, it.top, it.right, it.bottom) + } +} + +// = + +@BindingAdapter("binding_padding") +fun View.bindingPadding(padding: Int) { + ViewUtils.setPadding(this, padding) +} + +@BindingAdapter( + value = ["binding_paddingLeft", "binding_paddingReset"], + requireAll = false +) +fun View.bindingPaddingLeft( + padding: Int, + reset: Boolean +) { + ViewUtils.setPaddingLeft(this, padding, reset) +} + +@BindingAdapter( + value = ["binding_paddingTop", "binding_paddingReset"], + requireAll = false +) +fun View.bindingPaddingTop( + padding: Int, + reset: Boolean +) { + ViewUtils.setPaddingTop(this, padding, reset) +} + +@BindingAdapter( + value = ["binding_paddingRight", "binding_paddingReset"], + requireAll = false +) +fun View.bindingPaddingRight( + padding: Int, + reset: Boolean +) { + ViewUtils.setPaddingRight(this, padding, reset) +} + +@BindingAdapter( + value = ["binding_paddingBottom", "binding_paddingReset"], + requireAll = false +) +fun View.bindingPaddingBottom( + padding: Int, + reset: Boolean +) { + ViewUtils.setPaddingBottom(this, padding, reset) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/binding/widget/LoadProgressBar.kt b/lib/DevMVVM/src/main/java/dev/mvvm/binding/widget/LoadProgressBar.kt new file mode 100644 index 0000000000..625ab2f7e4 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/binding/widget/LoadProgressBar.kt @@ -0,0 +1,36 @@ +package dev.mvvm.binding.widget + +import androidx.databinding.BindingAdapter +import dev.utils.common.ColorUtils +import dev.widget.ui.LoadProgressBar + +// ================================== +// = LoadProgressBar BindingAdapter = +// ================================== + +@BindingAdapter( + value = [ + "binding_dev_progress", + "binding_dev_max", + "binding_dev_progressColor", + "binding_dev_outerRingColor" + ], + requireAll = false +) +fun LoadProgressBar.bindingProgressAndColor( + progress: Double, + max: Double, + progressColor: Int = 0, + outerRingColor: Int = 0 +) { + if (progressColor != 0) { + setProgressColor(progressColor) + if (outerRingColor != 0) { + setOuterRingColor(outerRingColor) + } else { + setOuterRingColor(ColorUtils.setAlpha(progressColor, 0.4F)) + } + } + setProgress((progress * 10).toInt()) + setMax((max * 10).toInt()) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingClick.kt b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingClick.kt new file mode 100644 index 0000000000..55cc0b0b8f --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingClick.kt @@ -0,0 +1,10 @@ +package dev.mvvm.command + +/** + * detail: DataBinding 点击事件 + * @author Ttt + */ +interface BindingClick { + + fun onClick(value: T) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingConsumer.kt b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingConsumer.kt new file mode 100644 index 0000000000..3375205db2 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingConsumer.kt @@ -0,0 +1,14 @@ +package dev.mvvm.command + +/** + * detail: DataBinding 消费事件 + * @author Ttt + */ +interface BindingConsumer { + + /** + * 传入指定参数执行操作 + * @param value T + */ + fun accept(value: T) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingGet.kt b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingGet.kt new file mode 100644 index 0000000000..ebaec2f41b --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/command/BindingGet.kt @@ -0,0 +1,14 @@ +package dev.mvvm.command + +/** + * detail: DataBinding 通用获取接口 + * @author Ttt + */ +interface BindingGet { + + /** + * 获取泛型值 + * @return T + */ + fun get(): T +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/Price.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Price.kt new file mode 100644 index 0000000000..49f62bc63c --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Price.kt @@ -0,0 +1,136 @@ +package dev.mvvm.utils + +import dev.utils.common.BigDecimalUtils +import dev.utils.common.NumberUtils +import java.math.BigDecimal + +// ========== +// = 快捷方法 = +// ========== + +/** + * 拼接 RMB 符号 + * @return ¥xxx + */ +fun String.toRMB(): String { + return "¥$this" +} + +/** + * 去掉结尾多余的 . 与 0 + * @return 处理后的字符串 + */ +fun String.toSubZeroAndDot(): String { + return NumberUtils.subZeroAndDot(this) +} + +/** + * 去掉结尾多余的 . 与 0 并拼接 RMB 符号 + * @return 处理后的字符串 + */ +fun String.toRMBSubZeroAndDot(): String { + return "¥${NumberUtils.subZeroAndDot(this)}" +} + +// ============= +// = 价格转换处理 = +// ============= + +/** + * 提供精确的小数位四舍五入处理 + * @param defaultValue 默认值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ +fun Any.toPriceString( + defaultValue: String? = null, + scale: Int = 2, + roundingMode: Int = BigDecimal.ROUND_DOWN +): String? { + return try { + BigDecimalUtils.roundThrow( + this, scale, roundingMode + ).toString() + } catch (ignored: Exception) { + defaultValue + } +} + +/** + * 提供精确的小数位四舍五入处理 + * @param defaultValue 默认值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ +fun Any.toPriceDouble( + defaultValue: Double = 0.0, + scale: Int = 2, + roundingMode: Int = BigDecimal.ROUND_DOWN +): Double { + return try { + BigDecimalUtils.roundThrow( + this, scale, roundingMode + ) + } catch (ignored: Exception) { + defaultValue + } +} + +/** + * 提供精确的小数位四舍五入处理 + * @param defaultValue 默认值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ +fun Any.toPriceFloat( + defaultValue: Float = 0F, + scale: Int = 2, + roundingMode: Int = BigDecimal.ROUND_DOWN +): Float { + return toPriceDouble( + defaultValue = defaultValue.toDouble(), + scale = scale, + roundingMode = roundingMode + ).toFloat() +} + +/** + * 提供精确的小数位四舍五入处理 + * @param defaultValue 默认值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ +fun Any.toPriceLong( + defaultValue: Long = 0, + scale: Int = 2, + roundingMode: Int = BigDecimal.ROUND_DOWN +): Long { + return toPriceDouble( + defaultValue = defaultValue.toDouble(), + scale = scale, + roundingMode = roundingMode + ).toLong() +} + +/** + * 提供精确的小数位四舍五入处理 + * @param defaultValue 默认值 + * @param scale 保留 scale 位小数 + * @param roundingMode 舍入模式 + * @return 四舍五入后的结果 + */ +fun Any.toPriceInt( + defaultValue: Int = 0, + scale: Int = 2, + roundingMode: Int = BigDecimal.ROUND_DOWN +): Int { + return toPriceDouble( + defaultValue = defaultValue.toDouble(), + scale = scale, + roundingMode = roundingMode + ).toInt() +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/Resource.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Resource.kt new file mode 100644 index 0000000000..156b2b8ba5 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Resource.kt @@ -0,0 +1,15 @@ +package dev.mvvm.utils + +import dev.utils.app.ResourceUtils + +// ============ +// = Resource = +// ============ + +/** + * 获取 R.string.id String + * @return String + */ +fun Int.toResString(): String { + return ResourceUtils.getString(this) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/Source.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Source.kt new file mode 100644 index 0000000000..2a3a67bab1 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Source.kt @@ -0,0 +1,48 @@ +package dev.mvvm.utils + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import dev.base.DevSource +import java.io.File +import java.io.InputStream + +// ============ +// = DevSource = +// ============ + +fun String.toSource(): DevSource { + return DevSource.create(this) +} + +fun String.toSourceByPath(): DevSource { + return DevSource.createWithPath(this) +} + +fun Uri.toSource(): DevSource { + return DevSource.create(this) +} + +fun ByteArray.toSource(): DevSource { + return DevSource.create(this) +} + +fun Int.toSource(): DevSource { + return DevSource.create(this) +} + +fun File.toSource(): DevSource { + return DevSource.create(this) +} + +fun InputStream.toSource(): DevSource { + return DevSource.create(this) +} + +fun Drawable.toSource(): DevSource { + return DevSource.create(this) +} + +fun Bitmap.toSource(): DevSource { + return DevSource.create(this) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/Standard.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Standard.kt new file mode 100644 index 0000000000..3f77a5a790 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/Standard.kt @@ -0,0 +1,22 @@ +package dev.mvvm.utils + +// ============== +// = 通用扩展函数 = +// ============== + +/** + * 在 let 基础上增加如果 null 执行方法体 + * @receiver T? + * @param block 不为 null 执行方法体 + * @param nullBlock null 执行方法体 + */ +inline fun T?.letNull( + block: (T) -> R, + nullBlock: () -> R +): R { + return if (this != null) { + block.invoke(this) + } else { + nullBlock.invoke() + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppAutoImageConfig.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppAutoImageConfig.kt new file mode 100644 index 0000000000..cb920b6a00 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppAutoImageConfig.kt @@ -0,0 +1,79 @@ +package dev.mvvm.utils.image + +import dev.base.DevVariableExt +import dev.engine.image.ImageConfig + +// ================================ +// = dev.engine.image.ImageConfig = +// ================================ + +/** + * detail: App ImageConfig 创建 + * @author Ttt + */ +class AppAutoImageConfig( + // 变量创建器 + creator: DevVariableExt.Creator, + // ImageConfig 默认值 + private var defaultValue: ImageConfig? = null +) { + // 变量操作基类扩展类 + private val varExt = DevVariableExt(creator) + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取变量操作基类扩展类 + * @return DevVariableExt + */ + fun varExt(): DevVariableExt { + return varExt + } + + /** + * 通过 Key 获取 ImageConfig + * @param key 对应 ImageConfig Key + * @return [ImageConfig] + */ + fun toImageConfig(key: String?): ImageConfig? { + return toImageConfig(key, defaultValue) + } + + /** + * 通过 Key 获取 ImageConfig + * @param key 对应 ImageConfig Key + * @param defaultValue 默认 ImageConfig + * @return [ImageConfig] + */ + fun toImageConfig( + key: String?, + defaultValue: ImageConfig? + ): ImageConfig? { + if (key != null) { + return varExt.getVariableValue(key, null) ?: defaultValue + } + return defaultValue + } + + // = + + /** + * 获取 ImageConfig 默认值 + * @return [ImageConfig] + */ + fun defaultValue(): ImageConfig? { + return defaultValue + } + + /** + * 设置 ImageConfig 默认值 + * @param value 默认 ImageConfig + * @return AppAutoImageConfig + */ + fun setDefaultValue(value: ImageConfig?): AppAutoImageConfig { + this.defaultValue = value + return this + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppImageConfig.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppImageConfig.kt new file mode 100644 index 0000000000..f44b7bb44c --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/image/AppImageConfig.kt @@ -0,0 +1,117 @@ +package dev.mvvm.utils.image + +import dev.base.DevVariableExt +import dev.engine.image.ImageConfig +import dev.mvvm.utils.letNull + +// ================================ +// = dev.engine.image.ImageConfig = +// ================================ + +/** + * detail: App ImageConfig 缓存类 + * @author Ttt + */ +object AppImageConfig { + + private val AUTO_CONFIG = + AppAutoImageConfig(object : DevVariableExt.Creator { + override fun create( + key: String?, + param: ImageConfig? + ): ImageConfig? { + return innerCreator.letNull({ + return@letNull it.create(key, param) + }, { + return@letNull param + }) + } + }, ImageConfig.create()) + + // ImageConfig 内部创建器 + private var innerCreator: DevVariableExt.Creator? = null + + // = + + /** + * 设置 ImageConfig 创建器 + * @param creator Creator + */ + fun setCreator(creator: DevVariableExt.Creator) { + innerCreator = creator + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取变量操作基类扩展类 + * @return DevVariableExt + */ + fun varExt(): DevVariableExt { + return AUTO_CONFIG.varExt() + } + + /** + * 通过 Key 获取 ImageConfig + * @param key 对应 ImageConfig Key + * @return [ImageConfig] + */ + fun toImageConfig(key: String?): ImageConfig? { + return AUTO_CONFIG.toImageConfig(key) + } + + /** + * 通过 Key 获取 ImageConfig + * @param key 对应 ImageConfig Key + * @param defaultValue 默认 ImageConfig + * @return [ImageConfig] + */ + fun toImageConfig( + key: String?, + defaultValue: ImageConfig? + ): ImageConfig? { + return AUTO_CONFIG.toImageConfig(key, defaultValue) + } + + // = + + /** + * 获取 ImageConfig 默认值 + * @return [ImageConfig] + */ + fun defaultValue(): ImageConfig? { + return AUTO_CONFIG.defaultValue() + } + + /** + * 设置 ImageConfig 默认值 + * @param value 默认 ImageConfig + * @return AppAutoImageConfig + */ + fun setDefaultValue(value: ImageConfig?): AppAutoImageConfig { + return AUTO_CONFIG.setDefaultValue(value) + } +} + +// ========== +// = 扩展函数 = +// ========== + +/** + * 通过 Key 获取 ImageConfig + * @return [ImageConfig] + */ +fun String?.toImageConfig(): ImageConfig? { + return AppImageConfig.toImageConfig(this) +} + +/** + * 通过 Key 获取 ImageConfig + * @param defaultValue 默认 ImageConfig + * @return [ImageConfig] + */ +fun String?.toImageConfig(defaultValue: ImageConfig?): ImageConfig? { + return AppImageConfig.toImageConfig(this, defaultValue) +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppAutoSize.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppAutoSize.kt new file mode 100644 index 0000000000..eabc9b01d0 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppAutoSize.kt @@ -0,0 +1,472 @@ +package dev.mvvm.utils.size + +import android.content.Context +import androidx.annotation.DimenRes +import dev.DevUtils +import dev.base.DevVariableExt +import dev.utils.app.ResourceUtils +import dev.utils.app.SizeUtils +import dev.utils.app.assist.ResourceAssist + +/** + * detail: App 适配值转换快捷类 + * @author Ttt + * 该类主要用于内部缓存适配值, 便于第二次直接从缓存中读取 + */ +class AppAutoSize { + + // ============= + // = 对外公开方法 = + // ============= + + val CONVERT = Convert() + + // ============= + // = 无 Context = + // ============= + + /** + * dp 转 px + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2px(value: Float): Int { + return dp2px(null, value) + } + + /** + * dp 转 px ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2pxf(value: Float): Float { + return dp2pxf(null, value) + } + + /** + * px 转 dp + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dp(value: Float): Int { + return px2dp(null, value) + } + + /** + * px 转 dp ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dpf(value: Float): Float { + return px2dpf(null, value) + } + + /** + * sp 转 px + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2px(value: Float): Int { + return sp2px(null, value) + } + + /** + * sp 转 px ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2pxf(value: Float): Float { + return sp2pxf(null, value) + } + + /** + * px 转 sp + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2sp(value: Float): Int { + return px2sp(null, value) + } + + /** + * px 转 sp ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2spf(value: Float): Float { + return px2spf(null, value) + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + fun getDimension(@DimenRes id: Int): Float { + return getDimension(null, id) + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + fun getDimensionInt(@DimenRes id: Int): Int { + return getDimensionInt(null, id) + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + fun getDimension(resName: String?): Float { + return getDimension(null, resName) + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + fun getDimensionInt(resName: String?): Int { + return getDimensionInt(null, resName) + } + + // =========== + // = Context = + // =========== + + /** + * dp 转 px + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2px( + context: Context?, + value: Float + ): Int { + return dp2pxf(context, value).toInt() + } + + /** + * dp 转 px ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2pxf( + context: Context?, + value: Float + ): Float { + return CONVERT.innerConvert( + context, Type.dp2px, value + ) + } + + /** + * px 转 dp + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dp( + context: Context?, + value: Float + ): Int { + return px2dpf(context, value).toInt() + } + + /** + * px 转 dp ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dpf( + context: Context?, + value: Float + ): Float { + return CONVERT.innerConvert( + context, Type.px2dp, value + ) + } + + /** + * sp 转 px + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2px( + context: Context?, + value: Float + ): Int { + return sp2pxf(context, value).toInt() + } + + /** + * sp 转 px ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2pxf( + context: Context?, + value: Float + ): Float { + return CONVERT.innerConvert( + context, Type.sp2px, value + ) + } + + /** + * px 转 sp + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2sp( + context: Context?, + value: Float + ): Int { + return px2spf(context, value).toInt() + } + + /** + * px 转 sp ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2spf( + context: Context?, + value: Float + ): Float { + return CONVERT.innerConvert( + context, Type.px2sp, value + ) + } + + /** + * 获取 Dimension + * @param context Context + * @param id resource identifier + * @return Dimension + */ + fun getDimension( + context: Context?, + @DimenRes id: Int + ): Float { + val value = CONVERT.DIMEN_ID.getVariableValue( + id, context + ) + if (value == 0F) { + CONVERT.DIMEN_ID.variable.removeVariable(id) + } + return value + } + + /** + * 获取 Dimension + * @param context Context + * @param id resource identifier + * @return Dimension + */ + fun getDimensionInt( + context: Context?, + @DimenRes id: Int + ): Int { + return getDimension(context, id).toInt() + } + + /** + * 获取 Dimension + * @param context Context + * @param resName resource name + * @return Dimension + */ + fun getDimension( + context: Context?, + resName: String? + ): Float { + val value = CONVERT.DIMEN_NAME.getVariableValue( + resName, context + ) + if (value == 0F) { + CONVERT.DIMEN_NAME.variable.removeVariable(resName) + } + return value + } + + /** + * 获取 Dimension + * @param context Context + * @param resName resource name + * @return Dimension + */ + fun getDimensionInt( + context: Context?, + resName: String? + ): Int { + return getDimension(context, resName).toInt() + } + + // ============= + // = 具体实现代码 = + // ============= + + // ============= + // = 内部转换存储 = + // ============= + + enum class Type { + + dp2px, + + px2dp, + + sp2px, + + px2sp + } + + /** + * detail: 转换适配值封装 + * @author Ttt + */ + class Convert { + + // dp 转 px ( float ) + val DP_2_PX: DevVariableExt by lazy { + DevVariableExt { key, context -> + innerCreator(context, Type.dp2px, key) + } + } + + // px 转 dp ( float ) + val PX_2_DP: DevVariableExt by lazy { + DevVariableExt { key, context -> + innerCreator(context, Type.px2dp, key) + } + } + + // sp 转 px ( float ) + val SP_2_PX: DevVariableExt by lazy { + DevVariableExt { key, context -> + innerCreator(context, Type.sp2px, key) + } + } + + // px 转 sp ( float ) + val PX_2_SP: DevVariableExt by lazy { + DevVariableExt { key, context -> + innerCreator(context, Type.px2sp, key) + } + } + + // dimen id 值获取 ( float ) + val DIMEN_ID: DevVariableExt by lazy { + DevVariableExt { key, context -> + val ctx = context ?: DevUtils.getTopActivity() + ResourceUtils.getDimension( + ResourceAssist.get( + DevUtils.getContext(ctx) + ), key + ) + } + } + + // dimen id Name 值获取 ( float ) + val DIMEN_NAME: DevVariableExt by lazy { + DevVariableExt { key, context -> + val ctx = context ?: DevUtils.getTopActivity() + ResourceUtils.getDimension( + ResourceAssist.get( + DevUtils.getContext(ctx) + ), key + ) + } + } + + // =============== + // = Type 转换处理 = + // =============== + + /** + * 适配值内部创建实现 + * @param context Context + * @param type Type + * @param key Float + * @return Float + */ + private fun innerCreator( + context: Context?, + type: Type, + key: Float + ): Float { + val ctx = context ?: DevUtils.getTopActivity() + return when (type) { + Type.dp2px -> { + SizeUtils.dp2pxf(DevUtils.getContext(ctx), key) + } + Type.px2dp -> { + SizeUtils.px2dpf(DevUtils.getContext(ctx), key) + } + Type.sp2px -> { + SizeUtils.sp2pxf(DevUtils.getContext(ctx), key) + } + Type.px2sp -> { + SizeUtils.px2spf(DevUtils.getContext(ctx), key) + } + } + } + + /** + * 适配值内部转换 + * @param context Context? + * @param type Type + * @param key Float + * @return Float + * 该方法主要解决出现转换失败返回 0 的情况 + * 导致后续获取缓存值为 0 直接返回使用 + */ + internal fun innerConvert( + context: Context?, + type: Type, + key: Float + ): Float { + when (type) { + Type.dp2px -> { + val value = DP_2_PX.getVariableValue(key, context) + if (value == 0F) { + DP_2_PX.variable.removeVariable(key) + } + return value + } + Type.px2dp -> { + val value = PX_2_DP.getVariableValue(key, context) + if (value == 0F) { + PX_2_DP.variable.removeVariable(key) + } + return value + } + Type.sp2px -> { + val value = SP_2_PX.getVariableValue(key, context) + if (value == 0F) { + SP_2_PX.variable.removeVariable(key) + } + return value + } + Type.px2sp -> { + val value = PX_2_SP.getVariableValue(key, context) + if (value == 0F) { + PX_2_SP.variable.removeVariable(key) + } + return value + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppSize.kt b/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppSize.kt new file mode 100644 index 0000000000..102720a387 --- /dev/null +++ b/lib/DevMVVM/src/main/java/dev/mvvm/utils/size/AppSize.kt @@ -0,0 +1,299 @@ +package dev.mvvm.utils.size + +import android.content.Context +import androidx.annotation.DimenRes + +/** + * detail: App 适配值转换快捷类 + * @author Ttt + * 内部使用 [AppAutoSize] + */ +object AppSize { + + // ============= + // = 对外公开方法 = + // ============= + + private val AUTO_SIZE = AppAutoSize() + + // 允许获取存储集合清空缓存 + val CONVERT: AppAutoSize.Convert by lazy { + AUTO_SIZE.CONVERT + } + + // =============== + // = AppAutoSize = + // =============== + + // ============= + // = 无 Context = + // ============= + + /** + * dp 转 px + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2px(value: Float): Int { + return dp2px(null, value) + } + + /** + * dp 转 px ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2pxf(value: Float): Float { + return dp2pxf(null, value) + } + + /** + * px 转 dp + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dp(value: Float): Int { + return px2dp(null, value) + } + + /** + * px 转 dp ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dpf(value: Float): Float { + return px2dpf(null, value) + } + + /** + * sp 转 px + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2px(value: Float): Int { + return sp2px(null, value) + } + + /** + * sp 转 px ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2pxf(value: Float): Float { + return sp2pxf(null, value) + } + + /** + * px 转 sp + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2sp(value: Float): Int { + return px2sp(null, value) + } + + /** + * px 转 sp ( float ) + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2spf(value: Float): Float { + return px2spf(null, value) + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + fun getDimension(@DimenRes id: Int): Float { + return getDimension(null, id) + } + + /** + * 获取 Dimension + * @param id resource identifier + * @return Dimension + */ + fun getDimensionInt(@DimenRes id: Int): Int { + return getDimensionInt(null, id) + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + fun getDimension(resName: String?): Float { + return getDimension(null, resName) + } + + /** + * 获取 Dimension + * @param resName resource name + * @return Dimension + */ + fun getDimensionInt(resName: String?): Int { + return getDimensionInt(null, resName) + } + + // =========== + // = Context = + // =========== + + /** + * dp 转 px + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2px( + context: Context?, + value: Float + ): Int { + return dp2pxf(context, value).toInt() + } + + /** + * dp 转 px ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun dp2pxf( + context: Context?, + value: Float + ): Float { + return AUTO_SIZE.dp2pxf(context, value) + } + + /** + * px 转 dp + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dp( + context: Context?, + value: Float + ): Int { + return px2dpf(context, value).toInt() + } + + /** + * px 转 dp ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2dpf( + context: Context?, + value: Float + ): Float { + return AUTO_SIZE.px2dpf(context, value) + } + + /** + * sp 转 px + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2px( + context: Context?, + value: Float + ): Int { + return sp2pxf(context, value).toInt() + } + + /** + * sp 转 px ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun sp2pxf( + context: Context?, + value: Float + ): Float { + return AUTO_SIZE.sp2pxf(context, value) + } + + /** + * px 转 sp + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2sp( + context: Context?, + value: Float + ): Int { + return px2spf(context, value).toInt() + } + + /** + * px 转 sp ( float ) + * @param context Context + * @param value 待转换值 + * @return 转换后的值 + */ + fun px2spf( + context: Context?, + value: Float + ): Float { + return AUTO_SIZE.px2spf(context, value) + } + + /** + * 获取 Dimension + * @param context Context + * @param id resource identifier + * @return Dimension + */ + fun getDimension( + context: Context?, + @DimenRes id: Int + ): Float { + return AUTO_SIZE.getDimension(context, id) + } + + /** + * 获取 Dimension + * @param context Context + * @param id resource identifier + * @return Dimension + */ + fun getDimensionInt( + context: Context?, + @DimenRes id: Int + ): Int { + return getDimension(context, id).toInt() + } + + /** + * 获取 Dimension + * @param context Context + * @param resName resource name + * @return Dimension + */ + fun getDimension( + context: Context?, + resName: String? + ): Float { + return AUTO_SIZE.getDimension(context, resName) + } + + /** + * 获取 Dimension + * @param context Context + * @param resName resource name + * @return Dimension + */ + fun getDimensionInt( + context: Context?, + resName: String? + ): Int { + return getDimension(context, resName).toInt() + } +} \ No newline at end of file diff --git a/lib/DevMVVM/src/main/res/values/tools.xml b/lib/DevMVVM/src/main/res/values/tools.xml new file mode 100644 index 0000000000..e35e352956 --- /dev/null +++ b/lib/DevMVVM/src/main/res/values/tools.xml @@ -0,0 +1,17 @@ + + + + + + ¥ + ¥17.2 + 17.2 + 17 + + Title + Value + Name + \ No newline at end of file diff --git a/lib/DevRetrofit/.gitignore b/lib/DevRetrofit/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevRetrofit/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevRetrofit/CHANGELOG.md b/lib/DevRetrofit/CHANGELOG.md new file mode 100644 index 0000000000..678ed61cb5 --- /dev/null +++ b/lib/DevRetrofit/CHANGELOG.md @@ -0,0 +1,17 @@ +Change Log +========== + +Version 1.0.2 *(2022-09-18)* +---------------------------- + +* `[Upgrade]` 升级第三方库以及对应 API 变动更新 + +Version 1.0.1 *(2022-07-04)* +---------------------------- + +* `[Upgrade]` 升级第三方库以及对应 API 变动更新 + +Version 1.0.0 *(2022-06-17)* +---------------------------- + +* Initial release diff --git a/lib/DevRetrofit/README.md b/lib/DevRetrofit/README.md new file mode 100644 index 0000000000..341fe09848 --- /dev/null +++ b/lib/DevRetrofit/README.md @@ -0,0 +1,473 @@ + + +## 摘要 + +* [框架功能介绍](#框架功能介绍) +* [API 文档](#API-文档) +* [使用示例](#使用示例) +* [使用步骤](#使用步骤) +* [总结与扩展](#总结与扩展) + + +## Gradle + +```gradle +implementation 'io.github.afkt:DevRetrofit:1.0.2' +``` + +## 框架功能介绍 + +DevRetrofit 是基于 Retrofit + Kotlin Coroutines 进行封装的网络层封装库, +针对 `CoroutineScope`、`ViewModel`、`Lifecycle`、`LifecycleOwner` 及 `LifecycleOwner` 实现类 ( `Activity`、`Fragment` 等 ) 封装快捷扩展函数。 + +并对上述封装的请求方法扩展函数支持传入 `LiveData`、`方法体`、`Callback`、`ResultCallback`。 + + +## 项目类结构 - [包目录][包目录] + +* 数据模型类 [model.kt][model.kt]:DevRetrofit Base 数据模型、接口汇总类 + +* 数据模型扩展类 [model_ext.kt][model_ext.kt]:`model.kt` 扩展函数汇总类 + +* 封装请求方法 [request.kt][request.kt]:整个 DevRetrofit 库最终实现,统一调用该请求封装方法 + +* 请求方法协程扩展函数 [request_coroutines.kt][request_coroutines.kt]:在 `request.kt` 基础上封装使用协程 + +* Callback 扩展函数 [request_coroutines_simple.kt][request_coroutines_simple.kt]:在 `request_coroutines.kt` 基础上减少 start、success、error、finish 方法体传参,使用 Callback、ResultCallback + +* LiveData 扩展函数 [request_coroutines_simple_livedata.kt][request_coroutines_simple_livedata.kt]:在 `request_coroutines_simple.kt` 基础上使用 LiveData + + +## API 文档 + +* **Base.Response 请求响应解析类 ( 接口 ) ->** [Base.Response][Base.Response] + +| 方法 | 注释 | +| :- | :- | +| getData | 获取响应数据 | +| getCode | 获取响应 Code | +| getMessage | 获取提示 Message | +| isSuccess | 判断请求是否成功 | + +* **Base.Result 请求结果包装类 ->** [Base.Result][Base.Result] + +| 方法 | 注释 | +| :- | :- | +| getData | 获取响应数据 | +| getCode | 获取响应 Code | +| getMessage | 获取提示 Message | +| isSuccess | 判断请求是否成功 | +| getResponse | 获取 Base.Response | +| isError | 是否请求抛出异常 | +| getError | 获取异常信息 | +| getErrorCode | 获取错误类型 | +| getParams | 获取额外携带参数 | +| setParams | 设置额外携带参数 | + +* **Notify.Callback 请求每个阶段通知回调 ->** [Notify.Callback][Notify.Callback] + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始请求 | +| onSuccess | 请求成功 | +| onError | 请求异常 | +| onFinish | 请求结束 | +| getParams | 获取额外携带参数 | +| setParams | 设置额外携带参数 | + +* **Notify.ResultCallback 请求结果阶段通知回调 ->** [Notify.ResultCallback][Notify.ResultCallback] + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始请求 | +| onSuccess | 请求成功 | +| onFinish | 请求结束 | +| getParams | 获取额外携带参数 | +| setParams | 设置额外携带参数 | + +* **Notify.GlobalCallback 全局通知回调 ->** [Notify.GlobalCallback][Notify.GlobalCallback] + +| 方法 | 注释 | +| :- | :- | +| onStart | 开始请求 | +| onSuccess | 请求成功 | +| onError | 请求异常 | +| onFinish | 请求结束 | + +* **整个 DevRetrofit 库最终调用方法 ->** [request.kt][request.kt] + +| 方法 | 注释 | +| :- | :- | +| finalExecute | 无任何额外逻辑封装, 支持自定义解析、处理等代码 | +| finalExecuteResponse | 封装为 Base.Response、Base.Result 进行响应 | + +* **请求方法协程扩展函数 ->** [request_coroutines.kt][request_coroutines.kt] + +| 方法 | 注释 | +| :- | :- | +| CoroutineScope.scopeExecuteRequest | 无任何额外逻辑封装, 支持自定义解析、处理等代码 | +| CoroutineScope.scopeExecuteResponseRequest | 封装为 Base.Response、Base.Result 进行响应 | +| launchExecuteRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | +| launchExecuteResponseRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | + +* **Callback 扩展函数 ->** [request_coroutines_simple.kt][request_coroutines_simple.kt] + +| 方法 | 注释 | +| :- | :- | +| CoroutineScope.simpleScopeExecuteRequest | 无任何额外逻辑封装, 支持自定义解析、处理等代码 | +| CoroutineScope.simpleScopeExecuteResponseRequest | 封装为 Base.Response、Base.Result 进行响应 | +| simpleLaunchExecuteRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | +| simpleLaunchExecuteResponseRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | + +* **LiveData 扩展函数 ->** [request_coroutines_simple_livedata.kt][request_coroutines_simple_livedata.kt] + +| 方法 | 注释 | +| :- | :- | +| CoroutineScope.liveDataScopeExecuteRequest | 无任何额外逻辑封装, 支持自定义解析、处理等代码 | +| CoroutineScope.liveDataScopeExecuteResponseRequest | 封装为 Base.Response、Base.Result 进行响应 | +| liveDataLaunchExecuteRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | +| liveDataLaunchExecuteResponseRequest | ViewModel、Lifecycle、LifecycleOwner 扩展函数 ( 功能如上 ) | + + +## 使用示例 + +具体实现代码可以查看 [DevRetrofitCoroutinesDemo][DevRetrofitCoroutinesDemo]。 + + +## 使用步骤 + +### 1. 首先创建 Response 请求响应解析类 + +`需要创建 Response 类并实现 Base.Response 解析类` + +主要是为了解决一个问题: + +假设 A 公司,后台返回响应数据结构为 + +```json +{ + "resultData": Object, + "resultCode": 200, + "errorMessage": "错误提示", + "isToast": true +} +``` + +B 公司,后台返回响应数据结构为 + +```json +{ + "response": Object, + "code": "200", + "toast": "提示消息" +} +``` + +等等诸如此类不同公司差异化字段命名、字段类型,以 A 公司为例定义 `BaseResponse` + +```kotlin +/** + * detail: 统一响应实体类 + * @author Ttt + */ +open class BaseResponse : Base.Response { + + private var resultData: T? = null + private var resultCode: Int = 0 + private var errorMessage: String? = null + private var isToast: Boolean = false + + // ================= + // = Base.Response = + // ================= + + override fun getData(): T? { + return resultData + } + + override fun getCode(): String? { + return resultCode.toString() + } + + override fun getMessage(): String? { + return errorMessage + } + + override fun isSuccess(): Boolean { + return resultCode == 200 + } + + // ============== + // = 自定义差异化 = + // ============== + + fun isToast(): Boolean { + return isToast + } +} +``` + +只要实现 `Base.Response` 四个核心方法按照对应意思 return 即可 + +| 方法 | 注释 | +| :- | :- | +| getData | 获取响应数据 | +| getCode | 获取响应 Code | +| getMessage | 获取提示 Message | +| isSuccess | 判断请求是否成功 | + +> 有其他额外的字段如 `isToast` 则自行添加获取方法即可。 + +如果以 B 公司定义 `BaseResponse` 将会是这样 + +```kotlin +/** + * detail: 统一响应实体类 + * @author Ttt + */ +open class BaseResponse : Base.Response { + + private var response: T? = null + private var code: String? = null + private var toast: String? = null + + // ================= + // = Base.Response = + // ================= + + override fun getData(): T? { + return response + } + + override fun getCode(): String? { + return code + } + + override fun getMessage(): String? { + return toast + } + + override fun isSuccess(): Boolean { + return code?.let { code -> + // 自定义 code 为 200 表示请求成功 ( 后台定义 ) + StringUtils.equals(code, "200") + } ?: false + } +} +``` + +### 2. 创建 Callback 每个阶段通知回调类 ( 非必须 ) + +创建 Base Callback 是为了整个项目统一使用,内部可添加`额外逻辑处理`、`特殊字段存储`等,视项目情况定义,参考如下: + +```kotlin +// 封装 Base Notify.Callback +abstract class BaseCallback : Notify.Callback() + +// 封装 Base Notify.ResultCallback 简化代码 +abstract class BaseResultCallback : Notify.ResultCallback>() +``` + +### 3. 发起请求 ( 最后一步 ) + +以`获取文章列表`请求为例 + +```kotlin +/** + * detail: 文章实体类 + * @author Ttt + */ +data class ArticleBean( + val content: String?, + val cover: String? +) + +/** + * detail: 文章数据响应类 ( 可不定义, 只是为了方便理解、展示 ) + * @author Ttt + * data 映射实体类为 List + */ +class ArticleResponse : BaseResponse>() + +/** + * detail: 服务器接口 API Service + * @author Ttt + */ +interface APIService { + + @GET("xxx") + suspend fun loadArticleList(@Path("page") page: Int): ArticleResponse // or BaseResponse> +} +``` + +针对 `CoroutineScope`、`ViewModel`、`Lifecycle`、`LifecycleOwner` 及 `LifecycleOwner` 实现类 ( `Activity`、`Fragment` 等 ) 封装快捷扩展函数。 + +以下例子,模拟在 `Activity` 下使用 ( 在 `ViewModel ( 上述 )` 下使用也一样 ) + +```kotlin +// =================== +// = DevRetrofit 使用 = +// =================== + +/** + * 模拟在 Activity 下使用 + * 总的请求方法分为以下两种 + * execute Request + * execute Response Request + * 区别在于 Response 是直接使用内部封装的 Base.Result 以及 ResultCallback 进行回调通知等 + * 不管什么扩展函数方式请求, 最终都是执行 request.kt 中的 finalExecute、finalExecuteResponse 方法 + */ +class TestActivity : AppCompatActivity() { + + // 封装 Base Notify.Callback + abstract class BaseCallback : Notify.Callback() + + // 封装 Notify.ResultCallback 简化代码 + abstract class BaseResultCallback : Notify.ResultCallback>() + + // LiveData + private val _articleLiveData = MutableLiveData() + val articleLiveData: LiveData = _articleLiveData + + private fun request() { + // 加载文章列表方式一 + simpleLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : Notify.Callback() { + override fun onSuccess( + uuid: UUID, + data: ArticleResponse? + ) { + // 请求成功 + } + + override fun onError( + uuid: UUID, + error: Throwable? + ) { + // 请求异常 + } + } + ) + // 加载文章列表方式一 ( 使用封装 Callback ) + simpleLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, ArticleCallback() + ) + + // 加载文章列表方式二 + simpleLaunchExecuteResponseRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : Notify.ResultCallback, ArticleResponse>() { + override fun onSuccess( + uuid: UUID, + data: Base.Result, ArticleResponse> + ) { + + } + } + ) + + // 加载文章列表方式二 ( 使用 BaseResultCallback ) + simpleLaunchExecuteResponseRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, object : BaseResultCallback>() { + override fun onSuccess( + uuid: UUID, + data: Base.Result, BaseResponse>> + ) { + + } + } + ) + + // 加载文章列表方式三 + liveDataLaunchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, + liveData = _articleLiveData, + usePostValue = false // default true + ) + + // 加载文章列表方式四 ( 可自行添加额外流程等 ) + launchExecuteRequest( + block = { + RetrofitAPI.api().loadArticleList(1) + }, + start = { + + }, + success = { + + }, + error = { + + }, + finish = { + + }, + callback = ArticleCallback() + ) + } + + private class ArticleCallback : Notify.Callback() { + override fun onSuccess( + uuid: UUID, + data: ArticleResponse? + ) { + // 请求成功 + } + + override fun onError( + uuid: UUID, + error: Throwable? + ) { + // 请求异常 + } + + override fun onStart(uuid: UUID) { + super.onStart(uuid) + // 开始请求 + } + + override fun onFinish(uuid: UUID) { + super.onFinish(uuid) + // 请求结束 + } + } +} +``` + +## 总结与扩展 + +至此,整个 `DevRetrofit` 库使用及介绍如上,但是 **`强烈推荐`** 在该基础上进行二次封装,并搭配 [DevHttpCapture][DevHttpCapture]、[DevHttpManager][DevHttpManager] 使用。 + +**`二次封装`**:例针对 Activity、Fragment 所属 `ViewModel` 统一添加 Request Loading Dialog 等,以及其他`全局回调`、`请求阶段数据记录`等各种独立项目逻辑 + +并且针对请求方法二次封装,统一使用如 `ViewModel`、`LiveData` 等方法入参参数,方便排查调试以及统一维护。 + + + + + +[DevRetrofit]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit +[model.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt +[model_ext.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model_ext.kt +[request.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/request.kt +[request_coroutines.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines.kt +[request_coroutines_simple.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple.kt +[request_coroutines_simple_livedata.kt]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple_livedata.kt +[Base.Response]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt#L15 +[Base.Result]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt#L29 +[Notify.Callback]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt#L160 +[Notify.ResultCallback]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt#L230 +[Notify.GlobalCallback]: https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt#L280 +[DevRetrofitCoroutinesDemo]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/use_demo/DevRetrofitCoroutinesDemo.kt +[DevHttpCapture]: https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md +[DevHttpManager]: https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md diff --git a/lib/DevRetrofit/build.gradle b/lib/DevRetrofit/build.gradle new file mode 100644 index 0000000000..c20c8bc089 --- /dev/null +++ b/lib/DevRetrofit/build.gradle @@ -0,0 +1,30 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply plugin: 'kotlin-parcelize' + +android.defaultConfig { + versionCode versions.dev_retrofit_versionCode + versionName versions.dev_retrofit_versionName + // DevRetrofit Module Version + buildConfigField "int", "DevRetrofit_VersionCode", "${versions.dev_retrofit_versionCode}" + buildConfigField "String", "DevRetrofit_Version", "\"${versions.dev_retrofit_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.kotlin.core + api deps.kotlin.coroutines + api deps.kotlin.lifecycle_runtime + api deps.kotlin.lifecycle_viewmodel + api deps.kotlin.lifecycle_livedata +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevRetrofit/proguard-rules.pro b/lib/DevRetrofit/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/DevRetrofit/project.properties b/lib/DevRetrofit/project.properties new file mode 100644 index 0000000000..4bb476a97b --- /dev/null +++ b/lib/DevRetrofit/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevRetrofit +project.groupId=io.github.afkt +project.artifactId=DevRetrofit +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevRetrofit \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/AndroidManifest.xml b/lib/DevRetrofit/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..d2f8b994bf --- /dev/null +++ b/lib/DevRetrofit/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/DevRetrofit.kt b/lib/DevRetrofit/src/main/java/dev/DevRetrofit.kt new file mode 100644 index 0000000000..7a89bef302 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/DevRetrofit.kt @@ -0,0 +1,57 @@ +package dev + +import dev.retrofit.BuildConfig + +/** + * detail: Retrofit + Kotlin Coroutines 封装 + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevRetrofit { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevRetrofit 版本号 + * @return DevRetrofit versionCode + */ + fun getDevRetrofitVersionCode(): Int { + return BuildConfig.DevRetrofit_VersionCode + } + + /** + * 获取 DevRetrofit 版本 + * @return DevRetrofit versionName + */ + fun getDevRetrofitVersion(): String { + return BuildConfig.DevRetrofit_Version + } +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt new file mode 100644 index 0000000000..dba992eac0 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/model.kt @@ -0,0 +1,334 @@ +package dev.retrofit + +import java.util.* + +// ==================== +// = 数据模型、接口汇总类 = +// ==================== + +/** + * detail: DevRetrofit 封装 Base 类 + * @author Ttt + * 防止污染引用项目 BaseXxx 类 + * 使用 Base 进行包装 + */ +class Base private constructor() { + + /** + * detail: 请求响应解析类 ( 接口 ) + * @author Ttt + * 定义为接口是为了解决不同公司后端字段不一致情况 + * 如何解决: + * 接口定义通用方法, 自行编写 Custom Response 类以及后台定义的字段 + * 并实现 Base.Response 解析类, 在对应通用方法返回所属的自定义字段 + */ + interface Response { + + fun getData(): T? + + fun getCode(): String? + + fun getMessage(): String? + + fun isSuccess(): Boolean + } + + /** + * detail: 请求结果包装类 + * @author Ttt + */ + class Result> private constructor( + private val response: R?, + // 不为 null 表示出错抛出异常 + private val error: Throwable?, + // 额外携带参数 ( 扩展使用 ) + private var params: Any? = null + ) { // : Response + + private val innerErrorCode: ErrorCode by lazy { + error.errorCode() + } + + // =========== + // = get/set = + // =========== + + fun getResponse(): R? { + return response + } + + fun isError(): Boolean { + return error != null + } + + fun getError(): Throwable? { + return error + } + + fun getErrorCode(): ErrorCode { + return innerErrorCode + } + + // = + + fun getParams(): Any? { + return params + } + + fun setParams(params: Any?): Result { + this.params = params + return this + } + + // ============ + // = Response = + // ============ + + fun getData(): T? { + return response?.getData() + } + + fun getCode(): String? { + return response?.getCode() + } + + fun getMessage(): String? { + return response?.getMessage() + } + + fun isSuccess(): Boolean { + return response?.isSuccess() ?: false + } + + // ========= + // = Build = + // ========= + + class Build>( + private var response: R?, + private var error: Throwable? + ) { + + fun build(): Result { + return Result(response, error) + } + + // =========== + // = get/set = + // =========== + + fun getResponse(): R? { + return response + } + + fun setResponse(response: R?): Build { + this.response = response + return this + } + + fun getError(): Throwable? { + return error + } + + fun setError(error: Throwable?): Build { + this.error = error + return this + } + } + } + + /** + * detail: 错误类型 ( 简单定义 ) + * @author Ttt + */ + enum class ErrorCode { + + NO_ERROR, + + UNKNOWN, + + PARSE_ERROR, + + NETWORK_ERROR, + + TIMEOUT_ERROR, + + SSL_ERROR + } +} + +// ========== +// = Notify = +// ========== + +/** + * detail: DevRetrofit 封装 Notify ( Callback ) 类 + * @author Ttt + * 防止污染引用项目 Callback 类 + * 使用 Notify 来表示当前请求所进行的阶段通知 + */ +class Notify private constructor() { + + /** + * detail: 当前请求每个阶段进行通知 + * @author Ttt + * 允许继承自定义其他参数 + */ + abstract class Callback { + + // 额外携带参数 ( 扩展使用 ) + private var params: Any? = null + + // =========== + // = get/set = + // =========== + + fun getParams(): Any? { + return params + } + + fun setParams(params: Any?): Callback { + this.params = params + return this + } + + // ============ + // = abstract = + // ============ + + /** + * 开始请求 + * @param uuid UUID + */ + open fun onStart(uuid: UUID) {} + + /** + * 请求成功 + * @param uuid UUID + * @param data T + */ + abstract fun onSuccess( + uuid: UUID, + data: T? + ) + + /** + * 请求异常 + * @param uuid UUID + * @param error Throwable? + */ + abstract fun onError( + uuid: UUID, + error: Throwable? + ) + + /** + * 请求结束 + * @param uuid UUID + * 不管是 [onError]、[onSuccess] 最终都会触发该结束方法 + */ + open fun onFinish(uuid: UUID) {} + } + + /** + * detail: 当前请求每个阶段进行通知 + * @author Ttt + * 允许继承自定义其他参数 + */ + abstract class ResultCallback> { + + // 额外携带参数 ( 扩展使用 ) + private var params: Any? = null + + // =========== + // = get/set = + // =========== + + fun getParams(): Any? { + return params + } + + fun setParams(params: Any?): ResultCallback { + this.params = params + return this + } + + // ============ + // = abstract = + // ============ + + /** + * 开始请求 + * @param uuid UUID + */ + open fun onStart(uuid: UUID) {} + + /** + * 请求成功 + * @param uuid UUID + * @param data Result + */ + abstract fun onSuccess( + uuid: UUID, + data: Base.Result + ) + + /** + * 请求结束 + * @param uuid UUID + */ + open fun onFinish(uuid: UUID) {} + } + + /** + * detail: 全局通知回调方法 + * @author Ttt + */ + interface GlobalCallback { + + /** + * 开始请求 + * @param uuid UUID + * @param params [Notify.Callback.params] + */ + fun onStart( + uuid: UUID, + params: Any? + ) + + /** + * 请求成功 + * @param uuid UUID + * @param params [Notify.Callback.params] + * @param data Any? + */ + fun onSuccess( + uuid: UUID, + params: Any?, + data: Any? + ) + + /** + * 请求异常 + * @param uuid UUID + * @param params [Notify.Callback.params] + * @param error Throwable? + */ + fun onError( + uuid: UUID, + params: Any?, + error: Throwable? + ) + + /** + * 请求结束 + * @param uuid UUID + * @param params [Notify.Callback.params] + * 不管是 [onError]、[onSuccess] 最终都会触发该结束方法 + */ + fun onFinish( + uuid: UUID, + params: Any? + ) + } +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/model_ext.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/model_ext.kt new file mode 100644 index 0000000000..80045ab093 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/model_ext.kt @@ -0,0 +1,70 @@ +package dev.retrofit + +// =============== +// = 扩展函数汇总类 = +// =============== + +// ============ +// = model.kt = +// ============ + +// ================= +// = Base - 扩展函数 = +// ================= + +/** + * 通过 Base.Response 实现类创建 Result.Build 来决定请求结果 + * @receiver R + * @param error Throwable? + * @return Base.Result.Build + */ +fun > R?.result(error: Throwable? = null): Base.Result.Build { + return Base.Result.Build(this, error) +} + +/** + * 创建一个空白的 Result.Build 自行传参 + * @param error Throwable? + * @return Base.Result.Build + */ +fun > resultCreate(error: Throwable? = null): Base.Result.Build { + return Base.Result.Build(null, error) +} + +// = + +/** + * 通过 Throwable 判断简单的错误类型 + * @receiver Throwable? + * @return Base.ErrorCode + */ +fun Throwable?.errorCode(): Base.ErrorCode { + if (this == null) return Base.ErrorCode.NO_ERROR + // 获取类名进行判断 ( 不依赖第三方库 ) + return when (val className = javaClass.simpleName) { + "JSONException", + "ParseException", + "JsonParseException", + "MalformedJsonException" -> { + Base.ErrorCode.PARSE_ERROR + } + "ConnectException", + "HttpException" -> { + Base.ErrorCode.NETWORK_ERROR + } + "ConnectTimeoutException", + "SocketTimeoutException", + "UnknownHostException" -> { + Base.ErrorCode.TIMEOUT_ERROR + } + "SSLException" -> { + Base.ErrorCode.SSL_ERROR + } + else -> { + if (className.startsWith("SSL")) { + return Base.ErrorCode.SSL_ERROR + } + Base.ErrorCode.UNKNOWN + } + } +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/request.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/request.kt new file mode 100644 index 0000000000..83612760b0 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/request.kt @@ -0,0 +1,271 @@ +package dev.retrofit + +import java.util.* + +// ============= +// = 封装请求方法 = +// ============= + +// ======= +// = 原始 = +// ======= + +/** + * 最终执行方法 + * 无任何额外逻辑封装, 支持自定义解析、处理等代码 + */ +suspend inline fun finalExecute( + // 请求方法体 + crossinline block: suspend () -> T?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (T?) -> Unit, + // 请求异常 + crossinline error: suspend (Throwable) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +) { + val uuid = UUID.randomUUID() + runCatching { + // 开始请求 + innerOriginalStartCallback( + uuid, callback, globalCallback + ) + + start.invoke() + // 请求方法体 + block() + }.onSuccess { itData -> + // 请求成功、请求结束 + innerOriginalSuccessCallback( + uuid, callback, globalCallback, itData + ) + + success.invoke(itData) + finish.invoke() + }.onFailure { itError -> + // 请求异常、请求结束 + innerOriginalErrorCallback( + uuid, callback, globalCallback, itError + ) + + error.invoke(itError) + finish.invoke() + } +} + +// ============== +// = Base - 封装 = +// ============== + +/** + * 最终执行方法 + * 封装为 Base.Response、Base.Result 进行响应 + */ +suspend inline fun > finalExecuteResponse( + // 请求方法体 + crossinline block: suspend () -> R?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (Base.Result) -> Unit, + // 请求异常 + crossinline error: suspend (Base.Result) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +) { + val uuid = UUID.randomUUID() + runCatching { + // 开始请求 + innerBaseStartCallback( + uuid, callback, globalCallback + ) + + start.invoke() + // 请求方法体 + block() + }.onSuccess { itData -> + val result = itData.result().build() + // 设置额外携带参数 ( 扩展使用 ) + result.setParams(callback?.getParams()) + // 请求成功、请求结束 + innerBaseSuccessCallback( + uuid, callback, globalCallback, result + ) + + success.invoke(result) + finish.invoke() + }.onFailure { itError -> + val result = resultCreate(itError).build() + // 设置额外携带参数 ( 扩展使用 ) + result.setParams(callback?.getParams()) + // 请求异常、请求结束 + innerBaseErrorCallback( + uuid, callback, globalCallback, result, itError + ) + + error.invoke(result) + finish.invoke() + } +} + +// ================= +// = 内部封装美化代码 = +// ================= + +// ======= +// = 原始 = +// ======= + +/** + * 开始请求 + */ +fun innerOriginalStartCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +) { + val params = callback?.getParams() + // 开始请求 + globalCallback?.onStart( + uuid, params + ) + callback?.onStart(uuid) +} + +/** + * 请求成功、请求结束 + */ +fun innerOriginalSuccessCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null, + // 请求响应数据 + itData: T? +) { + val params = callback?.getParams() + // 请求成功、请求结束 + globalCallback?.apply { + onSuccess(uuid, params, itData) + onFinish(uuid, params) + } + callback?.apply { + onSuccess(uuid, itData) + onFinish(uuid) + } +} + +/** + * 请求异常、请求结束 + */ +fun innerOriginalErrorCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null, + // 请求异常 + itError: Throwable +) { + val params = callback?.getParams() + // 请求异常、请求结束 + globalCallback?.apply { + onError(uuid, params, itError) + onFinish(uuid, params) + } + callback?.apply { + onError(uuid, itError) + onFinish(uuid) + } +} + +// ============== +// = Base - 封装 = +// ============== + +/** + * 开始请求 + */ +fun > innerBaseStartCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +) { + val params = callback?.getParams() + // 开始请求 + globalCallback?.onStart( + uuid, params + ) + callback?.onStart(uuid) +} + +/** + * 请求成功、请求结束 + */ +fun > innerBaseSuccessCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null, + // 请求响应数据 + result: Base.Result +) { + val params = callback?.getParams() + // 请求成功、请求结束 + globalCallback?.apply { + onSuccess(uuid, params, result) + onFinish(uuid, params) + } + callback?.apply { + onSuccess(uuid, result) + onFinish(uuid) + } +} + +/** + * 请求异常、请求结束 + */ +fun > innerBaseErrorCallback( + // 每次请求唯一 id + uuid: UUID, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null, + // 请求响应数据 + result: Base.Result, + // 请求异常 + itError: Throwable +) { + val params = callback?.getParams() + // 请求异常、请求结束 + globalCallback?.apply { + onError(uuid, params, itError) + onFinish(uuid, params) + } + callback?.apply { + onSuccess(uuid, result) + onFinish(uuid) + } +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines.kt new file mode 100644 index 0000000000..3d3948c8b6 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines.kt @@ -0,0 +1,218 @@ +package dev.retrofit + +import androidx.lifecycle.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +// ==================== +// = 请求方法协程扩展函数 = +// ==================== + +// ================================ +// = 在 request.kt 基础上封装使用协程 = +// ================================ + +// ================== +// = CoroutineScope = +// ================== + +/** + * 执行请求 + * 无任何额外逻辑封装, 支持自定义解析、处理等代码 + *

+ * Android 上的 Kotlin 协程 + * @see https://developer.android.com/kotlin/coroutines + * 防止使用、阅读混淆 + * 通用 CoroutineScope 执行方法使用 scope 为方法名前缀 + */ +inline fun CoroutineScope.scopeExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (T?) -> Unit, + // 请求异常 + crossinline error: suspend (Throwable) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecute( + block, start, success, error, finish, callback, globalCallback + ) + } +} + +/** + * 执行请求 + * 封装为 Base.Response、Base.Result 进行响应 + *

+ * 防止使用、阅读混淆 + * 通用 CoroutineScope 执行方法使用 scope 为方法名前缀 + */ +inline fun > CoroutineScope.scopeExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (Base.Result) -> Unit, + // 请求异常 + crossinline error: suspend (Base.Result) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecuteResponse( + block, start, success, error, finish, callback, globalCallback + ) + } +} + +// ============= +// = ViewModel = +// ============= + +inline fun ViewModel.launchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (T?) -> Unit, + // 请求异常 + crossinline error: suspend (Throwable) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.scopeExecuteRequest( + block, start, success, error, finish, callback, globalCallback + ) +} + +inline fun > ViewModel.launchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (Base.Result) -> Unit, + // 请求异常 + crossinline error: suspend (Base.Result) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.scopeExecuteResponseRequest( + block, start, success, error, finish, callback, globalCallback + ) +} + +// ============= +// = Lifecycle = +// ============= + +inline fun Lifecycle.launchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (T?) -> Unit, + // 请求异常 + crossinline error: suspend (Throwable) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.scopeExecuteRequest( + block, start, success, error, finish, callback, globalCallback + ) +} + +inline fun > Lifecycle.launchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (Base.Result) -> Unit, + // 请求异常 + crossinline error: suspend (Base.Result) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.scopeExecuteResponseRequest( + block, start, success, error, finish, callback, globalCallback + ) +} + +// ================== +// = LifecycleOwner = +// ================== + +inline fun LifecycleOwner.launchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (T?) -> Unit, + // 请求异常 + crossinline error: suspend (Throwable) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.scopeExecuteRequest( + block, start, success, error, finish, callback, globalCallback + ) +} + +inline fun > LifecycleOwner.launchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 开始请求 + crossinline start: suspend () -> Unit, + // 请求成功 + crossinline success: suspend (Base.Result) -> Unit, + // 请求异常 + crossinline error: suspend (Base.Result) -> Unit, + // 请求结束 + crossinline finish: suspend () -> Unit, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.scopeExecuteResponseRequest( + block, start, success, error, finish, callback, globalCallback + ) +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple.kt new file mode 100644 index 0000000000..6640276bd4 --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple.kt @@ -0,0 +1,150 @@ +package dev.retrofit + +import androidx.lifecycle.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +// ==================== +// = 请求方法协程扩展函数 = +// ==================== + +// =========================================================================== +// = 在 request_coroutines.kt 基础上减少 start、success、error、finish 方法体传参 = +// =========================================================================== + +// ================== +// = CoroutineScope = +// ================== + +/** + * 执行请求 + * 无任何额外逻辑封装, 支持自定义解析、处理等代码 + */ +inline fun CoroutineScope.simpleScopeExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 当前请求每个阶段进行通知 + callback: Notify.Callback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecute( + block, start = {}, success = {}, + error = {}, finish = {}, + callback, globalCallback + ) + } +} + +/** + * 执行请求 + * 封装为 Base.Response、Base.Result 进行响应 + */ +inline fun > CoroutineScope.simpleScopeExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecuteResponse( + block, start = {}, success = {}, + error = {}, finish = {}, + callback, globalCallback + ) + } +} + +// ============= +// = ViewModel = +// ============= + +inline fun ViewModel.simpleLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 当前请求每个阶段进行通知 + callback: Notify.Callback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.simpleScopeExecuteRequest( + block, callback, globalCallback + ) +} + +inline fun > ViewModel.simpleLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.simpleScopeExecuteResponseRequest( + block, callback, globalCallback + ) +} + +// ============= +// = Lifecycle = +// ============= + +inline fun Lifecycle.simpleLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 当前请求每个阶段进行通知 + callback: Notify.Callback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.simpleScopeExecuteRequest( + block, callback, globalCallback + ) +} + +inline fun > Lifecycle.simpleLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.simpleScopeExecuteResponseRequest( + block, callback, globalCallback + ) +} + +// ================== +// = LifecycleOwner = +// ================== + +inline fun LifecycleOwner.simpleLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // 当前请求每个阶段进行通知 + callback: Notify.Callback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.simpleScopeExecuteRequest( + block, callback, globalCallback + ) +} + +inline fun > LifecycleOwner.simpleLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.simpleScopeExecuteResponseRequest( + block, callback, globalCallback + ) +} \ No newline at end of file diff --git a/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple_livedata.kt b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple_livedata.kt new file mode 100644 index 0000000000..81618b9f2b --- /dev/null +++ b/lib/DevRetrofit/src/main/java/dev/retrofit/request_coroutines_simple_livedata.kt @@ -0,0 +1,212 @@ +package dev.retrofit + +import androidx.lifecycle.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import java.util.* + +// ==================== +// = 请求方法协程扩展函数 = +// ==================== + +// ==================================================== +// = 在 request_coroutines_simple.kt 基础上使用 LiveData = +// ==================================================== + +// ================== +// = CoroutineScope = +// ================== + +/** + * 执行请求 + * 无任何额外逻辑封装, 支持自定义解析、处理等代码 + */ +inline fun CoroutineScope.liveDataScopeExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // LiveData + liveData: MutableLiveData, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecute( + block, start = {}, success = { + it?.let { data -> + if (usePostValue) { + liveData.postValue(data) + } else { + liveData.value = data + } + } + }, + error = {}, finish = {}, + callback, globalCallback + ) + } +} + +/** + * 执行请求 + * 封装为 Base.Response、Base.Result 进行响应 + */ +inline fun > CoroutineScope.liveDataScopeExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // LiveData + liveData: MutableLiveData>, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return launch { + finalExecuteResponse( + block, start = {}, success = {}, + error = {}, finish = {}, + object : Notify.ResultCallback() { + override fun onStart(uuid: UUID) { + callback?.onStart(uuid) + } + + override fun onSuccess( + uuid: UUID, + data: Base.Result + ) { + callback?.onSuccess(uuid, data) + + if (usePostValue) { + liveData.postValue(data) + } else { + liveData.value = data + } + } + + override fun onFinish(uuid: UUID) { + callback?.onFinish(uuid) + } + }, globalCallback + ) + } +} + +// ============= +// = ViewModel = +// ============= + +inline fun ViewModel.liveDataLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // LiveData + liveData: MutableLiveData, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.liveDataScopeExecuteRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} + +inline fun > ViewModel.liveDataLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // LiveData + liveData: MutableLiveData>, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return viewModelScope.liveDataScopeExecuteResponseRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} + +// ============= +// = Lifecycle = +// ============= + +inline fun Lifecycle.liveDataLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // LiveData + liveData: MutableLiveData, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.liveDataScopeExecuteRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} + +inline fun > Lifecycle.liveDataLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // LiveData + liveData: MutableLiveData>, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return coroutineScope.liveDataScopeExecuteResponseRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} + +// ================== +// = LifecycleOwner = +// ================== + +inline fun LifecycleOwner.liveDataLaunchExecuteRequest( + // 请求方法体 + crossinline block: suspend () -> T?, + // LiveData + liveData: MutableLiveData, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.Callback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.liveDataScopeExecuteRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} + +inline fun > LifecycleOwner.liveDataLaunchExecuteResponseRequest( + // 请求方法体 + crossinline block: suspend () -> R?, + // LiveData + liveData: MutableLiveData>, + // 是否使用 postValue + usePostValue: Boolean = true, + // 当前请求每个阶段进行通知 + callback: Notify.ResultCallback? = null, + // 全局通知回调方法 ( 创建一个全局通用传入 ) + globalCallback: Notify.GlobalCallback? = null +): Job { + return lifecycleScope.liveDataScopeExecuteResponseRequest( + block, liveData, usePostValue, callback, globalCallback + ) +} \ No newline at end of file diff --git a/lib/DevWidget/.gitignore b/lib/DevWidget/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/DevWidget/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/DevWidget/CHANGELOG.md b/lib/DevWidget/CHANGELOG.md new file mode 100644 index 0000000000..10e3bb9231 --- /dev/null +++ b/lib/DevWidget/CHANGELOG.md @@ -0,0 +1,131 @@ +Change Log +========== + +Version 1.2.0 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.9 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.8 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.7 *(2022-03-20)* +---------------------------- + +* `[Remove]` 移除 RecyclerView ItemDecoration ( FirstLineHorizontalItemDecoration、LastLineHorizontalItemDecoration、LineHorizontalItemDecoration、FirstLineItemDecoration、LastLineItemDecoration、LineItemDecoration ) + +* `[Add]` 新增 RecyclerView ItemDecoration ( GridColumnColorItemDecoration、GridRowColorItemDecoration、FirstLinearColorItemDecoration、LastLinearColorItemDecoration、LinearColorItemDecoration ) + +Version 1.1.6 *(2022-02-12)* +---------------------------- + +* `[Update]` RightIconEditText 继承 EditText + +* `[Update]` 移动 DevWidget ItemDecoration 位置 + +* `[Fix]` 修复 RoundImageView 重复绘制问题 + +Version 1.1.5 *(2022-01-23)* +---------------------------- + +* `[Add]` RightIconEditText Icon ClickListener + +Version 1.1.4 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.3 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.2 *(2021-11-26)* +---------------------------- + +* `[Add]` RecyclerView ItemDecoration ( FirstLineHorizontalItemDecoration、LastLineHorizontalItemDecoration、LineHorizontalItemDecoration ) + +Version 1.1.1 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +* `[Add]` RoundImageView、WaveView + +* `[Add]` RecyclerView ItemDecoration ( FirstLineItemDecoration、LastLineItemDecoration、LineItemDecoration ) + +* `[Add]` DevWidget + +Version 1.1.0 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.9 *(2021-03-28)* +---------------------------- + +* `[Add]` FlipCardView 翻转卡片 View + +* `[Add]` ResizableImageView 自动同比放大 ImageView + +Version 1.0.8 *(2021-03-05)* +---------------------------- + +* `[Add]` RoundDrawable、RoundLayout、RoundView + +Version 1.0.7 *(2021-01-24)* +---------------------------- + +* `[Perf]` 进行代码检测优化 + +Version 1.0.6 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Update]` 修改 CallBack 相关代码为 Callback + +Version 1.0.5 *(2020-11-15)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.0.4 *(2020-08-23)* +---------------------------- + +* `[Update]` RadiusUtils renamed from RadiusAttrs、RadiusView renamed from RadiusLayout + +Version 1.0.3 *(2020-08-04)* +---------------------------- + +* `[Add]` RadiusLayout + +Version 1.0.2 *(2020-06-08)* +---------------------------- + +* `[Add]` ViewAssist + +* `[Add]` StateLayout + +Version 1.0.1 *(2020-05-18)* +---------------------------- + +* `[Add]` CornerLabelView + +Version 1.0.0 *(2020-03-19)* +---------------------------- + +* `[Add]` adjust package View AdjustHeightGridView、AdjustHeightListView、AdjustHeightRecyclerView、AdjustHeightWebView + +* `[Add]` custom package View CustomGallery、CustomHorizontalScrollView、CustomNestedScrollView、CustomRecyclerView、CustomScrollView、CustomViewPager、CustomWebView + +* `[Add]` function package View LimitLayout、LineTextView、RightIconEditText、SignView + +* `[Add]` ui package View FlowLikeView、LoadProgressBar、ScanShapeView、WrapView diff --git a/lib/DevWidget/README.md b/lib/DevWidget/README.md new file mode 100644 index 0000000000..3e37b7e42c --- /dev/null +++ b/lib/DevWidget/README.md @@ -0,0 +1,824 @@ + +## Gradle + +```gradle +// AndroidX +implementation 'io.github.afkt:DevWidgetX:1.2.0' +``` + +## 目录结构 + +``` +- dev | 根目录 + - widget | 自定义 View 根目录 + - adjust | 自动调节高度 View + - assist | View 辅助类 + - custom | 自定义 View + - decoration | RecyclerView ItemDecoration + - grid | Grid ItemDecoration + - linear | Linear ItemDecoration + - function | 需求功能 View + - ui | UI View + - round | 圆角相关 View + - utils | 工具类目录 +``` + + +## 事项 + +- 部分 API 更新不及时或有遗漏等,`具体以对应的工具类为准` + +- [检测代码规范、注释内容排版,API 文档生成](https://github.com/afkT/JavaDoc) + +- [Change Log](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/CHANGELOG.md) + +## README + +- 效果可运行 DevUtils 项目点击 DevWidget UI 库查看 + +- 该库依赖 [DevApp](https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md) 开发,需引用 DevApp 库 + +- [Preview README](https://github.com/afkT/DevUtils-repo/blob/main/lib/DevWidget_Preview.md) + + +## API + + +- dev | 根目录 + - [widget](#devwidget) | 自定义 View 根目录 + - [adjust](#devwidgetadjust) | 自动调节高度 View + - [assist](#devwidgetassist) | View 辅助类 + - [custom](#devwidgetcustom) | 自定义 View + - [decoration](#devwidgetdecoration) | RecyclerView ItemDecoration + - [grid](#devwidgetdecorationgrid) | Grid ItemDecoration + - [linear](#devwidgetdecorationlinear) | Linear ItemDecoration + - [function](#devwidgetfunction) | 需求功能 View + - [ui](#devwidgetui) | UI View + - [round](#devwidgetuiround) | 圆角相关 View + - [utils](#devwidgetutils) | 工具类目录 + + +## **`dev`** + + +## **`dev.widget`** + + +* **DevWidget ->** [DevWidget.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/DevWidget.java) + +| 方法 | 注释 | +| :- | :- | +| getDevWidgetVersionCode | 获取 DevWidget 版本号 | +| getDevWidgetVersion | 获取 DevWidget 版本 | +| getDevAppVersionCode | 获取 DevApp 版本号 | +| getDevAppVersion | 获取 DevApp 版本 | + + +## **`dev.widget.adjust`** + + +* **自动调节高度 GridView ->** [AdjustHeightGridView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightGridView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | + + +* **自动调节高度 ListView ->** [AdjustHeightListView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightListView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | + + +* **自动调节高度 RecyclerView ->** [AdjustHeightRecyclerView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightRecyclerView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | + + +* **自动调节高度 WebView ->** [AdjustHeightWebView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightWebView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | + + +## **`dev.widget.assist`** + + +* **View 填充辅助类 ->** [ViewAssist.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/assist/ViewAssist.java) + +| 方法 | 注释 | +| :- | :- | +| wrap | 传入包裹 View | +| showIng | showIng | +| showFailed | showFailed | +| showSuccess | showSuccess | +| showEmptyData | showEmptyData | +| showType | 显示 Type Adapter View | +| notifyDataSetChanged | notifyDataSetChanged | +| gone | gone | +| visible | visible | +| register | 注册 type | +| unregister | 取消注册 type | +| reset | 重置处理 | +| getWrapper | getWrapper | +| getTag | getTag | +| setTag | setTag | +| getData | getData | +| setData | setData | +| getAdapter | getAdapter | +| getView | getView | +| getCurrentType | getCurrentType | +| getCurrentView | getCurrentView | +| setListener | setListener | + + +## **`dev.widget.custom`** + + +* **自定义 Gallery 滑动控制 ->** [CustomGallery.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomGallery.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onFling | onFling | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | + + +* **自定义 HorizontalScrollView 滑动监听、滑动控制 ->** [CustomHorizontalScrollView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomHorizontalScrollView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onScrollChanged | onScrollChanged | +| computeScrollDeltaToGetChildRectOnScreen | computeScrollDeltaToGetChildRectOnScreen | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| setScrollCallback | 设置滑动监听回调 | + + +* **自定义 NestedScrollView 滑动监听、滑动控制 ->** [CustomNestedScrollView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomNestedScrollView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onScrollChanged | onScrollChanged | +| computeScrollDeltaToGetChildRectOnScreen | computeScrollDeltaToGetChildRectOnScreen | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| setScrollCallback | 设置滑动监听回调 | + + +* **自定义 RecyclerView 滑动监听、滑动控制 ->** [CustomRecyclerView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomRecyclerView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onScrolled | onScrolled | +| onScrollStateChanged | onScrollStateChanged | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| setScrollCallback | 设置滑动监听回调 | +| getCustomScrollX | 获取距离左边距离 | +| getCustomScrollY | 获取距离顶部距离 | +| onScrollChanged | 滑动改变通知 | + + +* **自定义 ScrollView 滑动监听、滑动控制 ->** [CustomScrollView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomScrollView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onScrollChanged | onScrollChanged | +| computeScrollDeltaToGetChildRectOnScreen | computeScrollDeltaToGetChildRectOnScreen | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| setScrollCallback | 设置滑动监听回调 | + + +* **自定义 ViewPager 滑动监听、滑动控制 ->** [CustomViewPager.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomViewPager.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| onPageScrolled | onPageScrolled | +| onPageScrollStateChanged | onPageScrollStateChanged | +| onSlideDirection | 滑动方向 | + + +* **自定义 WebView 滑动监听、滑动控制 ->** [CustomWebView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/custom/CustomWebView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onScrollChanged | onScrollChanged | +| onTouchEvent | onTouchEvent | +| onInterceptTouchEvent | onInterceptTouchEvent | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | +| setScrollCallback | 设置滑动监听回调 | + + +## **`dev.widget.decoration`** + + +* **基础 RecyclerView Grid 分割线处理 ->** [BaseColorGridItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorGridItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| isRowItemDecoration | 是否 Grid Row ItemDecoration | +| isColumnItemDecoration | 是否 Grid Column ItemDecoration | +| getSpanCount | 获取 Span 总数 ( Grid 列 ) | +| setSpanCount | 设置 Span 总数 ( Grid 列 ) | + + +* **RecyclerView 分割线绘制基类 ->** [BaseColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| isSingleDraw | 获取单条数据是否绘制分割线 | +| setSingleDraw | 设置单条数据是否绘制分割线 | +| getPaint | 获取分割线画笔 | +| isVertical | 判断分割线绘制方向是否为 VERTICAL | +| isHorizontal | 判断分割线绘制方向是否为 HORIZONTAL | +| setVertical | 设置分割线绘制方向为 VERTICAL | +| setHorizontal | 设置分割线绘制方向为 HORIZONTAL | +| getHeight | 获取分割线高度 | +| setHeight | 设置分割线高度 | +| getLeft | 获取分割线距左边距 | +| setLeft | 设置分割线距左边距 | +| getRight | 获取分割线距右边距 | +| setRight | 设置分割线距右边距 | +| setLeftRight | 设置分割线距左、右边距 | +| getOffset | 获取分割线偏差值 | +| setOffset | 设置分割线偏差值 | + + +## **`dev.widget.decoration.grid`** + + +* **RecyclerView Grid 列分割线处理 ( 每一列数据 ) ->** [GridColumnColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridColumnColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| getItemOffsets | getItemOffsets | +| onDraw | onDraw | + + +* **RecyclerView Grid 行分割线处理 ( 每一行数据 ) ->** [GridRowColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridRowColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| getItemOffsets | getItemOffsets | +| onDraw | onDraw | + + +## **`dev.widget.decoration.linear`** + + +* **RecyclerView Linear 分割线处理 ( 第一条数据 ) ->** [FirstLinearColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/linear/FirstLinearColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| getItemOffsets | getItemOffsets | +| onDraw | onDraw | + + +* **RecyclerView Linear 分割线处理 ( 最后一条数据 ) ->** [LastLinearColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LastLinearColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| getItemOffsets | getItemOffsets | +| onDraw | onDraw | + + +* **RecyclerView Linear 分割线处理 ( 每一条数据 ) ->** [LinearColorItemDecoration.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LinearColorItemDecoration.java) + +| 方法 | 注释 | +| :- | :- | +| getItemOffsets | getItemOffsets | +| onDraw | onDraw | + + +## **`dev.widget.function`** + + +* **自定义 FrameLayout 设置最大、最小宽高 ->** [LimitLayout.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/function/LimitLayout.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | + + +* **TextView 换行监听 ->** [LineTextView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/function/LineTextView.java) + +| 方法 | 注释 | +| :- | :- | +| onDraw | onDraw | +| isNewLine | 判断是否换行 | +| setNewLineCallback | 设置换行监听回调 | +| onNewLine | 换行触发 | + + +* **自定义 EditText 右边 Icon ->** [RightIconEditText.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/function/RightIconEditText.java) + +| 方法 | 注释 | +| :- | :- | +| setCompoundDrawables | setCompoundDrawables | +| onTouchEvent | onTouchEvent | +| finalize | finalize | +| getRangeMultiple | 获取右边 Icon 触发范围倍数 | +| setRangeMultiple | 设置右边 Icon 触发范围倍数 | +| isDrawRightIcon | 是否绘制右边 Icon | +| setDrawRightIcon | 设置是否绘制右边 Icon | +| getRightClickListener | 获取右边 Icon 点击事件 | +| setRightClickListener | 设置右边 Icon 点击事件 | +| getRightIcon | 获取右边 Icon Drawable | +| setRightIcon | 设置右边 Icon Drawable | +| setTextWatcher | 设置输入监听回调 | + + +* **自定义签名 View ->** [SignView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/function/SignView.java) + +| 方法 | 注释 | +| :- | :- | +| onDraw | onDraw | +| onTouchEvent | onTouchEvent | +| clearCanvas | 清空画布 | +| snapshotByView | 通过 View 绘制为 Bitmap | +| getPath | 获取绘制路径 | +| setPath | 设置绘制路径 | +| getPaint | 获取绘制画笔 | +| setPaint | 设置绘制画笔 | +| setDrawCallback | 设置绘制回调事件 | + + +* **状态布局 ->** [StateLayout.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/function/StateLayout.java) + +| 方法 | 注释 | +| :- | :- | +| reset | 重置处理 | +| setGlobal | setGlobal | +| setListener | setListener | +| showIng | showIng | +| showFailed | showFailed | +| showSuccess | showSuccess | +| showEmptyData | showEmptyData | +| showType | showType | +| notifyDataSetChanged | notifyDataSetChanged | +| gone | gone | +| visible | visible | +| register | 注册 type | +| unregister | 取消注册 type | +| getAssistTag | getAssistTag | +| setAssistTag | setAssistTag | +| getData | getData | +| setData | setData | +| getView | getView | +| getCurrentType | getCurrentType | +| getCurrentView | getCurrentView | + + +## **`dev.widget.ui`** + + +* **自定义角标 View ->** [CornerLabelView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/CornerLabelView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onDraw | onDraw | +| getPainter1 | 获取 Text1 Painter | +| getPainter2 | 获取 Text2 Painter | +| setPaddingTop | 设置顶部边距 | +| setPaddingCenter | 设置 Text1、Text2 边距 | +| setPaddingBottom | 设置底部边距 | +| setFillColor | 设置背景填充颜色 | +| setFillShader | 设置背景填充渐变色 | +| left | 设置左边显示角标 | +| right | 设置右边显示角标 | +| top | 设置顶部显示角标 | +| bottom | 设置底部显示角标 | +| triangle | 设置角标三角形铺满样式 | +| setText1 | 设置文本 | +| setTextColor1 | 设置字体颜色 | +| setTextHeight1 | 设置字体高度 | +| setTextBold1 | 设置字体是否加粗 | +| setText2 | 设置文本 | +| setTextColor2 | 设置字体颜色 | +| setTextHeight2 | 设置字体高度 | +| setTextBold2 | 设置字体是否加粗 | +| refresh | 刷新重绘处理 | +| getPaint | 获取画笔 | +| getText | 获取文本 | +| getTextColor | 获取字体颜色 | +| getTextHeight | 获取字体高度 | +| isTextBold | 获取字体是否加粗 | + + +* **翻转卡片 View ->** [FlipCardView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/FlipCardView.java) + +| 方法 | 注释 | +| :- | :- | +| isFront | 当前是否显示正面 Layout | +| getCurrentPosition | 获取当前显示的索引 | +| getAdapter | 获取数据源适配器 | +| setAdapter | 设置数据源适配器 | +| notifyDataSetChanged | Adapter 数据源变更时调用 | +| flip | 翻转操作 | +| setInOutAnimator | 设置进出动画 | +| setFlipDistance | 设置翻牌角度 | +| getItemCount | getItemCount | +| getItemView | getItemView | + + +* **自定义点赞效果 View ->** [FlowLikeView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/FlowLikeView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onSizeChanged | onSizeChanged | +| like | 点赞操作 | +| getDrawables | 获取 Icon 集合 | +| setDrawables | 设置 Icon 集合 | +| setDrawablesById | 设置 Icon 集合 | +| getIconWidth | 获取点赞 Icon 宽度 | +| setIconWidth | 设置点赞 Icon 宽度 | +| getIconHeight | 获取点赞 Icon 高度 | +| setIconHeight | 设置点赞 Icon 高度 | +| getAnimDuration | 获取点赞动画执行时间 | +| setAnimDuration | 设置点赞动画执行时间 | + + +* **自定义加载 ProgressBar 样式 View ->** [LoadProgressBar.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/LoadProgressBar.java) + +| 方法 | 注释 | +| :- | :- | +| onDraw | onDraw | +| reset | 重置参数 | +| getMax | 获取最大值 | +| setMax | 设置最大值 | +| getProgress | 获取当前进度 | +| setProgress | 设置当前进度 | +| getProgressColor | 获取进度条颜色 | +| setProgressColor | 设置进度条颜色 | +| getOuterRingColor | 获取外环进度条颜色 | +| setOuterRingColor | 设置外环进度条颜色 | +| getInsideCircleWidth | 获取内环进度条宽度 | +| setInsideCircleWidth | 设置内环进度条宽度 | +| getOuterRingWidth | 获取外环进度条宽度 | +| setOuterRingWidth | 设置外环进度条宽度 | +| isCanvasNumber | 是否绘制数字 | +| setCanvasNumber | 设置是否绘制数字 | +| getNumberTextSize | 获取绘制的字体大小 | +| setNumberTextSize | 设置绘制的字体大小 | +| getNumberTextColor | 获取绘制的数字颜色 | +| setNumberTextColor | 设置绘制的数字颜色 | +| getProgressStyle | 获取进度条样式 | +| setProgressStyle | 设置进度条样式 | + + +* **自定义圆角 View ->** [RadiusLayout.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/RadiusLayout.java) + +| 方法 | 注释 | +| :- | :- | +| onSizeChanged | onSizeChanged | +| draw | draw | +| onSaveInstanceState | onSaveInstanceState | +| onRestoreInstanceState | onRestoreInstanceState | +| setRadius | 设置圆角值 | +| setRadiusLeftTop | 设置左上圆角值 | +| setRadiusLeftBottom | 设置左下圆角值 | +| setRadiusRightTop | 设置右上圆角值 | +| setRadiusRightBottom | 设置右下圆角值 | +| setRadiusLeft | 设置左上、左下圆角值 | +| setRadiusRight | 设置右上、右下圆角值 | +| setRadiusTop | 设置左上、右上圆角值 | +| setRadiusBottom | 设置左下、右下圆角值 | +| getRadius | 获取圆角值 | +| getRadiusLeftTop | 获取左上圆角值 | +| getRadiusLeftBottom | 获取左下圆角值 | +| getRadiusRightTop | 获取右上圆角值 | +| getRadiusRightBottom | 获取右下圆角值 | + + +* **自动同比放大 ImageView ->** [ResizableImageView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/ResizableImageView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| getZoomHeight | 获取缩放后的高度 | +| getWHListener | 获取宽高监听事件 | +| setWHListener | 设置宽高监听事件 | + + +* **自定义扫描 ( 二维码 / AR ) 效果形状 View ->** [ScanShapeView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/ScanShapeView.java) + +| 方法 | 注释 | +| :- | :- | +| onDraw | onDraw | +| destroy | 销毁处理 | +| getShapeType | 获取扫描形状类型 | +| setShapeType | 设置扫描形状类型 | +| getCornerRadius | 获取拐角角度大小 | +| setCornerEffect | 设置是否拐角圆角 ( 主要是控制绘制边框的线 ) | +| setRegion | 设置扫描区域大小 | +| getRegionLeft | 获取扫描绘制区域距离左 / 右边距 | +| getRegionTop | 获取扫描绘制区域距离上 / 下边距 | +| getRegionWidth | 获取扫描区域宽度 | +| getRegionHeight | 获取扫描区域高度 | +| getRegion | 获取扫描区域信息 | +| getRegionParent | 获取在父布局中实际的位置 | +| getBorderMargin | 获取边框边距 | +| setBorderMargin | 设置边框边距 | +| getBorderColor | 获取边框颜色 | +| setBorderColor | 设置边框颜色 | +| getBorderWidth | 获取边框宽度 | +| setBorderWidth | 设置边框宽度 | +| isDrawBorder | 是否绘制边框 | +| setDrawBorder | 设置是否绘制边框 | +| isDrawBackground | 是否绘制背景 | +| setDrawBackground | 设置是否绘制背景 | +| getBGColor | 获取绘制的背景颜色 | +| setBGColor | 设置绘制的背景颜色 | +| isDrawAnim | 是否绘制动画效果 | +| setDrawAnim | 设置是否绘制动画效果 | +| isAutoAnim | 是否自动播放动画 | +| setAutoAnim | 设置是否自动播放动画 | +| getBorderToSquare | 获取正方形描边 ( 边框 ) 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 | +| setBorderToSquare | 设置正方形描边 ( 边框 ) 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 | +| getBorderWidthToSquare | 获取正方形描边 ( 边框 ) 宽度 | +| setBorderWidthToSquare | 设置正方形描边 ( 边框 ) 宽度 | +| getTriAngleLength | 获取每个角的点距离 ( 长度 ) 正方形四个角落区域 | +| setTriAngleLength | 设置每个角的点距离 ( 长度 ) 正方形四个角落区域 | +| isSpecialToSquare | 是否特殊处理 ( 正方形边框 ) | +| setSpecialToSquare | 设置是否特殊处理 ( 正方形边框 ) | +| getLineDurationToSquare | 获取正方形扫描动画速度 ( 毫秒 ) | +| setLineDurationToSquare | 设置正方形扫描动画速度 ( 毫秒 ) | +| getBitmapToSquare | 获取正方形扫描线条 Bitmap | +| setBitmapToSquare | 设置正方形扫描线条 Bitmap | +| getLineMarginTopToSquare | 获取正方形扫描线条向上 ( 下 ) 边距 | +| setLineMarginTopToSquare | 设置正方形扫描线条向上 ( 下 ) 边距 | +| getLineMarginLeftToSquare | 获取正方形扫描线条向左 ( 右 ) 边距 | +| setLineMarginLeftToSquare | 设置正方形扫描线条向左 ( 右 ) 边距 | +| getLineColorToSquare | 获取正方形线条动画颜色 ( 着色 ) | +| setLineColorToSquare | 设置正方形线条动画 ( 着色 ) | +| getLineWidthToHexagon | 获取六边形线条动画 ( 线条宽度 ) | +| setLineWidthToHexagon | 设置六边形线条动画 ( 线条宽度 ) | +| getLineMarginToHexagon | 获取六边形线条动画 ( 线条边距 ) | +| setLineMarginToHexagon | 设置六边形线条动画 ( 线条边距 ) | +| isLineAnimDirection | 获取六边形线条动画方向 ( true = 从左到右, false = 从右到左 ) | +| setLineAnimDirection | 设置六边形线条动画方向 ( true = 从左到右, false = 从右到左 ) | +| getLineColorToHexagon | 获取六边形线条动画颜色 | +| setLineColorToHexagon | 设置六边形线条动画颜色 | +| getBitmapToAnnulus | 获取环形扫描线条 Bitmap | +| setBitmapToAnnulus | 设置环形扫描线条 Bitmap | +| getLineColorToAnnulus | 获取环形线条动画颜色 ( 着色 ) | +| setLineColorToAnnulus | 设置环形线条动画 ( 着色 ) | +| getLineOffsetSpeedToAnnulus | 获取环形扫描线条速度 | +| setLineOffsetSpeedToAnnulus | 设置环形扫描线条速度 | +| getAnnulusDraws | 获取环形对应的环是否绘制 | +| setAnnulusDraws | 设置环形对应的环是否绘制 | +| getAnnulusColors | 获取环形对应的环绘制颜色 | +| setAnnulusColors | 设置环形对应的环绘制颜色 | +| getAnnulusLengths | 获取环形对应的环绘制长度 | +| setAnnulusLengths | 设置环形对应的环绘制长度 | +| getAnnulusWidths | 获取环形对应的环绘制宽度 | +| setAnnulusWidths | 设置环形对应的环绘制宽度 | +| getAnnulusMargins | 获取环形对应的环绘制边距 | +| setAnnulusMargins | 设置环形对应的环绘制边距 | +| startAnim | 启动动画 | +| stopAnim | 停止动画 | +| isAnimRunning | 是否动画运行中 | +| getRadius | getRadius | + + +* **波浪 View ->** [WaveView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/WaveView.java) + +| 方法 | 注释 | +| :- | :- | +| onSizeChanged | onSizeChanged | +| onDraw | onDraw | +| getAmplitudeRatio | 获取波浪垂直振幅比率 | +| setAmplitudeRatio | 设置波浪垂直振幅比率 | +| getWaterLevelRatio | 获取波浪水位比率 | +| setWaterLevelRatio | 设置波浪水位比率 | +| getWaveLengthRatio | 获取波浪波长比率 | +| setWaveLengthRatio | 设置波浪波长比率 | +| getWaveShiftRatio | 获取波浪水平偏移比率 | +| setWaveShiftRatio | 设置波浪水平偏移比率 | +| getBorderWidth | 获取边框宽度 | +| getBorderColor | 获取边框颜色 | +| setBorder | 设置边框宽度、颜色 | +| getBehindWaveColor | 获取波浪背景色 | +| getFrontWaveColor | 获取波浪前景色 | +| setWaveColor | 设置波浪颜色 | +| getShapeType | 获取波浪外形形状 | +| setShapeType | 设置波浪外形形状 | +| isShowWave | 是否进行波浪图形处理 | +| setShowWave | 设置是否进行波浪图形处理 | + + +* **换行 View ->** [WrapView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/WrapView.java) + +| 方法 | 注释 | +| :- | :- | +| onMeasure | onMeasure | +| onLayout | onLayout | +| refreshDraw | 刷新绘制 ( 更新配置信息后, 必须调用 ) | +| getRowLine | 获取 View 换行行数 | +| getMaxLine | 获取最大行数 | +| setMaxLine | 设置最大行数 | +| getRowTopMargin | 获取每一行向上的边距 ( 行间隔 ) | +| setRowTopMargin | 设置每一行向上的边距 ( 行间隔 ) | +| getViewLeftMargin | 获取每个 View 之间的 Left 边距 | +| setViewLeftMargin | 设置每个 View 之间的 Left 边距 | +| setRowViewMargin | 设置 Row View 边距 | + + +## **`dev.widget.ui.round`** + + +* **圆角 Drawable ->** [RoundDrawable.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundDrawable.java) + +| 方法 | 注释 | +| :- | :- | +| onStateChange | onStateChange | +| isStateful | isStateful | +| onBoundsChange | onBoundsChange | +| getStrokeWidth | 获取描边粗细 | +| setStrokeColors | 设置描边粗细和颜色 | +| setStrokeData | 设置描边粗细和颜色 | +| setBgData | 设置按钮的背景色 ( 只支持纯色, 不支持 Bitmap 或 Drawable ) | +| setRadiusAdjustBounds | 设置圆角大小是否自适应为 View 的高度的一半 | +| fromAttributeSet | 通过 AttributeSet 构建 RoundDrawable | +| setBackgroundKeepingPadding | 设置背景 | + + +* **圆角图片 ->** [RoundImageView.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundImageView.java) + +| 方法 | 注释 | +| :- | :- | +| setScaleType | setScaleType | +| setAdjustViewBounds | setAdjustViewBounds | +| onDraw | onDraw | +| onSizeChanged | onSizeChanged | +| setPadding | setPadding | +| setPaddingRelative | setPaddingRelative | +| setImageBitmap | setImageBitmap | +| setImageDrawable | setImageDrawable | +| setImageResource | setImageResource | +| setImageURI | setImageURI | +| getImageAlpha | getImageAlpha | +| setImageAlpha | setImageAlpha | +| getColorFilter | getColorFilter | +| setColorFilter | setColorFilter | +| onTouchEvent | onTouchEvent | +| getBorderWidth | 获取边框宽度 | +| setBorderWidth | 设置边框宽度 | +| getBorderColor | 获取边框颜色 | +| setBorderColor | 设置边框颜色 | +| getCircleBackgroundColor | 获取圆圈背景颜色 | +| setCircleBackgroundColor | 设置圆圈背景颜色 | +| isBorderOverlay | 是否叠加边框 | +| setBorderOverlay | 设置是否叠加边框 | +| isDisableCircularTransformation | 是否开启圆圈处理 | +| setDisableCircularTransformation | 设置是否开启圆圈处理 | + + +## **`dev.widget.utils`** + + +* **RadiusLayout 圆角属性封装处理类 ->** [RadiusAttrs.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/utils/RadiusAttrs.java) + +| 方法 | 注释 | +| :- | :- | +| onSizeChanged | View 宽高改变时调用 | +| getPath | 获取绘制路径 | +| setRadius | 设置圆角值 | +| setRadiusLeftTop | 设置左上圆角值 | +| setRadiusLeftBottom | 设置左下圆角值 | +| setRadiusRightTop | 设置右上圆角值 | +| setRadiusRightBottom | 设置右下圆角值 | +| setRadiusLeft | 设置左上、左下圆角值 | +| setRadiusRight | 设置右上、右下圆角值 | +| setRadiusTop | 设置左上、右上圆角值 | +| setRadiusBottom | 设置左下、右下圆角值 | +| getRadius | 获取圆角值 | +| getRadiusLeftTop | 获取左上圆角值 | +| getRadiusLeftBottom | 获取左下圆角值 | +| getRadiusRightTop | 获取右上圆角值 | +| getRadiusRightBottom | 获取右下圆角值 | +| onSaveInstanceState | onSaveInstanceState | +| onRestoreInstanceState | onRestoreInstanceState | + + +* **波浪 View Helper 类 ->** [WaveHelper.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/utils/WaveHelper.java) + +| 方法 | 注释 | +| :- | :- | +| get | 获取 WaveHelper | +| start | 启动动画 | +| cancel | 关闭动画 | +| getAmplitudeRatio | 获取波浪垂直振幅比率 | +| setAmplitudeRatio | 设置波浪垂直振幅比率 | +| getWaterLevelRatio | 获取波浪水位比率 | +| setWaterLevelRatio | 设置波浪水位比率 | +| getWaveLengthRatio | 获取波浪波长比率 | +| setWaveLengthRatio | 设置波浪波长比率 | +| getWaveShiftRatio | 获取波浪水平偏移比率 | +| setWaveShiftRatio | 设置波浪水平偏移比率 | +| getBorderWidth | 获取边框宽度 | +| getBorderColor | 获取边框颜色 | +| setBorder | 设置边框宽度、颜色 | +| getBehindWaveColor | 获取波浪背景色 | +| getFrontWaveColor | 获取波浪前景色 | +| setWaveColor | 设置波浪颜色 | +| getShapeType | 获取波浪外形形状 | +| setShapeType | 设置波浪外形形状 | +| isShowWave | 是否进行波浪图形处理 | +| setShowWave | 设置是否进行波浪图形处理 | +| buildPropertyAnimation | 通过属性动画进行设置波浪 View 动画效果 | +| build | build | +| getWaveShiftRatioStart | getWaveShiftRatioStart | +| getWaveShiftRatioEnd | getWaveShiftRatioEnd | +| getWaveShiftRatioMillis | getWaveShiftRatioMillis | +| setWaveShiftRatioMillis | 设置波浪移动方向效果属性值 | +| getAmplitudeRatioStart | getAmplitudeRatioStart | +| getAmplitudeRatioEnd | getAmplitudeRatioEnd | +| getAmplitudeRatioMillis | getAmplitudeRatioMillis | +| setAmplitudeRatioMillis | 设置波浪大小 ( 上下波动 ) 效果属性值 | +| getWaterLevelRatioStart | getWaterLevelRatioStart | +| getWaterLevelRatioEnd | getWaterLevelRatioEnd | +| getWaterLevelRatioMillis | getWaterLevelRatioMillis | +| setWaterLevelRatioMillis | 设置水位高度属性值 | + + +* **DevWidget 属性封装处理类 ->** [WidgetAttrs.java](https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/src/main/java/dev/widget/utils/WidgetAttrs.java) + +| 方法 | 注释 | +| :- | :- | +| getMaxWidth | 获取 View 最大显示宽度 | +| setMaxWidth | 设置 View 最大显示宽度 | +| getMaxHeight | 获取 View 最大显示高度 | +| setMaxHeight | 设置 View 最大显示高度 | +| isSlide | 是否允许滑动 | +| setSlide | 设置是否允许滑动 | +| toggleSlide | 切换滑动控制状态 | \ No newline at end of file diff --git a/lib/DevWidget/build.gradle b/lib/DevWidget/build.gradle new file mode 100644 index 0000000000..41fa0933b8 --- /dev/null +++ b/lib/DevWidget/build.gradle @@ -0,0 +1,38 @@ +apply from: rootProject.file(files.lib_app_gradle) + +android.defaultConfig { + versionCode versions.dev_widget_versionCode + versionName versions.dev_widget_versionName + // DevWidget Module Version + buildConfigField "int", "DevWidget_VersionCode", "${versions.dev_widget_versionCode}" + buildConfigField "String", "DevWidget_Version", "\"${versions.dev_widget_versionName}\"" + // DevApp Module Version + buildConfigField "int", "DevApp_VersionCode", "${versions.dev_app_versionCode}" + buildConfigField "String", "DevApp_Version", "\"${versions.dev_app_versionName}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.androidx.design + api deps.androidx.appcompat + api deps.androidx.constraint_layout + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_app + } else { + // 编译时使用 + api project(':DevApp') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/DevWidget/proguard-rules.pro b/lib/DevWidget/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/DevWidget/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/DevWidget/project.properties b/lib/DevWidget/project.properties new file mode 100644 index 0000000000..6b9f22b88e --- /dev/null +++ b/lib/DevWidget/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevWidgetX +project.groupId=io.github.afkt +project.artifactId=DevWidgetX +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevWidgetX \ No newline at end of file diff --git a/lib/DevWidget/src/main/AndroidManifest.xml b/lib/DevWidget/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..2cdd475c1c --- /dev/null +++ b/lib/DevWidget/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/DevWidget.java b/lib/DevWidget/src/main/java/dev/widget/DevWidget.java new file mode 100644 index 0000000000..0529f16a72 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/DevWidget.java @@ -0,0 +1,78 @@ +package dev.widget; + +/** + * detail: DevWidget + * @author Ttt + *
+ *     GitHub
+ *     @see 
+ *     DevApp Api
+ *     @see 
+ *     DevAssist Api
+ *     @see 
+ *     DevBase README
+ *     @see 
+ *     DevBaseMVVM README
+ *     @see 
+ *     DevMVVM README
+ *     @see 
+ *     DevEngine README
+ *     @see 
+ *     DevHttpCapture Api
+ *     @see 
+ *     DevHttpManager Api
+ *     @see 
+ *     DevRetrofit Api
+ *     @see 
+ *     DevWidget Api
+ *     @see 
+ *     DevEnvironment Api
+ *     @see 
+ *     DevJava Api
+ *     @see 
+ * 
+ */ +public final class DevWidget { + + private DevWidget() { + } + + // 日志 TAG + public static final String TAG = DevWidget.class.getSimpleName(); + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevWidget 版本号 + * @return DevWidget versionCode + */ + public static int getDevWidgetVersionCode() { + return BuildConfig.DevWidget_VersionCode; + } + + /** + * 获取 DevWidget 版本 + * @return DevWidget versionName + */ + public static String getDevWidgetVersion() { + return BuildConfig.DevWidget_Version; + } + + /** + * 获取 DevApp 版本号 + * @return DevApp versionCode + */ + public static int getDevAppVersionCode() { + return BuildConfig.DevApp_VersionCode; + } + + /** + * 获取 DevApp 版本 + * @return DevApp versionName + */ + public static String getDevAppVersion() { + return BuildConfig.DevApp_Version; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightGridView.java b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightGridView.java new file mode 100644 index 0000000000..e40b26b5e1 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightGridView.java @@ -0,0 +1,55 @@ +package dev.widget.adjust; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.GridView; + +/** + * detail: 自动调节高度 GridView + * @author Ttt + * @deprecated 推荐使用 NestedScrollView + RecyclerView 实现 + */ +@Deprecated +public class AdjustHeightGridView + extends GridView { + + public AdjustHeightGridView(Context context) { + super(context); + } + + public AdjustHeightGridView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public AdjustHeightGridView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AdjustHeightGridView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightListView.java b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightListView.java new file mode 100644 index 0000000000..732648f3a5 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightListView.java @@ -0,0 +1,55 @@ +package dev.widget.adjust; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.ListView; + +/** + * detail: 自动调节高度 ListView + * @author Ttt + * @deprecated 推荐使用 NestedScrollView + RecyclerView 实现 + */ +@Deprecated +public class AdjustHeightListView + extends ListView { + + public AdjustHeightListView(Context context) { + super(context); + } + + public AdjustHeightListView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public AdjustHeightListView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AdjustHeightListView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightRecyclerView.java b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightRecyclerView.java new file mode 100644 index 0000000000..25c3318b7a --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightRecyclerView.java @@ -0,0 +1,45 @@ +package dev.widget.adjust; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.recyclerview.widget.RecyclerView; + +/** + * detail: 自动调节高度 RecyclerView + * @author Ttt + * @deprecated 推荐使用 NestedScrollView + RecyclerView 实现 + */ +@Deprecated +public class AdjustHeightRecyclerView + extends RecyclerView { + + public AdjustHeightRecyclerView(Context context) { + super(context); + } + + public AdjustHeightRecyclerView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public AdjustHeightRecyclerView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightWebView.java b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightWebView.java new file mode 100644 index 0000000000..e74c20278f --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/adjust/AdjustHeightWebView.java @@ -0,0 +1,55 @@ +package dev.widget.adjust; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.webkit.WebView; + +/** + * detail: 自动调节高度 WebView + * @author Ttt + * @deprecated 推荐使用 NestedScrollView + WebView 实现 + */ +@Deprecated +public class AdjustHeightWebView + extends WebView { + + public AdjustHeightWebView(Context context) { + super(context); + } + + public AdjustHeightWebView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public AdjustHeightWebView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public AdjustHeightWebView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/assist/ViewAssist.java b/lib/DevWidget/src/main/java/dev/widget/assist/ViewAssist.java new file mode 100644 index 0000000000..1a1c043353 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/assist/ViewAssist.java @@ -0,0 +1,328 @@ +package dev.widget.assist; + +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.HashMap; +import java.util.Map; + +import dev.utils.LogPrintUtils; + +/** + * detail: View 填充辅助类 + * @author Ttt + *
+ *     作用于 加载中、加载失败、无数据等视图或自定义视图 填充包裹 View
+ *     

+ * 使用方式: + * // 首先创建 ViewAssist + * ViewAssist assist = ViewAssist.wrap(ViewGroup); + * assist.register(100, new Adapter()); // 注册 Type - Adapter + * assist.showType(100); // 显示对应 Type Adapter View + *
+ */ +public final class ViewAssist { + + private ViewAssist() { + } + + // 标记 TAG + private Object mTag; + // 包裹 View + private ViewGroup mWrapper; + + /** + * 传入包裹 View + * @param wrapper 包裹 View + * @return {@link ViewAssist} + */ + public static ViewAssist wrap(final ViewGroup wrapper) { + return wrap(wrapper, null); + } + + /** + * 传入包裹 View + * @param wrapper 包裹 View + * @param tag TAG + * @return {@link ViewAssist} + */ + public static ViewAssist wrap( + final ViewGroup wrapper, + final Object tag + ) { + if (wrapper == null) return null; + ViewAssist assist = new ViewAssist(); + assist.mWrapper = wrapper; + assist.mTag = tag; + return assist; + } + + // = + + private Object mData; + private final Map mMapDatas = new HashMap<>(); + private final Map mMapAdapters = new HashMap<>(); + private final SparseArray mTypeViews = new SparseArray<>(3); + private int mCurrentType = -1; + private View mCurrentView; + private Listener mListener; + + public interface Adapter { + + View onCreateView( + ViewAssist assist, + LayoutInflater inflater + ); + + void onBindView( + ViewAssist assist, + View view, + int type + ); + } + + public interface Listener { + + void onRemove( + ViewAssist assist, + int type, + boolean removeView + ); + + void onNotFound( + ViewAssist assist, + int type + ); + + void onChange( + ViewAssist assist, + int type, + int oldType + ); + } + + // ============= + // = 对外公开方法 = + // ============= + + // Type: 操作中、失败、成功、空数据 + public static final int TYPE_ING = 1; + public static final int TYPE_FAILED = 2; + public static final int TYPE_SUCCESS = 3; + public static final int TYPE_EMPTY_DATA = 4; + + public void showIng() { + showType(TYPE_ING); + } + + public void showFailed() { + showType(TYPE_FAILED); + } + + public void showSuccess() { + showType(TYPE_SUCCESS); + } + + public void showEmptyData() { + showType(TYPE_EMPTY_DATA); + } + + /** + * 显示 Type Adapter View + * @param type Type + */ + public void showType(final int type) { + if (mWrapper == null) return; + Adapter adapter = mMapAdapters.get(type); + if (adapter != null) { + View view = getView(type); + if (view != null) { + visible(); + int oldType = mCurrentType; + mCurrentType = type; + mCurrentView = view; + mTypeViews.put(type, view); + // 添加 View + if (mWrapper.indexOfChild(view) == -1) { + mWrapper.removeAllViews(); + mWrapper.addView(view); + } + adapter.onBindView(this, view, type); + + if (mListener != null) { + mListener.onChange(this, type, oldType); + } + return; + } + } + //gone(); + mCurrentType = type; + mCurrentView = null; + mWrapper.removeAllViews(); + if (mListener != null) { + mListener.onNotFound(this, type); + } + } + + public ViewAssist notifyDataSetChanged() { + showType(mCurrentType); + return this; + } + + // ============ + // = function = + // ============ + + public ViewAssist gone() { + if (mWrapper != null) mWrapper.setVisibility(View.GONE); + return this; + } + + public ViewAssist visible() { + if (mWrapper != null) mWrapper.setVisibility(View.VISIBLE); + return this; + } + + /** + * 注册 type + * @param type Type + * @param adapter {@link Adapter} + * @return {@link ViewAssist} + */ + public ViewAssist register( + final int type, + final Adapter adapter + ) { + if (adapter == null) return this; + mMapAdapters.put(type, adapter); + return this; + } + + /** + * 取消注册 type + * @param type Type + * @return {@link ViewAssist} + */ + public ViewAssist unregister(final int type) { + return unregister(type, true); + } + + /** + * 取消注册 type + * @param type Type + * @param remove 判断解绑的 type 正在显示是否删除 + * @return {@link ViewAssist} + */ + public ViewAssist unregister( + final int type, + final boolean remove + ) { + mTypeViews.remove(type); + mMapAdapters.remove(type); + boolean removeView = false; + if (remove && type == mCurrentType) { + if (mWrapper != null) mWrapper.removeAllViews(); + mCurrentType = -1; + mCurrentView = null; + //gone(); + removeView = true; + } + if (mListener != null) { + mListener.onRemove(this, type, removeView); + } + return this; + } + + /** + * 重置处理 + * @return {@link ViewAssist} + */ + public ViewAssist reset() { + //gone(); + if (mWrapper != null) mWrapper.removeAllViews(); + mData = null; + mMapDatas.clear(); + mMapAdapters.clear(); + mTypeViews.clear(); + mCurrentType = -1; + mCurrentView = null; + return this; + } + + // =========== + // = get/set = + // =========== + + public ViewGroup getWrapper() { + return mWrapper; + } + + public Object getTag() { + return mTag; + } + + public ViewAssist setTag(final Object tag) { + this.mTag = tag; + return this; + } + + public T getData() { + try { + return (T) mData; + } catch (Exception e) { + LogPrintUtils.e(e); + } + return null; + } + + public ViewAssist setData(final Object data) { + this.mData = data; + return this; + } + + public T getData(final String key) { + try { + return (T) mMapDatas.get(key); + } catch (Exception e) { + LogPrintUtils.e(e); + } + return null; + } + + public ViewAssist setData( + final String key, + final Object data + ) { + mMapDatas.put(key, data); + return this; + } + + public T getAdapter(final int type) { + return (T) mMapAdapters.get(type); + } + + public View getView(final int type) { + View view = mTypeViews.get(type); + if (view != null) return view; + Adapter adapter = mMapAdapters.get(type); + if (mWrapper == null || adapter == null) return null; + view = adapter.onCreateView(this, LayoutInflater.from(mWrapper.getContext())); + mTypeViews.put(type, view); + return view; + } + + public int getCurrentType() { + return mCurrentType; + } + + public View getCurrentView() { + return mCurrentView; + } + + public ViewAssist setListener(final Listener listener) { + this.mListener = listener; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomGallery.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomGallery.java new file mode 100644 index 0000000000..604e13c788 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomGallery.java @@ -0,0 +1,199 @@ +package dev.widget.custom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.Gallery; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 Gallery 滑动控制 + * @author Ttt + * @deprecated 推荐使用 {@link CustomRecyclerView} + LinearSnapHelper 实现 + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +@Deprecated +public class CustomGallery + extends Gallery { + + private WidgetAttrs mWidgetAttrs; + + public CustomGallery(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomGallery( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomGallery( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CustomGallery( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + public boolean onFling( + MotionEvent e1, + MotionEvent e2, + float velocityX, + float velocityY + ) { + int keyCode; + if (isScrollingLeft(e1, e2)) { + keyCode = KeyEvent.KEYCODE_DPAD_LEFT; + } else { + keyCode = KeyEvent.KEYCODE_DPAD_RIGHT; + } + onKeyDown(keyCode, null); + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + /** + * 判断是否向左滑动 + * @param e1 {@link MotionEvent} + * @param e2 {@link MotionEvent} + * @return {@code true} yes, {@code false} no + */ + private boolean isScrollingLeft( + final MotionEvent e1, + final MotionEvent e2 + ) { + return e2.getX() > e1.getX(); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomGallery} + */ + public CustomGallery setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomGallery} + */ + public CustomGallery setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomGallery} + */ + public CustomGallery setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomGallery} + */ + public CustomGallery toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomHorizontalScrollView.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomHorizontalScrollView.java new file mode 100644 index 0000000000..2a790d3cd2 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomHorizontalScrollView.java @@ -0,0 +1,218 @@ +package dev.widget.custom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.HorizontalScrollView; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 HorizontalScrollView 滑动监听、滑动控制 + * @author Ttt + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +public class CustomHorizontalScrollView + extends HorizontalScrollView { + + private WidgetAttrs mWidgetAttrs; + // 滑动监听回调 + private ScrollCallback mCallback = null; + + public CustomHorizontalScrollView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomHorizontalScrollView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomHorizontalScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CustomHorizontalScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + protected void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ) { + super.onScrollChanged(left, top, oldLeft, oldTop); + if (mCallback != null) { + mCallback.onScrollChanged(left, top, oldLeft, oldTop); + } + } + + @Override + protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + return 0; // 解决禁止 ScrollView 内的控件改变之后自动滚动 + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomHorizontalScrollView} + */ + public CustomHorizontalScrollView setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomHorizontalScrollView} + */ + public CustomHorizontalScrollView setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomHorizontalScrollView} + */ + public CustomHorizontalScrollView setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomHorizontalScrollView} + */ + public CustomHorizontalScrollView toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + /** + * 设置滑动监听回调 + * @param callback {@link ScrollCallback} + * @return {@link CustomHorizontalScrollView} + */ + public CustomHorizontalScrollView setScrollCallback(final ScrollCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 滑动监听回调 + * @author Ttt + */ + public interface ScrollCallback { + + /** + * 滑动改变通知 + * @param left 距离左边距离 + * @param top 距离顶部距离 + * @param oldLeft 旧的 ( 之前 ) 距离左边距离 + * @param oldTop 旧的 ( 之前 ) 距离顶部距离 + */ + void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomNestedScrollView.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomNestedScrollView.java new file mode 100644 index 0000000000..0a9d0c0089 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomNestedScrollView.java @@ -0,0 +1,209 @@ +package dev.widget.custom; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.core.widget.NestedScrollView; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 NestedScrollView 滑动监听、滑动控制 + * @author Ttt + *
+ *     ScrollView 默认位置不是最顶部解决方案
+ *     @see 
+ *     

+ * app:dev_slide="" + * app:dev_maxWidth="" + * app:dev_maxHeight="" + *
+ */ +public class CustomNestedScrollView + extends NestedScrollView { + + private WidgetAttrs mWidgetAttrs; + // 滑动监听回调 + private ScrollCallback mCallback = null; + + public CustomNestedScrollView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomNestedScrollView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomNestedScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + protected void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ) { + super.onScrollChanged(left, top, oldLeft, oldTop); + if (mCallback != null) { + mCallback.onScrollChanged(left, top, oldLeft, oldTop); + } + } + + @Override + protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + return 0; // 解决禁止 ScrollView 内的控件改变之后自动滚动 + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomNestedScrollView} + */ + public CustomNestedScrollView setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomNestedScrollView} + */ + public CustomNestedScrollView setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomNestedScrollView} + */ + public CustomNestedScrollView setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomNestedScrollView} + */ + public CustomNestedScrollView toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + /** + * 设置滑动监听回调 + * @param callback {@link ScrollCallback} + * @return {@link CustomNestedScrollView} + */ + public CustomNestedScrollView setScrollCallback(final ScrollCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 滑动监听回调 + * @author Ttt + */ + public interface ScrollCallback { + + /** + * 滑动改变通知 + * @param left 距离左边距离 + * @param top 距离顶部距离 + * @param oldLeft 旧的 ( 之前 ) 距离左边距离 + * @param oldTop 旧的 ( 之前 ) 距离顶部距离 + */ + void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomRecyclerView.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomRecyclerView.java new file mode 100644 index 0000000000..6713e13aac --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomRecyclerView.java @@ -0,0 +1,245 @@ +package dev.widget.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.recyclerview.widget.RecyclerView; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 RecyclerView 滑动监听、滑动控制 + * @author Ttt + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +public class CustomRecyclerView + extends RecyclerView { + + private WidgetAttrs mWidgetAttrs; + // 滑动监听回调 + private ScrollCallback mCallback = null; + // 距离左边距离 + private int mScrollX = 0; + // 距离顶部距离 + private int mScrollY = 0; + + public CustomRecyclerView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomRecyclerView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomRecyclerView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + public void onScrolled( + int dx, + int dy + ) { + super.onScrolled(dx, dy); + mScrollX += dx; + mScrollY += dy; + // 防止出现负数 + mScrollX = Math.abs(mScrollX); + mScrollY = Math.abs(mScrollY); + // 触发回调 + if (mCallback != null) { + mCallback.onScrollChanged(this, dx, dy, mScrollX, mScrollY); + } + } + + @Override + public void onScrollStateChanged(int state) { + super.onScrollStateChanged(state); + // 触发回调 + if (mCallback != null) { + mCallback.onScrollStateChanged(this, state); + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomRecyclerView} + */ + public CustomRecyclerView setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomRecyclerView} + */ + public CustomRecyclerView setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomRecyclerView} + */ + public CustomRecyclerView setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomRecyclerView} + */ + public CustomRecyclerView toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + /** + * 设置滑动监听回调 + * @param callback {@link ScrollCallback} + * @return {@link CustomRecyclerView} + */ + public CustomRecyclerView setScrollCallback(final ScrollCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 滑动监听回调 + * @author Ttt + */ + public interface ScrollCallback { + + /** + * 滑动状态通知 + * @param recyclerView {@link RecyclerView} + * @param state 滑动状态 + */ + void onScrollStateChanged( + RecyclerView recyclerView, + int state + ); + + /** + * 滑动改变通知 + * @param recyclerView {@link RecyclerView} + * @param dx 左边滑动距离 + * @param dy 顶部滑动距离 + * @param scrollX 距离左边距离 + * @param scrollY 距离顶部距离 + */ + void onScrollChanged( + RecyclerView recyclerView, + int dx, + int dy, + int scrollX, + int scrollY + ); + } + + /** + * 获取距离左边距离 + * @return 距离左边距离 + */ + public int getCustomScrollX() { + return mScrollX; + } + + /** + * 获取距离顶部距离 + * @return 距离顶部距离 + */ + public int getCustomScrollY() { + return mScrollY; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomScrollView.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomScrollView.java new file mode 100644 index 0000000000..0e65e3f8b5 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomScrollView.java @@ -0,0 +1,220 @@ +package dev.widget.custom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 ScrollView 滑动监听、滑动控制 + * @author Ttt + * @deprecated 推荐使用 {@link CustomNestedScrollView} + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +@Deprecated +public class CustomScrollView + extends ScrollView { + + private WidgetAttrs mWidgetAttrs; + // 滑动监听回调 + private ScrollCallback mCallback = null; + + public CustomScrollView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomScrollView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CustomScrollView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + protected void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ) { + super.onScrollChanged(left, top, oldLeft, oldTop); + if (mCallback != null) { + mCallback.onScrollChanged(left, top, oldLeft, oldTop); + } + } + + @Override + protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + return 0; // 解决禁止 ScrollView 内的控件改变之后自动滚动 + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomScrollView} + */ + public CustomScrollView setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomScrollView} + */ + public CustomScrollView setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomScrollView} + */ + public CustomScrollView setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomScrollView} + */ + public CustomScrollView toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + /** + * 设置滑动监听回调 + * @param callback {@link ScrollCallback} + * @return {@link CustomScrollView} + */ + public CustomScrollView setScrollCallback(final ScrollCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 滑动监听回调 + * @author Ttt + */ + public interface ScrollCallback { + + /** + * 滑动改变通知 + * @param left 距离左边距离 + * @param top 距离顶部距离 + * @param oldLeft 旧的 ( 之前 ) 距离左边距离 + * @param oldTop 旧的 ( 之前 ) 距离顶部距离 + */ + void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomViewPager.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomViewPager.java new file mode 100644 index 0000000000..505e858da2 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomViewPager.java @@ -0,0 +1,234 @@ +package dev.widget.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.viewpager.widget.ViewPager; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 ViewPager 滑动监听、滑动控制 + * @author Ttt + * @deprecated 推荐使用 ViewPager2 + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +@Deprecated +public class CustomViewPager + extends ViewPager { + + private WidgetAttrs mWidgetAttrs; + + public CustomViewPager(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomViewPager( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomViewPager} + */ + public CustomViewPager setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomViewPager} + */ + public CustomViewPager setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomViewPager} + */ + public CustomViewPager setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomViewPager} + */ + public CustomViewPager toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + // ========== + // = 滑动方向 = + // ========== + + /** + * detail: 滑动方向监听 + * @author Ttt + *
+     *     viewpager.setOnPageChangeListener(new CustomViewPager.OnDirectionListener() {
+     *             @Override
+     *             public void onSlideDirection(boolean left, boolean right) {
+     *             }
+     *             @Override
+     *             public void onPageSelected(int index) {
+     *             }
+     *         });
+     * 
+ */ + public static abstract class OnDirectionListener + implements OnPageChangeListener { + + // 最后滑动的位置 + private int mLastValue = -1; + // 是否滑动中 + private boolean mScrolling; + // 是否滑向左边、右边 + private boolean mLeft, mRight; + // 是否向左滑动 + protected boolean mLeftScroll = false; + + @Override + public void onPageScrolled( + int pos, + float arg1, + int arg2 + ) { + // pos 当前页面及你点击滑动的页面 + // arg1 当前页面偏移的百分比 + // arg2 当前页面偏移的像素位置 + + if (mScrolling) { + if (mLastValue > arg2) { + mRight = true; + mLeft = false; + mLeftScroll = false; + } else if (mLastValue < arg2) { + mRight = false; + mLeft = true; + mLeftScroll = true; + } else { + mRight = mLeft = false; + } + // 触发滑动方向回调 + onSlideDirection(mLeft, mRight); + } + mLastValue = arg2; + } + + @Override + public void onPageScrollStateChanged(int state) { + // 有三种状态 + // state == 0 表示什么都没做 + // state == 1 表示正在滑动 + // state == 2 表示滑动完毕了 + + // 判断是否滑动中 + mScrolling = (state == 1); + + if (state == 2) { + // 触发滑动方向回调 + onSlideDirection(mLeft, mRight); + // 重置方向 + mRight = mLeft = false; + } + } + + /** + * 滑动方向 + * @param left 是否向左滑动 + * @param right 是否向右滑动 + */ + public abstract void onSlideDirection( + boolean left, + boolean right + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/custom/CustomWebView.java b/lib/DevWidget/src/main/java/dev/widget/custom/CustomWebView.java new file mode 100644 index 0000000000..7e86c73bea --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/custom/CustomWebView.java @@ -0,0 +1,212 @@ +package dev.widget.custom; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.webkit.WebView; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 WebView 滑动监听、滑动控制 + * @author Ttt + *
+ *     app:dev_slide=""
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +public class CustomWebView + extends WebView { + + private WidgetAttrs mWidgetAttrs; + // 滑动监听回调 + private ScrollCallback mCallback = null; + + public CustomWebView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CustomWebView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CustomWebView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CustomWebView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + @Override + protected void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ) { + super.onScrollChanged(left, top, oldLeft, oldTop); + if (mCallback != null) { + mCallback.onScrollChanged(left, top, oldLeft, oldTop); + } + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent arg0) { + if (!mWidgetAttrs.isSlide()) return false; + return super.onInterceptTouchEvent(arg0); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link CustomWebView} + */ + public CustomWebView setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link CustomWebView} + */ + public CustomWebView setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mWidgetAttrs.isSlide(); + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link CustomWebView} + */ + public CustomWebView setSlide(final boolean isSlide) { + mWidgetAttrs.setSlide(isSlide); + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link CustomWebView} + */ + public CustomWebView toggleSlide() { + mWidgetAttrs.toggleSlide(); + return this; + } + + /** + * 设置滑动监听回调 + * @param callback {@link ScrollCallback} + * @return {@link CustomWebView} + */ + public CustomWebView setScrollCallback(final ScrollCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 滑动监听回调 + * @author Ttt + */ + public interface ScrollCallback { + + /** + * 滑动改变通知 + * @param left 距离左边距离 + * @param top 距离顶部距离 + * @param oldLeft 旧的 ( 之前 ) 距离左边距离 + * @param oldTop 旧的 ( 之前 ) 距离顶部距离 + */ + void onScrollChanged( + int left, + int top, + int oldLeft, + int oldTop + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorGridItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorGridItemDecoration.java new file mode 100644 index 0000000000..279f86976c --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorGridItemDecoration.java @@ -0,0 +1,84 @@ +package dev.widget.decoration; + +import android.graphics.Color; + +import androidx.annotation.ColorInt; + +/** + * detail: 基础 RecyclerView Grid 分割线处理 + * @author Ttt + *
+ *     纯色值绘制 行、列 分割线
+ * 
+ */ +public class BaseColorGridItemDecoration + extends BaseColorItemDecoration { + + // 是否 Grid Column ItemDecoration + private final boolean mColumnItemDecoration; + // Span 总数 ( Grid 列 ) + protected int mSpanCount; + + // ========== + // = 构造函数 = + // ========== + + public BaseColorGridItemDecoration( + final boolean columnItemDecoration, + final int spanCount, + final boolean vertical, + final float height + ) { + this(columnItemDecoration, spanCount, vertical, height, Color.TRANSPARENT); + } + + public BaseColorGridItemDecoration( + final boolean columnItemDecoration, + final int spanCount, + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(vertical, height, color); + this.mColumnItemDecoration = columnItemDecoration; + this.mSpanCount = spanCount; + } + + // ========== + // = 对外公开 = + // ========== + + /** + * 是否 Grid Row ItemDecoration + * @return {@code true} yes, {@code false} no + */ + public boolean isRowItemDecoration() { + return !mColumnItemDecoration; + } + + /** + * 是否 Grid Column ItemDecoration + * @return {@code true} yes, {@code false} no + */ + public boolean isColumnItemDecoration() { + return mColumnItemDecoration; + } + + /** + * 获取 Span 总数 ( Grid 列 ) + * @return Span 总数 ( Grid 列 ) + */ + public int getSpanCount() { + return mSpanCount; + } + + /** + * 设置 Span 总数 ( Grid 列 ) + * @param spanCount Span 总数 ( Grid 列 ) + * @return {@link BaseColorGridItemDecoration} + */ + public BaseColorGridItemDecoration setSpanCount(final int spanCount) { + this.mSpanCount = spanCount; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorItemDecoration.java new file mode 100644 index 0000000000..bf56e2d5ef --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/BaseColorItemDecoration.java @@ -0,0 +1,219 @@ +package dev.widget.decoration; + +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; + +import androidx.annotation.ColorInt; +import androidx.recyclerview.widget.RecyclerView; + +/** + * detail: RecyclerView 分割线绘制基类 + * @author Ttt + *
+ *     纯色值绘制分割线
+ * 
+ */ +public class BaseColorItemDecoration + extends RecyclerView.ItemDecoration { + + // 单条数据是否绘制分割线 + protected boolean mSingleDraw = true; + + // ========== + // = 通用属性 = + // ========== + + // 分割线边界测算 + protected final Rect mBounds = new Rect(); + // 分割线画笔 + protected final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + // 分割线绘制方向 ( RecyclerView Orientation ) + private int mOrientation = RecyclerView.VERTICAL; + // 分割线高度 ( Horizontal 为宽度 ) + protected float mHeight; + // 分割线距左边距 ( Horizontal 为上边距 ) + protected float mLeft = 0.0F; + // 分割线距右边距 ( Horizontal 为下边距 ) + protected float mRight = 0.0F; + // 分割线偏差值 ( 用于解决多个 ItemDecoration 叠加覆盖问题 ) + protected float mOffset = 0.0F; + + // ========== + // = 构造函数 = + // ========== + + public BaseColorItemDecoration( + final boolean vertical, + final float height + ) { + this(vertical, height, Color.TRANSPARENT); + } + + public BaseColorItemDecoration( + final boolean vertical, + final float height, + @ColorInt final int color + ) { + if (vertical) { + setVertical(); + } else { + setHorizontal(); + } + this.mHeight = height; + this.mPaint.setColor(color); + } + + // ========== + // = 对外公开 = + // ========== + + /** + * 获取单条数据是否绘制分割线 + * @return {@code true} yes, {@code false} no + */ + public boolean isSingleDraw() { + return mSingleDraw; + } + + /** + * 设置单条数据是否绘制分割线 + * @param singleDraw {@code true} yes, {@code false} no + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setSingleDraw(final boolean singleDraw) { + this.mSingleDraw = singleDraw; + return this; + } + + // = + + /** + * 获取分割线画笔 + * @return 分割线画笔 + */ + public Paint getPaint() { + return mPaint; + } + + /** + * 判断分割线绘制方向是否为 VERTICAL + * @return {@code true} yes, {@code false} no + */ + public boolean isVertical() { + return this.mOrientation == RecyclerView.VERTICAL; + } + + /** + * 判断分割线绘制方向是否为 HORIZONTAL + * @return {@code true} yes, {@code false} no + */ + public boolean isHorizontal() { + return this.mOrientation == RecyclerView.HORIZONTAL; + } + + /** + * 设置分割线绘制方向为 VERTICAL + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setVertical() { + this.mOrientation = RecyclerView.VERTICAL; + return this; + } + + /** + * 设置分割线绘制方向为 HORIZONTAL + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setHorizontal() { + this.mOrientation = RecyclerView.HORIZONTAL; + return this; + } + + /** + * 获取分割线高度 + * @return 分割线高度 + */ + public float getHeight() { + return mHeight; + } + + /** + * 设置分割线高度 + * @param height 分割线高度 + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setHeight(final float height) { + this.mHeight = height; + return this; + } + + /** + * 获取分割线距左边距 + * @return 分割线距左边距 + */ + public float getLeft() { + return mLeft; + } + + /** + * 设置分割线距左边距 + * @param left 分割线距左边距 + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setLeft(final float left) { + this.mLeft = left; + return this; + } + + /** + * 获取分割线距右边距 + * @return 分割线距右边距 + */ + public float getRight() { + return mRight; + } + + /** + * 设置分割线距右边距 + * @param right 分割线距右边距 + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setRight(final float right) { + this.mRight = right; + return this; + } + + /** + * 设置分割线距左、右边距 + * @param left 分割线距左边距 + * @param right 分割线距右边距 + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setLeftRight( + final float left, + final float right + ) { + this.mLeft = left; + this.mRight = right; + return this; + } + + /** + * 获取分割线偏差值 + * @return 分割线偏差值 + */ + public float getOffset() { + return mOffset; + } + + /** + * 设置分割线偏差值 + * @param offset 分割线偏差值 + * @return {@link BaseColorItemDecoration} + */ + public BaseColorItemDecoration setOffset(final float offset) { + this.mOffset = offset; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridColumnColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridColumnColorItemDecoration.java new file mode 100644 index 0000000000..1c32eb876b --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridColumnColorItemDecoration.java @@ -0,0 +1,155 @@ +package dev.widget.decoration.grid; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import dev.widget.decoration.BaseColorGridItemDecoration; + +/** + * detail: RecyclerView Grid 列分割线处理 ( 每一列数据 ) + * @author Ttt + *
+ *     效果:
+ *     每一列数据底部绘制分割线 ( 最后一列不绘制 )
+ * 
+ */ +public class GridColumnColorItemDecoration + extends BaseColorGridItemDecoration { + + public GridColumnColorItemDecoration( + final int spanCount, + final boolean vertical, + final float height + ) { + super(true, spanCount, vertical, height); + } + + public GridColumnColorItemDecoration( + final int spanCount, + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(true, spanCount, vertical, height, color); + } + + // ========== + // = 处理方法 = + // ========== + + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalItemOffsets(outRect, view, parent, state); + } else if (isHorizontal()) { + horizontalItemOffsets(outRect, view, parent, state); + } + } + + @Override + public void onDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalDraw(canvas, parent, state); + } else if (isHorizontal()) { + horizontalDraw(canvas, parent, state); + } + } + + // ============ + // = vertical = + // ============ + + private void verticalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + float value = mHeight / mSpanCount; + int index = parent.getChildAdapterPosition(view); + int spanIndex = index % mSpanCount; + if (spanIndex == 0) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set((int) (value * spanIndex), 0, 0, 0); + } + } + + private void verticalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + int index = parent.getChildAdapterPosition(child); + int spanIndex = index % mSpanCount; + if (spanIndex != 0) { + canvas.drawRect( + child.getLeft() - mHeight - mOffset, + child.getTop() + mLeft, + child.getLeft() - mOffset, + child.getBottom() - mRight, + mPaint + ); + } + } + } + + // ============== + // = horizontal = + // ============== + + private void horizontalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= mSpanCount) return; + + int index = parent.getChildAdapterPosition(view); + if (index < mSpanCount) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set((int) mHeight, 0, 0, 0); + } + } + + private void horizontalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + int index = parent.getChildAdapterPosition(child); + if (index >= mSpanCount) { + canvas.drawRect( + child.getLeft() - mHeight - mOffset, + child.getTop() + mLeft, + child.getLeft() - mOffset, + child.getBottom() - mRight, + mPaint + ); + } + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridRowColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridRowColorItemDecoration.java new file mode 100644 index 0000000000..b1703b30b7 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/grid/GridRowColorItemDecoration.java @@ -0,0 +1,155 @@ +package dev.widget.decoration.grid; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import dev.widget.decoration.BaseColorGridItemDecoration; + +/** + * detail: RecyclerView Grid 行分割线处理 ( 每一行数据 ) + * @author Ttt + *
+ *     效果:
+ *     每一行数据底部绘制分割线 ( 最后一行不绘制 )
+ * 
+ */ +public class GridRowColorItemDecoration + extends BaseColorGridItemDecoration { + + public GridRowColorItemDecoration( + final int spanCount, + final boolean vertical, + final float height + ) { + super(false, spanCount, vertical, height); + } + + public GridRowColorItemDecoration( + final int spanCount, + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(false, spanCount, vertical, height, color); + } + + // ========== + // = 处理方法 = + // ========== + + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalItemOffsets(outRect, view, parent, state); + } else if (isHorizontal()) { + horizontalItemOffsets(outRect, view, parent, state); + } + } + + @Override + public void onDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalDraw(canvas, parent, state); + } else if (isHorizontal()) { + horizontalDraw(canvas, parent, state); + } + } + + // ============ + // = vertical = + // ============ + + private void verticalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= mSpanCount) return; + + int index = parent.getChildAdapterPosition(view); + if (index < mSpanCount) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set(0, (int) mHeight, 0, 0); + } + } + + private void verticalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + int index = parent.getChildAdapterPosition(child); + if (index >= mSpanCount) { + canvas.drawRect( + child.getLeft() + mLeft, + child.getTop() - mHeight - mOffset, + child.getRight() - mRight, + child.getTop() - mOffset, + mPaint + ); + } + } + } + + // ============== + // = horizontal = + // ============== + + private void horizontalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + float value = mHeight / mSpanCount; + int index = parent.getChildAdapterPosition(view); + int spanIndex = index % mSpanCount; + if (spanIndex == 0) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set(0, (int) (value * spanIndex), 0, 0); + } + } + + private void horizontalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + int index = parent.getChildAdapterPosition(child); + int spanIndex = index % mSpanCount; + if (spanIndex != 0) { + canvas.drawRect( + child.getLeft() + mLeft, + child.getTop() - mHeight - mOffset, + child.getRight() - mRight, + child.getTop() - mOffset, + mPaint + ); + } + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/linear/FirstLinearColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/FirstLinearColorItemDecoration.java new file mode 100644 index 0000000000..9c69976597 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/FirstLinearColorItemDecoration.java @@ -0,0 +1,157 @@ +package dev.widget.decoration.linear; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import dev.widget.decoration.BaseColorItemDecoration; + +/** + * detail: RecyclerView Linear 分割线处理 ( 第一条数据 ) + * @author Ttt + *
+ *     效果:
+ *     第一条数据顶部添加一条分割线
+ * 
+ */ +public class FirstLinearColorItemDecoration + extends BaseColorItemDecoration { + + public FirstLinearColorItemDecoration( + final boolean vertical, + final float height + ) { + super(vertical, height); + } + + public FirstLinearColorItemDecoration( + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(vertical, height, color); + } + + // ========== + // = 处理方法 = + // ========== + + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalItemOffsets(outRect, view, parent, state); + } else if (isHorizontal()) { + horizontalItemOffsets(outRect, view, parent, state); + } + } + + @Override + public void onDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalDraw(canvas, parent, state); + } else if (isHorizontal()) { + horizontalDraw(canvas, parent, state); + } + } + + // ============ + // = vertical = + // ============ + + private void verticalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + if (parent.getChildAdapterPosition(view) == 0) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + outRect.set(0, (int) mHeight, 0, 0); + } else { + outRect.set(0, 0, 0, 0); + } + } + + private void verticalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + + View child = parent.getChildAt(0); + int position = parent.getChildAdapterPosition(child); + if (position == 0) { + canvas.drawRect( + child.getLeft() + mLeft, + child.getTop() - mHeight - mOffset, + child.getRight() - mRight, + child.getTop() - mOffset, + mPaint + ); + } + } + + // ============== + // = horizontal = + // ============== + + private void horizontalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + if (parent.getChildAdapterPosition(view) == 0) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + outRect.set((int) mHeight, 0, 0, 0); + } else { + outRect.set(0, 0, 0, 0); + } + } + + private void horizontalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + + View child = parent.getChildAt(0); + int position = parent.getChildAdapterPosition(child); + if (position == 0) { + canvas.drawRect( + child.getLeft() - mHeight - mOffset, + child.getTop() + mLeft, + child.getLeft() - mOffset, + child.getBottom() - mRight, + mPaint + ); + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LastLinearColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LastLinearColorItemDecoration.java new file mode 100644 index 0000000000..c6037c40f7 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LastLinearColorItemDecoration.java @@ -0,0 +1,181 @@ +package dev.widget.decoration.linear; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import dev.widget.decoration.BaseColorItemDecoration; + +/** + * detail: RecyclerView Linear 分割线处理 ( 最后一条数据 ) + * @author Ttt + *
+ *     效果:
+ *     最后一条数据底部添加一条分割线
+ * 
+ */ +public class LastLinearColorItemDecoration + extends BaseColorItemDecoration { + + public LastLinearColorItemDecoration( + final boolean vertical, + final float height + ) { + super(vertical, height); + } + + public LastLinearColorItemDecoration( + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(vertical, height, color); + } + + // ========== + // = 处理方法 = + // ========== + + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalItemOffsets(outRect, view, parent, state); + } else if (isHorizontal()) { + horizontalItemOffsets(outRect, view, parent, state); + } + } + + @Override + public void onDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalDraw(canvas, parent, state); + } else if (isHorizontal()) { + horizontalDraw(canvas, parent, state); + } + } + + // ============ + // = vertical = + // ============ + + private void verticalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (parent.getChildAdapterPosition(view) == itemCount - 1) { + if (!mSingleDraw && itemCount <= 1) { + return; + } + outRect.set(0, 0, 0, (int) mHeight); + } else { + outRect.set(0, 0, 0, 0); + } + } + + private void verticalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + + int lastPosition = (itemCount - 1); + if (lastPosition >= 0) { + int childCount = parent.getChildCount(); + int last = (childCount - 1); + if (last >= 0) { + // 判断当前屏幕最后一个 View 是否最后一条索引 + View child = parent.getChildAt(last); + int position = parent.getChildAdapterPosition(child); + if (position == lastPosition) { + parent.getDecoratedBoundsWithMargins(child, mBounds); + final float bottom = mBounds.bottom; + final float top = bottom - mHeight; + + canvas.drawRect( + child.getLeft() + mLeft, + top - mOffset, + child.getRight() - mRight, + bottom - mOffset, + mPaint + ); + } + } + } + } + + // ============== + // = horizontal = + // ============== + + private void horizontalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (parent.getChildAdapterPosition(view) == itemCount - 1) { + if (!mSingleDraw && itemCount <= 1) { + return; + } + outRect.set(0, 0, (int) mHeight, 0); + } else { + outRect.set(0, 0, 0, 0); + } + } + + private void horizontalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (!mSingleDraw && itemCount <= 1) { + return; + } + + int lastPosition = (itemCount - 1); + if (lastPosition >= 0) { + int childCount = parent.getChildCount(); + int last = (childCount - 1); + if (last >= 0) { + // 判断当前屏幕最后一个 View 是否最后一条索引 + View child = parent.getChildAt(last); + int position = parent.getChildAdapterPosition(child); + if (position == lastPosition) { + parent.getDecoratedBoundsWithMargins(child, mBounds); + final float right = mBounds.right; + final float left = right - mHeight; + + canvas.drawRect( + left - mOffset, + child.getTop() + mLeft, + right - mOffset, + child.getBottom() - mRight, + mPaint + ); + } + } + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LinearColorItemDecoration.java b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LinearColorItemDecoration.java new file mode 100644 index 0000000000..0501b6c45f --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/decoration/linear/LinearColorItemDecoration.java @@ -0,0 +1,162 @@ +package dev.widget.decoration.linear; + +import android.graphics.Canvas; +import android.graphics.Rect; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.RecyclerView; + +import dev.widget.decoration.BaseColorItemDecoration; + +/** + * detail: RecyclerView Linear 分割线处理 ( 每一条数据 ) + * @author Ttt + *
+ *     效果:
+ *     每一条数据底部添加一条分割线, 最后一条数据不绘制 ( 绘制 ItemCount - 1 条分割线 )
+ *     

+ * 也可以使用内置 {@link DividerItemDecoration} + * 自定义分割线使用方法 + * DividerItemDecoration decoration = new DividerItemDecoration(context, DividerItemDecoration.VERTICAL) + * decoration.setDrawable(Drawable) + * recyclerView.addItemDecoration(decoration) + *
+ */ +public class LinearColorItemDecoration + extends BaseColorItemDecoration { + + public LinearColorItemDecoration( + final boolean vertical, + final float height + ) { + super(vertical, height); + } + + public LinearColorItemDecoration( + final boolean vertical, + final float height, + @ColorInt final int color + ) { + super(vertical, height, color); + } + + // ========== + // = 处理方法 = + // ========== + + @Override + public void getItemOffsets( + @NonNull Rect outRect, + @NonNull View view, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalItemOffsets(outRect, view, parent, state); + } else if (isHorizontal()) { + horizontalItemOffsets(outRect, view, parent, state); + } + } + + @Override + public void onDraw( + @NonNull Canvas canvas, + @NonNull RecyclerView parent, + @NonNull RecyclerView.State state + ) { + if (isVertical()) { + verticalDraw(canvas, parent, state); + } else if (isHorizontal()) { + horizontalDraw(canvas, parent, state); + } + } + + // ============ + // = vertical = + // ============ + + private void verticalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= 1) return; + + if (parent.getChildAdapterPosition(view) == 0) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set(0, (int) mHeight, 0, 0); + } + } + + private void verticalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= 1) return; + + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + if (parent.getChildAdapterPosition(child) != 0) { + canvas.drawRect( + child.getLeft() + mLeft, + child.getTop() - mHeight - mOffset, + child.getRight() - mRight, + child.getTop() - mOffset, + mPaint + ); + } + } + } + + // ============== + // = horizontal = + // ============== + + private void horizontalItemOffsets( + final Rect outRect, + final View view, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= 1) return; + + if (parent.getChildAdapterPosition(view) == 0) { + outRect.set(0, 0, 0, 0); + } else { + outRect.set((int) mHeight, 0, 0, 0); + } + } + + private void horizontalDraw( + final Canvas canvas, + final RecyclerView parent, + final RecyclerView.State state + ) { + int itemCount = state.getItemCount(); + if (itemCount <= 1) return; + + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = parent.getChildAt(i); + if (parent.getChildAdapterPosition(child) != 0) { + canvas.drawRect( + child.getLeft() - mHeight - mOffset, + child.getTop() + mLeft, + child.getLeft() - mOffset, + child.getBottom() - mRight, + mPaint + ); + } + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/function/LimitLayout.java b/lib/DevWidget/src/main/java/dev/widget/function/LimitLayout.java new file mode 100644 index 0000000000..59f69068c2 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/function/LimitLayout.java @@ -0,0 +1,125 @@ +package dev.widget.function; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import dev.utils.app.WidgetUtils; +import dev.widget.utils.WidgetAttrs; + +/** + * detail: 自定义 FrameLayout 设置最大、最小宽高 + * @author Ttt + *
+ *     app:dev_maxWidth=""
+ *     app:dev_maxHeight=""
+ * 
+ */ +public class LimitLayout + extends FrameLayout { + + private WidgetAttrs mWidgetAttrs; + + public LimitLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public LimitLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public LimitLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public LimitLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mWidgetAttrs = new WidgetAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + int[] measureSpecs = WidgetUtils.viewMeasure( + this, widthMeasureSpec, heightMeasureSpec, + mWidgetAttrs.getMaxWidth(), mWidgetAttrs.getMaxHeight() + ); + super.onMeasure(measureSpecs[0], measureSpecs[1]); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mWidgetAttrs.getMaxWidth(); + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link LimitLayout} + */ + public LimitLayout setMaxWidth(final int maxWidth) { + mWidgetAttrs.setMaxWidth(maxWidth); + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mWidgetAttrs.getMaxHeight(); + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link LimitLayout} + */ + public LimitLayout setMaxHeight(final int maxHeight) { + mWidgetAttrs.setMaxHeight(maxHeight); + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/function/LineTextView.java b/lib/DevWidget/src/main/java/dev/widget/function/LineTextView.java new file mode 100644 index 0000000000..41b05b9b1b --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/function/LineTextView.java @@ -0,0 +1,101 @@ +package dev.widget.function; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatTextView; + +import dev.utils.app.TextViewUtils; + +/** + * detail: TextView 换行监听 + * @author Ttt + */ +public class LineTextView + extends AppCompatTextView { + + // 是否换行 + private boolean mNewLine = false; + // 换行监听回调 + private OnNewLineCallback mCallback; + + public LineTextView(Context context) { + super(context); + } + + public LineTextView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public LineTextView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + Paint paint = getPaint(); + // 当前文本 + String text = this.getText().toString(); + // 计算文本一共多少行 + int line = TextViewUtils.calcTextLine(paint, text, + getWidth() - getPaddingLeft() - getPaddingRight() + ); + // 是否换行 + mNewLine = line > 1; + + // 触发回调 + if (mCallback != null) { + mCallback.onNewLine(mNewLine, line); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 判断是否换行 + * @return {@code true} yes, {@code false} no + */ + public boolean isNewLine() { + return mNewLine; + } + + /** + * 设置换行监听回调 + * @param callback {@link OnNewLineCallback} + * @return {@link LineTextView} + */ + public LineTextView setNewLineCallback(final OnNewLineCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 换行监听回调 + * @author Ttt + */ + public interface OnNewLineCallback { + + /** + * 换行触发 + * @param isNewLine 是否换行 + * @param line 行数 + */ + void onNewLine( + boolean isNewLine, + int line + ); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/function/RightIconEditText.java b/lib/DevWidget/src/main/java/dev/widget/function/RightIconEditText.java new file mode 100644 index 0000000000..058bac66cd --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/function/RightIconEditText.java @@ -0,0 +1,296 @@ +package dev.widget.function; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.EditText; + +import dev.widget.R; + +/** + * detail: 自定义 EditText 右边 Icon + * @author Ttt + *
+ *     // 设置触发范围倍数
+ *     app:dev_rangeMultiple=""
+ *     // 控制是否显示右边 Icon
+ *     app:dev_drawRightIcon=""
+ *     // 设置右边 Icon
+ *     android:drawableRight="@drawable/xx"
+ *     输入监听可调用 {@link #addTextChangedListener} 或 {@link #setTextWatcher}
+ * 
+ */ +@SuppressLint("AppCompatCustomView") +public class RightIconEditText + extends EditText { + + // drawable ( left、top、right、bottom 四个方向图片 ) + private Drawable mLeft, mTop, mRight, mBottom; + // Right Drawable 自身坐标信息 + private Rect mRightBounds; + // 右边 Icon 触发范围倍数 + private float mRangeMultiple = 2.0F; + // 是否绘制右边 Icon + private boolean mDrawRightIcon = true; + // 右边 Icon 点击事件 + private OnClickListener mRightClickListener; + // 输入监听回调 + private TextWatcher mTextWatcher; + + public RightIconEditText(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RightIconEditText( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RightIconEditText( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mRangeMultiple = a.getFloat(R.styleable.DevWidget_dev_rangeMultiple, 2.0F); + mDrawRightIcon = a.getBoolean(R.styleable.DevWidget_dev_drawRightIcon, true); + a.recycle(); + } + drawOperate(); + addTextChangedListener(_listener); + } + + @Override + public void setCompoundDrawables( + Drawable left, + Drawable top, + Drawable right, + Drawable bottom + ) { + if (mLeft == null) this.mLeft = left; + if (mTop == null) this.mTop = top; + if (mRight == null) this.mRight = right; + if (mBottom == null) this.mBottom = bottom; + super.setCompoundDrawables(left, top, mDrawRightIcon ? right : null, bottom); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + // 判断抬起时, 是否在 right drawable 上 + if (mDrawRightIcon && mRight != null && event.getAction() == MotionEvent.ACTION_UP) { + // 获取 drawable 自身坐标信息 + this.mRightBounds = mRight.getBounds(); + mRight.getIntrinsicWidth(); + // 获取触摸坐标 + int eventX = (int) event.getX(); + int width = mRightBounds.width(); + int right = getRight(); + int left = getLeft(); + // icon 点击范围 ( 用于小 Icon 扩大触发范围使用 ) + int iconRange = (int) (width * mRangeMultiple); + // 判断是否按在 right drawable 上 + if (eventX > (right - left - iconRange)) { + setText(""); // 清空内容 + event.setAction(MotionEvent.ACTION_CANCEL); + if (mRightClickListener != null) { + mRightClickListener.onClick(this); + } + } + } + return super.onTouchEvent(event); + } + + @Override + protected void finalize() + throws Throwable { + super.finalize(); + // GC Finalize Daemon 回收资源 + this.mLeft = null; + this.mTop = null; + this.mRight = null; + this.mBottom = null; + this.mRightBounds = null; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取右边 Icon 触发范围倍数 + * @return 右边 Icon 触发范围倍数 + */ + public float getRangeMultiple() { + return mRangeMultiple; + } + + /** + * 设置右边 Icon 触发范围倍数 + * @param rangeMultiple 触发范围倍数 + * @return {@link RightIconEditText} + */ + public RightIconEditText setRangeMultiple(final float rangeMultiple) { + this.mRangeMultiple = rangeMultiple; + return this; + } + + /** + * 是否绘制右边 Icon + * @return {@code true} yes, {@code false} no + */ + public boolean isDrawRightIcon() { + return mDrawRightIcon; + } + + /** + * 设置是否绘制右边 Icon + * @param drawRightIcon {@code true} yes, {@code false} no + * @return {@link RightIconEditText} + */ + public RightIconEditText setDrawRightIcon(final boolean drawRightIcon) { + this.mDrawRightIcon = drawRightIcon; + postInvalidate(); + return this; + } + + /** + * 获取右边 Icon 点击事件 + * @return 右边 Icon 点击事件 + */ + public OnClickListener getRightClickListener() { + return mRightClickListener; + } + + /** + * 设置右边 Icon 点击事件 + * @param listener {@link OnClickListener} + * @return {@link RightIconEditText} + */ + public RightIconEditText setRightClickListener(final OnClickListener listener) { + this.mRightClickListener = listener; + return this; + } + + /** + * 获取右边 Icon Drawable + * @return {@link Drawable} + */ + public Drawable getRightIcon() { + return mRight; + } + + /** + * 设置右边 Icon Drawable + * @param right 右边 Icon + * @return {@link RightIconEditText} + */ + public RightIconEditText setRightIcon(final Drawable right) { + this.mRight = right; + drawOperate(); + return this; + } + + /** + * 设置输入监听回调 + * @param textWatcher {@link TextWatcher} + * @return {@link RightIconEditText} + */ + public RightIconEditText setTextWatcher(final TextWatcher textWatcher) { + this.mTextWatcher = textWatcher; + return this; + } + + // = + + /** + * 绘制操作 ( 控制右边 Icon 显示 ) + */ + private void drawOperate() { + setCompoundDrawables(mLeft, mTop, (length() == 0) ? null : mRight, mBottom); + } + + /** + * 内部 TextWatcher + */ + private final TextWatcher _listener = new TextWatcher() { + /** + * 在文本变化前调用 + * @param text 修改之前的文字 + * @param start 字符串中即将发生修改的位置 + * @param count 字符串中即将被修改的文字的长度, 如果是新增的话则为 0 + * @param after 被修改的文字修改之后的长度, 如果是删除的话则为 0 + */ + @Override + public void beforeTextChanged( + CharSequence text, + int start, + int count, + int after + ) { + if (mTextWatcher != null) { + mTextWatcher.beforeTextChanged(text, start, count, after); + } + } + + /** + * 在文本变化后调用 + * @param text 改变后的字符串 + * @param start 有变动的字符串的位置 + * @param before 被改变的字符串长度, 如果是新增则为 0 + * @param count 添加的字符串长度, 如果是删除则为 0 + */ + @Override + public void onTextChanged( + CharSequence text, + int start, + int before, + int count + ) { + if (mTextWatcher != null) { + mTextWatcher.onTextChanged(text, start, before, count); + } + } + + /** + * 在文本变化后调用 + * @param text 修改后的文字 + */ + @Override + public void afterTextChanged(Editable text) { + if (mTextWatcher != null) { + mTextWatcher.afterTextChanged(text); + } + drawOperate(); + } + }; +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/function/SignView.java b/lib/DevWidget/src/main/java/dev/widget/function/SignView.java new file mode 100644 index 0000000000..73d30436bf --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/function/SignView.java @@ -0,0 +1,230 @@ +package dev.widget.function; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import dev.utils.app.CapturePictureUtils; + +/** + * detail: 自定义签名 View + * @author Ttt + *
+ *     如果需要实现不同颜色绘制不同路径, 则在 onTouchEvent {@link MotionEvent#ACTION_UP} 抬起时
+ *     存储每次的 Path、Paint 为一个 Item Bean, 并且在 onDraw 循环 List 进行 canvas.drawPath(item.path, item.paint)
+ *     则能够实现类似画图功能 ( SVG 绘制也是该思路 )
+ * 
+ */ +public class SignView + extends View { + + // 绘制路径 + private Path mPath; + // 绘制画笔 + private Paint mPaint; + // 绘制路径 X、Y ( 临时变量 ) + private float mX, mY; + // 是否清空画布 + private boolean mClearCanvas = false; + // 绘制回调事件 + private OnDrawCallback mCallback; + + public SignView(Context context) { + super(context); + } + + public SignView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public SignView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SignView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + Path path = getPath(); + Paint paint = getPaint(); + // 触发回调 + if (mCallback != null) { + if (mCallback.onDraw(canvas, path, paint, mClearCanvas)) { + // 绘制路径 + canvas.drawPath(path, paint); + } + mClearCanvas = false; + } else { + // 绘制路径 + canvas.drawPath(path, paint); + // 重置画布操作 + innerResetCanvas(canvas); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mX = event.getX(); + mY = event.getY(); + getPath().moveTo(mX, mY); + break; + case MotionEvent.ACTION_MOVE: + float x1 = event.getX(); + float y1 = event.getY(); + float cx = (x1 + mX) / 2; + float cy = (y1 + mY) / 2; + getPath().quadTo(mX, mY, cx, cy); + mX = x1; + mY = y1; + break; + } + invalidate(); + return true; + } + + /** + * 重置画布处理 + * @param canvas 画布 + */ + private void innerResetCanvas(final Canvas canvas) { + if (mClearCanvas && canvas != null) { + canvas.drawColor(Color.WHITE, PorterDuff.Mode.CLEAR); + } + mClearCanvas = false; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 清空画布 + * @return {@link SignView} + */ + public SignView clearCanvas() { + mX = mY = 0; + mPath = null; + mClearCanvas = true; + postInvalidate(); + return this; + } + + /** + * 通过 View 绘制为 Bitmap + *
+     *     // 可以自己设置背景色
+     *     CapturePictureUtils.setBackgroundColor(Color.WHITE);
+     * 
+ * @return {@link Bitmap} + */ + public Bitmap snapshotByView() { + return CapturePictureUtils.snapshotByView(this); + } + + /** + * 获取绘制路径 + * @return {@link Path} + */ + public Path getPath() { + if (mPath == null) mPath = new Path(); + return mPath; + } + + /** + * 设置绘制路径 + * @param path {@link Path} + * @return {@link SignView} + */ + public SignView setPath(final Path path) { + this.mPath = path; + return this; + } + + /** + * 获取绘制画笔 + * @return {@link Paint} + */ + public Paint getPaint() { + if (mPaint == null) { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setStyle(Paint.Style.STROKE); + mPaint.setStrokeWidth(10); + } + return mPaint; + } + + /** + * 设置绘制画笔 + * @param paint {@link Paint} + * @return {@link SignView} + */ + public SignView setPaint(final Paint paint) { + this.mPaint = paint; + return this; + } + + /** + * 设置绘制回调事件 + * @param callback 绘制回调事件 + * @return {@link SignView} + */ + public SignView setDrawCallback(final OnDrawCallback callback) { + this.mCallback = callback; + return this; + } + + /** + * detail: 绘制回调事件 + * @author Ttt + */ + public static abstract class OnDrawCallback { + + /** + * 绘制方法 + * @param canvas 画布 + * @param path 绘制路径 + * @param paint 绘制画笔 + * @param clearCanvas 是否清空画布 + * @return 是否接着进行绘制 drawPath + */ + public boolean onDraw( + Canvas canvas, + Path path, + Paint paint, + boolean clearCanvas + ) { + if (clearCanvas && canvas != null) { + canvas.drawColor(Color.WHITE, PorterDuff.Mode.CLEAR); + } + return true; + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/function/StateLayout.java b/lib/DevWidget/src/main/java/dev/widget/function/StateLayout.java new file mode 100644 index 0000000000..178a4ec129 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/function/StateLayout.java @@ -0,0 +1,373 @@ +package dev.widget.function; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.LayoutRes; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import dev.utils.LogPrintUtils; +import dev.widget.assist.ViewAssist; + +/** + * detail: 状态布局 + * @author Ttt + *
+ *     使用 type-layoutId 进行注册, 封装 {@link dev.widget.assist.ViewAssist} ( type-Adapter )
+ * 
+ */ +public class StateLayout + extends FrameLayout { + + // 日志 TAG + private final String TAG = StateLayout.class.getSimpleName(); + // View 填充辅助类 + private ViewAssist mAssist; + // 类型改变接口 + private Listener mListener; + // 全局配置 + private static Global sGlobal; + + public StateLayout(Context context) { + super(context); + initialize(); + } + + public StateLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initialize(); + } + + public StateLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public StateLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + // = + + public interface Listener { + + void onRemove( + StateLayout layout, + int type, + boolean removeView + ); + + void onNotFound( + StateLayout layout, + int type + ); + + void onChange( + StateLayout layout, + int type, + int oldType, + View view + ); + } + + // =============== + // = 初始化相关代码 = + // =============== + + /** + * 重置处理 + * @return {@link StateLayout} + */ + public StateLayout reset() { + initialize(); // 重新初始化 + post(new Runnable() { + @Override + public void run() { + removeAllViews(); + } + }); + return this; + } + + /** + * 内部初始化方法 + */ + private void initialize() { + mAssist = ViewAssist.wrap(this); + mAssist.setListener(new ViewAssist.Listener() { + @Override + public void onRemove( + ViewAssist assist, + int type, + boolean removeView + ) { + if (mListener != null) { + mListener.onRemove(StateLayout.this, type, removeView); + } + if (sGlobal != null && sGlobal.listener != null) { + sGlobal.listener.onRemove(StateLayout.this, type, removeView); + } + } + + @Override + public void onNotFound( + ViewAssist assist, + int type + ) { + if (mListener != null) { + mListener.onNotFound(StateLayout.this, type); + } + if (sGlobal != null && sGlobal.listener != null) { + sGlobal.listener.onNotFound(StateLayout.this, type); + } + } + + @Override + public void onChange( + ViewAssist assist, + int type, + int oldType + ) { + if (mListener != null) { + mListener.onChange(StateLayout.this, type, oldType, assist.getView(type)); + } + if (sGlobal != null && sGlobal.listener != null) { + sGlobal.listener.onChange(StateLayout.this, type, oldType, assist.getView(type)); + } + } + }); + // 添加全局配置 + if (sGlobal != null) { + Map mapLayouts = new HashMap<>(sGlobal.mapLayouts); + Iterator> iterator = mapLayouts.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + int type = entry.getKey(); + Integer layout = entry.getValue(); + mAssist.register(type, new TypeAdapter(layout)); + } + } + } + + // = + + /** + * detail: 全局配置 + * @author Ttt + */ + public static class Global { + + // type-layout + private final Map mapLayouts = new HashMap<>(); + // 类型改变接口 + private final Listener listener; + + public Global(final Listener listener) { + this.listener = listener; + } + + /** + * 注册 type + * @param type Type + * @param layout Layout + * @return {@link Global} + */ + public Global register( + final int type, + @LayoutRes final int layout + ) { + mapLayouts.put(type, layout); + return this; + } + + /** + * 取消注册 type + * @param type Type + * @return {@link Global} + */ + public Global unregister(final int type) { + mapLayouts.remove(type); + return this; + } + } + + private class TypeAdapter + implements ViewAssist.Adapter { + + private final int resource; + + public TypeAdapter(final int layout) { + this.resource = layout; + } + + @Override + public View onCreateView( + ViewAssist assist, + LayoutInflater inflater + ) { + try { + return inflater.inflate(resource, null); + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e); + return null; + } + } + + @Override + public void onBindView( + ViewAssist assist, + View view, + int type + ) { + } + } + + // ============= + // = 对外公开方法 = + // ============= + + public static void setGlobal(final Global global) { + StateLayout.sGlobal = global; + } + + public StateLayout setListener(final Listener listener) { + this.mListener = listener; + return this; + } + + public void showIng() { + mAssist.showIng(); + } + + public void showFailed() { + mAssist.showFailed(); + } + + public void showSuccess() { + mAssist.showSuccess(); + } + + public void showEmptyData() { + mAssist.showEmptyData(); + } + + public void showType(final int type) { + mAssist.showType(type); + } + + public StateLayout notifyDataSetChanged() { + mAssist.notifyDataSetChanged(); + return this; + } + + public StateLayout gone() { + setVisibility(View.GONE); + return this; + } + + public StateLayout visible() { + setVisibility(View.VISIBLE); + return this; + } + + /** + * 注册 type + * @param type Type + * @param layout Layout + * @return {@link StateLayout} + */ + public StateLayout register( + final int type, + @LayoutRes final int layout + ) { + mAssist.register(type, new TypeAdapter(layout)); + return this; + } + + /** + * 取消注册 type + * @param type Type + * @return {@link StateLayout} + */ + public StateLayout unregister(final int type) { + return unregister(type, true); + } + + /** + * 取消注册 type + * @param type Type + * @param remove 判断解绑的 type 正在显示是否删除 + * @return {@link StateLayout} + */ + public StateLayout unregister( + final int type, + final boolean remove + ) { + mAssist.unregister(type, remove); + return this; + } + + public Object getAssistTag() { + return mAssist.getTag(); + } + + public StateLayout setAssistTag(final Object tag) { + mAssist.setTag(tag); + return this; + } + + public T getData() { + return mAssist.getData(); + } + + public StateLayout setData(final Object data) { + mAssist.setData(data); + return this; + } + + public T getData(final String key) { + return mAssist.getData(key); + } + + public StateLayout setData( + final String key, + final Object data + ) { + mAssist.setData(key, data); + return this; + } + + public View getView(final int type) { + return mAssist.getView(type); + } + + public int getCurrentType() { + return mAssist.getCurrentType(); + } + + public View getCurrentView() { + return mAssist.getCurrentView(); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/CornerLabelView.java b/lib/DevWidget/src/main/java/dev/widget/ui/CornerLabelView.java new file mode 100644 index 0000000000..51e7e1abc9 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/CornerLabelView.java @@ -0,0 +1,560 @@ +package dev.widget.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.Shader; +import android.os.Build; +import android.text.TextPaint; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.ColorInt; + +import dev.utils.app.SizeUtils; +import dev.widget.R; + +/** + * detail: 自定义角标 View + * @author Ttt + *
+ *     

+ * app:dev_paddingTop="" + * app:dev_paddingCenter="" + * app:dev_paddingBottom="" + * app:dev_fillColor="" + * app:dev_flags="" + *

+ * app:dev_text1="" + * app:dev_textColor1="" + * app:dev_textHeight1="" + * app:dev_textBold1="" + *

+ * app:dev_text2="" + * app:dev_textColor2="" + * app:dev_textHeight2="" + * app:dev_textBold2="" + *
+ */ +public class CornerLabelView + extends View { + + // 三角形斜边长 + private static final float SQRT2 = (float) Math.sqrt(2); + // 内部画笔 + private Paint mPaint; + // Text1 Paint + private final Painter mText1 = new Painter(); + // Text2 Paint + private final Painter mText2 = new Painter(); + // 顶部边距 + private float mPaddingTop = 0; + // Text1、Text2 边距 + private float mPaddingCenter = 0; + // 底部边距 + private float mPaddingBottom = 0; + // 是否左边显示角标 + private boolean mLeft = true; + // 是否顶部显示角标 + private boolean mTop = true; + // 是否角标三角形铺满样式 + private boolean mTriangle = true; + // 当前 View 高度 + private int mHeight; + + public CornerLabelView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public CornerLabelView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public CornerLabelView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CornerLabelView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mPaint = new Paint(); + mPaint.setStyle(Paint.Style.FILL); + mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER)); + mPaint.setAntiAlias(true); + + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + + mPaddingTop = a.getDimension(R.styleable.DevWidget_dev_paddingTop, SizeUtils.dp2px(getContext(), 10)); + mPaddingCenter = a.getDimension(R.styleable.DevWidget_dev_paddingCenter, 0); + mPaddingBottom = a.getDimension(R.styleable.DevWidget_dev_paddingBottom, SizeUtils.dp2px(getContext(), 3)); + + mText1.text = a.getString(R.styleable.DevWidget_dev_text1); + mText1.textColor = a.getColor(R.styleable.DevWidget_dev_textColor1, 0xffffffff); + mText1.textHeight = a.getDimension(R.styleable.DevWidget_dev_textHeight1, SizeUtils.dp2px(getContext(), 12)); + mText1.textBold = a.getBoolean(R.styleable.DevWidget_dev_textBold1, false); + mText1.initialize(); + + mText2.text = a.getString(R.styleable.DevWidget_dev_text2); + mText2.textColor = a.getColor(R.styleable.DevWidget_dev_textColor2, 0x66ffffff); + mText2.textHeight = a.getDimension(R.styleable.DevWidget_dev_textHeight2, SizeUtils.dp2px(getContext(), 8)); + mText2.textBold = a.getBoolean(R.styleable.DevWidget_dev_textBold2, false); + mText2.initialize(); + + int fillColor = a.getColor(R.styleable.DevWidget_dev_fillColor, 0x66000000); + int flags = a.getInteger(R.styleable.DevWidget_dev_flags, 0); + a.recycle(); + + mLeft = (flags & 1) == 0; + mTop = (flags & 2) == 0; + mTriangle = (flags & 4) > 0; + mPaint.setColor(fillColor); + } + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + this.mHeight = (int) (mPaddingTop + mPaddingCenter + mPaddingBottom + + mText1.textHeight + mText2.textHeight); + int size = (int) (mHeight * SQRT2); + setMeasuredDimension(size, size); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float xy = mHeight / SQRT2; + canvas.save(); + canvas.translate(xy, xy); + canvas.rotate((mTop ? 1 : -1) * (mLeft ? -45 : 45)); + canvas.drawPath(calcPath(), mPaint); + + mText1.draw(canvas, mPaddingBottom, mTop); + if (mTriangle) { + mText2.draw(canvas, mPaddingBottom + mPaddingCenter + mText1.textHeight, mTop); + } + canvas.restore(); + } + + /** + * 计算路径 + * @return {@link Path} + */ + private Path calcPath() { + Path path = new Path(); + path.moveTo(-mHeight, 0); + path.lineTo(mHeight, 0); + int factor = mTop ? -1 : 1; + if (mTriangle) { + path.lineTo(0, factor * mHeight); + } else { + int lineHeight = factor * (int) (mPaddingCenter + mPaddingBottom + mText1.textHeight); + path.lineTo(mHeight + lineHeight, lineHeight); + path.lineTo(-mHeight + lineHeight, lineHeight); + } + path.close(); + return path; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 Text1 Painter + * @return {@link Painter} + */ + public Painter getPainter1() { + return mText1; + } + + /** + * 获取 Text2 Painter + * @return {@link Painter} + */ + public Painter getPainter2() { + return mText2; + } + + // = + + /** + * 设置顶部边距 + * @param px 边距值 ( px ) + * @return {@link CornerLabelView} + */ + public CornerLabelView setPaddingTop(final float px) { + this.mPaddingTop = px; + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置 Text1、Text2 边距 + * @param px 边距值 ( px ) + * @return {@link CornerLabelView} + */ + public CornerLabelView setPaddingCenter(final float px) { + this.mPaddingCenter = px; + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置底部边距 + * @param px 边距值 ( px ) + * @return {@link CornerLabelView} + */ + public CornerLabelView setPaddingBottom(final float px) { + this.mPaddingBottom = px; + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置背景填充颜色 + * @param color Color + * @return {@link CornerLabelView} + */ + public CornerLabelView setFillColor(@ColorInt final int color) { + this.mPaint.setColor(color); + postInvalidate(); + return this; + } + + /** + * 设置背景填充渐变色 + * @param shader {@link Shader} + * @return {@link CornerLabelView} + */ + public CornerLabelView setFillShader(final Shader shader) { + this.mPaint.setShader(shader); + postInvalidate(); + return this; + } + + /** + * 设置左边显示角标 + * @return {@link CornerLabelView} + */ + public CornerLabelView left() { + this.mLeft = true; + postInvalidate(); + return this; + } + + /** + * 设置右边显示角标 + * @return {@link CornerLabelView} + */ + public CornerLabelView right() { + this.mLeft = false; + postInvalidate(); + return this; + } + + /** + * 设置顶部显示角标 + * @return {@link CornerLabelView} + */ + public CornerLabelView top() { + this.mTop = true; + postInvalidate(); + return this; + } + + /** + * 设置底部显示角标 + * @return {@link CornerLabelView} + */ + public CornerLabelView bottom() { + this.mTop = false; + postInvalidate(); + return this; + } + + /** + * 设置角标三角形铺满样式 + * @param value {@code true} 展示 text1、text2, {@code false} 展示 text1 + * @return {@link CornerLabelView} + */ + public CornerLabelView triangle(final boolean value) { + this.mTriangle = value; + postInvalidate(); + return this; + } + + // ========= + // = Text1 = + // ========= + + /** + * 设置文本 + * @param text 文本 + * @return {@link CornerLabelView} + */ + public CornerLabelView setText1(final String text) { + mText1.text = text; + mText1.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置字体颜色 + * @param textColor 字体颜色 + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextColor1(@ColorInt final int textColor) { + mText1.textColor = textColor; + mText1.initialize(); + postInvalidate(); + return this; + } + + /** + * 设置字体高度 + * @param textHeight 字体高度 ( px ) + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextHeight1(final float textHeight) { + mText1.textHeight = textHeight; + mText1.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置字体是否加粗 + * @param textBold {@code true} yes, {@code false} no + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextBold1(final boolean textBold) { + mText1.textBold = textBold; + mText1.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + // ========= + // = Text2 = + // ========= + + /** + * 设置文本 + * @param text 文本 + * @return {@link CornerLabelView} + */ + public CornerLabelView setText2(final String text) { + mText2.text = text; + mText2.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置字体颜色 + * @param textColor 字体颜色 + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextColor2(@ColorInt final int textColor) { + mText2.textColor = textColor; + mText2.initialize(); + postInvalidate(); + return this; + } + + /** + * 设置字体高度 + * @param textHeight 字体高度 ( px ) + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextHeight2(final float textHeight) { + mText2.textHeight = textHeight; + mText2.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + /** + * 设置字体是否加粗 + * @param textBold {@code true} yes, {@code false} no + * @return {@link CornerLabelView} + */ + public CornerLabelView setTextBold2(final boolean textBold) { + mText2.textBold = textBold; + mText2.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + // = + + /** + * 刷新重绘处理 + * @return {@link CornerLabelView} + */ + public CornerLabelView refresh() { + mText1.initialize(); + mText2.initialize(); + requestLayout(); + postInvalidate(); + return this; + } + + // = + + /** + * detail: 画笔 + * @author Ttt + */ + public static final class Painter { + + private Painter() { + } + + // 画笔 + private final TextPaint paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + // 绘制文本 + private String text = ""; + // 绘制字体颜色 + private int textColor = Color.WHITE; + // 字体高度 + private float textHeight = 0; + // 字体是否加粗 + private boolean textBold = false; + // 偏移值 + private float offset = 0; + + /** + * 初始化处理 + */ + void initialize() { + paint.setTextAlign(Paint.Align.CENTER); + paint.setColor(textColor); + paint.setTextSize(textHeight); + paint.setFakeBoldText(textBold); + // 计算偏移值 + if (text == null) text = ""; + Rect rect = new Rect(); + paint.getTextBounds(text, 0, text.length(), rect); + offset = rect.height() / 2; + } + + /** + * 绘制处理 + * @param canvas 画布 + * @param y 绘制 Y 轴坐标 + * @param isTop 是否顶部绘制 + */ + void draw( + final Canvas canvas, + final float y, + final boolean isTop + ) { + canvas.drawText(text, 0, (isTop ? -1 : 1) * (y + textHeight / 2) + offset, paint); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取画笔 + *
+         *     设置了部分属性样式等, 需调用 {@link #refresh}
+         * 
+ * @return {@link TextPaint} + */ + public TextPaint getPaint() { + return paint; + } + + /** + * 获取文本 + * @return 文本 + */ + public String getText() { + return text; + } + + /** + * 获取字体颜色 + * @return 字体颜色 + */ + public int getTextColor() { + return textColor; + } + + /** + * 获取字体高度 + * @return 字体高度 ( px ) + */ + public float getTextHeight() { + return textHeight; + } + + /** + * 获取字体是否加粗 + * @return {@code true} yes, {@code false} no + */ + public boolean isTextBold() { + return textBold; + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/FlipCardView.java b/lib/DevWidget/src/main/java/dev/widget/ui/FlipCardView.java new file mode 100644 index 0000000000..f287ce1bf3 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/FlipCardView.java @@ -0,0 +1,320 @@ +package dev.widget.ui; + +import android.animation.Animator; +import android.animation.AnimatorInflater; +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import dev.utils.LogPrintUtils; +import dev.utils.app.ViewUtils; +import dev.widget.R; + +/** + * detail: 翻转卡片 View + * @author Ttt + */ +public class FlipCardView + extends FrameLayout { + + // 日志 TAG + public static final String TAG = FlipCardView.class.getSimpleName(); + + // 正面 Layout + private FrameLayout mFrontLayout; + // 背面 Layout + private FrameLayout mBackLayout; + // 当前是否显示正面 Layout + private boolean mFront = true; + // 翻转出入动画 + private Animator mOutAnim; + private Animator mInAnim; + // 数据源适配器 + private Adapter mAdapter; + // 当前显示索引 + private int mPosition = 0; + + public FlipCardView(Context context) { + super(context); + initialize(); + } + + public FlipCardView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initialize(); + } + + public FlipCardView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public FlipCardView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + /** + * 初始化 + */ + private void initialize() { + removeAllViews(); + // 初始化 View + mFrontLayout = new FrameLayout(getContext()); + mFrontLayout.setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + ); + mBackLayout = new FrameLayout(getContext()); + mBackLayout.setLayoutParams( + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + ); + addView(mBackLayout); + addView(mFrontLayout); + // 设置翻牌角度 + setFlipDistance(16000); + // 初始化动画 + mOutAnim = AnimatorInflater.loadAnimator(getContext(), R.animator.dev_flip_card_out); + mInAnim = AnimatorInflater.loadAnimator(getContext(), R.animator.dev_flip_card_in); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 当前是否显示正面 Layout + * @return {@code true} yes, {@code false} no + */ + public boolean isFront() { + return mFront; + } + + /** + * 获取当前显示的索引 + * @return 当前显示的索引 + */ + public int getCurrentPosition() { + if (mAdapter != null) { + int index = mPosition - 2; + int size = mAdapter.getItemCount(); + if (index >= 0) return index; + index = index + size; + return Math.max(index, 0); + } + return 0; + } + + /** + * 获取数据源适配器 + * @return {@link Adapter} + */ + public Adapter getAdapter() { + return mAdapter; + } + + /** + * 设置数据源适配器 + * @param adapter {@link Adapter} + * @return {@link FlipCardView} + */ + public FlipCardView setAdapter(final Adapter adapter) { + // 取消动画 + mOutAnim.cancel(); + mInAnim.cancel(); + // 默认两面都显示 + ViewUtils.setVisibilitys(true, mFrontLayout, mBackLayout); + // 重置处理 + mFront = true; + mPosition = 0; + // 设置适配器 + this.mAdapter = adapter; + // 开始加载 View ( 连续加载两次 正面、背面 ) + loadView(true).loadView(false); + return this; + } + + /** + * Adapter 数据源变更时调用 + */ + public void notifyDataSetChanged() { + setAdapter(mAdapter); + } + + /** + * 翻转操作 + *
+     *     需先调用 {@link #setAdapter(Adapter)}
+     *     且在定时器每隔一段时间调用该方法
+     * 
+ */ + public void flip() { + if (this.mAdapter == null) return; + // 开始翻转时都显示 View + ViewUtils.setVisibilitys(true, mFrontLayout, mBackLayout); + + if (mFront) { // 正面朝上 + mFront = false; + mOutAnim.setTarget(mFrontLayout); + mInAnim.setTarget(mBackLayout); + } else { // 背面朝上 + mFront = true; + mOutAnim.setTarget(mBackLayout); + mInAnim.setTarget(mFrontLayout); + } + // 设置动画结束监听 + mInAnim.removeListener(mInnerInAnimListener); + mInAnim.addListener(mInnerInAnimListener); + // 启动动画 + mOutAnim.start(); + mInAnim.start(); + } + + /** + * 设置进出动画 + *
+     *     内部不判空, 传入自行控制
+     *     可参考 R.animator.dev_flip_card_in、dev_flip_card_out 复制修改
+     *     需要注意的是 startOffset 需要是 duration 的一半
+     *     背面布局出现时间 ( 也是正面布局消失时间, 正好是总共旋转时间的一半 )
+     * 
+ * @param inAnim 翻牌淡入动画 + * @param outAnim 翻牌淡出动画 + * @return {@link FlipCardView} + */ + public FlipCardView setInOutAnimator( + final Animator inAnim, + final Animator outAnim + ) { + this.mInAnim = inAnim; + this.mOutAnim = outAnim; + return this; + } + + /** + * 设置翻牌角度 + * @param distance 翻转角度 + * @return {@link FlipCardView} + */ + public FlipCardView setFlipDistance(final int distance) { + float scale = getResources().getDisplayMetrics().density * distance; + mFrontLayout.setCameraDistance(scale); + mBackLayout.setCameraDistance(scale); + return this; + } + + /** + * detail: 翻牌适配器 + * @author Ttt + */ + public interface Adapter { + + int getItemCount(); + + View getItemView( + Context context, + int position, + boolean isFrontView + ); + } + + // ========== + // = 内部方法 = + // ========== + + // 内部进入动画监听 + private final Animator.AnimatorListener mInnerInAnimListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + postDelayed(() -> { + // 进行翻转 View 显示状态 + ViewUtils.reverseVisibilitys(mFront, mFrontLayout, mBackLayout); + // 加载下一索引 View + loadView(!mFront); + }, 100); + } + + @Override + public void onAnimationCancel(Animator animation) { + + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }; + + /** + * 加载翻牌 View + * @param front 是否显示正面 View + * @return {@link FlipCardView} + */ + private FlipCardView loadView(final boolean front) { + if (mAdapter != null) { + int size = mAdapter.getItemCount(); + if (size == 0) return this; + int position = calculatePosition(size); + if (front) { + try { + mFrontLayout.removeAllViews(); + // 初始化 View 并添加 + View itemView = mAdapter.getItemView(getContext(), position, true); + if (itemView != null) { + mFrontLayout.addView(itemView); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "loadView - front"); + } + } else { + try { + mBackLayout.removeAllViews(); + // 初始化 View 并添加 + View itemView = mAdapter.getItemView(getContext(), position, false); + if (itemView != null) { + mBackLayout.addView(itemView); + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "loadView - back"); + } + } + } + return this; + } + + /** + * 计算待显示索引 + * @param size {@link Adapter#getItemCount()} + * @return 计算后的索引 + */ + private int calculatePosition(final int size) { + if (mPosition >= size) { + mPosition = 1; + return 0; + } else { + int position = mPosition; + mPosition++; + return position; + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/FlowLikeView.java b/lib/DevWidget/src/main/java/dev/widget/ui/FlowLikeView.java new file mode 100644 index 0000000000..244c539b53 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/FlowLikeView.java @@ -0,0 +1,434 @@ +package dev.widget.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TypeEvaluator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.DrawableRes; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import dev.utils.app.ResourceUtils; +import dev.widget.R; + +/** + * detail: 自定义点赞效果 View + * @author Ttt + *
+ *     通过 setDrawables、setDrawablesById 设置点赞漂浮 Icon
+ *     注意事项:
+ *     新添加的 View 属于居中下, 向下边距为子 View 总高度
+ *     如果 FlowLikeView layout_width 设置为非 wrap_content 则内部子 View 需要 android:layout_gravity="bottom|center"
+ *     这样点赞效果, 才会在子 View 上方显示
+ *     

+ * app:dev_animDuration="" + * app:dev_iconHeight="" + * app:dev_iconWidth="" + *
+ */ +public class FlowLikeView + extends FrameLayout { + + // 在 XML 布局文件中添加的子 View 的总高度 + private int mChildViewHeight; + // View 宽度、高度 + private int mViewWidth, mViewHeight; + // 添加动画 View Layout 参数 + private LayoutParams mLayoutParams; + // 用于产生随机数 + private Random mRandom; + // Icon 集合 + private List mDrawables; + // 点赞 Icon 宽高 + private int mIconWidth, mIconHeight; + // 点赞动画执行时间 + private long mAnimDuration = 2000L; + + public FlowLikeView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public FlowLikeView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public FlowLikeView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public FlowLikeView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + // 初始化操作 + mRandom = new Random(); + mDrawables = new ArrayList<>(); + + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mAnimDuration = a.getInt(R.styleable.DevWidget_dev_animDuration, 2000); + mIconWidth = a.getLayoutDimension(R.styleable.DevWidget_dev_iconWidth, 0); + mIconHeight = a.getLayoutDimension(R.styleable.DevWidget_dev_iconHeight, 0); + a.recycle(); + } + + // 重置 LayoutParams + resetLayoutParams(); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (mChildViewHeight <= 0) { + for (int i = 0, len = getChildCount(); i < len; i++) { + View childView = getChildAt(i); + measureChild(childView, widthMeasureSpec, heightMeasureSpec); + mChildViewHeight += childView.getMeasuredHeight(); + } + // 重置 LayoutParams + resetLayoutParams(); +// // 设置底部间距 ( 防止当 addView 显示与动画起始位置相差太远 ) +// mLayoutParams.bottomMargin = mChildViewHeight; + } + } + + @Override + protected void onSizeChanged( + int w, + int h, + int oldw, + int oldh + ) { + super.onSizeChanged(w, h, oldw, oldh); + mViewWidth = getWidth(); + mViewHeight = getHeight(); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * detail: 动画结束监听器, 用于释放无用的资源 + * @author Ttt + */ + private class AnimationEndListener + extends AnimatorListenerAdapter { + + private final View target; + + public AnimationEndListener(View target) { + this.target = target; + } + + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + removeView(target); + } + } + + /** + * detail: 动画曲线路径更新监听器, 用于动态更新动画作用对象的位置 + * @author Ttt + */ + private static class CurveUpdateLister + implements ValueAnimator.AnimatorUpdateListener { + + private final View target; + + public CurveUpdateLister(final View target) { + this.target = target; + } + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (target != null) { + // 获取当前动画运行的状态值, 使得动 x 画作用对象沿着曲线 ( 涉及贝塞儿曲线 ) 运动 + PointF pointF = (PointF) animation.getAnimatedValue(); + target.setX(pointF.x); + target.setY(pointF.y); + target.setAlpha(1 - animation.getAnimatedFraction()); + } + } + } + + /** + * detail: 自定义估值算法, 计算对象当前运动的具体位置 Point + * @author Ttt + */ + private static class CurveEvaluator + implements TypeEvaluator { + + private final PointF ctrlPointF; + + public CurveEvaluator(final PointF ctrlPointF) { + this.ctrlPointF = ctrlPointF; + } + + @Override + public PointF evaluate( + float fraction, + PointF startValue, + PointF endValue + ) { + float leftTime = 1.0F - fraction; + PointF resultPointF = new PointF(); + // 二阶贝塞儿曲线 + resultPointF.x = (float) Math.pow(leftTime, 2) * startValue.x + 2 * fraction * leftTime * ctrlPointF.x + + ((float) Math.pow(fraction, 2)) * endValue.x; + resultPointF.y = (float) Math.pow(leftTime, 2) * startValue.y + 2 * fraction * leftTime * ctrlPointF.y + + ((float) Math.pow(fraction, 2)) * endValue.y; + return resultPointF; + } + } + + /** + * 生成贝塞儿曲线的控制点 + * @param value 设置控制点 y 轴上取值区域 + * @return 控制点的 x y 坐标 + */ + private PointF generateCTRLPointF(final int value) { + PointF pointF = new PointF(); + pointF.x = mViewWidth / 2 - mRandom.nextInt(100); + pointF.y = mRandom.nextInt(mViewHeight / value); + return pointF; + } + + /** + * 生成曲线运动动画 + * @param target 动画作用 View + * @return 动画集合 + */ + private ValueAnimator generateCurveAnimation(final View target) { + CurveEvaluator evaluator = new CurveEvaluator(generateCTRLPointF(1)); + ValueAnimator valueAnimator = ValueAnimator.ofObject( + evaluator, + new PointF((mViewWidth - mIconWidth) / 2, mViewHeight - mChildViewHeight - mIconHeight), + new PointF((mViewWidth) / 2 + (mRandom.nextBoolean() ? 1 : -1) * mRandom.nextInt(100), 0) + ); + valueAnimator.setDuration(mAnimDuration); + valueAnimator.addUpdateListener(new CurveUpdateLister(target)); + valueAnimator.setTarget(target); + return valueAnimator; + } + + /** + * 生成进入动画 + * @param target 动画作用 View + * @return 动画集合 + */ + private AnimatorSet generateEnterAnimation(final View target) { + ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2F, 1F); + ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5F, 1F); + ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5F, 1F); + AnimatorSet enterAnimation = new AnimatorSet(); + enterAnimation.playTogether(alpha, scaleX, scaleY); + enterAnimation.setDuration(10); + enterAnimation.setTarget(target); + return enterAnimation; + } + + /** + * 开始动画处理 + * @param target 动画作用 View + */ + private void startAnimation(final View target) { + // 进入动画 + AnimatorSet enterAnimator = generateEnterAnimation(target); + // 路径动画 + ValueAnimator curveAnimator = generateCurveAnimation(target); + + // 设置动画集合, 先执行进入动画, 最后再执行运动曲线动画 + AnimatorSet finalAnimatorSet = new AnimatorSet(); + finalAnimatorSet.setTarget(target); + finalAnimatorSet.playSequentially(enterAnimator, curveAnimator); + finalAnimatorSet.addListener(new AnimationEndListener(target)); + finalAnimatorSet.start(); + } + + /** + * 重置 LayoutParams + */ + private void resetLayoutParams() { + mLayoutParams = new LayoutParams(mIconWidth, mIconHeight); + mLayoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; + // 设置底部间距 ( 防止当 addView 显示与动画起始位置相差太远 ) + mLayoutParams.bottomMargin = mChildViewHeight; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 点赞操作 + */ + public void like() { + int iconSizes = mDrawables.size(); + if (iconSizes != 0) { + ImageView likeView = new ImageView(getContext()); + likeView.setImageDrawable(mDrawables.get(mRandom.nextInt(iconSizes))); + likeView.setLayoutParams(mLayoutParams); + + addView(likeView); + startAnimation(likeView); + } + } + + /** + * 获取 Icon 集合 + * @return Icon 集合 + */ + public List getDrawables() { + return mDrawables; + } + + /** + * 设置 Icon 集合 + * @param drawables Icon 集合 + * @return {@link FlowLikeView} + */ + public FlowLikeView setDrawables(final List drawables) { + this.mDrawables.clear(); + if (drawables != null) { + this.mDrawables.addAll(drawables); + // 如果没有设置 Icon 宽高则获取传入的 Icon 集合宽高 + if (mIconWidth == 0 || mIconHeight == 0) { + for (Drawable drawable : drawables) { + if (drawable != null) { + mIconWidth = drawable.getIntrinsicWidth(); + mIconHeight = drawable.getIntrinsicHeight(); + resetLayoutParams(); + break; + } + } + } + } + return this; + } + + /** + * 设置 Icon 集合 + * @param drawableIds Icon 集合 + * @return {@link FlowLikeView} + */ + public FlowLikeView setDrawablesById(@DrawableRes final int... drawableIds) { + if (drawableIds != null) { + List lists = new ArrayList<>(); + for (int drawableId : drawableIds) { + Drawable drawable = ResourceUtils.getDrawable(drawableId); + lists.add(drawable); + } + return setDrawables(lists); + } + return this; + } + + /** + * 获取点赞 Icon 宽度 + * @return 点赞 Icon 宽度 + */ + public int getIconWidth() { + return mIconWidth; + } + + /** + * 设置点赞 Icon 宽度 + * @param iconWidth 点赞 Icon 宽度 + * @return {@link FlowLikeView} + */ + public FlowLikeView setIconWidth(final int iconWidth) { + this.mIconWidth = iconWidth; + resetLayoutParams(); + return this; + } + + /** + * 获取点赞 Icon 高度 + * @return 点赞 Icon 高度 + */ + public int getIconHeight() { + return mIconHeight; + } + + /** + * 设置点赞 Icon 高度 + * @param iconHeight 点赞 Icon 高度 + * @return {@link FlowLikeView} + */ + public FlowLikeView setIconHeight(final int iconHeight) { + this.mIconHeight = iconHeight; + resetLayoutParams(); + return this; + } + + /** + * 获取点赞动画执行时间 + * @return 点赞动画执行时间 + */ + public long getAnimDuration() { + return mAnimDuration; + } + + /** + * 设置点赞动画执行时间 + * @param animDuration 点赞动画执行时间 + * @return {@link FlowLikeView} + */ + public FlowLikeView setAnimDuration(final long animDuration) { + this.mAnimDuration = animDuration; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/LoadProgressBar.java b/lib/DevWidget/src/main/java/dev/widget/ui/LoadProgressBar.java new file mode 100644 index 0000000000..6012a2e71c --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/LoadProgressBar.java @@ -0,0 +1,643 @@ +package dev.widget.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.ColorInt; + +import dev.utils.app.SizeUtils; +import dev.utils.app.TextViewUtils; +import dev.widget.R; + +/** + * detail: 自定义加载 ProgressBar 样式 View + * @author Ttt + *
+ *     内外圆环 + 数字 + 无扇形
+ *     view.setProgressStyle(LoadProgressBar.ProgressStyle.RINGS)
+ *              .setOuterRingWidth(SizeUtils.dp2px(5)) // 内环宽度
+ *              .setOuterRingColor(ResourceUtils.getColor(R.color.khaki)) // 内环颜色
+ *              .setProgressColor(ResourceUtils.getColor(R.color.color_88)) // 进度颜色
+ *              .setCanvasNumber(true); // 是否绘制数字
+ *     
+ *     

+ * 扇形 + 数字 + 无内外圆环 + * view.setProgressStyle(CustomProgressBar.ProgressStyle.FAN_SHAPED) + * .setProgressColor(ResourceUtils.getColor(R.color.sky_blue)) // 进度颜色 + * .setCanvasNumber(true); // 是否绘制数字 + * + *

+ * 扇形 + 数字 + 外圆环 + * view.setProgressStyle(LoadProgressBar.ProgressStyle.ARC_FAN_SHAPED) + * .setOuterRingWidth(SizeUtils.dp2px(1)) // 内环宽度 + * .setOuterRingColor(Color.RED) // 内环颜色 + * .setProgressColor(ResourceUtils.getColor(R.color.mediumturquoise)) // 进度颜色 + * .setNumberTextColor(Color.parseColor("#FB7D00")) // 字体颜色 + * .setCanvasNumber(true); // 是否绘制数字 + * + *

+ * 单独字体 + * view.setProgressStyle(CustomProgressBar.ProgressStyle.NUMBER) + * .setNumberTextSize(20F) // 字体大小 + * .setNumberTextColor(ResourceUtils.getColor(R.color.deeppink)); // 字体颜色 + * + *

+ * app:dev_canvasNumber="" + * app:dev_progressColor="" + * app:dev_outerRingColor="" + * app:dev_insideCircleWidth="" + * app:dev_outerRingWidth="" + * app:dev_numberTextSize="" + * app:dev_numberTextColor="" + * app:dev_progressStyle="" + *
+ */ +public class LoadProgressBar + extends View { + + // 画笔 + private Paint mPaint; + // 字体画笔 + private final Paint mTextPaint = new Paint(); + // 最大进度 + private int mMax = 100; + // 当前进度 + private int mProgress = 0; + // 进度条样式 + private ProgressStyle mProgressStyle = ProgressStyle.RINGS; + // 进度条颜色 + private int mProgressColor; + // 外环进度条颜色 + private int mOuterRingColor; + // 内环进度条宽度 + private float mInsideCircleWidth; + // 外环进度条宽度 + private float mOuterRingWidth; + // 是否绘制数字 + private boolean mCanvasNumber = false; + // 绘制的字体大小 + private float mNumberTextSize; + // 绘制的数字颜色 + private int mNumberTextColor; + + public LoadProgressBar(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public LoadProgressBar( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public LoadProgressBar( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public LoadProgressBar( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + initialize(); // 默认初始化配置 + + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mCanvasNumber = a.getBoolean(R.styleable.DevWidget_dev_canvasNumber, mCanvasNumber); + mProgressColor = a.getColor(R.styleable.DevWidget_dev_progressColor, mProgressColor); + mOuterRingColor = a.getColor(R.styleable.DevWidget_dev_outerRingColor, mOuterRingColor); + mInsideCircleWidth = a.getLayoutDimension(R.styleable.DevWidget_dev_insideCircleWidth, (int) mInsideCircleWidth); + mOuterRingWidth = a.getLayoutDimension(R.styleable.DevWidget_dev_outerRingWidth, (int) mOuterRingWidth); + mNumberTextSize = a.getDimensionPixelSize(R.styleable.DevWidget_dev_numberTextSize, (int) mNumberTextSize); + mNumberTextColor = a.getColor(R.styleable.DevWidget_dev_numberTextColor, mNumberTextColor); + int progressStyle = a.getInt(R.styleable.DevWidget_dev_progressStyle, 0); + a.recycle(); + + switch (progressStyle) { + case 0: + mProgressStyle = ProgressStyle.RINGS; + break; + case 1: + mProgressStyle = ProgressStyle.FAN_SHAPED; + break; + case 2: + mProgressStyle = ProgressStyle.ARC_FAN_SHAPED; + break; + case 3: + mProgressStyle = ProgressStyle.NUMBER; + break; + default: + mProgressStyle = ProgressStyle.RINGS; + break; + } + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化方法 + * @return {@link LoadProgressBar} + */ + private LoadProgressBar initialize() { + // 初始化画笔 + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + // 设置进度颜色值 ( 白色 ) + mProgressColor = Color.rgb(255, 255, 255); // Color.WHITE + // 设置外环颜色 + mOuterRingColor = Color.argb(30, 255, 255, 255); + // 设置外环进度条的宽度 + mOuterRingWidth = SizeUtils.dp2px(getContext(), 4.0F); + // 设置绘制的数字颜色 + mNumberTextColor = Color.BLACK; + // 初始化处理 + mNumberTextSize = 0; + mInsideCircleWidth = 0; + mCanvasNumber = false; + return this; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + // 是否绘制数字 + boolean isDrawNumber = mCanvasNumber; + // 防止没有设置样式 + if (mProgressStyle == null) mProgressStyle = ProgressStyle.RINGS; + // 属于默认类型 + if (mProgressStyle == ProgressStyle.RINGS) { // 绘制圆环 + float centre = getWidth() / 2; // 获取圆心的 x 坐标 + float radius = (centre - mOuterRingWidth / 2); // 圆环的半径 + mPaint.setColor(mOuterRingColor); // 设置圆环的颜色 + mPaint.setStyle(Paint.Style.STROKE); // 设置空心 + mPaint.setStrokeWidth(mOuterRingWidth); // 设置圆环的宽度 + mPaint.setAntiAlias(true); // 消除锯齿 + canvas.drawCircle(centre, centre, radius, mPaint); // 画出圆环 + + // 设置进度是实心还是空心 + mPaint.setStrokeWidth(mOuterRingWidth); // 设置圆环的宽度 + mPaint.setStyle(Paint.Style.STROKE); // 设置空心 + mPaint.setColor(mProgressColor); // 设置进度的颜色 + mPaint.setAntiAlias(true); // 消除锯齿 + // 用于定义的圆弧的形状和大小的界限 + RectF oval = new RectF( + centre - radius, centre - radius, + centre + radius, centre + radius + ); + // 根据进度画圆弧 ( 0 从右边开始 , 270 从上边开始 ) + canvas.drawArc( + oval, 270, 360 * mProgress / mMax, + false, mPaint + ); + } else if (mProgressStyle == ProgressStyle.FAN_SHAPED) { // 绘制扇形 + int centre = getWidth() / 2; // 获取圆心的 x 坐标 + int radius = centre; // 扇形的半径 + mPaint.setStrokeWidth(centre); // 设置扇形的宽度 + mPaint.setStyle(Paint.Style.FILL); // 设置实心 + mPaint.setColor(mProgressColor); // 设置进度的颜色 + mPaint.setAntiAlias(true); // 消除锯齿 + // 绘制扇形 + RectF ovalBorder = new RectF( + centre - radius, centre - radius, + centre + radius, centre + radius + ); + canvas.drawArc( + ovalBorder, 270, 360 * mProgress / mMax, + true, mPaint + ); + } else if (mProgressStyle == ProgressStyle.ARC_FAN_SHAPED) { + // 判断是否没设置内圈宽度, 如果没有设置, 则设置为贴着外圈 + if (mInsideCircleWidth <= 0) { + // 设置内圈大小 + mInsideCircleWidth = getWidth() - mOuterRingWidth * 2; + } + // 绘制圆环 + 扇形 + float centre = getWidth() / 2; // 获取圆心的 x 坐标 + float radius = (centre - mOuterRingWidth / 2); // 圆环的半径 + // 设置进度是实心还是空心 + mPaint.setStrokeWidth(mOuterRingWidth); // 设置圆环的宽度 + mPaint.setStyle(Paint.Style.STROKE); // 设置空心 + mPaint.setColor(mOuterRingColor); // 设置进度的颜色 + mPaint.setAntiAlias(true); // 消除锯齿 + // 绘制圆外环 + RectF ovalBorder = new RectF( + centre - radius, centre - radius, + centre + radius, centre + radius + ); + canvas.drawArc(ovalBorder, 270, 360, false, mPaint); + // 绘制椭圆 + mPaint.setStrokeWidth(mInsideCircleWidth); // 设置圆的宽度 + mPaint.setStyle(Paint.Style.FILL); // 设置实心 + mPaint.setColor(mProgressColor); // 设置进度的颜色 + // 获取圆心的 x 坐标 + centre = (mInsideCircleWidth / 2); + // 圆的半径 + radius = centre; + // 边距 + float margin = (getWidth() - mInsideCircleWidth) / 2; + // 绘制扇形 + RectF oval = new RectF( + margin + centre - radius, margin + centre - radius, + margin + centre + radius, margin + centre + radius + ); + // 根据进度画圆弧 + canvas.drawArc( + oval, 270, (360 * mProgress) / mMax, + true, mPaint + ); + } else if (mProgressStyle == ProgressStyle.NUMBER) { + // 绘制的内容 + String progressText = (mProgress * 100 / mMax) + "%"; + // 判断是否存在计算的字体大小 + if (mNumberTextSize <= 0) { + int tempWidth = getWidth(); + // 计算字体大小 + mNumberTextSize = TextViewUtils.reckonTextSizeByWidth( + tempWidth, mTextPaint, + SizeUtils.px2sp(getContext(), tempWidth), "100%" + ); + } + // 绘制进度文本 + drawProgressText(canvas, mNumberTextSize, mNumberTextColor, progressText); + // 已经绘制数字, 则不绘制 + isDrawNumber = false; + } + // 判断是否绘制内容 + if (isDrawNumber) { + // 设置实心 + mPaint.setStyle(Paint.Style.FILL); + // 判断样式 + switch (mProgressStyle) { + case RINGS: // 圆环 + case FAN_SHAPED: // 扇形 + // 判断是否存在计算的字体大小 + if (mNumberTextSize <= 0) { + int tempWidth = getWidth() / 3 * 2; + // 计算字体大小 + mNumberTextSize = TextViewUtils.reckonTextSizeByWidth( + tempWidth, mTextPaint, + SizeUtils.px2sp(getContext(), tempWidth), "100%" + ); + } + // 绘制进度文本 + drawProgressText( + canvas, mNumberTextSize, mNumberTextColor, + (mProgress * 100 / mMax) + "%" + ); + break; + case ARC_FAN_SHAPED: + // 判断是否存在计算的字体大小 + if (mNumberTextSize <= 0) { + // 计算字体大小 + if (mInsideCircleWidth < 0F) { + int tempWidth = getWidth() / 3 * 2; + // 计算字体大小 + mNumberTextSize = TextViewUtils.reckonTextSizeByWidth( + tempWidth, mTextPaint, + SizeUtils.px2sp(getContext(), tempWidth), "100%" + ); + } else { + int tempWidth = (int) mInsideCircleWidth / 3 * 2; + // 计算字体大小 + mNumberTextSize = TextViewUtils.reckonTextSizeByWidth( + tempWidth, mTextPaint, + SizeUtils.px2sp(getContext(), tempWidth), "100%" + ); + } + } + // 绘制进度文本 + drawProgressText( + canvas, mNumberTextSize, mNumberTextColor, + (mProgress * 100 / mMax) + "%" + ); + break; + } + } + } + + /** + * 绘制进度文本 + * @param canvas {@link Canvas} + * @param textSize 字体大小 + * @param textColor 字体颜色 + * @param progressText 绘制文本 + */ + private void drawProgressText( + final Canvas canvas, + final float textSize, + @ColorInt final int textColor, + final String progressText + ) { + drawProgressText(canvas, getWidth(), getHeight(), textSize, textColor, progressText); + } + + /** + * 绘制进度文本 + * @param canvas {@link Canvas} + * @param width 对应的宽度 ( 居中使用 ) + * @param height 对应的高度 ( 居中使用 ) + * @param textSize 字体大小 + * @param textColor 字体颜色 + * @param progressText 绘制文本 + */ + private void drawProgressText( + final Canvas canvas, + final int width, + final int height, + final float textSize, + @ColorInt final int textColor, + final String progressText + ) { + mPaint.setColor(textColor); // 设置进度的颜色 + mPaint.setTextSize(textSize); // 设置字体大小 + // 获取字体高度 + Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt(); + // 获取字体内容宽度 + float tWidth = mPaint.measureText(progressText); + // 计算字体内容高度 + float fontTotalHeight = fontMetrics.bottom - fontMetrics.top; + float offY = fontTotalHeight / 2 - fontMetrics.bottom; + // 设置左、上边距 + float x = (width - tWidth) / 2; + float y = height / 2 + offY; + // 绘制内容 + canvas.drawText(progressText, x, y, mPaint); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 重置参数 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar reset() { + return initialize(); + } + + /** + * 获取最大值 + * @return 最大值 + */ + public synchronized int getMax() { + return mMax; + } + + /** + * 设置最大值 + * @param max 最大值 + * @return {@link LoadProgressBar} + */ + public synchronized LoadProgressBar setMax(final int max) { + if (max <= 0) throw new IllegalArgumentException("max not less than 0"); + this.mMax = max; + return this; + } + + /** + * 获取当前进度 + * @return 当前进度 + */ + public synchronized int getProgress() { + return mProgress; + } + + /** + * 设置当前进度 + * @param progress 当前进度 + * @return {@link LoadProgressBar} + */ + public synchronized LoadProgressBar setProgress(int progress) { + if (progress < 0) throw new IllegalArgumentException("progress not less than 0"); + if (progress > mMax) { + progress = mMax; + } + this.mProgress = progress; + postInvalidate(); + return this; + } + + /** + * 获取进度条颜色 + * @return 进度条颜色 + */ + public int getProgressColor() { + return mProgressColor; + } + + /** + * 设置进度条颜色 + * @param progressColor 进度条颜色 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setProgressColor(@ColorInt final int progressColor) { + this.mProgressColor = progressColor; + return this; + } + + /** + * 获取外环进度条颜色 + * @return 外环进度条颜色 + */ + public int getOuterRingColor() { + return mOuterRingColor; + } + + /** + * 设置外环进度条颜色 + * @param outerRingColor 外环进度条颜色 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setOuterRingColor(@ColorInt final int outerRingColor) { + this.mOuterRingColor = outerRingColor; + return this; + } + + /** + * 获取内环进度条宽度 + * @return 内环进度条宽度 + */ + public float getInsideCircleWidth() { + return mInsideCircleWidth; + } + + /** + * 设置内环进度条宽度 + * @param insideCircleWidth 内环进度条宽度 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setInsideCircleWidth(final float insideCircleWidth) { + this.mInsideCircleWidth = Math.abs(insideCircleWidth); + return this; + } + + /** + * 获取外环进度条宽度 + * @return 外环进度条宽度 + */ + public float getOuterRingWidth() { + return mOuterRingWidth; + } + + /** + * 设置外环进度条宽度 + * @param outerRingWidth 外环进度条宽度 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setOuterRingWidth(final float outerRingWidth) { + this.mOuterRingWidth = Math.abs(outerRingWidth); + return this; + } + + /** + * 是否绘制数字 + * @return {@code true} yes, {@code false} no + */ + public boolean isCanvasNumber() { + return mCanvasNumber; + } + + /** + * 设置是否绘制数字 + * @param canvasNumber {@code true} yes, {@code false} no + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setCanvasNumber(final boolean canvasNumber) { + this.mCanvasNumber = canvasNumber; + return this; + } + + /** + * 获取绘制的字体大小 + * @return 绘制的字体大小 + */ + public float getNumberTextSize() { + return mNumberTextSize; + } + + /** + * 设置绘制的字体大小 + * @param numberTextSize 绘制的字体大小 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setNumberTextSize(final float numberTextSize) { + this.mNumberTextSize = numberTextSize; + return this; + } + + /** + * 获取绘制的数字颜色 + * @return 绘制的数字颜色 + */ + public int getNumberTextColor() { + return mNumberTextColor; + } + + /** + * 设置绘制的数字颜色 + * @param numberTextColor 绘制的数字颜色 + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setNumberTextColor(@ColorInt final int numberTextColor) { + this.mNumberTextColor = numberTextColor; + return this; + } + + /** + * 获取进度条样式 + * @return {@link ProgressStyle} + */ + public ProgressStyle getProgressStyle() { + return mProgressStyle; + } + + /** + * 设置进度条样式 + * @param progressStyle {@link ProgressStyle} + * @return {@link LoadProgressBar} + */ + public LoadProgressBar setProgressStyle(final ProgressStyle progressStyle) { + this.mProgressStyle = (progressStyle == null) ? ProgressStyle.RINGS : progressStyle; + return this; + } + + // ============ + // = 进度条样式 = + // ============ + + /** + * detail: 进度条样式 + * @author Ttt + */ + public enum ProgressStyle { + + // 圆环 + RINGS, + + // 扇形进度样式 + FAN_SHAPED, + + // 圆环扇形进度样式 + ARC_FAN_SHAPED, + + // 绘制数字 + NUMBER + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/RadiusLayout.java b/lib/DevWidget/src/main/java/dev/widget/ui/RadiusLayout.java new file mode 100644 index 0000000000..d06df013ea --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/RadiusLayout.java @@ -0,0 +1,262 @@ +package dev.widget.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.os.Build; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import dev.widget.utils.RadiusAttrs; + +/** + * detail: 自定义圆角 View + * @author Ttt + *
+ *     注意事项:
+ *     该 View 会有锯齿情况
+ *     如果需要裁剪 View 且四个角落都统一圆角可以使用 CardView 不增加阴影
+ *     

+ * 区别于 {@link dev.widget.ui.round.RoundDrawable} 属于设置 Drawable + * 这个是裁剪 View 为圆角效果 + *

+ * app:dev_radius="" + * app:dev_radiusLeftTop="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusRightTop="" + * app:dev_radiusRightBottom="" + *
+ */ +public class RadiusLayout + extends FrameLayout { + + private RadiusAttrs mRadiusAttrs; + + public RadiusLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RadiusLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RadiusLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public RadiusLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + mRadiusAttrs = new RadiusAttrs(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); + } + + @Override + protected void onSizeChanged( + int w, + int h, + int oldw, + int oldh + ) { + super.onSizeChanged(w, h, oldw, oldh); + mRadiusAttrs.onSizeChanged(w, h); + } + + @Override + public void draw(Canvas canvas) { + canvas.save(); + canvas.clipPath(mRadiusAttrs.getPath()); + super.draw(canvas); + canvas.restore(); + } + + @Override + protected Parcelable onSaveInstanceState() { + return mRadiusAttrs.onSaveInstanceState(super.onSaveInstanceState()); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + super.onRestoreInstanceState(mRadiusAttrs.onRestoreInstanceState(state)); + mRadiusAttrs.onSizeChanged(getWidth(), getHeight()); + } + + // =========== + // = get/set = + // =========== + + /** + * 设置圆角值 + * @param radius 圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadius(final float radius) { + mRadiusAttrs.setRadius(radius); + postInvalidate(); + return this; + } + + /** + * 设置左上圆角值 + * @param radiusLeftTop 左上圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusLeftTop(final float radiusLeftTop) { + mRadiusAttrs.setRadiusLeftTop(radiusLeftTop); + postInvalidate(); + return this; + } + + /** + * 设置左下圆角值 + * @param radiusLeftBottom 左下圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusLeftBottom(final float radiusLeftBottom) { + mRadiusAttrs.setRadiusLeftBottom(radiusLeftBottom); + postInvalidate(); + return this; + } + + /** + * 设置右上圆角值 + * @param radiusRightTop 右上圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusRightTop(final float radiusRightTop) { + mRadiusAttrs.setRadiusRightTop(radiusRightTop); + postInvalidate(); + return this; + } + + /** + * 设置右下圆角值 + * @param radiusRightBottom 右下圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusRightBottom(final float radiusRightBottom) { + mRadiusAttrs.setRadiusRightBottom(radiusRightBottom); + postInvalidate(); + return this; + } + + // = + + /** + * 设置左上、左下圆角值 + * @param radiusLeft 左边圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusLeft(final int radiusLeft) { + mRadiusAttrs.setRadiusLeft(radiusLeft); + postInvalidate(); + return this; + } + + /** + * 设置右上、右下圆角值 + * @param radiusRight 右边圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusRight(final int radiusRight) { + mRadiusAttrs.setRadiusRight(radiusRight); + postInvalidate(); + return this; + } + + /** + * 设置左上、右上圆角值 + * @param radiusTop 上边圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusTop(final int radiusTop) { + mRadiusAttrs.setRadiusTop(radiusTop); + postInvalidate(); + return this; + } + + /** + * 设置左下、右下圆角值 + * @param radiusBottom 下边圆角值 + * @return {@link RadiusLayout} + */ + public RadiusLayout setRadiusBottom(final int radiusBottom) { + mRadiusAttrs.setRadiusBottom(radiusBottom); + postInvalidate(); + return this; + } + + // = + + /** + * 获取圆角值 + * @return 圆角值 + */ + public float getRadius() { + return mRadiusAttrs.getRadius(); + } + + /** + * 获取左上圆角值 + * @return 左上圆角值 + */ + public float getRadiusLeftTop() { + return mRadiusAttrs.getRadiusLeftTop(); + } + + /** + * 获取左下圆角值 + * @return 左下圆角值 + */ + public float getRadiusLeftBottom() { + return mRadiusAttrs.getRadiusLeftBottom(); + } + + /** + * 获取右上圆角值 + * @return 右上圆角值 + */ + public float getRadiusRightTop() { + return mRadiusAttrs.getRadiusRightTop(); + } + + /** + * 获取右下圆角值 + * @return 右下圆角值 + */ + public float getRadiusRightBottom() { + return mRadiusAttrs.getRadiusRightBottom(); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/ResizableImageView.java b/lib/DevWidget/src/main/java/dev/widget/ui/ResizableImageView.java new file mode 100644 index 0000000000..0e026ed547 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/ResizableImageView.java @@ -0,0 +1,99 @@ +package dev.widget.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatImageView; + +import dev.utils.app.ViewUtils; + +/** + * detail: 自动同比放大 ImageView + * @author Ttt + *
+ *     铺满宽度, 高度根据比例自动缩放
+ *     如需要圆角, 推荐使用 CardView 包装进行裁剪 View, 减少因全图展示对 Bitmap 操作出现 OOM
+ * 
+ */ +public class ResizableImageView + extends AppCompatImageView { + + // 缩放后的高度 + private int mZoomHeight; + // 宽高监听 + private ViewUtils.OnWHListener mWHListener; + + public ResizableImageView(Context context) { + super(context); + } + + public ResizableImageView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + } + + public ResizableImageView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + Drawable bg = getDrawable(); + if (bg != null) { + int width = MeasureSpec.getSize(widthMeasureSpec); + // 高度根据使得图片的宽度充满屏幕计算而得 + int height = (int) Math.ceil( + (float) width * (float) bg.getIntrinsicHeight() / (float) bg.getIntrinsicWidth() + ); + // 保存缩放后的高度 + this.mZoomHeight = height; + setMeasuredDimension(width, height); + // 触发回调 + if (mWHListener != null) { + mWHListener.onWidthHeight(this, width, height); + } + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取缩放后的高度 + * @return 缩放后的高度 + */ + public int getZoomHeight() { + return mZoomHeight; + } + + /** + * 获取宽高监听事件 + * @return {@link dev.utils.app.ViewUtils.OnWHListener} + */ + public ViewUtils.OnWHListener getWHListener() { + return mWHListener; + } + + /** + * 设置宽高监听事件 + * @param whListener {@link dev.utils.app.ViewUtils.OnWHListener} + * @return {@link ResizableImageView} + */ + public ResizableImageView setWHListener(final ViewUtils.OnWHListener whListener) { + this.mWHListener = whListener; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/ScanShapeView.java b/lib/DevWidget/src/main/java/dev/widget/ui/ScanShapeView.java new file mode 100644 index 0000000000..a21e4f7f91 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/ScanShapeView.java @@ -0,0 +1,2170 @@ +package dev.widget.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.SweepGradient; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.ColorInt; +import androidx.core.graphics.drawable.DrawableCompat; + +import java.util.Arrays; + +import dev.utils.LogPrintUtils; +import dev.utils.app.SizeUtils; +import dev.widget.R; + +/** + * detail: 自定义扫描 ( 二维码 / AR ) 效果形状 View + * @author Ttt + */ +public class ScanShapeView + extends View { + + // 日志 TAG + private final String TAG = ScanShapeView.class.getSimpleName(); + + /** + * detail: 形状类型 + * @author Ttt + */ + public enum Shape { + + // 正方形 + Square, + + // 六边形 + Hexagon, // 大小以宽度为准 ( 正方形 ) + + // 环形 + Annulus, // 以最小的为基准 + } + + /** + * detail: 自定义 CornerPathEffect + * @author Ttt + *
+     *     便于获取拐角圆角大小
+     * 
+ */ + public static final class CornerEffect + extends CornerPathEffect { + + // 拐角圆角大小 + private final float radius; + + public CornerEffect(float radius) { + super(radius); + this.radius = radius; + } + + public float getRadius() { + return radius; + } + } + + // 形状类型 ( 默认正方形 ) + private Shape mShapeType = Shape.Square; + // 是否需要重新处理动画 + private boolean mReAnim = true; + // 默认通用 DP + private float mDFCommonDP; + // 空白画笔 ( 绘制边框使用, 不绘制边框时 ) + private final Paint mEmptyPaint = new Paint(); + // 是否设置拐角圆角 ( 圆润 ) + private CornerEffect mCornerEffect = new CornerEffect(10); + + // ========== + // = 背景相关 = + // ========== + + // 是否绘制背景 + private boolean mDrawBackground = true; + // 绘制背景画笔 + private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + // =============== + // = 绘制扫描 View = + // =============== + + // 是否绘制扫描区域边框 + private boolean mDrawBorder = true; + // 扫描边框 View + private final Paint mBorderPaint = new Paint(); + // 边框边距 + private float mBorderMargin = 0; + // 边框宽度 + private float mBorderWidth; + // 扫描区域块 ( 默认 700x700 ) - 绘制的宽 (x), 高 (y) + private final PointF mPointF = new PointF(700, 700); + + // ==================== + // = 正方形 ( 边框相关 ) = + // ==================== + + // 正方形描边 ( 边框 ) 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 + private int mBorderToSquare = 0; + // 正方形描边 ( 边框 ) 宽度 + private float mBorderWidthToSquare; + // 每个角的点距离 ( 长度 ) 正方形四个角落区域 + private float mTriAngleLength; + // 是否特殊处理 + private boolean mSpecialToSquare = false; + + // ========== + // = 环形相关 = + // ========== + + // 环形画笔, [] { 0 - 外环, 1 - 中间环, 2 - 外环 } + private final Paint[] mAnnulusPaints = new Paint[3]; + // 三个环宽度 + private float[] mAnnulusWidths = new float[3]; + // 三个环长度 + private int[] mAnnulusLengths = new int[]{20, 30, 85}; + // 三个环是否绘制 + private boolean[] mAnnulusDraws = new boolean[]{true, true, true}; + // 三个环分别角度 + private final int[] mAnnulusAngles = new int[]{0, -15, 0}; + // 三个环颜色值 + private int[] mAnnulusColors = new int[]{Color.BLUE, Color.RED, Color.WHITE}; + // 三个环之间的边距 + private float[] mAnnulusMargins = new float[3]; + + // ========== + // = 动画相关 = + // ========== + + // 是否绘制动画 + private boolean mDrawAnim = true; + // 是否自动开启动画 + private boolean mAutoAnim = true; + + // ===================== + // = 正方形 ( 动画 ) 相关 = + // ===================== + + // 正方形扫描动画 对象 + private ValueAnimator mAnimToSquare; + // 正方形扫描动画速度 ( 毫秒 ) + private long mLineDurationToSquare = 10L; + // 正方形线条画笔 + private final Paint mLinePaintToSquare = new Paint(Paint.ANTI_ALIAS_FLAG); + // 扫描线条 Bitmap + private Bitmap mBitmapToSquare; + // 线条偏离值 + private int mLineOffsetToSquare = 0; + // 线条向上 ( 下 ) 边距 + private float mLineMarginTopToSquare = 0F; + // 线条向左 ( 右 ) 边距 + private float mLineMarginLeftToSquare = 0F; + // 线条颜色 + private int mLineColorToSquare = 0; + + // ===================== + // = 六边形 ( 动画 ) 相关 = + // ===================== + + // 边框外动画 对象 + private ValueAnimator mAnimToHexagon; + // 六边形线条画笔 + private final Paint mLinePaintToHexagon = new Paint(); + // 六边形线条路径 + private Path mLinePathToHexagon = new Path(); + // 六边形线条 Canvas( 动画中实时绘制计算路径 ) + private Canvas mCanvasToHexagon; + // 六边形线条 绘制出来的 Bitmap + private Bitmap mBitmapToHexagon; + // 线条中心点 + private float mCenterToHexagon = 0; + // 线条宽度 + private final float mLineWidthToHexagon = 4F; + // 绘制线条边距 ( 针对绘制区域 ) + private float mLineMarginToHexagon = 20F; + // 动画方向 ( 六边形线条 ) - true = 左, false = 右 + private boolean mLineAnimDirection = true; + + // =================== + // = 环形 ( 动画 ) 相关 = + // =================== + + // 环形动画 对象 + private ValueAnimator mAnimToAnnulus; + // 动画效果临时变量 + private float mAnimOffsetToAnnulus = 0F; + // 是否达到偏移值最大值 + private boolean mOffsetMaxToAnnulus = true; + // 线条向上 ( 下 ) 边距 + private float mLineOffsetToAnnulus = 0F; + // 扫描线条 Bitmap + private Bitmap mBitmapToAnnulus; + // 线条颜色 + private int mLineColorToAnnulus = 0; + // 绘制扫描线条偏移速度 + private float mLineOffsetSpeedToAnnulus = 4F; + + // ========== + // = 构造函数 = + // ========== + + public ScanShapeView(Context context) { + super(context); + initialize(); + } + + public ScanShapeView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initialize(); + } + + public ScanShapeView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initialize(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public ScanShapeView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initialize(); + } + + /** + * 初始化处理 + */ + private void initialize() { + mDFCommonDP = SizeUtils.dp2px(getContext(), 5); + mBorderWidth = SizeUtils.dp2px(getContext(), 2); + mBorderWidthToSquare = SizeUtils.dp2px(getContext(), 1); + mTriAngleLength = SizeUtils.dp2pxf(getContext(), 20); + + mAnnulusWidths[0] = SizeUtils.dp2px(getContext(), 3); + mAnnulusWidths[1] = SizeUtils.dp2px(getContext(), 7); + mAnnulusWidths[2] = SizeUtils.dp2px(getContext(), 7); + + mAnnulusMargins[0] = SizeUtils.dp2px(getContext(), 7); + mAnnulusMargins[1] = SizeUtils.dp2px(getContext(), 7); + mAnnulusMargins[2] = SizeUtils.dp2px(getContext(), 7); + + // 设置背景颜色 ( 黑色 百分之 40 透明度 ) #66000000 + mBackgroundPaint.setColor(Color.argb(102, 0, 0, 0)); + + // 扫描边框 View 画笔 + mBorderPaint.setStrokeWidth(mBorderWidth); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setColor(Color.WHITE); + mBorderPaint.setStyle(Paint.Style.STROKE); + + // 空白画笔 ( 绘制边框使用, 不绘制边框时 ) + mEmptyPaint.setStrokeWidth(0); + mEmptyPaint.setAntiAlias(true); + mEmptyPaint.setColor(Color.TRANSPARENT); + mEmptyPaint.setStyle(Paint.Style.STROKE); + + // ======= + // = 环形 = + // ======= + + // 外环 + mAnnulusPaints[0] = new Paint(); + mAnnulusPaints[0].setColor(mAnnulusColors[0]); + mAnnulusPaints[0].setAntiAlias(true); + mAnnulusPaints[0].setStyle(Paint.Style.STROKE); + mAnnulusPaints[0].setStrokeWidth(mAnnulusWidths[0]); + // 中间环 + mAnnulusPaints[1] = new Paint(); + mAnnulusPaints[1].setColor(mAnnulusColors[1]); + mAnnulusPaints[1].setAntiAlias(true); + mAnnulusPaints[1].setStyle(Paint.Style.STROKE); + mAnnulusPaints[1].setStrokeWidth(mAnnulusWidths[1]); + // 内环 + mAnnulusPaints[2] = new Paint(); + mAnnulusPaints[2].setColor(mAnnulusColors[2]); + mAnnulusPaints[2].setAntiAlias(true); + mAnnulusPaints[2].setStyle(Paint.Style.STROKE); + mAnnulusPaints[2].setStrokeWidth(mAnnulusWidths[2]); + + // ============= + // = 动画相关画笔 = + // ============= + + // 六边形线条画笔 + mLinePaintToHexagon.setStrokeWidth(mLineWidthToHexagon); + mLinePaintToHexagon.setAntiAlias(true); + mLinePaintToHexagon.setStyle(Paint.Style.STROKE); + + // 统一处理画笔拐角 + handlerCornerPathEffect(); + + // 加载正方形扫描线条 + mBitmapToSquare = ((BitmapDrawable) (getResources().getDrawable( + R.drawable.dev_scan_line)) + ).getBitmap(); + + // 加载圆环扫描 + mBitmapToAnnulus = ((BitmapDrawable) (getResources().getDrawable( + R.drawable.dev_scan_line)) + ).getBitmap(); + + // 重置动画处理 + initAnim(); + } + + /** + * 处理拐角 + */ + private void handlerCornerPathEffect() { + // 设置绘制边框拐角 + mBorderPaint.setPathEffect(mCornerEffect); + // 判断是否加入拐角 + switch (mShapeType) { + case Square: // 正方形 + mBackgroundPaint.setPathEffect(mCornerEffect); + break; + default: // 其他不设置拐角 + mBackgroundPaint.setPathEffect(null); + break; + } + // 设置绘制六边形线条拐角 + mLinePaintToHexagon.setPathEffect(mCornerEffect); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // ================ + // = 判断是否绘制背景 = + // ================ + + if (mDrawBackground) { // 绘制计算背景 + makeBackground(calcShapeRegion(mBorderMargin), canvas); + } + + // ========================= + // = 绘制扫描区域 ( 包括边框 ) = + // ========================= + + // 绘制计算边框 + makeShape( + calcShapeRegion(), canvas, + mDrawBorder ? mBorderPaint : mEmptyPaint, true + ); + + // ========== + // = 动画相关 = + // ========== + + // 判断是否需要重新处理动画 + if (mReAnim) { // 为了节省资源, 只用绘制一次 + mReAnim = false; + + // 判断是否需要动画 + if (mDrawAnim) { + // 计算动画信息 + makeAnim(canvas); + // 判断是否自动开启动画 + if (mAutoAnim) { + // 开始动画 + startAnim(); + } + } + } + + // ============= + // = 绘制扫描动画 = + // ============= + + // 判断是否需要动画 + if (mDrawAnim) { + drawAnim(canvas); + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 销毁处理 + */ + public void destroy() { + // 停止动画 + stopAnim(); + // 清空 Bitmap + mBitmapToSquare = null; + mBitmapToHexagon = null; + mBitmapToAnnulus = null; + } + + /** + * 获取扫描形状类型 + * @return 扫描形状类型 + */ + public Shape getShapeType() { + return mShapeType; + } + + /** + * 设置扫描形状类型 + * @param shapeType 扫描形状类型 + * @return {@link ScanShapeView} + */ + public ScanShapeView setShapeType(Shape shapeType) { + if (shapeType == null) { + shapeType = Shape.Square; + } + // 停止动画 + stopAnim(); + // 设置类型 + this.mShapeType = shapeType; + // 刷新拐角处理 + handlerCornerPathEffect(); + // 动画处理 + resetAnim(true); + return this; + } + + /** + * 获取拐角角度大小 + * @return 拐角角度大小 + */ + public float getCornerRadius() { + if (this.mCornerEffect != null) { + return mCornerEffect.getRadius(); + } + return 0F; + } + + /** + * 设置是否拐角圆角 ( 主要是控制绘制边框的线 ) + * @param cornerEffect 拐角角度大小 + * @return {@link ScanShapeView} + */ + public ScanShapeView setCornerEffect(CornerEffect cornerEffect) { + this.mCornerEffect = cornerEffect; + // 判断是否小于 0 + if (getCornerRadius() <= 0) { + this.mCornerEffect = null; + } + // 刷新拐角处理 + handlerCornerPathEffect(); + return this; + } + + /** + * 设置扫描区域大小 + * @param wide 扫描区域大小 + * @return {@link ScanShapeView} + */ + public ScanShapeView setRegion(float wide) { + if (wide > 0) { + // 设置宽高 + mPointF.x = mPointF.y = wide; + } + return this; + } + + /** + * 设置扫描区域大小 + * @param width 扫描区域宽 + * @param height 扫描区域高 + * @return {@link ScanShapeView} + */ + public ScanShapeView setRegion( + float width, + float height + ) { + if (width > 0 && height > 0) { + // 设置宽 + mPointF.x = width; + // 设置高 + mPointF.y = height; + } + return this; + } + + /** + * 设置扫描区域大小 + * @param rect 扫描区域 + * @return {@link ScanShapeView} + */ + public ScanShapeView setRegion(Rect rect) { + if (rect != null) { + // 设置宽 + mPointF.x = rect.right - rect.left; + // 设置高 + mPointF.y = rect.bottom - rect.top; + } + return this; + } + + /** + * 获取扫描绘制区域距离左 / 右边距 + * @return 扫描绘制区域距离左 / 右边距 + */ + public float getRegionLeft() { + return getRegionMarginLeft(); + } + + /** + * 获取扫描绘制区域距离上 / 下边距 + * @return 扫描绘制区域距离上 / 下边距 + */ + public float getRegionTop() { + return getRegionMarginTop(); + } + + /** + * 获取扫描区域宽度 + * @return 扫描区域宽度 + */ + public float getRegionWidth() { + return mPointF.x; + } + + /** + * 获取扫描区域高度 + * @return 扫描区域高度 + */ + public float getRegionHeight() { + return mPointF.y; + } + + /** + * 获取扫描区域信息 + * @return 扫描区域信息 + */ + public RectF getRegion() { + return calcShapeRegion(); + } + + /** + * 获取扫描区域信息 + * @param left 向左偏差距离 ( 实际屏幕中位置 ) + * @param top 向上偏差距离 ( 实际屏幕中位置 ) + * @return 扫描区域信息 + */ + public RectF getRegion( + float left, + float top + ) { + RectF rectF = calcShapeRegion(); + rectF.left += left; + rectF.right += left; + rectF.top += top; + rectF.bottom += top; + return rectF; + } + + /** + * 获取在父布局中实际的位置 + *
+     *     如该 View 没有铺满, 但是为了扫描优化速度, 专门获取扫描区域实际位置
+     * 
+ * @return 在父布局中实际的位置 + */ + public RectF getRegionParent() { + return getRegion(getLeft(), getTop()); + } + + /** + * 获取边框边距 + * @return 边框边距 + */ + public float getBorderMargin() { + return mBorderMargin; + } + + /** + * 设置边框边距 + * @param borderMargin 边框边距 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBorderMargin(float borderMargin) { + this.mBorderMargin = borderMargin; + return this; + } + + /** + * 获取边框颜色 + * @return 边框颜色 + */ + public int getBorderColor() { + return mBorderPaint.getColor(); + } + + /** + * 设置边框颜色 + * @param color 边框颜色 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBorderColor(@ColorInt int color) { + mBorderPaint.setColor(color); + return this; + } + + /** + * 获取边框宽度 + * @return 边框宽度 + */ + public float getBorderWidth() { + return mBorderPaint.getStrokeWidth(); + } + + /** + * 设置边框宽度 + * @param width 边框宽度 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBorderWidth(float width) { + if (width <= 0) { + width = mBorderWidth; + } + mBorderPaint.setStrokeWidth(width); + return this; + } + + /** + * 是否绘制边框 + * @return {@code true} yes, {@code false} no + */ + public boolean isDrawBorder() { + return mDrawBorder; + } + + /** + * 设置是否绘制边框 + * @param drawBorder 是否绘制边框 + * @return {@link ScanShapeView} + */ + public ScanShapeView setDrawBorder(boolean drawBorder) { + mDrawBorder = drawBorder; + return this; + } + + /** + * 是否绘制背景 + * @return {@code true} yes, {@code false} no + */ + public boolean isDrawBackground() { + return mDrawBackground; + } + + /** + * 设置是否绘制背景 + * @param drawBackground 是否绘制背景 + * @return {@link ScanShapeView} + */ + public ScanShapeView setDrawBackground(boolean drawBackground) { + mDrawBackground = drawBackground; + return this; + } + + /** + * 获取绘制的背景颜色 + * @return 绘制的背景颜色 + */ + public int getBGColor() { + return mBackgroundPaint.getColor(); + } + + /** + * 设置绘制的背景颜色 + * @param color 绘制的背景颜色 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBGColor(@ColorInt int color) { + mBackgroundPaint.setColor(color); + return this; + } + + /** + * 是否绘制动画效果 + * @return {@code true} yes, {@code false} no + */ + public boolean isDrawAnim() { + return mDrawAnim; + } + + /** + * 设置是否绘制动画效果 + * @param drawAnim 是否绘制动画效果 + * @return {@link ScanShapeView} + */ + public ScanShapeView setDrawAnim(boolean drawAnim) { + mDrawAnim = drawAnim; + // 动画处理 + resetAnim(true); + return this; + } + + /** + * 是否自动播放动画 + * @return {@code true} yes, {@code false} no + */ + public boolean isAutoAnim() { + return mAutoAnim; + } + + /** + * 设置是否自动播放动画 + * @param autoAnim 是否自动播放动画 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAutoAnim(boolean autoAnim) { + mAutoAnim = autoAnim; + // 动画处理 + resetAnim(true); + return this; + } + + // ========= + // = 正方形 = + // ========= + + /** + * 获取正方形描边 ( 边框 ) 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 + * @return 正方形描边 ( 边框 ) 类型 + */ + public int getBorderToSquare() { + return mBorderToSquare; + } + + /** + * 设置正方形描边 ( 边框 ) 类型 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 + * @param borderToSquare 0 = 单独四个角落, 1 = 单独边框, 2 = 全部 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBorderToSquare(int borderToSquare) { + // 防止出现负数 + borderToSquare = Math.max(0, borderToSquare); + // 防止出现大于异常值 + if (borderToSquare > 2) { + borderToSquare = 0; + } + this.mBorderToSquare = borderToSquare; + return this; + } + + /** + * 获取正方形描边 ( 边框 ) 宽度 + * @return 正方形描边 ( 边框 ) 宽度 + */ + public float getBorderWidthToSquare() { + return mBorderWidthToSquare; + } + + /** + * 设置正方形描边 ( 边框 ) 宽度 + * @param borderWidthToSquare 正方形描边 ( 边框 ) 宽度 + * @return {@link ScanShapeView} + */ + public ScanShapeView setBorderWidthToSquare(float borderWidthToSquare) { + this.mBorderWidthToSquare = borderWidthToSquare; + return this; + } + + /** + * 获取每个角的点距离 ( 长度 ) 正方形四个角落区域 + * @return 每个角的点距离 ( 长度 ) 正方形四个角落区域 + */ + public float getTriAngleLength() { + return mTriAngleLength; + } + + /** + * 设置每个角的点距离 ( 长度 ) 正方形四个角落区域 + * @param triAngleLength 每个角的点距离 ( 长度 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setTriAngleLength(float triAngleLength) { + this.mTriAngleLength = triAngleLength; + return this; + } + + /** + * 是否特殊处理 ( 正方形边框 ) + * @return {@code true} yes, {@code false} no + */ + public boolean isSpecialToSquare() { + return mSpecialToSquare; + } + + /** + * 设置是否特殊处理 ( 正方形边框 ) + * @param specialToSquare 是否特殊处理 ( 正方形边框 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setSpecialToSquare(boolean specialToSquare) { + this.mSpecialToSquare = specialToSquare; + return this; + } + + /** + * 获取正方形扫描动画速度 ( 毫秒 ) + * @return 正方形扫描动画速度 ( 毫秒 ) + */ + public long getLineDurationToSquare() { + return mLineDurationToSquare; + } + + /** + * 设置正方形扫描动画速度 ( 毫秒 ) + * @param lineDurationToSquare 正方形扫描动画速度 ( 毫秒 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineDurationToSquare(long lineDurationToSquare) { + if (lineDurationToSquare <= 0) { + lineDurationToSquare = 10L; + } + this.mLineDurationToSquare = lineDurationToSquare; + return this; + } + + /** + * 获取正方形扫描线条 Bitmap + * @return 正方形扫描线条 Bitmap + */ + public Bitmap getBitmapToSquare() { + return mBitmapToSquare; + } + + /** + * 设置正方形扫描线条 Bitmap + * @param bitmapToSquare 正方形扫描线条 Bitmap + * @return {@link ScanShapeView} + */ + public ScanShapeView setBitmapToSquare(Bitmap bitmapToSquare) { + this.mBitmapToSquare = bitmapToSquare; + // 刷新颜色 + refLineColorToSquare(); + return this; + } + + /** + * 获取正方形扫描线条向上 ( 下 ) 边距 + * @return 正方形扫描线条向上 ( 下 ) 边距 + */ + public float getLineMarginTopToSquare() { + return mLineMarginTopToSquare; + } + + /** + * 设置正方形扫描线条向上 ( 下 ) 边距 + * @param lineMarginTopToSquare 正方形扫描线条向上 ( 下 ) 边距 + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineMarginTopToSquare(float lineMarginTopToSquare) { + if (lineMarginTopToSquare < 0F) { + lineMarginTopToSquare = 0F; + } + this.mLineMarginTopToSquare = lineMarginTopToSquare; + return this; + } + + /** + * 获取正方形扫描线条向左 ( 右 ) 边距 + * @return 正方形扫描线条向左 ( 右 ) 边距 + */ + public float getLineMarginLeftToSquare() { + return mLineMarginLeftToSquare; + } + + /** + * 设置正方形扫描线条向左 ( 右 ) 边距 + * @param lineMarginLeftToSquare 正方形扫描线条向左 ( 右 ) 边距 + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineMarginLeftToSquare(float lineMarginLeftToSquare) { + if (lineMarginLeftToSquare < 0F) { + lineMarginLeftToSquare = 0F; + } + this.mLineMarginLeftToSquare = lineMarginLeftToSquare; + return this; + } + + /** + * 获取正方形线条动画颜色 ( 着色 ) + * @return 正方形线条动画颜色 ( 着色 ) + */ + public int getLineColorToSquare() { + return mLineColorToSquare; + } + + /** + * 设置正方形线条动画 ( 着色 ) + * @param lineColorToSquare 正方形线条动画颜色 ( 着色 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineColorToSquare(int lineColorToSquare) { + this.mLineColorToSquare = lineColorToSquare; + // 刷新颜色 + refLineColorToSquare(); + return this; + } + + // ========= + // = 六边形 = + // ========= + + /** + * 获取六边形线条动画 ( 线条宽度 ) + * @return 六边形线条动画 ( 线条宽度 ) + */ + public float getLineWidthToHexagon() { + return mLinePaintToHexagon.getStrokeWidth(); + } + + /** + * 设置六边形线条动画 ( 线条宽度 ) + * @param lineWidthToHexagon 六边形线条动画 ( 线条宽度 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineWidthToHexagon(float lineWidthToHexagon) { + if (lineWidthToHexagon <= 0) { + lineWidthToHexagon = this.mBorderWidth; + } + mLinePaintToHexagon.setStrokeWidth(lineWidthToHexagon); + return this; + } + + /** + * 获取六边形线条动画 ( 线条边距 ) + * @return 六边形线条动画 ( 线条边距 ) + */ + public float getLineMarginToHexagon() { + return mLineMarginToHexagon; + } + + /** + * 设置六边形线条动画 ( 线条边距 ) + * @param lineMarginToHexagon 六边形线条动画 ( 线条边距 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineMarginToHexagon(float lineMarginToHexagon) { + this.mLineMarginToHexagon = lineMarginToHexagon; + return this; + } + + /** + * 获取六边形线条动画方向 ( true = 从左到右, false = 从右到左 ) + * @return 六边形线条动画方向 ( true = 从左到右, false = 从右到左 ) + */ + public boolean isLineAnimDirection() { + return mLineAnimDirection; + } + + /** + * 设置六边形线条动画方向 ( true = 从左到右, false = 从右到左 ) + * @param lineAnimDirection 六边形线条动画方向 + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineAnimDirection(boolean lineAnimDirection) { + this.mLineAnimDirection = lineAnimDirection; + return this; + } + + /** + * 获取六边形线条动画颜色 + * @return 六边形线条动画颜色 + */ + public int getLineColorToHexagon() { + return mLineColorToHexagon; + } + + /** + * 设置六边形线条动画颜色 + * @param lineColorToHexagon 六边形线条动画颜色 + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineColorToHexagon(int lineColorToHexagon) { + this.mLineColorToHexagon = lineColorToHexagon; + // 刷新颜色 + refLineColorToHexagon(); + return this; + } + + // ======== + // = 环形 = + // ======== + + /** + * 获取环形扫描线条 Bitmap + * @return 环形扫描线条 Bitmap + */ + public Bitmap getBitmapToAnnulus() { + return mBitmapToAnnulus; + } + + /** + * 设置环形扫描线条 Bitmap + * @param bitmapToAnnulus 环形扫描线条 Bitmap + * @return {@link ScanShapeView} + */ + public ScanShapeView setBitmapToAnnulus(Bitmap bitmapToAnnulus) { + this.mBitmapToAnnulus = bitmapToAnnulus; + // 刷新颜色 + refLineColorToAnnulus(); + return this; + } + + /** + * 获取环形线条动画颜色 ( 着色 ) + * @return 环形线条动画颜色 ( 着色 ) + */ + public int getLineColorToAnnulus() { + return mLineColorToAnnulus; + } + + /** + * 设置环形线条动画 ( 着色 ) + * @param lineColorToAnnulus 环形线条动画 ( 着色 ) + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineColorToAnnulus(int lineColorToAnnulus) { + this.mLineColorToAnnulus = lineColorToAnnulus; + // 刷新颜色 + refLineColorToAnnulus(); + return this; + } + + /** + * 获取环形扫描线条速度 + * @return 环形扫描线条速度 + */ + public float getLineOffsetSpeedToAnnulus() { + return mLineOffsetSpeedToAnnulus; + } + + /** + * 设置环形扫描线条速度 + * @param lineOffsetSpeedToAnnulus 尽量接近 3 - 6 + * @return {@link ScanShapeView} + */ + public ScanShapeView setLineOffsetSpeedToAnnulus(float lineOffsetSpeedToAnnulus) { + if (lineOffsetSpeedToAnnulus < 0) { + lineOffsetSpeedToAnnulus = 4F; + } + this.mLineOffsetSpeedToAnnulus = lineOffsetSpeedToAnnulus; + return this; + } + + /** + * 获取环形对应的环是否绘制 + * @return 环形对应的环是否绘制 + */ + public boolean[] getAnnulusDraws() { + return mAnnulusDraws; + } + + /** + * 设置环形对应的环是否绘制 + * @param annulusDraws 环形对应的环是否绘制 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAnnulusDraws(boolean... annulusDraws) { + if (annulusDraws == null) { + annulusDraws = new boolean[]{true, true, true}; + } + // 设置临时数据 + boolean[] temp = Arrays.copyOf(annulusDraws, 3); + // 如果小于 3 位, 则特殊处理 + if (annulusDraws.length < 3) { + // 没有传递的, 则使用之前的配置 + for (int i = annulusDraws.length; i < 3; i++) { + temp[i] = this.mAnnulusDraws[i]; + } + } + this.mAnnulusDraws = temp; + return this; + } + + /** + * 获取环形对应的环绘制颜色 + * @return 环形对应的环绘制颜色 + */ + public int[] getAnnulusColors() { + return mAnnulusColors; + } + + /** + * 设置环形对应的环绘制颜色 + * @param annulusColors 环形对应的环绘制颜色 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAnnulusColors(@ColorInt int... annulusColors) { + if (annulusColors == null) { + annulusColors = new int[]{Color.BLUE, Color.RED, Color.WHITE}; + } + // 设置临时数据 + int[] temp = Arrays.copyOf(annulusColors, 3); + // 如果小于 3 位, 则特殊处理 + if (annulusColors.length < 3) { + // 没有传递的, 则使用之前的配置 + for (int i = annulusColors.length; i < 3; i++) { + temp[i] = this.mAnnulusColors[i]; + } + } + this.mAnnulusColors = temp; + // 刷新环形画笔信息 + refPaintToAnnulus(); + return this; + } + + /** + * 获取环形对应的环绘制长度 + * @return 环形对应的环绘制长度 + */ + public int[] getAnnulusLengths() { + return mAnnulusLengths; + } + + /** + * 设置环形对应的环绘制长度 + * @param annulusLengths 环形对应的环绘制长度 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAnnulusLengths(int... annulusLengths) { + if (annulusLengths == null) { + annulusLengths = new int[]{20, 30, 85}; + } + // 设置临时数据 + int[] temp = Arrays.copyOf(annulusLengths, 3); + // 如果小于 3 位, 则特殊处理 + if (annulusLengths.length < 3) { + // 没有传递的, 则使用之前的配置 + for (int i = annulusLengths.length; i < 3; i++) { + temp[i] = this.mAnnulusLengths[i]; + } + } + this.mAnnulusLengths = temp; + return this; + } + + /** + * 获取环形对应的环绘制宽度 + * @return 环形对应的环绘制宽度 + */ + public float[] getAnnulusWidths() { + return mAnnulusWidths; + } + + /** + * 设置环形对应的环绘制宽度 + * @param annulusWidths 环形对应的环绘制宽度 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAnnulusWidths(float... annulusWidths) { + if (annulusWidths == null) { + annulusWidths = new float[3]; + annulusWidths[0] = SizeUtils.dp2px(getContext(), 3); + annulusWidths[1] = SizeUtils.dp2px(getContext(), 7); + annulusWidths[2] = SizeUtils.dp2px(getContext(), 7); + } + // 设置临时数据 + float[] temp = Arrays.copyOf(annulusWidths, 3); + // 如果小于 3 位, 则特殊处理 + if (annulusWidths.length < 3) { + // 没有传递的, 则使用之前的配置 + for (int i = annulusWidths.length; i < 3; i++) { + temp[i] = this.mAnnulusWidths[i]; + } + } + this.mAnnulusWidths = temp; + // 刷新环形画笔信息 + refPaintToAnnulus(); + return this; + } + + /** + * 获取环形对应的环绘制边距 + * @return 环形对应的环绘制边距 + */ + public float[] getAnnulusMargins() { + return mAnnulusMargins; + } + + /** + * 设置环形对应的环绘制边距 + * @param annulusMargins 环形对应的环绘制边距 + * @return {@link ScanShapeView} + */ + public ScanShapeView setAnnulusMargins(float... annulusMargins) { + if (annulusMargins == null) { + int dp = SizeUtils.dp2px(getContext(), 7); + annulusMargins = new float[]{dp, dp, dp}; + } + // 设置临时数据 + float[] temp = Arrays.copyOf(annulusMargins, 3); + // 如果小于 3 位, 则特殊处理 + if (annulusMargins.length < 3) { + // 没有传递的, 则使用之前的配置 + for (int i = annulusMargins.length; i < 3; i++) { + temp[i] = this.mAnnulusMargins[i]; + } + } + this.mAnnulusMargins = temp; + return this; + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 刷新环形画笔信息 + */ + private void refPaintToAnnulus() { + // 循环重置宽度、颜色值 + for (int i = 0; i < 3; i++) { + mAnnulusPaints[i].setColor(mAnnulusColors[i]); + mAnnulusPaints[i].setStrokeWidth(mAnnulusWidths[i]); + } + } + + // ========== + // = 计算相关 = + // ========== + + /** + * Math.sin 的参数为弧度, 使用起来不方便, 重新封装一个根据角度求 sin 的方法 + * @param num 角度 + * @return sin + */ + private float sin(int num) { + return (float) Math.sin(num * Math.PI / 180); + } + + /** + * 获取扫描区域左边边距 ( 左右相等 ) = ( View 宽度 - 扫描区域宽度 ) / 2 + * @return 扫描区域左边边距 + */ + private float getRegionMarginLeft() { + return (getWidth() - mPointF.x) / 2; + } + + /** + * 获取扫描区域向上边距 ( 上下相等 ) = ( View 宽度 - 扫描区域宽度 ) / 2 + * @return 扫描区域向上边距 + */ + private float getRegionMarginTop() { + return (getHeight() - mPointF.y) / 2; + } + + /** + * 计算扫描区域, 并返回区域信息 + * @return 扫描区域信息 + */ + private RectF calcShapeRegion() { + return calcShapeRegion(0F); + } + + /** + * 计算扫描区域, 并返回区域信息 + * @param margin 边距 + * @return 扫描区域信息 + */ + private RectF calcShapeRegion(float margin) { + // 获取左边边距 + float left = getRegionMarginLeft(); + // 获取向上边距 + float top = getRegionMarginTop(); + // 生成扫描区域信息 + return new RectF( + left - margin, top - margin, + mPointF.x + left + margin, mPointF.y + top + margin + ); + } + + // ========== + // = 绘制形状 = + // ========== + + /** + * 绘制计算形状 ( 边框外形 ) + * @param rectF 绘制区域块 + * @param canvas 画布 + * @param paint 画笔 + * @param isDraw 是否进行绘制 + * @return 绘制路径信息 {@link Path} + */ + private Path makeShape( + RectF rectF, + Canvas canvas, + Paint paint, + boolean isDraw + ) { + // 绘制路径 + Path path = new Path(); + + // 位置信息 + float r = (rectF.right - rectF.left) / 2; // 半径 + float mX = (rectF.right + rectF.left) / 2; // X 轴中心点位置 + float mY = (rectF.top + rectF.bottom) / 2; // Y 轴中心点位置 + + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + boolean[] isBorderToSquare = new boolean[]{false, false}; + // 判断正方形描边类型 + switch (mBorderToSquare) { + case 0: // 表示只需要四个角落 + isBorderToSquare[1] = true; + break; + case 1: // 表示只需要边缘 + isBorderToSquare[0] = true; + break; + case 2: // 表示全部绘制 + isBorderToSquare[0] = true; + isBorderToSquare[1] = true; + break; + default: // 默认只需要四个角落 + isBorderToSquare[1] = true; + break; + } + + if (isBorderToSquare[0]) { + Paint borderPaint = new Paint(paint); + // 判断是否也显示角落, 是的话才重置 + if (isBorderToSquare[1]) { + borderPaint.setStrokeWidth(mBorderWidthToSquare); + } + // 完整绘制的横线 ( 正方形 ) + path.moveTo(mX, mY - r); // 设置起始点 + // 左边 + path.lineTo(mX - r, mY - r); // 左边第一条上横线 + path.lineTo(mX - r, mY + r); // 左边第二条竖线 +// path.lineTo(mX - r, mY + r); // 左边第三条下横线 // 不绘制不会有直角 + // 右边 + path.lineTo(mX + r, mY + r); // 右边边第一条下横线 + path.lineTo(mX + r, mY - r); // 右边边第二条竖线 + path.close(); + // 进行绘制 + canvas.drawPath(path, borderPaint); + } + + if (isBorderToSquare[1]) { + Paint borderPaint = new Paint(paint); + // 判断是否特殊处理 + if (mSpecialToSquare) { + // 如果已经绘制边框, 则不设置圆角 + if (isBorderToSquare[0] && borderPaint.getPathEffect() != null) { + borderPaint.setPathEffect(null); + } + } + + rectF.left += mBorderWidth / 2; + rectF.top += mBorderWidth / 2; + rectF.right -= mBorderWidth / 2; + rectF.bottom -= mBorderWidth / 2; + // 四个角落的三角 + Path leftTopPath = new Path(); + leftTopPath.moveTo(rectF.left + mTriAngleLength, rectF.top); + leftTopPath.lineTo(rectF.left, rectF.top); + leftTopPath.lineTo(rectF.left, rectF.top + mTriAngleLength); + canvas.drawPath(leftTopPath, borderPaint); + + Path rightTopPath = new Path(); + rightTopPath.moveTo(rectF.right - mTriAngleLength, rectF.top); + rightTopPath.lineTo(rectF.right, rectF.top); + rightTopPath.lineTo(rectF.right, rectF.top + mTriAngleLength); + canvas.drawPath(rightTopPath, borderPaint); + + Path leftBottomPath = new Path(); + leftBottomPath.moveTo(rectF.left, rectF.bottom - mTriAngleLength); + leftBottomPath.lineTo(rectF.left, rectF.bottom); + leftBottomPath.lineTo(rectF.left + mTriAngleLength, rectF.bottom); + canvas.drawPath(leftBottomPath, borderPaint); + + Path rightBottomPath = new Path(); + rightBottomPath.moveTo(rectF.right - mTriAngleLength, rectF.bottom); + rightBottomPath.lineTo(rectF.right, rectF.bottom); + rightBottomPath.lineTo(rectF.right, rectF.bottom - mTriAngleLength); + canvas.drawPath(rightBottomPath, borderPaint); + } + break; + case Hexagon: // 六方形 + // 对应 6 条线角度计算 + path.moveTo(mX, mY - r); // 1 + path.lineTo(mX + r * sin(60), mY - r / 2); // 3 + path.lineTo(mX + r * sin(60), mY + r / 2); // 5 + path.lineTo(mX, mY + r); // 6 + path.lineTo(mX - r * sin(60), mY + r / 2); // 4 + path.lineTo(mX - r * sin(60), mY - r / 2); // 2 + path.close(); + // 判断是否需要绘制 + if (isDraw) { + // 进行绘制 + canvas.drawPath(path, paint); + } + break; + case Annulus: // 环形 + // 判断是否动画中 + if (isAnimRunning()) { + // 判断是否绘制最外层 + if (mAnnulusDraws[0]) { + // 第一个小弧度 + canvas.drawArc( + rectF, mAnnulusAngles[0], mAnnulusLengths[0], + false, mAnnulusPaints[0] + ); + // 第二个小弧度 + canvas.drawArc( + rectF, mAnnulusAngles[0] + 180, mAnnulusLengths[0], + false, mAnnulusPaints[0] + ); + } + // 判断是否绘制中间层 + if (mAnnulusDraws[1]) { + // 计算缩放动画偏移 + if (mAnimOffsetToAnnulus > 0) { + mAnimOffsetToAnnulus -= 2; + } else { + mAnimOffsetToAnnulus = 0F; + } + // 计算中间层间隔距离 + float middleSpace = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusMargins[0] + mAnimOffsetToAnnulus; + canvas.drawCircle(mX, mY, r - middleSpace, mAnnulusPaints[1]); + // 中间层, 两个弧 + if (mAnimOffsetToAnnulus == 0F && mAnnulusMargins[0] / 2 > 0F) { // 小于 0 则不绘制 + // 计算中间层边距 + float middleMargin = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusMargins[0] / 2; + // 计算新的路径 + RectF outsiderRectF = new RectF(rectF); + outsiderRectF.left += middleMargin; + outsiderRectF.top += middleMargin; + outsiderRectF.right -= middleMargin; + outsiderRectF.bottom -= middleMargin; + // 第一个小弧度 + canvas.drawArc( + outsiderRectF, mAnnulusAngles[1], mAnnulusLengths[1], + false, mAnnulusPaints[1] + ); + // 第二个小弧度 + canvas.drawArc( + outsiderRectF, mAnnulusAngles[1] + 180, mAnnulusLengths[1], + false, mAnnulusPaints[1] + ); + } + } + + // 判断是否绘制最内层 + if (mAnnulusDraws[2]) { + // 计算最内层间隔距离 + float insideSpace = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusWidths[2] + mAnnulusMargins[0]; + // 计算新的路径 + RectF outsiderRectF = new RectF(rectF); + outsiderRectF.left += insideSpace; + outsiderRectF.top += insideSpace; + outsiderRectF.right -= insideSpace; + outsiderRectF.bottom -= insideSpace; + // 绘制最内层, 4 个弧 + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2], mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 90, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 180, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 270, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + } + } else { // 停止结束状态 + // 判断绘制动画效果 ( 只有结束后, 做的一个动画效果 ) + boolean isDrawAnim = false; + // 判断是否绘制中间层 + if (mAnnulusDraws[1]) { + // 计算中间层间隔距离 + float middleSpace = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusMargins[0] + mAnimOffsetToAnnulus; + // 计算最内层间隔距离 + float insideSpace = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusWidths[2] + mAnnulusMargins[0]; + // 计算缩放动画偏移 + if (middleSpace < insideSpace + mAnnulusWidths[1]) { + middleSpace += 2; + mAnimOffsetToAnnulus += 2; + isDrawAnim = true; + } + canvas.drawCircle(mX, mY, r - middleSpace, mAnnulusPaints[1]); + } + + // 判断是否绘制最内层 + if (mAnnulusDraws[2]) { + // 计算最内层间隔距离 + float insideSpace = mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusWidths[2] + mAnnulusMargins[0]; + // 计算新的路径 + RectF outsiderRectF = new RectF(rectF); + outsiderRectF.left += insideSpace; + outsiderRectF.top += insideSpace; + outsiderRectF.right -= insideSpace; + outsiderRectF.bottom -= insideSpace; + // 绘制最内层, 4 个弧 + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2], mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 90, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 180, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + canvas.drawArc( + outsiderRectF, mAnnulusAngles[2] + 270, mAnnulusLengths[2], + false, mAnnulusPaints[2] + ); + } + + // 设置是否绘制 + if (isDrawAnim) { + postInvalidate(); + } + } + break; + } + return path; + } + + /** + * 绘制背景 + * @param rectF 绘制区域块 + * @param canvas 画布 + */ + private void makeBackground( + RectF rectF, + Canvas canvas + ) { + // 都小于 0 则不处理 + if (rectF.left <= 0 && rectF.top <= 0) { + return; + } + + Path leftPath = new Path(); // 左边路径 + Path rightPath = new Path(); // 右边路径 + // 位置信息 + float r = (rectF.right - rectF.left) / 2; // 半径 + float mX = (rectF.right + rectF.left) / 2; // X 轴中心点位置 + float mY = (rectF.top + rectF.bottom) / 2; // Y 轴中心点位置 + + // 获取 View 宽度 + final int width = getWidth(); + // 获取 View 高度 + final int height = getHeight(); + + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + // 因为使用正方形, 如果使用圆角, 在拐角处, 会有圆圈, 所以拐角处, 统一加大边距处理 + // 解决路径拐角有圆圈, 透过底层颜色 + + // 获取拐角大小 + float radius = getCornerRadius(); + // = + leftPath.moveTo(mX, 0); // 设置起始点 + leftPath.lineTo(0 - radius, 0); // 从中间到顶部边缘 + leftPath.lineTo(0 - radius, height + radius); // 从顶部到最下面 + leftPath.lineTo(mX + radius, height + radius); // 底部到中间点 + leftPath.lineTo(mX + radius, rectF.bottom); // 中间点到区域底部 + leftPath.lineTo(mX + radius, rectF.bottom); // 再次绘制覆盖左侧底部中间上方拐角 + leftPath.lineTo(rectF.left, rectF.bottom); // 区域底部到区域左 + leftPath.lineTo(rectF.left, rectF.top); // 区域底部到区域顶部 + leftPath.lineTo(mX + radius, rectF.top); // 区域顶部到 ( 顶部 ) 中心点 + leftPath.lineTo(mX + radius, rectF.top); // 再次绘制覆盖左侧顶部中间上方拐角 + leftPath.lineTo(mX + radius, -radius); // 回到起始点 + leftPath.close(); + // 进行绘制背景 + canvas.drawPath(leftPath, mBackgroundPaint); + // = + rightPath.moveTo(mX, 0); // 设置起始点 + rightPath.lineTo(width + radius, 0); // 从中间到顶部边缘 + rightPath.lineTo(width + radius, height + radius); // 从顶部到最下面 + rightPath.lineTo(mX + radius, height + radius); // 底部到中间点 + rightPath.lineTo(mX + radius, rectF.bottom); // 中间点到区域底部 + rightPath.lineTo(mX + radius, rectF.bottom); // 再次绘制覆盖右侧底部中间上方拐角 + rightPath.lineTo(rectF.right, rectF.bottom); // 区域底部到区域右 + rightPath.lineTo(rectF.right, rectF.top); // 区域底部到区域顶部 + rightPath.lineTo(mX + radius, rectF.top); // 区域顶部到 ( 顶部 ) 中心点 + rightPath.lineTo(mX + radius, rectF.top); // 再次绘制覆盖右侧顶部中间上方拐角 + rightPath.lineTo(mX + radius, -radius); // 回到起始点 + rightPath.close(); + // 进行绘制背景 + canvas.drawPath(rightPath, mBackgroundPaint); + break; + case Hexagon: // 六方形 + leftPath.moveTo(0, 0); // 左上 + leftPath.lineTo(width / 2, 0); // 顶部中心点 + leftPath.lineTo(mX, mY - r); // 1 + leftPath.lineTo(mX - r * sin(60), mY - r / 2); // 2 + leftPath.lineTo(mX - r * sin(60), mY + r / 2); // 4 + leftPath.lineTo(mX, mY + r); // 6 + leftPath.lineTo(width / 2, height); // 底部中心点 + leftPath.lineTo(0, height); // 左下 + leftPath.close(); + // 进行绘制背景 + canvas.drawPath(leftPath, mBackgroundPaint); + // = + rightPath.moveTo(width, 0); // 右上 + rightPath.lineTo(width / 2, 0); // 顶部中心点 + rightPath.lineTo(mX, mY - r); // 1 + rightPath.lineTo(mX + r * sin(60), mY - r / 2); // 3 + rightPath.lineTo(mX + r * sin(60), mY + r / 2); // 5 + rightPath.lineTo(mX, mY + r); // 6 + rightPath.lineTo(width / 2, height); // 底部中心点 + rightPath.lineTo(width, height); // 右下 + rightPath.close(); + // 进行绘制背景 + canvas.drawPath(rightPath, mBackgroundPaint); + break; + case Annulus: // 环形 + leftPath.moveTo(mX, 0); // 中心点 + leftPath.lineTo(0, 0); // 顶部最左边 + leftPath.lineTo(0, height); // 底部最左边 + leftPath.lineTo(mX, height); // 底部中间 + leftPath.lineTo(mX, rectF.bottom); // 底部 bottom 位置 + leftPath.arcTo(rectF, -270, 180); // 从第三象限到第一象限 + //leftPath.lineTo(mX, rectF.top); + leftPath.lineTo(mX, 0); + leftPath.close(); + // 进行绘制背景 + canvas.drawPath(leftPath, mBackgroundPaint); + // = + rightPath.moveTo(mX, 0); // 中心点 + rightPath.lineTo(width, 0); // 顶部最右边 + rightPath.lineTo(width, height); // 底部最左边 + rightPath.lineTo(mX, height); // 底部中间 + rightPath.lineTo(mX, rectF.bottom); // 底部 bottom 位置 + rightPath.arcTo(rectF, -270, -180); // 从第三象限到第一象限 + //rightPath.lineTo(mX, rectF.top); + rightPath.lineTo(mX, 0); + rightPath.close(); + // 进行绘制背景 + canvas.drawPath(rightPath, mBackgroundPaint); + break; + } + } + + /** + * 计算动画相关信息 + * @param canvas 画布 + */ + private void makeAnim(Canvas canvas) { + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + // 正方形不需要绘制计算 ( 初始化 ) + break; + case Hexagon: // 六边形 + // 获取绘制的区域 ( 绘制扫描区域 + 线条居于绘制边框距离 ) + RectF rectF = calcShapeRegion(mLineMarginToHexagon); + // 线条路径计算重置起始位置 + RectF lineRectF = new RectF( + 0, 0, rectF.right - rectF.left, + rectF.right - rectF.left + ); + // 计算边距处理 + mLinePathToHexagon = makeShape(lineRectF, canvas, mLinePaintToHexagon, false); + // 生成 Bitmap + mBitmapToHexagon = Bitmap.createBitmap( + (int) (rectF.right - rectF.left), (int) (rectF.right - rectF.left), + Bitmap.Config.ARGB_8888 + ); + // 生成新的 Canvas + mCanvasToHexagon = new Canvas(mBitmapToHexagon); + // 计算中心点 + mCenterToHexagon = ((rectF.right - rectF.left) / 2); + break; + case Annulus: // 环形 + // 环形不需要绘制计算 ( 初始化 ) + break; + } + } + + /** + * 绘制动画相关处理 + * @param canvas 画布 + */ + private void drawAnim(Canvas canvas) { + try { + // 位置信息 + float r; // 半径 + float mX; // X 轴中心点位置 + float mY; // Y 轴中心点位置 + // 获取扫描区域大小 + RectF rectF; + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + // 如果 bitmap 不为 null, 才处理 + if (mBitmapToSquare != null) { + // 获取扫描区域大小 ( 正方形在内部绘制, 不需要加上外边距 ) + rectF = calcShapeRegion(); + // 计算边距处理 + rectF.left = rectF.left + mLineMarginLeftToSquare; + rectF.top = rectF.top + mLineMarginTopToSquare; + rectF.right = rectF.right - mLineMarginLeftToSquare; + rectF.bottom = rectF.bottom - mLineMarginTopToSquare; + // 循环划线, 从上到下 + if (mLineOffsetToSquare > rectF.bottom - rectF.top - mDFCommonDP) { + mLineOffsetToSquare = 0; + } else { + mLineOffsetToSquare = mLineOffsetToSquare + 6; + // 设置线条区域 + Rect lineRect = new Rect(); + lineRect.left = (int) rectF.left; + lineRect.top = (int) (rectF.top + mLineOffsetToSquare); + lineRect.right = (int) rectF.right; + lineRect.bottom = (int) (rectF.top + mDFCommonDP + mLineOffsetToSquare); + canvas.drawBitmap(mBitmapToSquare, null, lineRect, mLinePaintToSquare); + } + } + break; + case Hexagon: // 六边形 + // 获取扫描区域大小 + rectF = calcShapeRegion(mLineMarginToHexagon); + // 位置信息 + r = (rectF.right - rectF.left) / 2; // 半径 +// mX = (rectF.right + rectF.left) / 2; // X 轴中心点位置 + mY = (rectF.top + rectF.bottom) / 2; // Y 轴中心点位置 + // 绘制线条 + canvas.drawBitmap(mBitmapToHexagon, rectF.left, mY - r, null); + break; + case Annulus: // 环形 + // 动画运行中才处理 + if (isAnimRunning()) { + if (mBitmapToAnnulus != null) { + float margin = -(mAnnulusWidths[0] + mAnnulusWidths[1] + mAnnulusWidths[2] + mAnnulusMargins[0]); + // 获取扫描区域大小 + rectF = calcShapeRegion(margin); + // 位置信息 + r = (rectF.right - rectF.left) / 2; // 半径 + mX = (rectF.right + rectF.left) / 2; // X 轴中心点位置 +// mY = (rectF.top + rectF.bottom) / 2; // Y 轴中心点位置 + // = + mLineOffsetToAnnulus += mLineOffsetSpeedToAnnulus; + if (mLineOffsetToAnnulus > r * 2 - (mAnnulusWidths[2])) { + mLineOffsetToAnnulus = 0; + } + float p1, p2, hw; + if (mLineOffsetToAnnulus >= r) { + p1 = (mLineOffsetToAnnulus - r) * (mLineOffsetToAnnulus - r); + } else { + p1 = (r - mLineOffsetToAnnulus) * (r - mLineOffsetToAnnulus); + } + p2 = r * r; + hw = (int) Math.sqrt(p2 - p1) - mAnnulusMargins[2]; + + // 获取图片高度 + int bitmapHeight = mBitmapToAnnulus.getHeight(); + + RectF lineRectF = new RectF(); + lineRectF.left = mX - hw; + lineRectF.top = rectF.top + mLineOffsetToAnnulus; + lineRectF.right = mX + hw; + lineRectF.bottom = rectF.top + mLineOffsetToAnnulus + bitmapHeight; + canvas.drawBitmap(mBitmapToAnnulus, null, lineRectF, null); + } + } + break; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "drawAnim %s", mShapeType.name()); + } + } + + // ========== + // = 动画相关 = + // ========== + + // ========== + // = 内部方法 = + // ========== + + /** + * 重新设置动画 + * @param init 是否重新初始化 + */ + private void resetAnim(boolean init) { + if (init) { + // 停止动画 + stopAnim(); + // 重置动画处理 + initAnim(); + } + // 表示需要重新处理 + mReAnim = true; + } + + // 动画操作 + private final int START_ANIM = 10; // 开始动画 + private final int STOP_ANIM = 11; // 停止动画 + + /** + * 启动动画 + */ + public void startAnim() { + // 已经在运行了, 则不处理 + if (isAnimRunning()) return; + animSwitch(START_ANIM); + } + + /** + * 停止动画 + */ + public void stopAnim() { + animSwitch(STOP_ANIM); + } + + /** + * 动画开关统一方法 + * @param operate 动画操作 + */ + private void animSwitch(int operate) { + try { + // 动画对象 + ValueAnimator valueAnimator = null; + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + valueAnimator = mAnimToSquare; + break; + case Hexagon: // 六边形 + valueAnimator = mAnimToHexagon; + break; + case Annulus: // 环形 + valueAnimator = mAnimToAnnulus; + break; + } + if (valueAnimator != null) { + switch (operate) { + case START_ANIM: + valueAnimator.start(); + break; + case STOP_ANIM: + valueAnimator.cancel(); + break; + } + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "animSwitch %s", mShapeType.name()); + } + } + + /** + * 是否动画运行中 + * @return {@code true} yes, {@code false} no + */ + public boolean isAnimRunning() { + try { + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + if (mAnimToSquare != null) { + return mAnimToSquare.isRunning(); + } + break; + case Hexagon: // 六边形 + if (mAnimToHexagon != null) { + return mAnimToHexagon.isRunning(); + } + break; + case Annulus: // 环形 + if (mAnimToAnnulus != null) { + return mAnimToAnnulus.isRunning(); + } + break; + } + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "isAnimRunning %s", mShapeType.name()); + } + return false; + } + + // =============== + // = 正方形动画参数 = + // =============== + + /** + * 重置线条颜色 ( 进行着色 ) + */ + private void refLineColorToSquare() { + if (mBitmapToSquare != null && mLineColorToSquare != 0) { + try { + // 转换 Drawable + Drawable drawable = new BitmapDrawable(getResources(), mBitmapToSquare); + Drawable tintDrawable = DrawableCompat.wrap(drawable); + // 进行着色 + DrawableCompat.setTint(tintDrawable, mLineColorToSquare); + // 保存着色后的 Bitmap +// mBitmapToSquare = ((BitmapDrawable) tintDrawable).getBitmap(); + // 临时 Bitmap + Bitmap bitmap; + // 创建新的 Bitmap + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565 + ); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565 + ); + } + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + // 保存着色后的 Bitmap + mBitmapToSquare = bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "refLineColorToSquare"); + } + } + } + + // =============== + // = 六边形动画参数 = + // =============== + + private float mStartLinePoint; // 起点位置 + private float mEndLinePoint; // 结束点位置 + private float mOffsetLinePoint; // 移动点位置 + // 线条颜色数组 ( 渐变 ) + private int[] mLineColorArray; + // 线条移动位置数组 + private float[] mLinePathArray; + // 线条颜色 + private int mLineColorToHexagon = Color.WHITE; + // 线条 rgb 色值 + private int mLineRed, mLineGreen, mLineBlue; + // 透明度 0, 透明度 255 对应的颜色 + private int mLineTran00Color, mLineTran255Color; + + /** + * 刷新线条颜色 + *
+     *     每次设置颜色值, 需要同步更新
+     * 
+ */ + private void refLineColorToHexagon() { + // 获取红色色值 + mLineRed = Color.red(mLineColorToHexagon); + // 获取绿色色值 + mLineGreen = Color.green(mLineColorToHexagon); + // 获取蓝色色值 + mLineBlue = Color.blue(mLineColorToHexagon); + // 透明度 0 线条 + mLineTran00Color = Color.argb(0, mLineRed, mLineGreen, mLineBlue); + mLineTran255Color = Color.argb(255, mLineRed, mLineGreen, mLineBlue); + } + + // ============= + // = 环形动画参数 = + // ============= + + /** + * 重置线条颜色 ( 进行着色 ) + */ + private void refLineColorToAnnulus() { + if (mBitmapToAnnulus != null && mLineColorToAnnulus != 0) { + try { + // 转换 Drawable + Drawable drawable = new BitmapDrawable(getResources(), mBitmapToAnnulus); + Drawable tintDrawable = DrawableCompat.wrap(drawable); + // 进行着色 + DrawableCompat.setTint(tintDrawable, mLineColorToAnnulus); + // 保存着色后的 Bitmap +// mBitmapToAnnulus = ((BitmapDrawable) tintDrawable).getBitmap(); + // 临时 Bitmap + Bitmap bitmap; + // 创建新的 Bitmap + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565 + ); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), + drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565 + ); + } + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + // 保存着色后的 Bitmap + mBitmapToAnnulus = bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "refLineColorToAnnulus"); + } + } + } + + // ============ + // = 动画初始化 = + // ============ + + /** + * 初始化动画 + */ + private void initAnim() { + // 判断是否绘制动画 + if (!mDrawAnim) return; + // 判断形状类型 + switch (mShapeType) { + case Square: // 正方形 + mAnimToSquare = ValueAnimator.ofInt(10, 20); + mAnimToSquare.setDuration(mLineDurationToSquare); + mAnimToSquare.setRepeatCount(ValueAnimator.INFINITE); + mAnimToSquare.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationRepeat(Animator animation) { + super.onAnimationRepeat(animation); + // 绘制 + postInvalidate(); + } + }); + break; + case Hexagon: // 六边形 + // 刷新颜色值 + refLineColorToHexagon(); + // 360, 0 从左到右, 0, 360 从右到左 + mAnimToHexagon = ValueAnimator.ofInt(360, 0); // 暂时不修改该方法, 在内部更新方法写逻辑计算 + mAnimToHexagon.setDuration(5 * 360); + mAnimToHexagon.setRepeatCount(ValueAnimator.INFINITE); + mAnimToHexagon.setInterpolator(new TimeInterpolator() { + @Override + public float getInterpolation(float input) { + return input; + } + }); + mAnimToHexagon.addUpdateListener(animation -> { + if (mCanvasToHexagon == null) { + return; + } + // 获取递减值 => ofInt 360 递减 + Integer value = (Integer) animation.getAnimatedValue(); + // 从左往右动画 + if (mLineAnimDirection) { + mStartLinePoint = value / 360F; + if (mStartLinePoint >= 0.25F) { + mStartLinePoint = mStartLinePoint - 0.25F; + } else { + mStartLinePoint = mStartLinePoint + 0.75F; + } + // 计算结束点的位置 + mEndLinePoint = mStartLinePoint + 0.5F; + if (mStartLinePoint > 0.5F) { + // 计算移动距离, 对应的透明度 + mOffsetLinePoint = mStartLinePoint - 0.5F; + // 转换 argb + int splitColor = Color.argb((int) (255 * (mOffsetLinePoint / 0.5F)), mLineRed, mLineGreen, mLineBlue); + // 设置线条颜色 + mLineColorArray = new int[]{splitColor, mLineTran00Color, 0, 0, mLineTran255Color, splitColor}; + // 设置线条动画路径 + mLinePathArray = new float[]{0F, mOffsetLinePoint, mOffsetLinePoint, mStartLinePoint, mStartLinePoint, 1F}; + } else { + // 设置线条颜色 + mLineColorArray = new int[]{0, 0, mLineTran255Color, mLineTran00Color, 0, 0}; + // 设置线条动画路径 + mLinePathArray = new float[]{0F, mStartLinePoint, mStartLinePoint, mEndLinePoint, mEndLinePoint, 1F}; + } + } else { // 从右向左动画 + mStartLinePoint = (360 - value) / 360F; + if (mStartLinePoint >= 0.25F) { + mStartLinePoint = mStartLinePoint - 0.25F; + } else { + mStartLinePoint = mStartLinePoint + 0.75F; + } + // 计算结束点的位置 + mEndLinePoint = mStartLinePoint + 0.5F; + if (mStartLinePoint > 0.5F) { + // 计算移动距离, 对应的透明度 + mOffsetLinePoint = mStartLinePoint - 0.5F; + // 转换 argb + int splitColor = Color.argb((int) (255 * (mOffsetLinePoint / 0.5F)), mLineRed, mLineGreen, mLineBlue); + // 设置线条颜色 + mLineColorArray = new int[]{splitColor, mLineTran00Color, 0, 0, mLineTran255Color, splitColor}; + // 设置线条动画路径 + mLinePathArray = new float[]{0F, mOffsetLinePoint, mOffsetLinePoint, mStartLinePoint, mStartLinePoint, 1F}; + } else { + // 设置线条颜色 + mLineColorArray = new int[]{0, 0, mLineTran255Color, mLineTran00Color, 0, 0}; + // 设置线条动画路径 + mLinePathArray = new float[]{0F, mStartLinePoint, mStartLinePoint, mEndLinePoint, mEndLinePoint, 1F}; + } + } + // 绘制线条渐变效果 + SweepGradient mShader = new SweepGradient(mCenterToHexagon, mCenterToHexagon, mLineColorArray, mLinePathArray); + mLinePaintToHexagon.setShader(mShader); + mCanvasToHexagon.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + mCanvasToHexagon.drawPath(mLinePathToHexagon, mLinePaintToHexagon); + // 绘制 + postInvalidate(); + }); + break; + case Annulus: // 环形 + mAnimToAnnulus = ValueAnimator.ofInt(10, 20); + mAnimToAnnulus.setDuration(1L); + mAnimToAnnulus.setRepeatCount(ValueAnimator.INFINITE); + mAnimToAnnulus.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationRepeat(Animator animation) { + super.onAnimationRepeat(animation); + // 动画计算旋转 + mAnnulusAngles[0] += 4; + mAnnulusAngles[1] += 2; + if (mOffsetMaxToAnnulus) { + if (mAnnulusAngles[2] == 30) { + mOffsetMaxToAnnulus = false; + } else { + mAnnulusAngles[2]++; + } + } else { + if (mAnnulusAngles[2] == -30) { + mOffsetMaxToAnnulus = true; + } else { + mAnnulusAngles[2]--; + } + } + // 绘制 + postInvalidate(); + } + }); + break; + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/WaveView.java b/lib/DevWidget/src/main/java/dev/widget/ui/WaveView.java new file mode 100644 index 0000000000..eb6aba6359 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/WaveView.java @@ -0,0 +1,516 @@ +package dev.widget.ui; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.Shader; +import android.util.AttributeSet; +import android.view.View; + +import dev.widget.R; + +/** + * detail: 波浪 View + * @author gelitenight + *
+ *     Android 实现波浪效果 - WaveView
+ *     @see 
+ *     

+ * app:dev_amplitudeRatio="" + * app:dev_waveWaterLevel="" + * app:dev_waveLengthRatio="" + * app:dev_waveShiftRatio="" + * app:dev_behindWaveColor="" + * app:dev_frontWaveColor="" + * app:dev_waveShape="" + * app:dev_showWave="" + *
+ */ +public class WaveView + extends View { + /** + * +------------------------+ + * |<--wave length-> |______ + * | /\ | /\ | | + * | / \ | / \ | amplitude + * | / \ | / \ | | + * |/ \ |/ \|__|____ + * | \ / | | + * | \ / | | + * | \ / | | + * | \/ | water level + * | | | + * | | | + * +------------------------+__|____ + */ + + // Amplitude ( 振幅 ) - 波浪垂直振动时偏离水面的最大距离 + public static final float DEFAULT_AMPLITUDE_RATIO = 0.05F; + // Water Level ( 水位 ) - 波浪静止时水面距离底部的高度 + public static final float DEFAULT_WATER_LEVEL_RATIO = 0.5F; + // Wave Length ( 波长 ) - 一个完整的波浪的水平长度 + public static final float DEFAULT_WAVE_LENGTH_RATIO = 1.0F; + // Wave Shift ( 偏移 ) - 波浪相对于初始位置的水平偏移 + public static final float DEFAULT_WAVE_SHIFT_RATIO = 0.0F; + // 波浪背景色 + public static final int DEFAULT_BEHIND_WAVE_COLOR = Color.parseColor("#28FFFFFF"); + // 波浪前景色 + public static final int DEFAULT_FRONT_WAVE_COLOR = Color.parseColor("#3CFFFFFF"); + // 波浪外形形状 + public static final ShapeType DEFAULT_WAVE_SHAPE = ShapeType.CIRCLE; + // 边框宽度 + public static final int DEFAULT_BORDER_WIDTH = 0; + // 边框颜色 + public static final int DEFAULT_BORDER_COLOR = Color.parseColor("#44FFFFFF"); + + /** + * detail: 波浪外形形状 + * @author gelitenight + */ + public enum ShapeType { + CIRCLE, + SQUARE + } + + // if true, the shader will display the wave + private boolean mShowWave; + + // shader containing repeated waves + private BitmapShader mWaveShader; + // shader matrix + private Matrix mShaderMatrix; + // paint to draw wave + private Paint mViewPaint; + // paint to draw border + private Paint mBorderPaint; + + private float mDefaultAmplitude; + private float mDefaultWaterLevel; + private float mDefaultWaveLength; + private double mDefaultAngularFrequency; + + private float mAmplitudeRatio = DEFAULT_AMPLITUDE_RATIO; + private float mWaveLengthRatio = DEFAULT_WAVE_LENGTH_RATIO; + private float mWaterLevelRatio = DEFAULT_WATER_LEVEL_RATIO; + private float mWaveShiftRatio = DEFAULT_WAVE_SHIFT_RATIO; + + private int mBehindWaveColor = DEFAULT_BEHIND_WAVE_COLOR; + private int mFrontWaveColor = DEFAULT_FRONT_WAVE_COLOR; + private ShapeType mShapeType = DEFAULT_WAVE_SHAPE; + + private int mBorderColor; + private float mBorderWidth; + + public WaveView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public WaveView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public WaveView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mAmplitudeRatio = a.getFloat(R.styleable.DevWidget_dev_amplitudeRatio, DEFAULT_AMPLITUDE_RATIO); + mWaterLevelRatio = a.getFloat(R.styleable.DevWidget_dev_waveWaterLevel, DEFAULT_WATER_LEVEL_RATIO); + mWaveLengthRatio = a.getFloat(R.styleable.DevWidget_dev_waveLengthRatio, DEFAULT_WAVE_LENGTH_RATIO); + mWaveShiftRatio = a.getFloat(R.styleable.DevWidget_dev_waveShiftRatio, DEFAULT_WAVE_SHIFT_RATIO); + mBehindWaveColor = a.getColor(R.styleable.DevWidget_dev_behindWaveColor, DEFAULT_BEHIND_WAVE_COLOR); + mFrontWaveColor = a.getColor(R.styleable.DevWidget_dev_frontWaveColor, DEFAULT_FRONT_WAVE_COLOR); + mBorderWidth = a.getDimensionPixelSize(R.styleable.DevWidget_dev_borderWidth, DEFAULT_BORDER_WIDTH); + mBorderColor = a.getColor(R.styleable.DevWidget_dev_borderColor, DEFAULT_BORDER_COLOR); + mShapeType = a.getInt(R.styleable.DevWidget_dev_waveShape, 0) == 0 ? ShapeType.CIRCLE : ShapeType.SQUARE; + mShowWave = a.getBoolean(R.styleable.DevWidget_dev_showWave, true); + a.recycle(); + } + initialize(); + } + + private void initialize() { + mShaderMatrix = new Matrix(); + mViewPaint = new Paint(); + mViewPaint.setAntiAlias(true); + + // 设置边框宽度、颜色 + setBorder(mBorderWidth, mBorderColor); + } + + // = + + @Override + protected void onSizeChanged( + int w, + int h, + int oldw, + int oldh + ) { + super.onSizeChanged(w, h, oldw, oldh); + createShader(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mShowWave && mWaveShader != null) { + if (mViewPaint.getShader() == null) { + mViewPaint.setShader(mWaveShader); + } + + // sacle shader according to mWaveLengthRatio and mAmplitudeRatio + // this decides the size(mWaveLengthRatio for width, mAmplitudeRatio for height) of waves + mShaderMatrix.setScale( + mWaveLengthRatio / DEFAULT_WAVE_LENGTH_RATIO, + mAmplitudeRatio / DEFAULT_AMPLITUDE_RATIO, + 0, mDefaultWaterLevel + ); + + // translate shader according to mWaveShiftRatio and mWaterLevelRatio + // this decides the start position(mWaveShiftRatio for x, mWaterLevelRatio for y) of waves + mShaderMatrix.postTranslate( + mWaveShiftRatio * getWidth(), + (DEFAULT_WATER_LEVEL_RATIO - mWaterLevelRatio) * getHeight() + ); + + mWaveShader.setLocalMatrix(mShaderMatrix); + + float borderWidth = mBorderPaint == null ? 0F : mBorderPaint.getStrokeWidth(); + switch (mShapeType) { + case CIRCLE: + if (borderWidth > 0) { + canvas.drawCircle( + getWidth() / 2F, getHeight() / 2F, + (getWidth() - borderWidth) / 2F - 1F, + mBorderPaint + ); + } + float radius = getWidth() / 2F - borderWidth; + canvas.drawCircle( + getWidth() / 2F, getHeight() / 2F, + radius, mViewPaint + ); + break; + case SQUARE: + if (borderWidth > 0) { + canvas.drawRect( + borderWidth / 2F, borderWidth / 2F, + getWidth() - borderWidth / 2F - 0.5F, + getHeight() - borderWidth / 2F - 0.5F, + mBorderPaint + ); + } + canvas.drawRect( + borderWidth, borderWidth, + getWidth() - borderWidth, + getHeight() - borderWidth, + mViewPaint + ); + break; + } + } else { + mViewPaint.setShader(null); + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * Create the shader with default waves which repeat horizontally, and clamp vertically + */ + private void createShader() { + mDefaultAngularFrequency = 2.0F * Math.PI / DEFAULT_WAVE_LENGTH_RATIO / getWidth(); + mDefaultAmplitude = getHeight() * DEFAULT_AMPLITUDE_RATIO; + mDefaultWaterLevel = getHeight() * DEFAULT_WATER_LEVEL_RATIO; + mDefaultWaveLength = getWidth(); + + Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Paint wavePaint = new Paint(); + wavePaint.setStrokeWidth(2); + wavePaint.setAntiAlias(true); + + // Draw default waves into the bitmap + // y = Asin(ωx+φ)+h + final int endX = getWidth() + 1; + final int endY = getHeight() + 1; + + float[] waveY = new float[endX]; + + wavePaint.setColor(mBehindWaveColor); + for (int beginX = 0; beginX < endX; beginX++) { + double wx = beginX * mDefaultAngularFrequency; + float beginY = (float) (mDefaultWaterLevel + mDefaultAmplitude * Math.sin(wx)); + canvas.drawLine(beginX, beginY, beginX, endY, wavePaint); + waveY[beginX] = beginY; + } + + wavePaint.setColor(mFrontWaveColor); + final int wave2Shift = (int) (mDefaultWaveLength / 4); + for (int beginX = 0; beginX < endX; beginX++) { + canvas.drawLine( + beginX, waveY[(beginX + wave2Shift) % endX], + beginX, endY, wavePaint + ); + } + + // use the bitamp to create the shader + mWaveShader = new BitmapShader( + bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP + ); + mViewPaint.setShader(mWaveShader); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取波浪垂直振幅比率 + * @return 波浪垂直振幅比率 + */ + public float getAmplitudeRatio() { + return mAmplitudeRatio; + } + + /** + * 设置波浪垂直振幅比率 + *
+     *     默认为 0.05 并且 amplitudeRatio + waterLevelRatio 需小于 1
+     * 
+ * @param amplitudeRatio 波浪垂直振幅比率 + * @return {@link WaveView} + */ + public WaveView setAmplitudeRatio(final float amplitudeRatio) { + if (mAmplitudeRatio != amplitudeRatio) { + mAmplitudeRatio = amplitudeRatio; + invalidate(); + } + return this; + } + + /** + * 获取波浪水位比率 + * @return 波浪水位比率 + */ + public float getWaterLevelRatio() { + return mWaterLevelRatio; + } + + /** + * 设置波浪水位比率 + *
+     *     默认为 0.5 值范围在 0-1 之间
+     * 
+ * @param waterLevelRatio 波浪水位比率 + * @return {@link WaveView} + */ + public WaveView setWaterLevelRatio(final float waterLevelRatio) { + if (mWaterLevelRatio != waterLevelRatio) { + mWaterLevelRatio = waterLevelRatio; + invalidate(); + } + return this; + } + + /** + * 获取波浪波长比率 + * @return 波浪波长比率 + */ + public float getWaveLengthRatio() { + return mWaveLengthRatio; + } + + /** + * 设置波浪波长比率 + *
+     *     默认为 1
+     * 
+ * @param waveLengthRatio 波浪波长比率 + * @return {@link WaveView} + */ + public WaveView setWaveLengthRatio(final float waveLengthRatio) { + if (mWaveLengthRatio != waveLengthRatio) { + mWaveLengthRatio = waveLengthRatio; + invalidate(); + } + return this; + } + + /** + * 获取波浪水平偏移比率 + * @return 波浪水平偏移比率 + */ + public float getWaveShiftRatio() { + return mWaveShiftRatio; + } + + /** + * 设置波浪水平偏移比率 + *
+     *     默认为 0 值范围在 0-1 之间
+     * 
+ * @param waveShiftRatio 波浪水平偏移比率 + * @return {@link WaveView} + */ + public WaveView setWaveShiftRatio(final float waveShiftRatio) { + if (mWaveShiftRatio != waveShiftRatio) { + mWaveShiftRatio = waveShiftRatio; + invalidate(); + } + return this; + } + + /** + * 获取边框宽度 + * @return 边框宽度 + */ + public float getBorderWidth() { + return mBorderWidth; + } + + /** + * 获取边框颜色 + * @return 边框颜色 + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * 设置边框宽度、颜色 + * @param width 边框宽度 + * @param color 边框颜色 + * @return {@link WaveView} + */ + public WaveView setBorder( + final float width, + final int color + ) { + mBorderWidth = width; + mBorderColor = color; + + if (mBorderPaint == null) { + mBorderPaint = new Paint(); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setStyle(Style.STROKE); + } + mBorderPaint.setColor(color); + mBorderPaint.setStrokeWidth(width); + + invalidate(); + return this; + } + + /** + * 获取波浪背景色 + * @return 波浪背景色 + */ + public int getBehindWaveColor() { + return mBehindWaveColor; + } + + /** + * 获取波浪前景色 + * @return 波浪前景色 + */ + public int getFrontWaveColor() { + return mFrontWaveColor; + } + + /** + * 设置波浪颜色 + * @param behindWaveColor 波浪背景色 + * @param frontWaveColor 波浪前景色 + * @return {@link WaveView} + */ + public WaveView setWaveColor( + final int behindWaveColor, + final int frontWaveColor + ) { + mBehindWaveColor = behindWaveColor; + mFrontWaveColor = frontWaveColor; + + if (getWidth() > 0 && getHeight() > 0) { + // need to recreate shader when color changed + mWaveShader = null; + createShader(); + invalidate(); + } + return this; + } + + /** + * 获取波浪外形形状 + * @return 波浪外形形状 + */ + public ShapeType getShapeType() { + return mShapeType; + } + + /** + * 设置波浪外形形状 + * @param shapeType 波浪外形形状 + * @return {@link WaveView} + */ + public WaveView setShapeType(final ShapeType shapeType) { + if (shapeType != null) { + mShapeType = shapeType; + invalidate(); + } + return this; + } + + /** + * 是否进行波浪图形处理 + * @return {@code true} yes, {@code false} no + */ + public boolean isShowWave() { + return mShowWave; + } + + /** + * 设置是否进行波浪图形处理 + * @param showWave {@code true} yes, {@code false} no + * @return {@link WaveView} + */ + public WaveView setShowWave(final boolean showWave) { + mShowWave = showWave; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/WrapView.java b/lib/DevWidget/src/main/java/dev/widget/ui/WrapView.java new file mode 100644 index 0000000000..af8420e8f9 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/WrapView.java @@ -0,0 +1,343 @@ +package dev.widget.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; + +import dev.widget.R; + +/** + * detail: 换行 View + * @author Ttt + *
+ *     Google FlexboxLayout 推荐使用该库, 支持 RecyclerView ( FlexboxLayoutManager )
+ *     @see 
+ *     Android 可伸缩布局 FlexboxLayout ( 支持 RecyclerView 集成 )
+ *     @see 
+ *     

+ * app:dev_maxLine="" + * app:dev_rowTopMargin="" + * app:dev_viewLeftMargin="" + *
+ */ +public class WrapView + extends ViewGroup { + + // View 换行行数 + private int mRowLine; + // 支持最大行数 + private int mMaxLine = Integer.MAX_VALUE; + // 每一行向上的边距 ( 行间隔 ) + private int mRowTopMargin = 20; + // 每个 View 之间的 Left 边距 + private int mViewLeftMargin = 20; + + public WrapView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public WrapView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public WrapView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public WrapView( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mMaxLine = a.getInt(R.styleable.DevWidget_dev_maxLine, Integer.MAX_VALUE); + mRowTopMargin = a.getLayoutDimension(R.styleable.DevWidget_dev_rowTopMargin, 20); + mViewLeftMargin = a.getLayoutDimension(R.styleable.DevWidget_dev_viewLeftMargin, 20); + a.recycle(); + } + } + + @Override + protected void onMeasure( + int widthMeasureSpec, + int heightMeasureSpec + ) { + for (int i = 0, len = getChildCount(); i < len; i++) { + getChildAt(i).measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + } + // 获取高度模式 + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + // 判断高度是否自动处理 + boolean isHeightWrapContent = (MeasureSpec.AT_MOST == heightMode || MeasureSpec.UNSPECIFIED == heightMode); + // 获取宽度、高度 + int width = resolveSize(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), widthMeasureSpec); + int height = resolveSize(getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec), heightMeasureSpec); + // 设置 View 的宽度高度 + setMeasuredDimension(width, (isHeightWrapContent ? calc(width, mRowTopMargin, mViewLeftMargin) : height)); + } + + @Override + protected void onLayout( + boolean changed, + int l, + int t, + int r, + int b + ) { + boolean newLine = false; // 判断是否换行 + boolean fillWidth = false; // 判断是否填满宽度 (View 直接超过一行宽度 ) + int paddingTop = getPaddingTop(); // 上边距 + int paddingLeft = getPaddingLeft(); // 左边距 + int drawX = 0; // 已绘制的 X 轴距离 + int rowLine = 0; // 当前绘制的行数 + int calcHeight = 0; // 计算累加的高度 + int width = getWidth() - (paddingLeft + getPaddingRight()); // 宽度减去左右边距 + int widthSubRight = getWidth() - getPaddingRight(); // 宽度减去右边距 + int rowTopMargin = mRowTopMargin; + int viewLeftMargin = mViewLeftMargin; + // 循环所有子 View + for (int i = 0, len = getChildCount(); i < len; i++) { + final View child = this.getChildAt(i); + int childViewWidth = child.getMeasuredWidth(); + int childViewHeight = child.getMeasuredHeight(); + // 左边距 + int leftMargin = (drawX == 0) ? 0 : viewLeftMargin; + // 赋值处理 ( 防止 View 宽度超出剩余宽度 ) + newLine = fillWidth = (childViewWidth > width - leftMargin); + // 换行了则不需要边距 + if (newLine) leftMargin = 0; + // 宽度处理 + if (fillWidth) childViewWidth = width - leftMargin; + // 保存绘制距离 + drawX += childViewWidth + leftMargin; + // 如果已绘制的宽度大于 View 的宽度则换行 + if (drawX > width) { + // 表示属于换行 + newLine = true; + // 累加行数 + rowLine++; + // 如果超过了则修改边距 + leftMargin = 0; + } else if (fillWidth) { // 属于铺满的, 则重置处理 + // 表示属于换行 + newLine = true; + // 属于第一条则不处理 ( 默认加 1 了 ) + if (i != 0) { + // 累加行数 + rowLine++; + } + } + // 重置起始位置 + if (newLine) drawX = childViewWidth + leftMargin; + // 累加高度 = ( 行数 + 1) * view 的高度 + ( 行数 * 每行向上的边距 ) + 内边距 + calcHeight = (rowLine + 1) * childViewHeight + (rowLine * rowTopMargin) + paddingTop; + // 防止超出整个 View 宽度 + int right = paddingLeft + drawX; + if (right > widthSubRight) right = widthSubRight; + // 绘制 View 位置 + child.layout(right - childViewWidth, calcHeight - childViewHeight, right, calcHeight); + } + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 通过 View 宽度计算绘制所需高度 + * @param rootWidth 宽度 + * @param rowTopMargin 每一行向上的边距 ( 行间隔 ) + * @param viewLeftMargin 每个 View 之间的 Left 边距 + * @return 计算 View 高度 + */ + private int calc( + final int rootWidth, + final int rowTopMargin, + final int viewLeftMargin + ) { + boolean newLine = false; // 判断是否换行 + boolean fillWidth = false; // 判断是否填满宽度 (View 直接超过一行宽度 ) + int drawX = 0; // 已绘制的 X 轴距离 + int rowLine = 0; // 当前绘制的行数 + int calcHeight = 0; // 计算累加的高度 + int width = rootWidth - (getPaddingLeft() + getPaddingRight()); // 宽度减去左右边距 + // 循环所有子 View + for (int i = 0, len = getChildCount(); i < len; i++) { + final View child = this.getChildAt(i); + int childViewWidth = child.getMeasuredWidth(); + int childViewHeight = child.getMeasuredHeight(); + // 左边距 + int leftMargin = (drawX == 0) ? 0 : viewLeftMargin; + // 赋值处理 ( 防止 View 宽度超出剩余宽度 ) + newLine = fillWidth = (childViewWidth > width - leftMargin); + // 换行了则不需要边距 + if (newLine) leftMargin = 0; + // 宽度处理 + if (fillWidth) childViewWidth = width - leftMargin; + // 保存绘制距离 + drawX += childViewWidth + leftMargin; + // 如果已绘制的宽度大于 View 的宽度则换行 + if (drawX > width) { + // 表示属于换行 + newLine = true; + // 累加行数 + rowLine++; + // 如果超过了则修改边距 + leftMargin = 0; + } else if (fillWidth) { // 属于铺满的, 则重置处理 + // 表示属于换行 + newLine = true; + // 属于第一条则不处理 ( 默认加 1 了 ) + if (i != 0) { + // 累加行数 + rowLine++; + } + } + // 重置起始位置 + if (newLine) drawX = childViewWidth + leftMargin; + // 累加高度 = ( 行数 + 1) * view 的高度 + ( 行数 * 每行向上的边距 ) + calcHeight = (rowLine + 1) * childViewHeight + (rowLine * rowTopMargin); + } + // 获取数据总数 + int childCount = getChildCount(); + // 如果超过了最大行, 则递减重新计算 + if (rowLine >= mMaxLine) { + // 如果已经没有子 View, 则返回边距 + if (childCount == 0) return getPaddingTop() + getPaddingBottom(); + try { // 减去最后一个 + removeViewAt(childCount - 1); + } catch (Exception ignored) { + } + return calc(rootWidth, rowTopMargin, viewLeftMargin); + } + // 保存行数 + mRowLine = (childCount > 0) ? (rowLine + 1) : 0; + // 保存高度 + return getPaddingTop() + calcHeight + getPaddingBottom(); + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 刷新绘制 ( 更新配置信息后, 必须调用 ) + */ + public void refreshDraw() { + requestLayout(); + postInvalidate(); + } + + /** + * 获取 View 换行行数 + * @return View 换行行数 + */ + public int getRowLine() { + return mRowLine; + } + + /** + * 获取最大行数 + * @return 最大行数 + */ + public int getMaxLine() { + return mMaxLine; + } + + /** + * 设置最大行数 + * @param maxLine 最大行数 + * @return {@link WrapView} + */ + public WrapView setMaxLine(final int maxLine) { + this.mMaxLine = maxLine; + return this; + } + + /** + * 获取每一行向上的边距 ( 行间隔 ) + * @return 每一行向上的边距 ( 行间隔 ) + */ + public int getRowTopMargin() { + return mRowTopMargin; + } + + /** + * 设置每一行向上的边距 ( 行间隔 ) + * @param rowTopMargin 每一行向上的边距 ( 行间隔 ) + * @return {@link WrapView} + */ + public WrapView setRowTopMargin(final int rowTopMargin) { + this.mRowTopMargin = rowTopMargin; + return this; + } + + /** + * 获取每个 View 之间的 Left 边距 + * @return 每个 View 之间的 Left 边距 + */ + public int getViewLeftMargin() { + return mViewLeftMargin; + } + + /** + * 设置每个 View 之间的 Left 边距 + * @param viewLeftMargin 每个 View 之间的 Left 边距 + * @return {@link WrapView} + */ + public WrapView setViewLeftMargin(final int viewLeftMargin) { + this.mViewLeftMargin = viewLeftMargin; + return this; + } + + /** + * 设置 Row View 边距 + * @param rowTopMargin 每一行向上的边距 ( 行间隔 ) + * @param viewLeftMargin 每个 View 之间的 Left 边距 + * @return {@link WrapView} + */ + public WrapView setRowViewMargin( + final int rowTopMargin, + final int viewLeftMargin + ) { + this.mRowTopMargin = rowTopMargin; + this.mViewLeftMargin = viewLeftMargin; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundButton.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundButton.java new file mode 100644 index 0000000000..e2e4135a3b --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundButton.java @@ -0,0 +1,70 @@ +package dev.widget.ui.round; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatButton; + +/** + * detail: 圆角 Button + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundButton + extends AppCompatButton { + + public RoundButton(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundButton( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundButton( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundConstraintLayout.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundConstraintLayout.java new file mode 100644 index 0000000000..851b522138 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundConstraintLayout.java @@ -0,0 +1,83 @@ +package dev.widget.ui.round; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; + +import androidx.constraintlayout.widget.ConstraintLayout; + +/** + * detail: 圆角 ConstraintLayout + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundConstraintLayout + extends ConstraintLayout { + + public RoundConstraintLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundConstraintLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundConstraintLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public RoundConstraintLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundDrawable.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundDrawable.java new file mode 100644 index 0000000000..ed4c53ce2d --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundDrawable.java @@ -0,0 +1,235 @@ +package dev.widget.ui.round; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.Nullable; + +import dev.widget.R; + +/** + * detail: 圆角 Drawable + * @author Ttt + *
+ *     使用 {@link #setBgData(ColorStateList)} 设置背景色
+ *     使用 {@link #setStrokeData(int, ColorStateList)} 设置描边大小、描边颜色
+ *     使用 {@link #setRadiusAdjustBounds(boolean)} 设置圆角大小是否自动适应为 {@link android.view.View} 的高度的一半, 默认为 true
+ *     

+ * 注意事项: + * 因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突 + * 如果在 xml 中用 android:background 指定 background, 该 background 不会生效 + *

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundDrawable + extends GradientDrawable { + + // 圆角大小是否自适应为 View 的高度的一半 + private boolean mRadiusAdjustBounds = true; + // 填充颜色 ( 背景色 ) + private ColorStateList mFillColors; + // 描边粗细、颜色 + private int mStrokeWidth = 0; + private ColorStateList mStrokeColors; + + @Override + protected boolean onStateChange(int[] stateSet) { + boolean superRet = super.onStateChange(stateSet); + if (mFillColors != null) { + int color = mFillColors.getColorForState(stateSet, 0); + setColor(color); + superRet = true; + } + if (mStrokeColors != null) { + int color = mStrokeColors.getColorForState(stateSet, 0); + setStroke(mStrokeWidth, color); + superRet = true; + } + return superRet; + } + + @Override + public boolean isStateful() { + return (mFillColors != null && mFillColors.isStateful()) + || (mStrokeColors != null && mStrokeColors.isStateful()) + || super.isStateful(); + } + + @Override + protected void onBoundsChange(Rect r) { + super.onBoundsChange(r); + if (mRadiusAdjustBounds) { + // 修改圆角为短边的一半 + setCornerRadius(Math.min(r.width(), r.height()) / 2); + } + } + + // ========== + // = 内部方法 = + // ========== + + private boolean hasNativeStateListAPI() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + // =========== + // = get/set = + // =========== + + /** + * 获取描边粗细 + * @return 描边粗细 + */ + public int getStrokeWidth() { + return mStrokeWidth; + } + + /** + * 设置描边粗细和颜色 + * @param colors 描边颜色 + * @return {@link RoundDrawable} + */ + public RoundDrawable setStrokeColors(@Nullable final ColorStateList colors) { + return setStrokeData(mStrokeWidth, colors); + } + + /** + * 设置描边粗细和颜色 + * @param width 描边粗细 + * @param colors 描边颜色 + * @return {@link RoundDrawable} + */ + public RoundDrawable setStrokeData( + final int width, + final ColorStateList colors + ) { + mStrokeWidth = width; + mStrokeColors = colors; + if (hasNativeStateListAPI()) { + super.setStroke(width, colors); + } else { + final int currentColor; + if (colors == null) { + currentColor = Color.TRANSPARENT; + } else { + currentColor = colors.getColorForState(getState(), 0); + } + setStroke(width, currentColor); + } + return this; + } + + /** + * 设置按钮的背景色 ( 只支持纯色, 不支持 Bitmap 或 Drawable ) + * @param colors {@link ColorStateList} + * @return {@link RoundDrawable} + */ + public RoundDrawable setBgData(final ColorStateList colors) { + if (hasNativeStateListAPI()) { + super.setColor(colors); + } else { + mFillColors = colors; + final int currentColor; + if (colors == null) { + currentColor = Color.TRANSPARENT; + } else { + currentColor = colors.getColorForState(getState(), 0); + } + setColor(currentColor); + } + return this; + } + + /** + * 设置圆角大小是否自适应为 View 的高度的一半 + * @param isRadiusAdjustBounds {@code true} yes, {@code false} no + * @return {@link RoundDrawable} + */ + public RoundDrawable setRadiusAdjustBounds(final boolean isRadiusAdjustBounds) { + mRadiusAdjustBounds = isRadiusAdjustBounds; + return this; + } + + /** + * 通过 AttributeSet 构建 RoundDrawable + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + * @return {@link RoundDrawable} + */ + public static RoundDrawable fromAttributeSet( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + ColorStateList colorBg = a.getColorStateList(R.styleable.DevWidget_dev_backgroundColor); + ColorStateList colorBorder = a.getColorStateList(R.styleable.DevWidget_dev_borderColor); + int borderWidth = a.getDimensionPixelSize(R.styleable.DevWidget_dev_borderWidth, 0); + boolean isRadiusAdjustBounds = a.getBoolean(R.styleable.DevWidget_dev_isRadiusAdjustBounds, false); + int radius = a.getDimensionPixelSize(R.styleable.DevWidget_dev_radius, 0); + int radiusLeftTop = a.getDimensionPixelSize(R.styleable.DevWidget_dev_radiusLeftTop, 0); + int radiusLeftBottom = a.getDimensionPixelSize(R.styleable.DevWidget_dev_radiusLeftBottom, 0); + int radiusRightTop = a.getDimensionPixelSize(R.styleable.DevWidget_dev_radiusRightTop, 0); + int radiusRightBottom = a.getDimensionPixelSize(R.styleable.DevWidget_dev_radiusRightBottom, 0); + a.recycle(); + + RoundDrawable bg = new RoundDrawable(); + bg.setBgData(colorBg); + bg.setStrokeData(borderWidth, colorBorder); + if (radiusLeftTop > 0 || radiusRightTop > 0 || radiusLeftBottom > 0 || radiusRightBottom > 0) { + float[] radii = new float[]{ + radiusLeftTop, radiusLeftTop, + radiusRightTop, radiusRightTop, + radiusRightBottom, radiusRightBottom, + radiusLeftBottom, radiusLeftBottom + }; + bg.setCornerRadii(radii); + isRadiusAdjustBounds = false; + } else { + bg.setCornerRadius(radius); + if (radius > 0) { + isRadiusAdjustBounds = false; + } + } + bg.setRadiusAdjustBounds(isRadiusAdjustBounds); + return bg; + } + + /** + * 设置背景 + * @param view {@link View} + * @param drawable {@link Drawable} + */ + public static void setBackgroundKeepingPadding( + final View view, + final Drawable drawable + ) { + int[] padding = new int[]{view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()}; + view.setBackgroundDrawable(drawable); + view.setPadding(padding[0], padding[1], padding[2], padding[3]); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundFrameLayout.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundFrameLayout.java new file mode 100644 index 0000000000..3a17b72a48 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundFrameLayout.java @@ -0,0 +1,82 @@ +package dev.widget.ui.round; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * detail: 圆角 FrameLayout + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundFrameLayout + extends FrameLayout { + + public RoundFrameLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundFrameLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundFrameLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public RoundFrameLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundImageView.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundImageView.java new file mode 100644 index 0000000000..c1616c2c4b --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundImageView.java @@ -0,0 +1,630 @@ +package dev.widget.ui.round; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewOutlineProvider; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; +import androidx.appcompat.widget.AppCompatImageView; + +import dev.utils.LogPrintUtils; +import dev.widget.R; + +/** + * detail: 圆角图片 + * @author hdodenhof + *
+ *     该类使用 CircleImageView 代码, 减少非必要代码依赖
+ *     

+ * app:dev_borderWidth="" + * app:dev_borderColor="" + * app:dev_borderOverlay="" + * app:dev_circleBackgroundColor="" + *
+ */ +public class RoundImageView + extends AppCompatImageView { + + // 日志 TAG + public static final String TAG = RoundImageView.class.getSimpleName(); + + // 默认缩放类型 + private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; + // 默认值 + private static final boolean DEFAULT_BORDER_OVERLAY = false; + private static final int DEFAULT_BORDER_WIDTH = 0; + private static final int DEFAULT_BORDER_COLOR = Color.BLACK; + private static final int DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT; + private static final int DEFAULT_IMAGE_ALPHA = 255; + + // ======= + // = 变量 = + // ======= + + // 圆角绘制坐标 + private final RectF mDrawableRect = new RectF(); + // 边框绘制坐标 + private final RectF mBorderRect = new RectF(); + // 画笔绘制相关 + private final Matrix mShaderMatrix = new Matrix(); + private final Paint mBitmapPaint = new Paint(); + private final Paint mBorderPaint = new Paint(); + private final Paint mCircleBackgroundPaint = new Paint(); + // 绘制相关属性 + private int mBorderColor = DEFAULT_BORDER_COLOR; + private int mBorderWidth = DEFAULT_BORDER_WIDTH; + private int mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR; + private int mImageAlpha = DEFAULT_IMAGE_ALPHA; + // 待绘制资源信息 + private Bitmap mBitmap; + // 绘制圆角数据 + private float mDrawableRadius; + private float mBorderRadius; + + private ColorFilter mColorFilter; + + private boolean mInitialized; + private boolean mRebuildShader; + private boolean mBorderOverlay; + private boolean mDisableCircularTransformation; + + public RoundImageView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundImageView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundImageView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mBorderWidth = a.getDimensionPixelSize(R.styleable.DevWidget_dev_borderWidth, DEFAULT_BORDER_WIDTH); + mBorderColor = a.getColor(R.styleable.DevWidget_dev_borderColor, DEFAULT_BORDER_COLOR); + mBorderOverlay = a.getBoolean(R.styleable.DevWidget_dev_borderOverlay, DEFAULT_BORDER_OVERLAY); + mCircleBackgroundColor = a.getColor(R.styleable.DevWidget_dev_circleBackgroundColor, DEFAULT_CIRCLE_BACKGROUND_COLOR); + a.recycle(); + } + initialize(); + } + + private void initialize() { + mInitialized = true; + + super.setScaleType(SCALE_TYPE); + + mBitmapPaint.setAntiAlias(true); + mBitmapPaint.setDither(true); + mBitmapPaint.setFilterBitmap(true); + mBitmapPaint.setAlpha(mImageAlpha); + mBitmapPaint.setColorFilter(mColorFilter); + + mBorderPaint.setStyle(Paint.Style.STROKE); + mBorderPaint.setAntiAlias(true); + mBorderPaint.setColor(mBorderColor); + mBorderPaint.setStrokeWidth(mBorderWidth); + + mCircleBackgroundPaint.setStyle(Paint.Style.FILL); + mCircleBackgroundPaint.setAntiAlias(true); + mCircleBackgroundPaint.setColor(mCircleBackgroundColor); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setOutlineProvider(new OutlineProvider()); + } + } + + // = + + @Override + public void setScaleType(ScaleType scaleType) { + if (scaleType != SCALE_TYPE) { + throw new IllegalArgumentException( + String.format("ScaleType %s not supported.", scaleType) + ); + } + } + + @Override + public void setAdjustViewBounds(boolean adjustViewBounds) { + if (adjustViewBounds) { + throw new IllegalArgumentException("adjustViewBounds not supported."); + } + } + + // = + + @SuppressLint("CanvasSize") + @Override + protected void onDraw(Canvas canvas) { + if (mDisableCircularTransformation) { + super.onDraw(canvas); + return; + } + + if (mCircleBackgroundColor != Color.TRANSPARENT) { + canvas.drawCircle( + mDrawableRect.centerX(), mDrawableRect.centerY(), + mDrawableRadius, mCircleBackgroundPaint + ); + } + + if (mBitmap != null) { + if (mRebuildShader) { + mRebuildShader = false; + + BitmapShader bitmapShader = new BitmapShader( + mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP + ); + bitmapShader.setLocalMatrix(mShaderMatrix); + + mBitmapPaint.setShader(bitmapShader); + } + canvas.drawCircle( + mDrawableRect.centerX(), mDrawableRect.centerY(), + mDrawableRadius, mBitmapPaint + ); + } + + if (mBorderWidth > 0) { + canvas.drawCircle( + mBorderRect.centerX(), mBorderRect.centerY(), + mBorderRadius, mBorderPaint + ); + } + } + + // = + + @Override + protected void onSizeChanged( + int w, + int h, + int oldw, + int oldh + ) { + super.onSizeChanged(w, h, oldw, oldh); + updateDimensions(); + invalidate(); + } + + @Override + public void setPadding( + int left, + int top, + int right, + int bottom + ) { + super.setPadding(left, top, right, bottom); + updateDimensions(); + invalidate(); + } + + @Override + public void setPaddingRelative( + int start, + int top, + int end, + int bottom + ) { + super.setPaddingRelative(start, top, end, bottom); + updateDimensions(); + invalidate(); + } + + // = + + @Override + public void setImageBitmap(Bitmap bm) { + super.setImageBitmap(bm); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + super.setImageDrawable(drawable); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageResource(@DrawableRes int resId) { + super.setImageResource(resId); + initializeBitmap(); + invalidate(); + } + + @Override + public void setImageURI(Uri uri) { + super.setImageURI(uri); + initializeBitmap(); + invalidate(); + } + + // = + + @Override + public int getImageAlpha() { + return mImageAlpha; + } + + @Override + public void setImageAlpha(int alpha) { + alpha &= 0xFF; + + if (alpha == mImageAlpha) { + return; + } + mImageAlpha = alpha; + + // This might be called during ImageView construction before + // member initialization has finished on API level >= 16. + if (mInitialized) { + mBitmapPaint.setAlpha(alpha); + invalidate(); + } + } + + @Override + public ColorFilter getColorFilter() { + return mColorFilter; + } + + @Override + public void setColorFilter(ColorFilter cf) { + if (cf == mColorFilter) { + return; + } + mColorFilter = cf; + + // This might be called during ImageView construction before + // member initialization has finished on API level <= 19. + if (mInitialized) { + mBitmapPaint.setColorFilter(cf); + invalidate(); + } + } + + // = + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (mDisableCircularTransformation) { + return super.onTouchEvent(event); + } + return inTouchableArea(event.getX(), event.getY()) && super.onTouchEvent(event); + } + + private boolean inTouchableArea( + final float x, + final float y + ) { + if (mBorderRect.isEmpty()) { + return true; + } + return (Math.pow(x - mBorderRect.centerX(), 2) + + Math.pow(y - mBorderRect.centerY(), 2) + ) <= Math.pow(mBorderRadius, 2); + } + + // ========== + // = 内部方法 = + // ========== + + /** + * detail: 描边轮廓 + * @author hdodenhof + */ + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private class OutlineProvider + extends ViewOutlineProvider { + @Override + public void getOutline( + View view, + Outline outline + ) { + if (mDisableCircularTransformation) { + ViewOutlineProvider.BACKGROUND.getOutline(view, outline); + } else { + Rect bounds = new Rect(); + mBorderRect.roundOut(bounds); + outline.setRoundRect(bounds, bounds.width() / 2.0F); + } + } + } + + /** + * 初始化 Bitmap + */ + private void initializeBitmap() { + mBitmap = getBitmapFromDrawable(getDrawable()); + + if (!mInitialized) return; + + if (mBitmap != null) { + updateShaderMatrix(); + } else { + mBitmapPaint.setShader(null); + } + } + + /** + * 更新尺寸信息 + */ + private void updateDimensions() { + mBorderRect.set(calculateBounds()); + mBorderRadius = Math.min( + (mBorderRect.height() - mBorderWidth) / 2.0F, + (mBorderRect.width() - mBorderWidth) / 2.0F + ); + mDrawableRect.set(mBorderRect); + if (!mBorderOverlay && mBorderWidth > 0) { + mDrawableRect.inset(mBorderWidth - 1.0F, mBorderWidth - 1.0F); + } + mDrawableRadius = Math.min( + mDrawableRect.height() / 2.0F, + mDrawableRect.width() / 2.0F + ); + updateShaderMatrix(); + } + + /** + * 计算边界 + * @return {@link RectF} + */ + private RectF calculateBounds() { + int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom(); + + int sideLength = Math.min(availableWidth, availableHeight); + + float left = getPaddingLeft() + (availableWidth - sideLength) / 2F; + float top = getPaddingTop() + (availableHeight - sideLength) / 2F; + + return new RectF(left, top, left + sideLength, top + sideLength); + } + + /** + * 更新着色器 Matrix + */ + private void updateShaderMatrix() { + if (mBitmap == null) return; + + float scale, dx = 0, dy = 0; + + mShaderMatrix.set(null); + + int bitmapHeight = mBitmap.getHeight(); + int bitmapWidth = mBitmap.getWidth(); + + if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) { + scale = mDrawableRect.height() / (float) bitmapHeight; + dx = (mDrawableRect.width() - bitmapWidth * scale) * 0.5F; + } else { + scale = mDrawableRect.width() / (float) bitmapWidth; + dy = (mDrawableRect.height() - bitmapHeight * scale) * 0.5F; + } + mShaderMatrix.setScale(scale, scale); + mShaderMatrix.postTranslate( + (int) (dx + 0.5F) + mDrawableRect.left, + (int) (dy + 0.5F) + mDrawableRect.top + ); + mRebuildShader = true; + } + + // ========================== + // = Drawable 转 Bitmap 常量 = + // ========================== + + private static final int COLORDRAWABLE_DIMENSION = 2; + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + + /** + * Drawable 转 Bitmap + * @param drawable 待转换图片 + * @return {@link Bitmap} + */ + private Bitmap getBitmapFromDrawable(final Drawable drawable) { + if (drawable == null) return null; + + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + try { + Bitmap bitmap; + if (drawable instanceof ColorDrawable) { + bitmap = Bitmap.createBitmap( + COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG + ); + } else { + bitmap = Bitmap.createBitmap( + drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG + ); + } + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } catch (Exception e) { + LogPrintUtils.eTag(TAG, e, "getBitmapFromDrawable"); + return null; + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取边框宽度 + * @return 边框宽度 + */ + public int getBorderWidth() { + return mBorderWidth; + } + + /** + * 设置边框宽度 + * @param borderWidth 边框宽度 + * @return {@link RoundImageView} + */ + public RoundImageView setBorderWidth(final int borderWidth) { + if (borderWidth == mBorderWidth) { + return this; + } + mBorderWidth = borderWidth; + mBorderPaint.setStrokeWidth(borderWidth); + updateDimensions(); + invalidate(); + return this; + } + + /** + * 获取边框颜色 + * @return 边框颜色 + */ + public int getBorderColor() { + return mBorderColor; + } + + /** + * 设置边框颜色 + * @param borderColor 边框颜色 + * @return {@link RoundImageView} + */ + public RoundImageView setBorderColor(@ColorInt final int borderColor) { + if (borderColor == mBorderColor) { + return this; + } + mBorderColor = borderColor; + mBorderPaint.setColor(borderColor); + invalidate(); + return this; + } + + /** + * 获取圆圈背景颜色 + * @return 圆圈背景颜色 + */ + public int getCircleBackgroundColor() { + return mCircleBackgroundColor; + } + + /** + * 设置圆圈背景颜色 + * @param circleBackgroundColor 圆圈背景颜色 + * @return {@link RoundImageView} + */ + public RoundImageView setCircleBackgroundColor(@ColorInt final int circleBackgroundColor) { + if (circleBackgroundColor == mCircleBackgroundColor) { + return this; + } + mCircleBackgroundColor = circleBackgroundColor; + mCircleBackgroundPaint.setColor(circleBackgroundColor); + invalidate(); + return this; + } + + /** + * 是否叠加边框 + * @return {@code true} yes, {@code false} no + */ + public boolean isBorderOverlay() { + return mBorderOverlay; + } + + /** + * 设置是否叠加边框 + * @param borderOverlay {@code true} yes, {@code false} no + * @return {@link RoundImageView} + */ + public RoundImageView setBorderOverlay(final boolean borderOverlay) { + if (borderOverlay == mBorderOverlay) { + return this; + } + mBorderOverlay = borderOverlay; + updateDimensions(); + invalidate(); + return this; + } + + /** + * 是否开启圆圈处理 + * @return {@code true} yes, {@code false} no + */ + public boolean isDisableCircularTransformation() { + return mDisableCircularTransformation; + } + + /** + * 设置是否开启圆圈处理 + * @param disableCircularTransformation {@code true} yes, {@code false} no + * @return {@link RoundImageView} + */ + public RoundImageView setDisableCircularTransformation(final boolean disableCircularTransformation) { + if (disableCircularTransformation == mDisableCircularTransformation) { + return this; + } + mDisableCircularTransformation = disableCircularTransformation; + + if (disableCircularTransformation) { + mBitmap = null; + mBitmapPaint.setShader(null); + } else { + initializeBitmap(); + } + invalidate(); + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundLinearLayout.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundLinearLayout.java new file mode 100644 index 0000000000..97429cea12 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundLinearLayout.java @@ -0,0 +1,82 @@ +package dev.widget.ui.round; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +/** + * detail: 圆角 LinearLayout + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundLinearLayout + extends LinearLayout { + + public RoundLinearLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundLinearLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundLinearLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public RoundLinearLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundRelativeLayout.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundRelativeLayout.java new file mode 100644 index 0000000000..fd9382816c --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundRelativeLayout.java @@ -0,0 +1,82 @@ +package dev.widget.ui.round; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.RelativeLayout; + +/** + * detail: 圆角 RelativeLayout + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundRelativeLayout + extends RelativeLayout { + + public RoundRelativeLayout(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundRelativeLayout( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundRelativeLayout( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public RoundRelativeLayout( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + super(context, attrs, defStyleAttr, defStyleRes); + initAttrs(context, attrs, defStyleAttr, defStyleRes); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundTextView.java b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundTextView.java new file mode 100644 index 0000000000..aeb77cc742 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/ui/round/RoundTextView.java @@ -0,0 +1,70 @@ +package dev.widget.ui.round; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatTextView; + +/** + * detail: 圆角 TextView + * @author Ttt + *
+ *     注意事项:
+ *     因为该控件的圆角采用 View 的 background 实现, 所以与原生的 android:background 有冲突
+ *     如果在 xml 中用 android:background 指定 background, 该 background 不会生效
+ *     

+ * 该类使用 QMUI QMUIRoundButtonDrawable 代码, 减少非必要代码依赖 + *

+ * app:dev_backgroundColor="" + * app:dev_borderColor="" + * app:dev_borderWidth="" + * app:dev_isRadiusAdjustBounds="" + * app:dev_radius="" + * app:dev_radiusLeftBottom="" + * app:dev_radiusLeftTop="" + * app:dev_radiusRightBottom="" + * app:dev_radiusRightTop="" + *
+ */ +public class RoundTextView + extends AppCompatTextView { + + public RoundTextView(Context context) { + super(context); + initAttrs(context, null, 0, 0); + } + + public RoundTextView( + Context context, + AttributeSet attrs + ) { + super(context, attrs); + initAttrs(context, attrs, 0, 0); + } + + public RoundTextView( + Context context, + AttributeSet attrs, + int defStyleAttr + ) { + super(context, attrs, defStyleAttr); + initAttrs(context, attrs, defStyleAttr, 0); + } + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + private void initAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + RoundDrawable bg = RoundDrawable.fromAttributeSet(context, attrs, defStyleAttr, defStyleRes); + RoundDrawable.setBackgroundKeepingPadding(this, bg); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/utils/RadiusAttrs.java b/lib/DevWidget/src/main/java/dev/widget/utils/RadiusAttrs.java new file mode 100644 index 0000000000..6063612871 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/utils/RadiusAttrs.java @@ -0,0 +1,262 @@ +package dev.widget.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Path; +import android.graphics.RectF; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; + +import dev.widget.R; + +/** + * detail: RadiusLayout 圆角属性封装处理类 + * @author arvinljw + */ +public class RadiusAttrs { + + // 绘制区域 + private RectF mRectF; + // 绘制路径 + private final Path mPath = new Path(); + // 圆角范围 + private float[] mRadii = new float[8]; + // 圆角值 + private float mRadius; + private float mRadiusLeftTop; + private float mRadiusLeftBottom; + private float mRadiusRightTop; + private float mRadiusRightBottom; + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + public RadiusAttrs( + final Context context, + final AttributeSet attrs, + final int defStyleAttr, + final int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mRadius = a.getLayoutDimension(R.styleable.DevWidget_dev_radius, 0); + mRadiusLeftTop = a.getLayoutDimension(R.styleable.DevWidget_dev_radiusLeftTop, 0); + mRadiusLeftBottom = a.getLayoutDimension(R.styleable.DevWidget_dev_radiusLeftBottom, 0); + mRadiusRightTop = a.getLayoutDimension(R.styleable.DevWidget_dev_radiusRightTop, 0); + mRadiusRightBottom = a.getLayoutDimension(R.styleable.DevWidget_dev_radiusRightBottom, 0); + a.recycle(); + + if (mRadius != 0) { + mRadiusLeftTop = mRadius; + mRadiusLeftBottom = mRadius; + mRadiusRightTop = mRadius; + mRadiusRightBottom = mRadius; + } + } + resetRadius(); + } + + /** + * 重置绘制圆角值 + */ + private void resetRadius() { + mRadii[0] = mRadii[1] = mRadiusLeftTop; + mRadii[2] = mRadii[3] = mRadiusRightTop; + mRadii[4] = mRadii[5] = mRadiusRightBottom; + mRadii[6] = mRadii[7] = mRadiusLeftBottom; + } + + /** + * View 宽高改变时调用 + * @param w 宽度 + * @param h 高度 + */ + public void onSizeChanged( + final int w, + final int h + ) { + mRectF = new RectF(0, 0, w, h); + } + + /** + * 获取绘制路径 + * @return {@link Path} + */ + public Path getPath() { + mPath.reset(); + if (mRectF != null) { + mPath.addRoundRect(mRectF, mRadii, Path.Direction.CW); + } + return mPath; + } + + // =========== + // = get/set = + // =========== + + /** + * 设置圆角值 + * @param radius 圆角值 + */ + public void setRadius(final float radius) { + this.mRadius = radius; + this.mRadiusLeftTop = radius; + this.mRadiusLeftBottom = radius; + this.mRadiusRightTop = radius; + this.mRadiusRightBottom = radius; + resetRadius(); + } + + /** + * 设置左上圆角值 + * @param radiusLeftTop 左上圆角值 + */ + public void setRadiusLeftTop(final float radiusLeftTop) { + this.mRadiusLeftTop = radiusLeftTop; + resetRadius(); + } + + /** + * 设置左下圆角值 + * @param radiusLeftBottom 左下圆角值 + */ + public void setRadiusLeftBottom(final float radiusLeftBottom) { + this.mRadiusLeftBottom = radiusLeftBottom; + resetRadius(); + } + + /** + * 设置右上圆角值 + * @param radiusRightTop 右上圆角值 + */ + public void setRadiusRightTop(final float radiusRightTop) { + this.mRadiusRightTop = radiusRightTop; + resetRadius(); + } + + /** + * 设置右下圆角值 + * @param radiusRightBottom 右下圆角值 + */ + public void setRadiusRightBottom(final float radiusRightBottom) { + this.mRadiusRightBottom = radiusRightBottom; + resetRadius(); + } + + // = + + /** + * 设置左上、左下圆角值 + * @param radiusLeft 左边圆角值 + */ + public void setRadiusLeft(final int radiusLeft) { + this.mRadiusLeftTop = radiusLeft; + this.mRadiusLeftBottom = radiusLeft; + resetRadius(); + } + + /** + * 设置右上、右下圆角值 + * @param radiusRight 右边圆角值 + */ + public void setRadiusRight(final int radiusRight) { + this.mRadiusRightTop = radiusRight; + this.mRadiusRightBottom = radiusRight; + resetRadius(); + } + + /** + * 设置左上、右上圆角值 + * @param radiusTop 上边圆角值 + */ + public void setRadiusTop(final int radiusTop) { + this.mRadiusLeftTop = radiusTop; + this.mRadiusRightTop = radiusTop; + resetRadius(); + } + + /** + * 设置左下、右下圆角值 + * @param radiusBottom 下边圆角值 + */ + public void setRadiusBottom(final int radiusBottom) { + this.mRadiusLeftBottom = radiusBottom; + this.mRadiusRightBottom = radiusBottom; + resetRadius(); + } + + // = + + /** + * 获取圆角值 + * @return 圆角值 + */ + public float getRadius() { + return mRadius; + } + + /** + * 获取左上圆角值 + * @return 左上圆角值 + */ + public float getRadiusLeftTop() { + return mRadiusLeftTop; + } + + /** + * 获取左下圆角值 + * @return 左下圆角值 + */ + public float getRadiusLeftBottom() { + return mRadiusLeftBottom; + } + + /** + * 获取右上圆角值 + * @return 右上圆角值 + */ + public float getRadiusRightTop() { + return mRadiusRightTop; + } + + /** + * 获取右下圆角值 + * @return 右下圆角值 + */ + public float getRadiusRightBottom() { + return mRadiusRightBottom; + } + + // ================ + // = 数据恢复 / 存储 = + // ================ + + private static final String SUPER_DATA = "super_data"; + private static final String DATA_RADII = "data_radii"; + + public Parcelable onSaveInstanceState(final Parcelable superState) { + Bundle data = new Bundle(); + data.putParcelable(SUPER_DATA, superState); + data.putFloatArray(DATA_RADII, mRadii); + return data; + } + + public Parcelable onRestoreInstanceState(final Parcelable state) { + Bundle bundle = (Bundle) state; + mRadii = bundle.getFloatArray(DATA_RADII); + if (mRadii != null) { + mRadiusLeftTop = mRadii[0]; + mRadiusRightTop = mRadii[2]; + mRadiusRightBottom = mRadii[4]; + mRadiusLeftBottom = mRadii[6]; + } + return bundle.getParcelable(SUPER_DATA); + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/utils/WaveHelper.java b/lib/DevWidget/src/main/java/dev/widget/utils/WaveHelper.java new file mode 100644 index 0000000000..9e0b6b1a79 --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/utils/WaveHelper.java @@ -0,0 +1,631 @@ +package dev.widget.utils; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.LinearInterpolator; + +import java.util.ArrayList; +import java.util.List; + +import dev.widget.ui.WaveView; + +/** + * detail: 波浪 View Helper 类 + * @author Ttt + */ +public class WaveHelper { + + // 波浪 View + private WaveView mWaveView; + // 波浪动画集合 + private AnimatorSet mAnimatorSet; + + /** + * 构造函数 + * @param waveView 波浪 View + */ + private WaveHelper(final WaveView waveView) { + mWaveView = waveView; + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 WaveHelper + * @param waveView 波浪 View + * @return {@link WaveHelper} + */ + public static WaveHelper get(final WaveView waveView) { + return new WaveHelper(waveView); + } + + // ========== + // = 动画开关 = + // ========== + + /** + * 启动动画 + */ + public void start() { + // 显示波浪效果 + setShowWave(true); + + if (mAnimatorSet != null) { + mAnimatorSet.start(); + } + } + + /** + * 关闭动画 + */ + public void cancel() { + if (mAnimatorSet != null) { + mAnimatorSet.end(); + } + } + + // =========== + // = get/set = + // =========== + + /** + * 获取波浪垂直振幅比率 + * @return 波浪垂直振幅比率 + */ + public float getAmplitudeRatio() { + if (mWaveView != null) { + return mWaveView.getAmplitudeRatio(); + } + return WaveView.DEFAULT_AMPLITUDE_RATIO; + } + + /** + * 设置波浪垂直振幅比率 + *
+     *     默认为 0.05 并且 amplitudeRatio + waterLevelRatio 需小于 1
+     * 
+ * @param amplitudeRatio 波浪垂直振幅比率 + * @return {@link WaveHelper} + */ + public WaveHelper setAmplitudeRatio(final float amplitudeRatio) { + if (mWaveView != null) { + mWaveView.setAmplitudeRatio(amplitudeRatio); + } + return this; + } + + /** + * 获取波浪水位比率 + * @return 波浪水位比率 + */ + public float getWaterLevelRatio() { + if (mWaveView != null) { + return mWaveView.getWaterLevelRatio(); + } + return WaveView.DEFAULT_WATER_LEVEL_RATIO; + } + + /** + * 设置波浪水位比率 + *
+     *     默认为 0.5 值范围在 0-1 之间
+     * 
+ * @param waterLevelRatio 波浪水位比率 + * @return {@link WaveHelper} + */ + public WaveHelper setWaterLevelRatio(final float waterLevelRatio) { + if (mWaveView != null) { + mWaveView.setWaterLevelRatio(waterLevelRatio); + } + return this; + } + + /** + * 获取波浪波长比率 + * @return 波浪波长比率 + */ + public float getWaveLengthRatio() { + if (mWaveView != null) { + return mWaveView.getWaveLengthRatio(); + } + return WaveView.DEFAULT_WAVE_LENGTH_RATIO; + } + + /** + * 设置波浪波长比率 + *
+     *     默认为 1
+     * 
+ * @param waveLengthRatio 波浪波长比率 + * @return {@link WaveHelper} + */ + public WaveHelper setWaveLengthRatio(final float waveLengthRatio) { + if (mWaveView != null) { + mWaveView.setWaveLengthRatio(waveLengthRatio); + } + return this; + } + + /** + * 获取波浪水平偏移比率 + * @return 波浪水平偏移比率 + */ + public float getWaveShiftRatio() { + if (mWaveView != null) { + return mWaveView.getWaveShiftRatio(); + } + return WaveView.DEFAULT_WAVE_SHIFT_RATIO; + } + + /** + * 设置波浪水平偏移比率 + *
+     *     默认为 0 值范围在 0-1 之间
+     * 
+ * @param waveShiftRatio 波浪水平偏移比率 + * @return {@link WaveHelper} + */ + public WaveHelper setWaveShiftRatio(final float waveShiftRatio) { + if (mWaveView != null) { + mWaveView.setWaveShiftRatio(waveShiftRatio); + } + return this; + } + + /** + * 获取边框宽度 + * @return 边框宽度 + */ + public float getBorderWidth() { + if (mWaveView != null) { + return mWaveView.getBorderWidth(); + } + return WaveView.DEFAULT_BORDER_WIDTH; + } + + /** + * 获取边框颜色 + * @return 边框颜色 + */ + public int getBorderColor() { + if (mWaveView != null) { + return mWaveView.getBorderColor(); + } + return WaveView.DEFAULT_BORDER_COLOR; + } + + /** + * 设置边框宽度、颜色 + * @param width 边框宽度 + * @param color 边框颜色 + * @return {@link WaveHelper} + */ + public WaveHelper setBorder( + final float width, + final int color + ) { + if (mWaveView != null) { + mWaveView.setBorder(width, color); + } + return this; + } + + /** + * 获取波浪背景色 + * @return 波浪背景色 + */ + public int getBehindWaveColor() { + if (mWaveView != null) { + return mWaveView.getBehindWaveColor(); + } + return WaveView.DEFAULT_BEHIND_WAVE_COLOR; + } + + /** + * 获取波浪前景色 + * @return 波浪前景色 + */ + public int getFrontWaveColor() { + if (mWaveView != null) { + return mWaveView.getFrontWaveColor(); + } + return WaveView.DEFAULT_FRONT_WAVE_COLOR; + } + + /** + * 设置波浪颜色 + * @param behindWaveColor 波浪背景色 + * @param frontWaveColor 波浪前景色 + * @return {@link WaveHelper} + */ + public WaveHelper setWaveColor( + final int behindWaveColor, + final int frontWaveColor + ) { + if (mWaveView != null) { + mWaveView.setWaveColor(behindWaveColor, frontWaveColor); + } + return this; + } + + /** + * 获取波浪外形形状 + * @return 波浪外形形状 + */ + public WaveView.ShapeType getShapeType() { + if (mWaveView != null) { + return mWaveView.getShapeType(); + } + return WaveView.ShapeType.CIRCLE; + } + + /** + * 设置波浪外形形状 + * @param shapeType 波浪外形形状 + * @return {@link WaveHelper} + */ + public WaveHelper setShapeType(final WaveView.ShapeType shapeType) { + if (mWaveView != null) { + mWaveView.setShapeType(shapeType); + } + return this; + } + + /** + * 是否进行波浪图形处理 + * @return {@code true} yes, {@code false} no + */ + public boolean isShowWave() { + if (mWaveView != null) { + return mWaveView.isShowWave(); + } + return true; + } + + /** + * 设置是否进行波浪图形处理 + * @param showWave {@code true} yes, {@code false} no + * @return {@link WaveHelper} + */ + public WaveHelper setShowWave(final boolean showWave) { + if (mWaveView != null) { + mWaveView.setShowWave(showWave); + } + return this; + } + + // ============= + // = 动画构建方法 = + // ============= + + /** + * 通过属性动画进行设置波浪 View 动画效果 + * @param property 波浪属性动画配置信息 + * @return {@link WaveHelper} + */ + public WaveHelper buildPropertyAnimation(final WaveProperty property) { + if (mWaveView != null && property != null) { + List animators = new ArrayList<>(); + + // 让波浪一直向右移动, 效果就是波形一直在波动 + ObjectAnimator waveShiftAnim = ObjectAnimator.ofFloat( + mWaveView, "waveShiftRatio", + property.waveShiftRatioStart, + property.waveShiftRatioEnd + ); + waveShiftAnim.setRepeatCount(ValueAnimator.INFINITE); + waveShiftAnim.setDuration(property.waveShiftRatioMillis); + waveShiftAnim.setInterpolator(new LinearInterpolator()); + animators.add(waveShiftAnim); + + // 水位高度从 xx 到 xx 值 + ObjectAnimator waterLevelAnim = ObjectAnimator.ofFloat( + mWaveView, "waterLevelRatio", + property.waterLevelRatioStart, + property.waterLevelRatioEnd + ); + waterLevelAnim.setDuration(property.waterLevelRatioMillis); + waterLevelAnim.setInterpolator(new DecelerateInterpolator()); + animators.add(waterLevelAnim); + + // 波浪大小 ( 上下波动 ) 效果, 先大后小, 再从小变大 + ObjectAnimator amplitudeAnim = ObjectAnimator.ofFloat( + mWaveView, "amplitudeRatio", + property.amplitudeRatioStart, + property.amplitudeRatioEnd + ); + amplitudeAnim.setRepeatCount(ValueAnimator.INFINITE); + amplitudeAnim.setRepeatMode(ValueAnimator.REVERSE); + amplitudeAnim.setDuration(property.amplitudeRatioMillis); + amplitudeAnim.setInterpolator(new LinearInterpolator()); + animators.add(amplitudeAnim); + + mAnimatorSet = new AnimatorSet(); + mAnimatorSet.playTogether(animators); + } + return this; + } + + /** + * detail: 波浪属性动画配置信息 + * @author Ttt + */ + public static final class WaveProperty { + + // 波浪移动方向效果属性值 + private final float waveShiftRatioStart; + private final float waveShiftRatioEnd; + private final long waveShiftRatioMillis; + + // 波浪大小 ( 上下波动 ) 效果属性值 + private final float amplitudeRatioStart; + private final float amplitudeRatioEnd; + private final long amplitudeRatioMillis; + + // 水位高度属性值 + private final float waterLevelRatioStart; + private final float waterLevelRatioEnd; + private final long waterLevelRatioMillis; + + private WaveProperty(final Builder builder) { + this.waveShiftRatioStart = builder.waveShiftRatioStart; + this.waveShiftRatioEnd = builder.waveShiftRatioEnd; + this.waveShiftRatioMillis = builder.waveShiftRatioMillis; + this.amplitudeRatioStart = builder.amplitudeRatioStart; + this.amplitudeRatioEnd = builder.amplitudeRatioEnd; + this.amplitudeRatioMillis = builder.amplitudeRatioMillis; + this.waterLevelRatioStart = builder.waterLevelRatioStart; + this.waterLevelRatioEnd = builder.waterLevelRatioEnd; + this.waterLevelRatioMillis = builder.waterLevelRatioMillis; + } + + public static final class Builder { + + // 波浪移动方向效果属性值 + private float waveShiftRatioStart = 0F; + private float waveShiftRatioEnd = 1F; + private long waveShiftRatioMillis = 1000L; + + // 波浪大小 ( 上下波动 ) 效果属性值 + private float amplitudeRatioStart = 0.0001F; + private float amplitudeRatioEnd = 0.05F; + private long amplitudeRatioMillis = 5000L; + + // 水位高度属性值 + private float waterLevelRatioStart = 0F; + private float waterLevelRatioEnd = 0F; + private long waterLevelRatioMillis = 5000L; + + // ========== + // = 构建方法 = + // ========== + + public Builder() { + } + + public Builder(final WaveProperty property) { + if (property != null) { + this.waveShiftRatioStart = property.waveShiftRatioStart; + this.waveShiftRatioEnd = property.waveShiftRatioEnd; + this.waveShiftRatioMillis = property.waveShiftRatioMillis; + this.amplitudeRatioStart = property.amplitudeRatioStart; + this.amplitudeRatioEnd = property.amplitudeRatioEnd; + this.amplitudeRatioMillis = property.amplitudeRatioMillis; + this.waterLevelRatioStart = property.waterLevelRatioStart; + this.waterLevelRatioEnd = property.waterLevelRatioEnd; + this.waterLevelRatioMillis = property.waterLevelRatioMillis; + } + } + + public Builder(final Builder builder) { + if (builder != null) { + this.waveShiftRatioStart = builder.waveShiftRatioStart; + this.waveShiftRatioEnd = builder.waveShiftRatioEnd; + this.waveShiftRatioMillis = builder.waveShiftRatioMillis; + this.amplitudeRatioStart = builder.amplitudeRatioStart; + this.amplitudeRatioEnd = builder.amplitudeRatioEnd; + this.amplitudeRatioMillis = builder.amplitudeRatioMillis; + this.waterLevelRatioStart = builder.waterLevelRatioStart; + this.waterLevelRatioEnd = builder.waterLevelRatioEnd; + this.waterLevelRatioMillis = builder.waterLevelRatioMillis; + } + } + + public WaveProperty build() { + return new WaveProperty(this); + } + + // =========== + // = get/set = + // =========== + + // ===================== + // = 波浪移动方向效果属性值 = + // ===================== + + public float getWaveShiftRatioStart() { + return waveShiftRatioStart; + } + + public float getWaveShiftRatioEnd() { + return waveShiftRatioEnd; + } + + public long getWaveShiftRatioMillis() { + return waveShiftRatioMillis; + } + + /** + * 设置波浪移动方向效果属性值 + * @param waveShiftRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setWaveShiftRatioMillis(final long waveShiftRatioMillis) { + this.waveShiftRatioMillis = waveShiftRatioMillis; + return this; + } + + /** + * 设置波浪移动方向效果属性值 + * @param waveShiftRatioStart 开始值 + * @param waveShiftRatioEnd 结束值 + * @return {@link Builder} + */ + public Builder setWaveShiftRatio( + final float waveShiftRatioStart, + final float waveShiftRatioEnd + ) { + return setWaveShiftRatio( + waveShiftRatioStart, + waveShiftRatioEnd, + this.waveShiftRatioMillis + ); + } + + /** + * 设置波浪移动方向效果属性值 + * @param waveShiftRatioStart 开始值 + * @param waveShiftRatioEnd 结束值 + * @param waveShiftRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setWaveShiftRatio( + final float waveShiftRatioStart, + final float waveShiftRatioEnd, + final long waveShiftRatioMillis + ) { + this.waveShiftRatioStart = waveShiftRatioStart; + this.waveShiftRatioEnd = waveShiftRatioEnd; + this.waveShiftRatioMillis = waveShiftRatioMillis; + return this; + } + + // =============================== + // = 波浪大小 ( 上下波动 ) 效果属性值 = + // =============================== + + public float getAmplitudeRatioStart() { + return amplitudeRatioStart; + } + + public float getAmplitudeRatioEnd() { + return amplitudeRatioEnd; + } + + public long getAmplitudeRatioMillis() { + return amplitudeRatioMillis; + } + + /** + * 设置波浪大小 ( 上下波动 ) 效果属性值 + * @param amplitudeRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setAmplitudeRatioMillis(final long amplitudeRatioMillis) { + this.amplitudeRatioMillis = amplitudeRatioMillis; + return this; + } + + /** + * 设置波浪大小 ( 上下波动 ) 效果属性值 + * @param amplitudeRatioStart 开始值 + * @param amplitudeRatioEnd 结束值 + * @return {@link Builder} + */ + public Builder setAmplitudeRatio( + final float amplitudeRatioStart, + final float amplitudeRatioEnd + ) { + return setAmplitudeRatio( + amplitudeRatioStart, + amplitudeRatioEnd, + this.amplitudeRatioMillis + ); + } + + /** + * 设置波浪大小 ( 上下波动 ) 效果属性值 + * @param amplitudeRatioStart 开始值 + * @param amplitudeRatioEnd 结束值 + * @param amplitudeRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setAmplitudeRatio( + final float amplitudeRatioStart, + final float amplitudeRatioEnd, + final long amplitudeRatioMillis + ) { + this.amplitudeRatioStart = amplitudeRatioStart; + this.amplitudeRatioEnd = amplitudeRatioEnd; + this.amplitudeRatioMillis = amplitudeRatioMillis; + return this; + } + + // =============== + // = 水位高度属性值 = + // =============== + + public float getWaterLevelRatioStart() { + return waterLevelRatioStart; + } + + public float getWaterLevelRatioEnd() { + return waterLevelRatioEnd; + } + + public float getWaterLevelRatioMillis() { + return waterLevelRatioMillis; + } + + /** + * 设置水位高度属性值 + * @param waterLevelRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setWaterLevelRatioMillis(final long waterLevelRatioMillis) { + this.waterLevelRatioMillis = waterLevelRatioMillis; + return this; + } + + /** + * 设置水位高度属性值 + * @param waterLevelRatioStart 开始值 + * @param waterLevelRatioEnd 结束值 + * @return {@link Builder} + */ + public Builder setWaterLevelRatio( + final float waterLevelRatioStart, + final float waterLevelRatioEnd + ) { + return setWaterLevelRatio( + waterLevelRatioStart, + waterLevelRatioEnd, + this.waterLevelRatioMillis + ); + } + + /** + * 设置水位高度属性值 + * @param waterLevelRatioStart 开始值 + * @param waterLevelRatioEnd 结束值 + * @param waterLevelRatioMillis 动画时间 + * @return {@link Builder} + */ + public Builder setWaterLevelRatio( + final float waterLevelRatioStart, + final float waterLevelRatioEnd, + final long waterLevelRatioMillis + ) { + this.waterLevelRatioStart = waterLevelRatioStart; + this.waterLevelRatioEnd = waterLevelRatioEnd; + this.waterLevelRatioMillis = waterLevelRatioMillis; + return this; + } + } + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/java/dev/widget/utils/WidgetAttrs.java b/lib/DevWidget/src/main/java/dev/widget/utils/WidgetAttrs.java new file mode 100644 index 0000000000..bfe888f8ff --- /dev/null +++ b/lib/DevWidget/src/main/java/dev/widget/utils/WidgetAttrs.java @@ -0,0 +1,109 @@ +package dev.widget.utils; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +import dev.utils.app.ViewUtils; +import dev.widget.R; + +/** + * detail: DevWidget 属性封装处理类 + * @author Ttt + */ +public class WidgetAttrs { + + // 是否允许滑动 + private boolean mSlide = true; + // 最大显示宽度 + private int mMaxWidth = ViewUtils.MATCH_PARENT; + // 最大显示高度 + private int mMaxHeight = ViewUtils.MATCH_PARENT; + + /** + * 初始化 + * @param context {@link Context} + * @param attrs {@link AttributeSet} + * @param defStyleAttr 默认样式 + * @param defStyleRes 默认样式资源 + */ + public WidgetAttrs( + Context context, + AttributeSet attrs, + int defStyleAttr, + int defStyleRes + ) { + if (context != null && attrs != null) { + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.DevWidget, defStyleAttr, defStyleRes + ); + mSlide = a.getBoolean(R.styleable.DevWidget_dev_slide, true); + mMaxWidth = a.getLayoutDimension(R.styleable.DevWidget_dev_maxWidth, ViewUtils.MATCH_PARENT); + mMaxHeight = a.getLayoutDimension(R.styleable.DevWidget_dev_maxHeight, ViewUtils.MATCH_PARENT); + a.recycle(); + } + } + + /** + * 获取 View 最大显示宽度 + * @return View 最大显示宽度 + */ + public int getMaxWidth() { + return mMaxWidth; + } + + /** + * 设置 View 最大显示宽度 + * @param maxWidth View 最大显示宽度 + * @return {@link WidgetAttrs} + */ + public WidgetAttrs setMaxWidth(final int maxWidth) { + this.mMaxWidth = maxWidth; + return this; + } + + /** + * 获取 View 最大显示高度 + * @return View 最大显示高度 + */ + public int getMaxHeight() { + return mMaxHeight; + } + + /** + * 设置 View 最大显示高度 + * @param maxHeight View 最大显示高度 + * @return {@link WidgetAttrs} + */ + public WidgetAttrs setMaxHeight(final int maxHeight) { + this.mMaxHeight = maxHeight; + return this; + } + + /** + * 是否允许滑动 + * @return {@code true} yes, {@code false} no + */ + public boolean isSlide() { + return mSlide; + } + + /** + * 设置是否允许滑动 + * @param isSlide {@code true} yes, {@code false} no + * @return {@link WidgetAttrs} + */ + public WidgetAttrs setSlide(final boolean isSlide) { + this.mSlide = isSlide; + return this; + } + + /** + * 切换滑动控制状态 + * @return {@link WidgetAttrs} + */ + public WidgetAttrs toggleSlide() { + this.mSlide = !this.mSlide; + return this; + } +} \ No newline at end of file diff --git a/lib/DevWidget/src/main/res/animator/dev_flip_card_in.xml b/lib/DevWidget/src/main/res/animator/dev_flip_card_in.xml new file mode 100644 index 0000000000..de250abd78 --- /dev/null +++ b/lib/DevWidget/src/main/res/animator/dev_flip_card_in.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/DevWidget/src/main/res/animator/dev_flip_card_out.xml b/lib/DevWidget/src/main/res/animator/dev_flip_card_out.xml new file mode 100644 index 0000000000..e2809a092a --- /dev/null +++ b/lib/DevWidget/src/main/res/animator/dev_flip_card_out.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/DevWidget/src/main/res/drawable/dev_scan_line.png b/lib/DevWidget/src/main/res/drawable/dev_scan_line.png new file mode 100644 index 0000000000..68c861c531 Binary files /dev/null and b/lib/DevWidget/src/main/res/drawable/dev_scan_line.png differ diff --git a/lib/DevWidget/src/main/res/values/attrs.xml b/lib/DevWidget/src/main/res/values/attrs.xml new file mode 100644 index 0000000000..0f516a3e1d --- /dev/null +++ b/lib/DevWidget/src/main/res/values/attrs.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/.gitignore b/lib/Environment/DevEnvironment/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/Environment/DevEnvironment/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/CHANGELOG.md b/lib/Environment/DevEnvironment/CHANGELOG.md new file mode 100644 index 0000000000..c66fec7000 --- /dev/null +++ b/lib/Environment/DevEnvironment/CHANGELOG.md @@ -0,0 +1,71 @@ +Change Log +========== + +Version 1.1.2 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +* `[Refactor]` 统一可视化页面 UI 样式 + +Version 1.1.1 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.5 *(2021-10-13)* +---------------------------- + +* `[Update]` 修改为 JDK 1.8 进行编译 + +Version 1.0.4 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.3 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.2 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +* `[Update]` 修改 CallBack 相关代码为 Callback + +Version 1.0.1 *(2020-11-27)* +---------------------------- + +* `[Refactor]` 使用 QAPlugs ( PMD、findbugs、checkstyle )、IDEA Analyze 进行代码质量分析、代码优化等 + +Version 1.0.0 *(2020-02-04)* +---------------------------- + +* Initial release diff --git a/lib/Environment/DevEnvironment/build.gradle b/lib/Environment/DevEnvironment/build.gradle new file mode 100644 index 0000000000..ba393185ce --- /dev/null +++ b/lib/Environment/DevEnvironment/build.gradle @@ -0,0 +1,31 @@ +apply from: rootProject.file(files.lib_app_gradle) + +android.defaultConfig { + versionCode versions.dev_environment_versionCode + versionName versions.dev_environment_version + // DevEnvironment Module Version + buildConfigField "int", "DevEnvironment_VersionCode", "${versions.dev_environment_versionCode}" + buildConfigField "String", "DevEnvironment_Version", "\"${versions.dev_environment_version}\"" +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_environment_base + } else { + // 编译时使用 + api project(':DevEnvironmentBase') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/proguard-rules.pro b/lib/Environment/DevEnvironment/proguard-rules.pro new file mode 100644 index 0000000000..1c9bba1715 --- /dev/null +++ b/lib/Environment/DevEnvironment/proguard-rules.pro @@ -0,0 +1,30 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontwarn dev.** +-keep class dev.environment.** { *; } + +-dontwarn java.nio.** +-dontwarn javax.tools.** +-dontwarn javax.lang.** +-dontwarn javax.annotation.** +-dontwarn com.squareup.javapoet.** \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/project.properties b/lib/Environment/DevEnvironment/project.properties new file mode 100644 index 0000000000..e29793ab6d --- /dev/null +++ b/lib/Environment/DevEnvironment/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevEnvironment +project.groupId=io.github.afkt +project.artifactId=DevEnvironment +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevEnvironment \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/AndroidManifest.xml b/lib/Environment/DevEnvironment/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..17713b0922 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/java/dev/environment/AdapterItem.java b/lib/Environment/DevEnvironment/src/main/java/dev/environment/AdapterItem.java new file mode 100644 index 0000000000..afe2e4753e --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/java/dev/environment/AdapterItem.java @@ -0,0 +1,135 @@ +package dev.environment; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; +import dev.environment.log.LogUtils; + +/** + * detail: 适配器 Item + * @author Ttt + */ +class AdapterItem { + + public static final int MODULE_TYPE = 1; + public static final int ENVIRONMENT_TYPE = 2; + + // Item 类型 + public final int itemType; + // 判断 Environment 是否选中 + private final int hashCodeEquals; + // Module 实体类 ( Module 类型使用 ) + public ModuleBean moduleBean; + // Environment 实体类 ( Environment 类型使用 ) + public EnvironmentBean environmentBean; + + public AdapterItem(ModuleBean moduleBean) { + this.itemType = MODULE_TYPE; + this.hashCodeEquals = -1; + this.moduleBean = moduleBean; + } + + public AdapterItem(EnvironmentBean environmentBean) { + this.itemType = ENVIRONMENT_TYPE; + this.hashCodeEquals = environmentBean.hashCode(); + this.environmentBean = environmentBean; + } + + /** + * 是否选中当前环境 + * @return {@code true} yes, {@code false} no + */ + public boolean isSelect() { + if (environmentBean != null) { + ModuleBean moduleBean = environmentBean.getModule(); + if (moduleBean != null) { + Integer hashCode = sModuleHashCodeMap.get(moduleBean.getName()); + return (hashCode != null && hashCode == hashCodeEquals); + } + } + return false; + } + + // = + + // 各个 Module 选中的 Environment HashCode + private static final HashMap sModuleHashCodeMap = new HashMap<>(); + + /** + * 刷新 HashCode + * @param context {@link Context} + */ + public static void refreshHashCode(final Context context) { + List modules = Utils.getModuleList(); + if (modules != null) { + for (ModuleBean moduleBean : modules) { + if (moduleBean != null) { + String key = moduleBean.getName(); + EnvironmentBean environmentBean = Utils.getModuleEnvironment(context, key); + if (environmentBean != null) { + sModuleHashCodeMap.put(moduleBean.getName(), environmentBean.hashCode()); + } + } + } + } + } + + /** + * 改变 HashCode + * @param newEnvironment environment bean + */ + public static void changeHashCode(final EnvironmentBean newEnvironment) { + try { + sModuleHashCodeMap.put(newEnvironment.getModule().getName(), newEnvironment.hashCode()); + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + } + + /** + * 获取 Item List + * @param context {@link Context} + * @return AdapterItem List + */ + public static List getAdapterItems(final Context context) { + List items = new ArrayList<>(); + List modules = Utils.getModuleList(); + if (modules != null) { + for (ModuleBean moduleBean : modules) { + if (moduleBean != null) { + List environments = moduleBean.getEnvironments(); + if (environments != null && environments.size() != 0) { + // 添加 Module Type + items.add(new AdapterItem(moduleBean)); + // 判断是否添加自定义配置 + boolean addCustom = true; + for (EnvironmentBean environmentBean : environments) { + if (environmentBean != null) { + // 添加 Environment Type + AdapterItem adapterItem = new AdapterItem(environmentBean); + items.add(adapterItem); + if (adapterItem.isSelect()) { + addCustom = false; + } + } + } + if (addCustom) { + // 获取选中的环境 + EnvironmentBean environmentSelect = Utils.getModuleEnvironment(context, moduleBean.getName()); + if (environmentSelect != null) { + // 添加 Environment Type + items.add(new AdapterItem(environmentSelect)); + } + } + } + } + } + } + return items; + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/java/dev/environment/DevEnvironmentActivity.java b/lib/Environment/DevEnvironment/src/main/java/dev/environment/DevEnvironmentActivity.java new file mode 100644 index 0000000000..bf28d9199a --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/java/dev/environment/DevEnvironmentActivity.java @@ -0,0 +1,227 @@ +package dev.environment; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.List; + +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; + +/** + * detail: DevEnvironment Activity + * @author Ttt + *
+ *     GitHub
+ *     @see 
+ *     DevApp Api
+ *     @see 
+ *     DevAssist Api
+ *     @see 
+ *     DevBase README
+ *     @see 
+ *     DevBaseMVVM README
+ *     @see 
+ *     DevMVVM README
+ *     @see 
+ *     DevEngine README
+ *     @see 
+ *     DevHttpCapture Api
+ *     @see 
+ *     DevHttpManager Api
+ *     @see 
+ *     DevRetrofit Api
+ *     @see 
+ *     DevWidget Api
+ *     @see 
+ *     DevEnvironment Api
+ *     @see 
+ *     DevJava Api
+ *     @see 
+ * 
+ */ +public final class DevEnvironmentActivity + extends Activity { + + // ============ + // = 工具类版本 = + // ============ + + /** + * 获取 DevEnvironment 版本号 + * @return DevEnvironment versionCode + */ + public static int getDevEnvironmentVersionCode() { + return BuildConfig.DevEnvironment_VersionCode; + } + + /** + * 获取 DevEnvironment 版本 + * @return DevEnvironment versionName + */ + public static String getDevEnvironmentVersion() { + return BuildConfig.DevEnvironment_Version; + } + + // ========== + // = 跳转方法 = + // ========== + + /** + * 跳转 DevEnvironment Activity + * @param context {@link Context} + * @return {@code true} success, {@code false} fail + */ + public static boolean start(final Context context) { + return start(context, null); + } + + /** + * 跳转 DevEnvironment Activity + * @param context {@link Context} + * @param callback 重启按钮点击回调 + * @return {@code true} success, {@code false} fail + */ + public static boolean start( + final Context context, + final RestartCallback callback + ) { + return Utils.start(context, callback); + } + + // ============ + // = Activity = + // ============ + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + try { + View decorView = getWindow().getDecorView(); + // 设置全屏和状态栏透明 + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + // 设置状态栏为主题色 + getWindow().setStatusBarColor(this.getResources().getColor(R.color.dev_environment_title_bg_color)); + } catch (Exception ignored) { + } + } + if (Utils.isRelease()) { + finish(); + return; + } + setContentView(R.layout.dev_environment_activity); + // back + findViewById(R.id.vid_back_iv).setOnClickListener(v -> finish()); + // restart + TextView vid_restart_tv = findViewById(R.id.vid_restart_tv); + if (Utils.sCallback != null) { + vid_restart_tv.setVisibility(View.VISIBLE); + vid_restart_tv.setOnClickListener(v -> { + finish(); + if (Utils.sCallback != null) { + Utils.sCallback.onRestart(); + } + }); + } + AdapterItem.refreshHashCode(this); + // 初始化适配器并绑定 + ListView vid_lv = findViewById(R.id.vid_lv); + vid_lv.setAdapter(new Adapter(AdapterItem.getAdapterItems(this))); + } + + // ========= + // = 适配器 = + // ========= + + /** + * detail: Item Adapter + * @author Ttt + */ + class Adapter + extends BaseAdapter { + + List lists; + + public Adapter(List lists) { + this.lists = lists; + } + + @Override + public int getCount() { + return lists.size(); + } + + @Override + public AdapterItem getItem(int position) { + return lists.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView( + int position, + View convertView, + ViewGroup parent + ) { + TextView vid_name_tv; + TextView vid_value_tv; + ImageView vid_mark_iv; + + final AdapterItem item = getItem(position); + switch (item.itemType) { + case AdapterItem.MODULE_TYPE: // Module Type + convertView = LayoutInflater.from(parent.getContext()).inflate( + R.layout.dev_environment_item_module, parent, false); + vid_name_tv = convertView.findViewById(R.id.vid_name_tv); + + ModuleBean moduleBean = item.moduleBean; + String moduleName = moduleBean.getName(); + String moduleAlias = moduleBean.getAlias(); + vid_name_tv.setText(TextUtils.isEmpty(moduleAlias) ? moduleName : moduleAlias); + break; + case AdapterItem.ENVIRONMENT_TYPE: // Environment Type + convertView = LayoutInflater.from(parent.getContext()).inflate( + R.layout.dev_environment_item_environment, parent, false); + vid_name_tv = convertView.findViewById(R.id.vid_name_tv); + vid_value_tv = convertView.findViewById(R.id.vid_value_tv); + vid_mark_iv = convertView.findViewById(R.id.vid_mark_iv); + + final EnvironmentBean environmentBean = item.environmentBean; + String environmentName = environmentBean.getName(); + String environmentAlias = environmentBean.getAlias(); + vid_name_tv.setText(TextUtils.isEmpty(environmentAlias) ? environmentName : environmentAlias); + vid_value_tv.setText(environmentBean.getValue()); + vid_mark_iv.setVisibility(item.isSelect() ? View.VISIBLE : View.INVISIBLE); + + convertView.setOnClickListener(v -> { + // 设置选中的环境 + if (Utils.setModuleEnvironment(DevEnvironmentActivity.this, environmentBean)) { +// AdapterItem.refreshHashCode(DevEnvironmentActivity.this); + AdapterItem.changeHashCode(environmentBean); + notifyDataSetChanged(); + } + }); + break; + } + return convertView; + } + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/java/dev/environment/RestartCallback.java b/lib/Environment/DevEnvironment/src/main/java/dev/environment/RestartCallback.java new file mode 100644 index 0000000000..c9641edf45 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/java/dev/environment/RestartCallback.java @@ -0,0 +1,10 @@ +package dev.environment; + +/** + * detail: Restart Callback + * @author Ttt + */ +public interface RestartCallback { + + void onRestart(); +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/java/dev/environment/Utils.java b/lib/Environment/DevEnvironment/src/main/java/dev/environment/Utils.java new file mode 100644 index 0000000000..46bc2ec912 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/java/dev/environment/Utils.java @@ -0,0 +1,152 @@ +package dev.environment; + +import android.content.Context; +import android.content.Intent; + +import java.lang.reflect.Method; +import java.util.List; + +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; +import dev.environment.log.LogUtils; + +/** + * detail: 内部工具类 + * @author Ttt + */ +class Utils { + + // 包名 + static final String PACKAGE_NAME = "dev.environment"; + // 工具类文件名 + static final String ENVIRONMENT_FILE_NAME = "DevEnvironment"; + // 方法名 + static final String METHOD_IS_RELEASE = "isRelease"; + static final String METHOD_GET_MODULE_LIST = "getModuleList"; + // 常量字符串 + static final String STR_ENVIRONMENT = "Environment"; + + // callback + protected static RestartCallback sCallback; + // DevEnvironment Class + static Class devEnvironmentClass; + + static { + try { + devEnvironmentClass = Class.forName(PACKAGE_NAME + "." + ENVIRONMENT_FILE_NAME); + } catch (ClassNotFoundException ignored) { + } + } + + // ========== + // = 跳转方法 = + // ========== + + /** + * 跳转 DevEnvironment Activity + * @param context {@link Context} + * @return {@code true} success, {@code false} fail + */ + public static boolean start(final Context context) { + return start(context, null); + } + + /** + * 跳转 DevEnvironment Activity + * @param context {@link Context} + * @param callback 重启按钮点击回调 + * @return {@code true} success, {@code false} fail + */ + public static boolean start( + final Context context, + final RestartCallback callback + ) { + if (context != null && !isRelease()) { + try { + Utils.sCallback = callback; + Intent intent = new Intent(context, DevEnvironmentActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + return true; + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + } + return false; + } + + // ========== + // = 反射方法 = + // ========== + + /** + * 是否使用 releaseAnnotationProcessor 构建 + * @return {@code true} yes, {@code false} no + */ + public static boolean isRelease() { + try { + Method isReleaseMethod = devEnvironmentClass.getMethod(METHOD_IS_RELEASE); + return (boolean) isReleaseMethod.invoke(null); + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + return true; + } + + /** + * 获取全部 ModuleBean 配置列表 + * @return List + */ + public static List getModuleList() { + try { + Method getModuleListMethod = devEnvironmentClass.getMethod(METHOD_GET_MODULE_LIST); + return (List) getModuleListMethod.invoke(null); + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + return null; + } + + /** + * 获取 Module Selected Environment + * @param context {@link Context} + * @param moduleName module Name + * @return {@link EnvironmentBean} + */ + public static EnvironmentBean getModuleEnvironment( + final Context context, + final String moduleName + ) { + try { + String getModuleEnvironmentMethodName = "get" + moduleName + STR_ENVIRONMENT; + Method getModuleEnvironmentMethod = devEnvironmentClass.getMethod(getModuleEnvironmentMethodName, Context.class); + return (EnvironmentBean) getModuleEnvironmentMethod.invoke(null, context); + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + return null; + } + + /** + * 设置 Module Selected Environment + * @param context {@link Context} + * @param newEnvironment environment bean + * @return {@code true} success, {@code false} fail + */ + public static boolean setModuleEnvironment( + final Context context, + final EnvironmentBean newEnvironment + ) { + try { + String moduleName = newEnvironment.getModule().getName(); + String setModuleEnvironmentMethodName = "set" + moduleName + STR_ENVIRONMENT; + Method setModuleEnvironmentMethod = devEnvironmentClass.getMethod(setModuleEnvironmentMethodName, + Context.class, EnvironmentBean.class + ); + return (boolean) setModuleEnvironmentMethod.invoke(null, context, newEnvironment); + } catch (Exception e) { + LogUtils.printStackTrace(e); + } + return false; + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_back.xml b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_back.xml new file mode 100644 index 0000000000..6d6983dbaa --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_back.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_item_selector.xml b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_item_selector.xml new file mode 100644 index 0000000000..707bd8ef34 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_item_selector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_mark.xml b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_mark.xml new file mode 100644 index 0000000000..820d3b3318 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/drawable/dev_environment_mark.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_activity.xml b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_activity.xml new file mode 100644 index 0000000000..e99089014a --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_activity.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_environment.xml b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_environment.xml new file mode 100644 index 0000000000..1c61c7a9db --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_environment.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_module.xml b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_module.xml new file mode 100644 index 0000000000..e1e7ab2f36 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/layout/dev_environment_item_module.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/values-zh-rCN/strings.xml b/lib/Environment/DevEnvironment/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..3ab0a2adbe --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,5 @@ + + + 重启 + 环境配置 + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/values/strings.xml b/lib/Environment/DevEnvironment/src/main/res/values/strings.xml new file mode 100644 index 0000000000..c0d6b7ba72 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Restart + Environment Config + \ No newline at end of file diff --git a/lib/Environment/DevEnvironment/src/main/res/values/unified.xml b/lib/Environment/DevEnvironment/src/main/res/values/unified.xml new file mode 100644 index 0000000000..036e89e4d1 --- /dev/null +++ b/lib/Environment/DevEnvironment/src/main/res/values/unified.xml @@ -0,0 +1,36 @@ + + + + + + #e9e9e9 + + #3f51b5 + 56.0dp + 5.0dp + + #ffffff + 18.0sp + + #ffffff + 16.0sp + 13.0dp + + + + #e9e9e9 + 50.0dp + 0.0dp + + #000000 + #aaaaaa + 15.0dp + + #e9e9e9 + 0.5dp + + + + #efefef + #ffffff + \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/.gitignore b/lib/Environment/DevEnvironmentBase/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/CHANGELOG.md b/lib/Environment/DevEnvironmentBase/CHANGELOG.md new file mode 100644 index 0000000000..a3238ab535 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/CHANGELOG.md @@ -0,0 +1,67 @@ +Change Log +========== + +Version 1.1.2 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.1 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.5 *(2021-10-13)* +---------------------------- + +* `[Update]` 修改为 JDK 1.8 进行编译 + +Version 1.0.4 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.3 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.2 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +Version 1.0.1 *(2020-11-27)* +---------------------------- + +* `[Add]` 新增 LogUtils 控制日志输出 + +Version 1.0.0 *(2020-02-04)* +---------------------------- + +* Initial release diff --git a/lib/Environment/DevEnvironmentBase/build.gradle b/lib/Environment/DevEnvironmentBase/build.gradle new file mode 100644 index 0000000000..22a2b3095a --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/build.gradle @@ -0,0 +1,20 @@ +apply from: rootProject.file(files.lib_java_gradle) + +version versions.dev_environment_base_version + +compileJava { + sourceCompatibility JavaVersion.VERSION_1_8.toString() + targetCompatibility JavaVersion.VERSION_1_8.toString() +} + +// 是否发布版本 +def isPublishing = false + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_java) +//apply from: rootProject.file(files.sonatype_upload_java) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_java) +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/project.properties b/lib/Environment/DevEnvironmentBase/project.properties new file mode 100644 index 0000000000..a15a32a3d7 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevEnvironmentBase +project.groupId=io.github.afkt +project.artifactId=DevEnvironmentBase +project.packaging=jar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevEnvironmentBase \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Environment.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Environment.java new file mode 100644 index 0000000000..ad9d696d79 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Environment.java @@ -0,0 +1,37 @@ +package dev.environment.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * detail: 环境配置 ( 注解标记类 ) + * @author Ttt + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface Environment { + + /** + * 当前 {@link Environment} 具体配置值 + * @return {@link Environment} 具体配置值 + */ + String value(); + + /** + * 当前 {@link Environment} 别名 + * @return {@link Environment} 别名 + */ + String alias() default ""; + + /** + * 是否 Release 环境 + *
+     *     同一个 {@link Module} 只能存在一个 Release 环境配置
+     *     用于构建 Release 常量实体类
+     * 
+ * @return {@code true} yes, {@code false} no + */ + boolean isRelease() default false; +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Module.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Module.java new file mode 100644 index 0000000000..43e15d4f05 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Module.java @@ -0,0 +1,21 @@ +package dev.environment.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * detail: 模块 ( 注解标记类 ) + * @author Ttt + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface Module { + + /** + * 当前 {@link Module} 别名 + * @return {@link Module} 别名 + */ + String alias() default ""; +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/EnvironmentBean.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/EnvironmentBean.java new file mode 100644 index 0000000000..08ec6280e6 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/EnvironmentBean.java @@ -0,0 +1,81 @@ +package dev.environment.bean; + +import java.io.Serializable; +import java.util.Objects; + +import dev.environment.annotation.Environment; + +/** + * detail: {@link Environment} 实体类 + * @author Ttt + */ +public class EnvironmentBean + implements Serializable { + + // 环境名 + private final String name; + // 环境配置值 + private final String value; + // 环境别名 + private final String alias; + // 所属模块 + private final ModuleBean module; + + public EnvironmentBean( + String name, + String value, + String alias, + ModuleBean module + ) { + this.name = name; + this.value = value; + this.alias = alias; + this.module = module; + } + + public String getName() { + return name == null ? "" : name; + } + + public String getValue() { + return value == null ? "" : value; + } + + public String getAlias() { + return alias == null ? "" : alias; + } + + public ModuleBean getModule() { + return module; + } + + @Override + public final boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + + EnvironmentBean that = (EnvironmentBean) object; + if (!Objects.equals(name, that.name)) return false; + if (!Objects.equals(value, that.value)) return false; + if (!Objects.equals(alias, that.alias)) return false; + return Objects.equals(module, that.module); + } + + @Override + public final int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (alias != null ? alias.hashCode() : 0); + result = 31 * result + (module != null ? module.hashCode() : 0); + return result; + } + + private final String JSON_FORMAT = "{\"name\":\"%s\",\"value\":\"%s\",\"alias\":\"%s\",\"module\":{\"name\":\"%s\",\"alias\":\"%s\"}}"; + + @Override + public final String toString() { + String moduleName = (module != null) ? module.getName() : ""; + String moduleAlias = (module != null) ? module.getAlias() : ""; + return String.format(JSON_FORMAT, getName(), getValue(), getAlias(), moduleName, moduleAlias); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/ModuleBean.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/ModuleBean.java new file mode 100644 index 0000000000..a95f7599e9 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/ModuleBean.java @@ -0,0 +1,71 @@ +package dev.environment.bean; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import dev.environment.annotation.Module; + +/** + * detail: {@link Module} 实体类 + * @author Ttt + */ +public class ModuleBean + implements Serializable { + + // 模块名 + private final String name; + // 模块别名 + private final String alias; + // 模块下的环境集合 + private final List environments = new ArrayList<>(); + + public ModuleBean( + String name, + String alias + ) { + this.name = name; + this.alias = alias; + } + + public String getName() { + return name == null ? "" : name; + } + + public String getAlias() { + return alias == null ? "" : alias; + } + + public List getEnvironments() { + return environments; + } + + @Override + public final boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + + ModuleBean that = (ModuleBean) object; + if (!Objects.equals(name, that.name)) return false; + return Objects.equals(alias, that.alias); +// // 不需要判断 List 因为内部 list 会调用 Object ( EnvironmentBean ) equals() 导致死循环 +// if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false; +// return environments != null ? environments.equals(that.environments) : that.environments == null; + } + + @Override + public final int hashCode() { + int result = name != null ? name.hashCode() : 0; + result = 31 * result + (alias != null ? alias.hashCode() : 0); +// // 同 equals +// result = 31 * result + (environments != null ? environments.hashCode() : 0); + return result; + } + + @Override + public final String toString() { + String JSON_FORMAT = "{\"name\":\"%s\",\"alias\":\"%s\",\"environments\":%s}"; + return String.format(JSON_FORMAT, getName(), getAlias(), getEnvironments()); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/listener/OnEnvironmentChangeListener.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/listener/OnEnvironmentChangeListener.java new file mode 100644 index 0000000000..27dfc7de7f --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/listener/OnEnvironmentChangeListener.java @@ -0,0 +1,23 @@ +package dev.environment.listener; + +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; + +/** + * detail: 模块环境改变触发事件 + * @author Ttt + */ +public interface OnEnvironmentChangeListener { + + /** + * 模块环境发生变化时触发 + * @param module 环境发生变化的模块 + * @param oldEnvironment 该模块的旧环境 + * @param newEnvironment 该模块的最新环境 + */ + void onEnvironmentChanged( + ModuleBean module, + EnvironmentBean oldEnvironment, + EnvironmentBean newEnvironment + ); +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/log/LogUtils.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/log/LogUtils.java new file mode 100644 index 0000000000..f007869138 --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/log/LogUtils.java @@ -0,0 +1,38 @@ +package dev.environment.log; + +/** + * detail: 日志打印工具类 ( 简化版 ) + * @author Ttt + */ +public final class LogUtils { + + private LogUtils() { + } + + // 是否打印日志 + private static boolean PRINT_LOG = true; + + /** + * 判断是否打印日志 + * @return {@code true} yes, {@code false} no + */ + public static boolean isPrintLog() { + return PRINT_LOG; + } + + /** + * 设置是否打印日志 + * @param printLog 是否允许打印日志 + */ + public static void setPrintLog(final boolean printLog) { + PRINT_LOG = printLog; + } + + /** + * 打印错误信息 + * @param error 异常信息 + */ + public static void printStackTrace(final Throwable error) { + if (PRINT_LOG && error != null) error.printStackTrace(); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/type/ParameterizedTypeImpl.java b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/type/ParameterizedTypeImpl.java new file mode 100644 index 0000000000..58387b02fb --- /dev/null +++ b/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/type/ParameterizedTypeImpl.java @@ -0,0 +1,55 @@ +package dev.environment.type; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Objects; + +public class ParameterizedTypeImpl + implements ParameterizedType { + + private final Type[] actualTypeArguments; + private final Type ownerType; + private final Type rawType; + + public ParameterizedTypeImpl( + Type[] actualTypeArguments, + Type ownerType, + Type rawType + ) { + this.actualTypeArguments = actualTypeArguments; + this.ownerType = ownerType; + this.rawType = rawType; + } + + public Type[] getActualTypeArguments() { + return actualTypeArguments; + } + + public Type getOwnerType() { + return ownerType; + } + + public Type getRawType() { + return rawType; + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + + ParameterizedTypeImpl that = (ParameterizedTypeImpl) object; + if (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false; + if (!Objects.equals(ownerType, that.ownerType)) return false; + return Objects.equals(rawType, that.rawType); + } + + @Override + public int hashCode() { + int result = actualTypeArguments != null ? Arrays.hashCode(actualTypeArguments) : 0; + result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0); + result = 31 * result + (rawType != null ? rawType.hashCode() : 0); + return result; + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompiler/.gitignore b/lib/Environment/DevEnvironmentCompiler/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompiler/CHANGELOG.md b/lib/Environment/DevEnvironmentCompiler/CHANGELOG.md new file mode 100644 index 0000000000..5dc6bf439f --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/CHANGELOG.md @@ -0,0 +1,73 @@ +Change Log +========== + +Version 1.1.2 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.1 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.5 *(2021-10-13)* +---------------------------- + +* `[Update]` 修改为 JDK 1.8 进行编译 + +Version 1.0.4 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.3 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.2 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +Version 1.0.1 *(2020-11-27)* +---------------------------- + +* `[Add]` 每个 Module 新增单独的 resetModule 方法 如 resetIM 用于删除当前选中的环境配置 + +* `[Add]` 每个 Module 新增单独的 isModuleAnnotation 方法 如 isIMAnnotation 用于判断当前环境是否属于注解环境配置 + +* `[Update]` 更新异常日志统一通过 LogUtils 输出控制 + +* `[Update]` 更新 getStorageDir、deleteStorageDir 方法修饰符为 private + +Version 1.0.0 *(2020-02-04)* +---------------------------- + +* Initial release diff --git a/lib/Environment/DevEnvironmentCompiler/build.gradle b/lib/Environment/DevEnvironmentCompiler/build.gradle new file mode 100644 index 0000000000..89a901d209 --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/build.gradle @@ -0,0 +1,34 @@ +apply from: rootProject.file(files.lib_java_gradle) + +version versions.dev_environment_compiler_version + +compileJava { + sourceCompatibility JavaVersion.VERSION_1_8.toString() + targetCompatibility JavaVersion.VERSION_1_8.toString() +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.aop.javapoet + compileOnly deps.aop.auto_service + annotationProcessor deps.aop.auto_service + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_environment_base + } else { + // 编译时使用 + api project(':DevEnvironmentBase') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_java) +//apply from: rootProject.file(files.sonatype_upload_java) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_java) +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompiler/project.properties b/lib/Environment/DevEnvironmentCompiler/project.properties new file mode 100644 index 0000000000..7369252d6c --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevEnvironmentCompiler +project.groupId=io.github.afkt +project.artifactId=DevEnvironmentCompiler +project.packaging=jar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevEnvironmentCompiler \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/DevEnvironmentCompilerDebug.java b/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/DevEnvironmentCompilerDebug.java new file mode 100644 index 0000000000..2174ed8d63 --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/DevEnvironmentCompilerDebug.java @@ -0,0 +1,78 @@ +package dev.environment.compiler; + +import com.google.auto.service.AutoService; +import com.squareup.javapoet.TypeSpec; + +import java.util.Collections; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import dev.environment.annotation.Module; + +/** + * detail: DevEnvironment Debug 编译生成类 + * @author Ttt + */ +@AutoService(Processor.class) +//@SupportedSourceVersion(SourceVersion.RELEASE_8) +//@SupportedAnnotationTypes("dev.environment.annotation.Module") +public class DevEnvironmentCompilerDebug + extends AbstractProcessor { + + /** + * Processor 支持的最新的源版本 + * @return {@link SourceVersion} + */ + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_8; + } + + /** + * 获取该 Processor 能够处理的注解 + * @return 该 Processor 能够处理的注解 + */ + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton(Module.class.getCanonicalName()); + } + + @Override + public boolean process( + Set set, + RoundEnvironment roundEnvironment + ) { + // 构建 DevEnvironment 类对象 + TypeSpec.Builder devEnvironmentClassBuilder = Utils.builderDevEnvironment_Class(); + // 获取使用注解修饰的 Module Element + Set elements = roundEnvironment.getElementsAnnotatedWith(Module.class); + for (Element element : elements) { + try { + // 创建 Module 数据 + Utils.builderModule_DATA(devEnvironmentClassBuilder, element, processingEnv); + } catch (Exception e) { + e.printStackTrace(); + } + } + // 构建 static{} 初始化代码 + Utils.builderStaticInit(devEnvironmentClassBuilder); + // 构建 isRelease 方法 + Utils.builderIsReleaseMethod(devEnvironmentClassBuilder); + // 构建 getXxx 方法代码 + Utils.builderGetMethod(devEnvironmentClassBuilder); + // 构建模块环境改变触发事件方法 + Utils.builderEnvironmentChangeListener(devEnvironmentClassBuilder); + // 构建存储相关方法 + Utils.builderStorageMethod(devEnvironmentClassBuilder); + // 构建 Reset 方法 + Utils.builderResetMethod(devEnvironmentClassBuilder); + // 创建 DevEnvironment JAVA 文件 + return Utils.createDevEnvironmentJavaFile(devEnvironmentClassBuilder, processingEnv); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/Utils.java b/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/Utils.java new file mode 100644 index 0000000000..7c40fce8fe --- /dev/null +++ b/lib/Environment/DevEnvironmentCompiler/src/main/java/dev/environment/compiler/Utils.java @@ -0,0 +1,940 @@ +package dev.environment.compiler; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +import dev.environment.annotation.Environment; +import dev.environment.annotation.Module; +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; +import dev.environment.listener.OnEnvironmentChangeListener; +import dev.environment.log.LogUtils; +import dev.environment.type.ParameterizedTypeImpl; + +/** + * detail: 内部工具类 + * @author Ttt + */ +final class Utils { + + // 包名 + static final String PACKAGE_NAME = "dev.environment"; + // 工具类文件名 + static final String ENVIRONMENT_FILE_NAME = "DevEnvironment"; + + // 方法名 + static final String METHOD_RESET = "reset"; + static final String METHOD_IS_RELEASE = "isRelease"; + static final String METHOD_GET_MODULE_LIST = "getModuleList"; + static final String METHOD_GET_MODULE_ENVIRONMENTS_LIST = "getEnvironments"; + static final String METHOD_GET_ENVIRONMENTS_VALUE = "getValue"; + static final String METHOD_ONENVIRONMENT_CHANGED = "onEnvironmentChanged"; + static final String METHOD_ADD_ONENVIRONMENT_CHANGE_LISTENER = "addOnEnvironmentChangeListener"; + static final String METHOD_REMOVE_ONENVIRONMENT_CHANGE_LISTENER = "removeOnEnvironmentChangeListener"; + static final String METHOD_CLEAR_ONENVIRONMENT_CHANGE_LISTENER = "clearOnEnvironmentChangeListener"; + static final String METHOD_NOTIFY_ONENVIRONMENT_CHANGE_LISTENER = "notifyOnEnvironmentChangeListener"; + static final String METHOD_GET_STORAGE_DIR = "getStorageDir"; + static final String METHOD_DELETE_STORAGE_DIR = "deleteStorageDir"; + static final String METHOD_DELETE_STORAGE = "deleteStorage"; + static final String METHOD_WRITE_STORAGE = "writeStorage"; + static final String METHOD_READ_STORAGE = "readStorage"; + static final String METHOD_IS_ANNOTATION = "is%sAnnotation"; + // 变量相关 + static final String VAR_MODULE_PREFIX = "MODULE_"; + static final String VAR_ENVIRONMENT_PREFIX = "ENVIRONMENT_"; + static final String VAR_MODULELIST = "moduleList"; + static final String VAR_MODULE_LIST = "MODULE_LIST"; + static final String VAR_SELECT_ENVIRONMENT = "sSelect"; + static final String VAR_LISTENER_LIST = "LISTENER_LIST"; + static final String VAR_CONTEXT = "context"; + static final String VAR_MODULE = "module"; + static final String VAR_MODULE_NAME = "moduleName"; + static final String VAR_ENVIRONMENT = "environment"; + static final String VAR_OLD_ENVIRONMENT = "oldEnvironment"; + static final String VAR_NEW_ENVIRONMENT = "newEnvironment"; + static final String VAR_LISTENER = "listener"; + static final String VAR_FILE_NAME = "fileName"; + static final String VAR_NAME = "name"; + static final String VAR_VALUE = "value"; + static final String VAR_ALIAS = "alias"; + // 常量字符串 + static final String STR_MODULE = "Module"; + static final String STR_ENVIRONMENT = "Environment"; + static final String STR_ENVIRONMENT_VALUE = "EnvironmentValue"; + static final String STR_RELEASE_ENVIRONMENT = "ReleaseEnvironment"; + // 其他 + static final String JSON_FILE = "\".json\""; + static final String JSON_FILE_FORMAT = "\"%s.json\""; + static final TypeName TYPE_NAME_CONTEXT = ClassName.get("android.content", "Context"); + static final TypeName TYPE_NAME_JSONOBJECT = ClassName.get("org.json", "JSONObject"); + + // ============= + // = 内部生成方法 = + // ============= + + // 用于记录 Module 名 Map> + static final LinkedHashMap> sModuleNameMap = new LinkedHashMap<>(); + + // ======= + // = 通用 = + // ======= + + /** + * 构建 DevEnvironment 类对象 + * @return {@link TypeSpec.Builder} + */ + public static TypeSpec.Builder builderDevEnvironment_Class() { + // 创建 DevEnvironment 类 + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(ENVIRONMENT_FILE_NAME) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addJavadoc("detail: 环境配置工具类\n") + .addJavadoc("@author Ttt\n"); + // 创建 DevEnvironment 无参构造函数 + // private DevEnvironment() {} + MethodSpec constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); + return classBuilder.addMethod(constructor); + } + + /** + * 创建 DevEnvironment JAVA 文件 + * @param classBuilder DevEnvironment 类构建对象 + * @param processingEnv {@link ProcessingEnvironment} + * @return {@code true} success, {@code false} fail + */ + public static boolean createDevEnvironmentJavaFile( + final TypeSpec.Builder classBuilder, + final ProcessingEnvironment processingEnv + ) { + JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, classBuilder.build()).build(); + try { + javaFile.writeTo(processingEnv.getFiler()); + return true; + } catch (IOException e) { +// e.printStackTrace(); + } + return false; + } + + // = + + /** + * 创建 Module 数据 + * @param classBuilder DevEnvironment 类构建对象 + * @param moduleElement 使用注解修饰的 Module Element + * @param processingEnv {@link ProcessingEnvironment} + */ + public static void builderModule_DATA( + final TypeSpec.Builder classBuilder, + final Element moduleElement, + final ProcessingEnvironment processingEnv + ) + throws Exception { + Module moduleAnnotation = moduleElement.getAnnotation(Module.class); + if (moduleAnnotation == null) return; + // Module 信息 + String moduleName = moduleElement.getSimpleName().toString(); + String moduleAlias = moduleAnnotation.alias(); + + // 获取 Module Release Environment 数据 + Element environmentElement = _getModuleReleaseEnvironment(moduleElement, processingEnv); + if (environmentElement != null) { + // 创建 Environment 变量名 List + List environmentVarNameList = new ArrayList<>(); + sModuleNameMap.put(moduleName, environmentVarNameList); + + // 创建私有常量变量 + // private static final ModuleBean MODULE_XXX = new ModuleBean(); + FieldSpec moduleField = FieldSpec + .builder(ModuleBean.class, _getModuleVarName_UpperCase(moduleName)) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T($S, $S)", ModuleBean.class, moduleName, moduleAlias) + .addJavadoc(String.format("[ Module ] name: %s, alias: %s\n", moduleName, moduleAlias)) + .build(); + classBuilder.addField(moduleField); + + // = + + // 创建私有变量 ( 用于记录当前选中的环境 ) + // private static EnvironmentBean sSelectModule = null; + FieldSpec sSelectModuleEnvironmentField = FieldSpec + .builder(EnvironmentBean.class, VAR_SELECT_ENVIRONMENT + moduleName) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC) + .addJavadoc(String.format("%s [ Module ] Environment of selected\n", moduleName)) + .build(); + classBuilder.addField(sSelectModuleEnvironmentField); + + // 创建 Module Environment 数据 + builderModuleEnvironment_DATA(classBuilder, moduleElement, environmentElement); + + // 创建 Module 非 Release Environment 数据 + builderModuleEnvironment_DATA_NON_RELEASE(classBuilder, moduleElement, processingEnv); + } + } + + /** + * 创建 Module Environment 数据 + * @param classBuilder DevEnvironment 类构建对象 + * @param moduleElement 使用注解修饰的 Module Element + * @param environmentElement 使用注解修饰的 Environment Element + */ + public static void builderModuleEnvironment_DATA( + final TypeSpec.Builder classBuilder, + final Element moduleElement, + final Element environmentElement + ) { + // Module 信息 + String moduleName = moduleElement.getSimpleName().toString(); + // Environment 信息 + Environment environmentAnnotation = environmentElement.getAnnotation(Environment.class); + String environmentName = environmentElement.getSimpleName().toString(); + String environmentValue = environmentAnnotation.value(); + String environmentAlias = environmentAnnotation.alias(); + + // 创建私有常量变量 + // private static final EnvironmentBean ENVIRONMENT_MODULENAME_XXX = new EnvironmentBean(); + FieldSpec environmentField = FieldSpec + .builder(EnvironmentBean.class, _getEnvironmentVarName_UpperCase(moduleName, environmentName)) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T($S, $S, $S, $N)", EnvironmentBean.class, environmentName, + environmentValue, environmentAlias, _getModuleVarName_UpperCase(moduleName) + ) + .addJavadoc(String.format("[ Environment ] name: %s, alias: %s, [ Module ] name: %s\n", + environmentName, environmentAlias, moduleName + )) + .build(); + classBuilder.addField(environmentField); + + // 记录 Environment 变量名 ( 传入的 environmentElement ) 属于 Release Environment Element 存储在 index 0 + sModuleNameMap.get(moduleName).add(_getEnvironmentVarName_UpperCase(moduleName, environmentName)); + } + + /** + * 创建 Module 非 Release Environment 数据 + * @param classBuilder DevEnvironment 类构建对象 + * @param moduleElement 使用注解修饰的 Module Element + * @param processingEnv {@link ProcessingEnvironment} + */ + public static void builderModuleEnvironment_DATA_NON_RELEASE( + final TypeSpec.Builder classBuilder, + final Element moduleElement, + final ProcessingEnvironment processingEnv + ) { + + List allMembers = processingEnv.getElementUtils().getAllMembers((TypeElement) moduleElement); + for (Element member : allMembers) { + Environment environmentAnnotation = member.getAnnotation(Environment.class); + if (environmentAnnotation == null) continue; + + if (!environmentAnnotation.isRelease()) { + // ( 方法复用 ) 该传入的 member 非 Release Environment Element + builderModuleEnvironment_DATA(classBuilder, moduleElement, member); + } + } + } + + /** + * 构建 Reset 方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderResetMethod(final TypeSpec.Builder classBuilder) { + StringBuilder varBuilder = new StringBuilder(); + Iterator>> iterator = sModuleNameMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + // Module 名 + String moduleName = entry.getKey(); + // 拼接变量设置为 null + varBuilder.append(String.format(" %s = null;\n", VAR_SELECT_ENVIRONMENT + moduleName)); + } + + // 构建 reset 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null && $N($N)) {\n", VAR_CONTEXT, METHOD_DELETE_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(varBuilder.toString()); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("}\n"); + + // public static final void reset() {} + MethodSpec resetMethod = MethodSpec + .methodBuilder(METHOD_RESET) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addCode(codeBlockBuilder.build()) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("重置操作\n") + .addJavadoc("

Reset Operating\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(resetMethod); + } + + /** + * 构建 isRelease 方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderIsReleaseMethod(final TypeSpec.Builder classBuilder) { + // public static final Boolean isRelease() {} + MethodSpec isReleaseMethod = MethodSpec + .methodBuilder(METHOD_IS_RELEASE) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("是否使用 releaseAnnotationProcessor 构建\n") + .addJavadoc("

Whether Release Annotation Processor Compile\n") + .addJavadoc("@return {@code true} yes, {@code false} no\n") + .build(); + classBuilder.addMethod(isReleaseMethod); + } + + /** + * 构建 static{} 初始化代码 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderStaticInit(final TypeSpec.Builder classBuilder) { + // 创建 Module List 集合变量 + FieldSpec moduleListField = FieldSpec + .builder(_getListType(ModuleBean.class), VAR_MODULE_LIST, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) +// .initializer("new $T<$T>()", ArrayList.class, ModuleBean.class) + .addJavadoc("Module List\n") + .build(); + classBuilder.addField(moduleListField); + + if (!sModuleNameMap.isEmpty()) { + // 构建 static {} 初始化代码 + CodeBlock.Builder staticCodeBlockBuilder = CodeBlock.builder(); + // 创建 module List 集合 VAR_MODULELIST + staticCodeBlockBuilder.addStatement("List<$T> $N = new $T<>()", ModuleBean.class, + VAR_MODULELIST, ArrayList.class + ); + + Iterator>> iterator = sModuleNameMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + // Module 名 + String moduleName = entry.getKey(); + // Environment 变量名集合 + List environmentVarNameList = entry.getValue(); + + // 添加 moduleVarName 到 VAR_MODULELIST 中 + staticCodeBlockBuilder.add("\n") + .addStatement("$N.add($N)", VAR_MODULELIST, _getModuleVarName_UpperCase(moduleName)); + // 添加 Environment 到对应 ModuleBean.getEnvironments() 中 + for (String environmentVarName : environmentVarNameList) { + staticCodeBlockBuilder.addStatement("$N.$N().add($N)", _getModuleVarName_UpperCase(moduleName), + METHOD_GET_MODULE_ENVIRONMENTS_LIST, environmentVarName + ); + } + } + // 初始化 Module List 集合变量 VAR_MODULE_LIST + staticCodeBlockBuilder.add("\n") + .addStatement("$N = $T.unmodifiableList($N)", VAR_MODULE_LIST, Collections.class, VAR_MODULELIST); + // 创建代码 + classBuilder.addStaticBlock(staticCodeBlockBuilder.build()); + } + } + + /** + * 构建 getXxx 方法代码 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderGetMethod(final TypeSpec.Builder classBuilder) { + // public static final List getModuleList() {} + MethodSpec getModuleListMethod = MethodSpec + .methodBuilder(METHOD_GET_MODULE_LIST) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(_getListType(ModuleBean.class)) + .addStatement("return $N", VAR_MODULE_LIST) + .addJavadoc("获取全部 $N 配置列表\n", ModuleBean.class.getSimpleName()) + .addJavadoc("

Get All $N List\n", ModuleBean.class.getSimpleName()) + .addJavadoc("@return List<$N>\n", ModuleBean.class.getSimpleName()) + .build(); + classBuilder.addMethod(getModuleListMethod); + + // 创建 Module Environment get 方法 + Iterator>> iterator = sModuleNameMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + // Module 名 + String moduleName = entry.getKey(); + // Environment 变量名 ( 因为 release 只能有一个, 这里直接获取 0 ) + String environmentVarName = entry.getValue().get(0); + + // public static final ModuleBean getModule() {} + String getModuleMethodName = "get" + moduleName + STR_MODULE; + MethodSpec getModuleMethod = MethodSpec + .methodBuilder(getModuleMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(ModuleBean.class) + .addStatement("return $N", _getModuleVarName_UpperCase(moduleName)) + .addJavadoc("获取 $N [ Module ] Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Bean\n", moduleName) + .addJavadoc("@return $N [ Module ] Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleMethod); + + // = + + // public static final EnvironmentBean getModuleReleaseEnvironment() {} + String getModuleReleaseEnvironmentMethodName = "get" + moduleName + STR_RELEASE_ENVIRONMENT; + MethodSpec getModuleReleaseEnvironmentMethod = MethodSpec + .methodBuilder(getModuleReleaseEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(EnvironmentBean.class) + .addStatement("return $N", environmentVarName) + .addJavadoc("获取 $N [ Module ] Release Environment Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Release Environment Bean\n", moduleName) + .addJavadoc("@return $N [ Module ] Release Environment Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleReleaseEnvironmentMethod); + + // = + + String sSelectModule = VAR_SELECT_ENVIRONMENT + moduleName; + // 构建 getModuleEnvironment 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null) return $N;\n", sSelectModule, sSelectModule); + codeBlockBuilder.add("EnvironmentBean environmentBean = $N($N, $S, $N);\n", METHOD_READ_STORAGE, + VAR_CONTEXT, _getModuleVarName_UpperCase(moduleName), _getModuleVarName_UpperCase(moduleName) + ); + codeBlockBuilder.add("if (environmentBean != null) return $N = environmentBean;\n", sSelectModule); + codeBlockBuilder.add("$N = $N();\n", sSelectModule, getModuleReleaseEnvironmentMethodName); + + // public static final EnvironmentBean getModuleEnvironment(final Context context) {} + String getModuleEnvironmentMethodName = "get" + moduleName + STR_ENVIRONMENT; + MethodSpec getModuleEnvironmentMethod = MethodSpec + .methodBuilder(getModuleEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(EnvironmentBean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return $N()", getModuleReleaseEnvironmentMethodName) + .addJavadoc("获取 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return $N [ Module ] Selected Environment Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleEnvironmentMethod); + + // = + + // public static final String getModuleEnvironmentValue(final Context context) {} + String getModuleEnvironmentValueMethodName = "get" + moduleName + STR_ENVIRONMENT_VALUE; + MethodSpec getModuleEnvironmentValueMethod = MethodSpec + .methodBuilder(getModuleEnvironmentValueMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(String.class) + .addStatement("return $N($N).$N()", getModuleEnvironmentMethodName, VAR_CONTEXT, METHOD_GET_ENVIRONMENTS_VALUE) + .addJavadoc("获取 $N [ Module ] Selected Environment Value\n", moduleName) + .addJavadoc("

Get $N [ Module ] Selected Environment Value\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return $N [ Module ] Selected Environment Value\n", moduleName) + .build(); + classBuilder.addMethod(getModuleEnvironmentValueMethod); + + // = + + // 构建 setModuleEnvironment 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N == null || $N == null) return false;\n", VAR_CONTEXT, VAR_NEW_ENVIRONMENT); + codeBlockBuilder.add("if ($N($N).equals($N)) return false;\n", getModuleEnvironmentMethodName, VAR_CONTEXT, VAR_NEW_ENVIRONMENT); + codeBlockBuilder.add("if ($N($N, $S, $N)) {\n", METHOD_WRITE_STORAGE, + VAR_CONTEXT, _getModuleVarName_UpperCase(moduleName), VAR_NEW_ENVIRONMENT + ); + codeBlockBuilder.add(" EnvironmentBean temp = $N;\n", sSelectModule); + codeBlockBuilder.add(" $N = $N;\n", sSelectModule, VAR_NEW_ENVIRONMENT); + codeBlockBuilder.add(" $N($N, temp, $N);\n", METHOD_NOTIFY_ONENVIRONMENT_CHANGE_LISTENER + , _getModuleVarName_UpperCase(moduleName), sSelectModule); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("}\n"); + + // public static final Boolean setModuleEnvironment(final Context context, final EnvironmentBean newEnvironment) {} + String setModuleEnvironmentMethodName = "set" + moduleName + STR_ENVIRONMENT; + MethodSpec setModuleEnvironmentMethod = MethodSpec + .methodBuilder(setModuleEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addParameter(EnvironmentBean.class, VAR_NEW_ENVIRONMENT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("设置 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Set $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@param $N environment bean\n", VAR_NEW_ENVIRONMENT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(setModuleEnvironmentMethod); + + // = + + // 构建 resetModule 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null && $N($N, $N)) {\n", VAR_CONTEXT, METHOD_DELETE_STORAGE, VAR_CONTEXT, + String.format(JSON_FILE_FORMAT, _getModuleVarName_UpperCase(moduleName)) + ); + codeBlockBuilder.add(String.format(" %s = null;\n", VAR_SELECT_ENVIRONMENT + moduleName)); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("}\n"); + + // public static final Boolean resetModule(final Context context) {} + String resetModuleMethodName = METHOD_RESET + moduleName; + MethodSpec resetModuleMethod = MethodSpec + .methodBuilder(resetModuleMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("重置 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Reset $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(resetModuleMethod); + + // = + + // 构建 isModuleAnnotation 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N == null) return false;\n", VAR_CONTEXT); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" $T environmentBean = $N($N);\n", EnvironmentBean.class, getModuleEnvironmentMethodName, VAR_CONTEXT); + codeBlockBuilder.add(" int hashCode = environmentBean.hashCode();\n"); + codeBlockBuilder.add(" $T iterator = $N.getEnvironments().iterator();\n", + Iterator.class, _getModuleVarName_UpperCase(moduleName) + ); + codeBlockBuilder.add(" while (iterator.hasNext()) {\n"); + codeBlockBuilder.add(" EnvironmentBean bean = iterator.next();\n"); + codeBlockBuilder.add(" if (bean != null && bean.hashCode() == hashCode) {\n"); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // public static final Boolean isModuleAnnotation(final Context context) {} + String isModuleAnnotationMethodName = String.format(METHOD_IS_ANNOTATION, moduleName); + MethodSpec isModuleAnnotationMethod = MethodSpec + .methodBuilder(isModuleAnnotationMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("是否 $N [ Module ] Annotation Environment Bean\n", moduleName) + .addJavadoc("

Whether $N [ Module ] Annotation Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(isModuleAnnotationMethod); + } + } + + /** + * 构建模块环境改变触发事件方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderEnvironmentChangeListener(final TypeSpec.Builder classBuilder) { + // 创建 Listener List 集合变量 + FieldSpec listenerListField = FieldSpec + .builder(_getListType(OnEnvironmentChangeListener.class), VAR_LISTENER_LIST, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T<$T>()", ArrayList.class, OnEnvironmentChangeListener.class) + .addJavadoc("Environment Change Listener List\n") + .build(); + classBuilder.addField(listenerListField); + + // = + + // 构建 addOnEnvironmentChangeListener 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null && !$N.contains($N)) {\n", VAR_LISTENER, VAR_LISTENER_LIST, VAR_LISTENER); + codeBlockBuilder.add(" try {\n"); + codeBlockBuilder.add(" return $N.add($N);\n", VAR_LISTENER_LIST, VAR_LISTENER); + codeBlockBuilder.add(" } catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("}\n"); + + // public static final Boolean addOnEnvironmentChangeListener(final OnEnvironmentChangeListener listener) {} + MethodSpec addOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_ADD_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(OnEnvironmentChangeListener.class, VAR_LISTENER, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("添加模块环境改变触发事件\n") + .addJavadoc("

Add Environment Change Listener\n") + .addJavadoc("@param $N environment change listener\n", VAR_LISTENER) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(addOnEnvironmentChangeListenerMethod); + + // = + + // 构建 removeOnEnvironmentChangeListener 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null) {\n", VAR_LISTENER); + codeBlockBuilder.add(" try {\n"); + codeBlockBuilder.add(" return $N.remove($N);\n", VAR_LISTENER_LIST, VAR_LISTENER); + codeBlockBuilder.add(" } catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("}\n"); + + // public static final Boolean removeOnEnvironmentChangeListener(final OnEnvironmentChangeListener listener) {} + MethodSpec removeOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_REMOVE_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(OnEnvironmentChangeListener.class, VAR_LISTENER, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("移除模块环境改变触发事件\n") + .addJavadoc("

Remove Environment Change Listener\n") + .addJavadoc("@param $N environment change listener\n", VAR_LISTENER) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(removeOnEnvironmentChangeListenerMethod); + + // = + + // 构建 clearOnEnvironmentChangeListener 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" $N.clear();\n", VAR_LISTENER_LIST); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // public static final Boolean clearOnEnvironmentChangeListener() {} + MethodSpec clearOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_CLEAR_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("清空模块环境改变触发事件\n") + .addJavadoc("

Clear All Environment Change Listener\n") + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(clearOnEnvironmentChangeListenerMethod); + + // ============= + // = 私有通知方法 = + // ============= + + // 构建 notifyOnEnvironmentChangeListener 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("List<$T> list = new ArrayList<>($N);\n", OnEnvironmentChangeListener.class, VAR_LISTENER_LIST); + codeBlockBuilder.add("for ($T $N : list) {\n", OnEnvironmentChangeListener.class, VAR_LISTENER); + codeBlockBuilder.add(" try {\n"); + codeBlockBuilder.add(" $N.$N($N, $N, $N);\n", VAR_LISTENER, METHOD_ONENVIRONMENT_CHANGED, + VAR_MODULE, VAR_OLD_ENVIRONMENT, VAR_NEW_ENVIRONMENT + ); + codeBlockBuilder.add(" } catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("}\n"); + + // private static final void notifyOnEnvironmentChangeListener(module, oldEnvironment, newEnvironment) {} + MethodSpec notifyOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_NOTIFY_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(ModuleBean.class, VAR_MODULE, Modifier.FINAL) + .addParameter(EnvironmentBean.class, VAR_OLD_ENVIRONMENT, Modifier.FINAL) + .addParameter(EnvironmentBean.class, VAR_NEW_ENVIRONMENT, Modifier.FINAL) + .addCode(codeBlockBuilder.build()) + .addJavadoc("模块环境发生变化时触发\n") + .addJavadoc("

Triggered when the module environment changes\n") + .addJavadoc("@param module Module\n") + .addJavadoc("@param oldEnvironment The old environment of the module\n") + .addJavadoc("@param newEnvironment The latest environment of the module\n") + .build(); + classBuilder.addMethod(notifyOnEnvironmentChangeListenerMethod); + } + + /** + * 构建存储相关方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderStorageMethod(final TypeSpec.Builder classBuilder) { + // 构建 getStorageDir 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File file = new File($N.getCacheDir(), $S);\n", VAR_CONTEXT, ENVIRONMENT_FILE_NAME); + codeBlockBuilder.add(" if (!file.exists()) file.mkdirs();\n"); + codeBlockBuilder.add(" return file;\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // private static final File getStorageDir(final Context context) {} + MethodSpec getStorageDirMethod = MethodSpec + .methodBuilder(METHOD_GET_STORAGE_DIR) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(File.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return null") + .addJavadoc("获取环境配置存储路径 - path /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .addJavadoc("

Get Environment Configure Storage Dir - path /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .build(); + classBuilder.addMethod(getStorageDirMethod); + + // = + + // 构建 deleteStorageDir 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File storage = $N($N);\n", METHOD_GET_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" if (storage != null) {\n"); + codeBlockBuilder.add(" String[] strs = storage.list();\n"); + codeBlockBuilder.add(" for (String fileName : strs) {\n"); + codeBlockBuilder.add(" File file = new File(storage, fileName);\n"); + codeBlockBuilder.add(" if (!file.isDirectory()) file.delete();\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // private static final Boolean deleteStorageDir(final Context context) {} + MethodSpec deleteStorageDirMethod = MethodSpec + .methodBuilder(METHOD_DELETE_STORAGE_DIR) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("删除环境存储配置文件\n") + .addJavadoc("

Delete Environment Storage Configure File\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(deleteStorageDirMethod); + + // = + + // 构建 deleteStorage 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File storage = $N($N);\n", METHOD_GET_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" File file = new File(storage, $N);\n", VAR_FILE_NAME); + codeBlockBuilder.add(" if (file.exists()) file.delete();\n"); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // private static final Boolean deleteStorage(final Context context, final String fileName) {} + MethodSpec deleteStorageMethod = MethodSpec + .methodBuilder(METHOD_DELETE_STORAGE) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addParameter(String.class, VAR_FILE_NAME, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("删除环境存储配置文件\n") + .addJavadoc("

Delete Environment Storage Configure File\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@param fileName 文件名\n") + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(deleteStorageMethod); + + // = + + // 构建 writeStorage 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N == null || $N == null || $N == null) return false;\n", VAR_CONTEXT, VAR_MODULE_NAME, VAR_ENVIRONMENT); + codeBlockBuilder.add("$T bw = null;\n", BufferedWriter.class); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File storage = $N($N);\n", METHOD_GET_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" File file = new File(storage, $N + $N);\n", VAR_MODULE_NAME, JSON_FILE); + codeBlockBuilder.add(" bw = new BufferedWriter(new $T(file, false));\n", FileWriter.class); + codeBlockBuilder.add(" bw.write($N.toString());\n", VAR_ENVIRONMENT); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("} finally {\n"); + codeBlockBuilder.add(" if (bw != null) {\n"); + codeBlockBuilder.add(" try {\n"); + codeBlockBuilder.add(" bw.close();\n"); + codeBlockBuilder.add(" } catch (Exception ignore) {\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("}\n"); + + // private static final Boolean writeStorage(context, moduleName, environment) {} + MethodSpec writeStorageMethod = MethodSpec + .methodBuilder(METHOD_WRITE_STORAGE) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addParameter(String.class, VAR_MODULE_NAME, Modifier.FINAL) + .addParameter(EnvironmentBean.class, VAR_ENVIRONMENT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("写入环境存储配置文件\n") + .addJavadoc("

Write Environment Storage Configure File\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@param $N module Name\n", VAR_MODULE_NAME) + .addJavadoc("@param $N environment bean\n", VAR_ENVIRONMENT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(writeStorageMethod); + + // = + + // 构建 readStorage 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N == null || $N == null || $N == null) return null;\n", VAR_CONTEXT, VAR_MODULE_NAME, VAR_MODULE); + codeBlockBuilder.add("$T br = null;\n", BufferedReader.class); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File storage = $N($N);\n", METHOD_GET_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" File file = new File(storage, $N + $N);\n", VAR_MODULE_NAME, JSON_FILE); + codeBlockBuilder.add(" if (!file.exists()) return null;\n"); + codeBlockBuilder.add(" $T builder = new StringBuilder();\n", StringBuilder.class); + codeBlockBuilder.add(" br = new BufferedReader(new $T(new $T(file)));\n", InputStreamReader.class, FileInputStream.class); + codeBlockBuilder.add(" String line;\n"); + codeBlockBuilder.add(" while ((line = br.readLine()) != null) {\n"); + codeBlockBuilder.add(" builder.append(line);\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" $T jsonObject = new JSONObject(builder.toString());\n", TYPE_NAME_JSONOBJECT); + codeBlockBuilder.add(" String name = jsonObject.getString($S);\n", VAR_NAME); + codeBlockBuilder.add(" String value = jsonObject.getString($S);\n", VAR_VALUE); + codeBlockBuilder.add(" String alias = jsonObject.getString($S);\n", VAR_ALIAS); + codeBlockBuilder.add(" return new $T(name, value, alias, $N);\n", EnvironmentBean.class, VAR_MODULE); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("} finally {\n"); + codeBlockBuilder.add(" if (br != null) {\n"); + codeBlockBuilder.add(" try {\n"); + codeBlockBuilder.add(" br.close();\n"); + codeBlockBuilder.add(" } catch (Exception ignore) {\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("}\n"); + + // private static final Boolean readStorage(context, moduleName, module) {} + MethodSpec readStorageMethod = MethodSpec + .methodBuilder(METHOD_READ_STORAGE) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addParameter(String.class, VAR_MODULE_NAME, Modifier.FINAL) + .addParameter(ModuleBean.class, VAR_MODULE, Modifier.FINAL) + .returns(EnvironmentBean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return null") + .addJavadoc("读取环境存储配置文件\n") + .addJavadoc("

Read Environment Storage Configure File\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@param $N module Name\n", VAR_MODULE_NAME) + .addJavadoc("@param $N module bean\n", VAR_MODULE) + .addJavadoc("@return {@link $N}\n", EnvironmentBean.class.getSimpleName()) + .build(); + classBuilder.addMethod(readStorageMethod); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 Module Release Environment 数据 + * @param moduleElement 使用注解修饰的 Module Element + * @param processingEnv {@link ProcessingEnvironment} + * @return Module Release Environment 数据 + */ + private static Element _getModuleReleaseEnvironment( + final Element moduleElement, + final ProcessingEnvironment processingEnv + ) + throws Exception { + Element environmentElement = null; + List allMembers = processingEnv.getElementUtils().getAllMembers((TypeElement) moduleElement); + for (Element member : allMembers) { + Environment environmentAnnotation = member.getAnnotation(Environment.class); + if (environmentAnnotation == null) continue; + + if (environmentAnnotation.isRelease()) { + if (environmentElement != null) { // 每个 Module 只能有一个 release 环境配置 + String moduleName = moduleElement.getSimpleName().toString(); + throw new Exception(moduleName + " module can be only one release environment configuration ( 每个 Module 只能有一个 release 环境配置 )"); + } + environmentElement = member; + } + } + if (environmentElement == null) { // 每个 Module 必须有一个 release 环境配置 + String moduleName = moduleElement.getSimpleName().toString(); + throw new Exception(moduleName + " module must have a release environment configuration ( 每个 Module 必须有一个 release 环境配置 )"); + } + return environmentElement; + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + private static Type _getListType(final Class type) { + return new ParameterizedTypeImpl(new Type[]{type}, null, List.class); + } + + /** + * 获取 Module 拼接变量名 + * @param moduleName Module Name + * @return Module 拼接变量名 + */ + private static String _getModuleVarName_UpperCase(final String moduleName) { + return VAR_MODULE_PREFIX + moduleName.toUpperCase(); + } + + /** + * 获取 Environment 拼接变量名 + * @param moduleName Module Name + * @param environmentName Environment Name + * @return Environment 拼接变量名 + */ + private static String _getEnvironmentVarName_UpperCase( + final String moduleName, + final String environmentName + ) { + return VAR_ENVIRONMENT_PREFIX + moduleName.toUpperCase() + "_" + environmentName.toUpperCase(); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompilerRelease/.gitignore b/lib/Environment/DevEnvironmentCompilerRelease/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompilerRelease/CHANGELOG.md b/lib/Environment/DevEnvironmentCompilerRelease/CHANGELOG.md new file mode 100644 index 0000000000..5dc6bf439f --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/CHANGELOG.md @@ -0,0 +1,73 @@ +Change Log +========== + +Version 1.1.2 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.1 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.1.0 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.9 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp 库同步升级 + +Version 1.0.5 *(2021-10-13)* +---------------------------- + +* `[Update]` 修改为 JDK 1.8 进行编译 + +Version 1.0.4 *(2021-09-20)* +---------------------------- + +* `[Refactor]` review code、代码格式化处理、方法名、参数名、变量名等规范排查 + +Version 1.0.3 *(2021-05-09)* +---------------------------- + +* `[sync]` sync to Maven Central + +Version 1.0.2 *(2020-12-10)* +---------------------------- + +* `[Style]` 代码格式化处理 ( 间距美化调整等 ) + +Version 1.0.1 *(2020-11-27)* +---------------------------- + +* `[Add]` 每个 Module 新增单独的 resetModule 方法 如 resetIM 用于删除当前选中的环境配置 + +* `[Add]` 每个 Module 新增单独的 isModuleAnnotation 方法 如 isIMAnnotation 用于判断当前环境是否属于注解环境配置 + +* `[Update]` 更新异常日志统一通过 LogUtils 输出控制 + +* `[Update]` 更新 getStorageDir、deleteStorageDir 方法修饰符为 private + +Version 1.0.0 *(2020-02-04)* +---------------------------- + +* Initial release diff --git a/lib/Environment/DevEnvironmentCompilerRelease/build.gradle b/lib/Environment/DevEnvironmentCompilerRelease/build.gradle new file mode 100644 index 0000000000..e116f31ecc --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/build.gradle @@ -0,0 +1,34 @@ +apply from: rootProject.file(files.lib_java_gradle) + +version versions.dev_environment_compiler_release_version + +compileJava { + sourceCompatibility JavaVersion.VERSION_1_8.toString() + targetCompatibility JavaVersion.VERSION_1_8.toString() +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + api deps.aop.javapoet + compileOnly deps.aop.auto_service + annotationProcessor deps.aop.auto_service + + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_environment_base + } else { + // 编译时使用 + api project(':DevEnvironmentBase') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_java) +//apply from: rootProject.file(files.sonatype_upload_java) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_java) +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompilerRelease/project.properties b/lib/Environment/DevEnvironmentCompilerRelease/project.properties new file mode 100644 index 0000000000..387d8f9d7d --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevEnvironmentCompilerRelease +project.groupId=io.github.afkt +project.artifactId=DevEnvironmentCompilerRelease +project.packaging=jar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevEnvironmentCompilerRelease \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/DevEnvironmentCompilerRelease.java b/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/DevEnvironmentCompilerRelease.java new file mode 100644 index 0000000000..cd1e9f6890 --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/DevEnvironmentCompilerRelease.java @@ -0,0 +1,78 @@ +package dev.environment.compiler; + +import com.google.auto.service.AutoService; +import com.squareup.javapoet.TypeSpec; + +import java.util.Collections; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import dev.environment.annotation.Module; + +/** + * detail: DevEnvironment Release 编译生成类 + * @author Ttt + */ +@AutoService(Processor.class) +//@SupportedSourceVersion(SourceVersion.RELEASE_8) +//@SupportedAnnotationTypes("dev.environment.annotation.Module") +public class DevEnvironmentCompilerRelease + extends AbstractProcessor { + + /** + * Processor 支持的最新的源版本 + * @return {@link SourceVersion} + */ + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.RELEASE_8; + } + + /** + * 获取该 Processor 能够处理的注解 + * @return 该 Processor 能够处理的注解 + */ + @Override + public Set getSupportedAnnotationTypes() { + return Collections.singleton(Module.class.getCanonicalName()); + } + + @Override + public boolean process( + Set set, + RoundEnvironment roundEnvironment + ) { + // 构建 DevEnvironment 类对象 + TypeSpec.Builder devEnvironmentClassBuilder = Utils.builderDevEnvironment_Class(); + // 获取使用注解修饰的 Module Element + Set elements = roundEnvironment.getElementsAnnotatedWith(Module.class); + for (Element element : elements) { + try { + // 创建 Module 数据 + Utils.builderModule_DATA(devEnvironmentClassBuilder, element, processingEnv); + } catch (Exception e) { + e.printStackTrace(); + } + } + // 构建 static{} 初始化代码 + Utils.builderStaticInit(devEnvironmentClassBuilder); + // 构建 isRelease 方法 + Utils.builderIsReleaseMethod(devEnvironmentClassBuilder); + // 构建 getXxx 方法代码 + Utils.builderGetMethod(devEnvironmentClassBuilder); + // 构建模块环境改变触发事件方法 + Utils.builderEnvironmentChangeListener(devEnvironmentClassBuilder); + // 构建存储相关方法 + Utils.builderStorageMethod(devEnvironmentClassBuilder); + // 构建 Reset 方法 + Utils.builderResetMethod(devEnvironmentClassBuilder); + // 创建 DevEnvironment JAVA 文件 + return Utils.createDevEnvironmentJavaFile(devEnvironmentClassBuilder, processingEnv); + } +} \ No newline at end of file diff --git a/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/Utils.java b/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/Utils.java new file mode 100644 index 0000000000..b2bb99743f --- /dev/null +++ b/lib/Environment/DevEnvironmentCompilerRelease/src/main/java/dev/environment/compiler/Utils.java @@ -0,0 +1,613 @@ +package dev.environment.compiler; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; + +import dev.environment.annotation.Environment; +import dev.environment.annotation.Module; +import dev.environment.bean.EnvironmentBean; +import dev.environment.bean.ModuleBean; +import dev.environment.listener.OnEnvironmentChangeListener; +import dev.environment.log.LogUtils; +import dev.environment.type.ParameterizedTypeImpl; + +/** + * detail: 内部工具类 + * @author Ttt + */ +final class Utils { + + // 包名 + static final String PACKAGE_NAME = "dev.environment"; + // 工具类文件名 + static final String ENVIRONMENT_FILE_NAME = "DevEnvironment"; + + // 方法名 + static final String METHOD_RESET = "reset"; + static final String METHOD_IS_RELEASE = "isRelease"; + static final String METHOD_GET_MODULE_LIST = "getModuleList"; + static final String METHOD_GET_MODULE_ENVIRONMENTS_LIST = "getEnvironments"; + static final String METHOD_GET_ENVIRONMENTS_VALUE = "getValue"; + static final String METHOD_ADD_ONENVIRONMENT_CHANGE_LISTENER = "addOnEnvironmentChangeListener"; + static final String METHOD_REMOVE_ONENVIRONMENT_CHANGE_LISTENER = "removeOnEnvironmentChangeListener"; + static final String METHOD_CLEAR_ONENVIRONMENT_CHANGE_LISTENER = "clearOnEnvironmentChangeListener"; + static final String METHOD_GET_STORAGE_DIR = "getStorageDir"; + static final String METHOD_DELETE_STORAGE_DIR = "deleteStorageDir"; + static final String METHOD_IS_ANNOTATION = "is%sAnnotation"; + // 变量相关 + static final String VAR_MODULE_PREFIX = "MODULE_"; + static final String VAR_ENVIRONMENT_PREFIX = "ENVIRONMENT_"; + static final String VAR_MODULELIST = "moduleList"; + static final String VAR_MODULE_LIST = "MODULE_LIST"; + static final String VAR_CONTEXT = "context"; + static final String VAR_NEW_ENVIRONMENT = "newEnvironment"; + static final String VAR_LISTENER = "listener"; + static final String VAR_FILE_NAME = "fileName"; + // 常量字符串 + static final String STR_MODULE = "Module"; + static final String STR_ENVIRONMENT = "Environment"; + static final String STR_ENVIRONMENT_VALUE = "EnvironmentValue"; + static final String STR_RELEASE_ENVIRONMENT = "ReleaseEnvironment"; + // 其他 + static final TypeName TYPE_NAME_CONTEXT = ClassName.get("android.content", "Context"); + + // ============= + // = 内部生成方法 = + // ============= + + // 用于记录 Module 名 Map> + static final LinkedHashMap> sModuleNameMap = new LinkedHashMap<>(); + + // ======= + // = 通用 = + // ======= + + /** + * 构建 DevEnvironment 类对象 + * @return {@link TypeSpec.Builder} + */ + public static TypeSpec.Builder builderDevEnvironment_Class() { + // 创建 DevEnvironment 类 + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(ENVIRONMENT_FILE_NAME) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addJavadoc("detail: 环境配置工具类\n") + .addJavadoc("@author Ttt\n"); + // 创建 DevEnvironment 无参构造函数 + // private DevEnvironment() {} + MethodSpec constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build(); + return classBuilder.addMethod(constructor); + } + + /** + * 创建 DevEnvironment JAVA 文件 + * @param classBuilder DevEnvironment 类构建对象 + * @param processingEnv {@link ProcessingEnvironment} + * @return {@code true} success, {@code false} fail + */ + public static boolean createDevEnvironmentJavaFile( + final TypeSpec.Builder classBuilder, + final ProcessingEnvironment processingEnv + ) { + JavaFile javaFile = JavaFile.builder(PACKAGE_NAME, classBuilder.build()).build(); + try { + javaFile.writeTo(processingEnv.getFiler()); + return true; + } catch (IOException e) { +// e.printStackTrace(); + } + return false; + } + + // = + + /** + * 创建 Module 数据 + * @param classBuilder DevEnvironment 类构建对象 + * @param moduleElement 使用注解修饰的 Module Element + * @param processingEnv {@link ProcessingEnvironment} + */ + public static void builderModule_DATA( + final TypeSpec.Builder classBuilder, + final Element moduleElement, + final ProcessingEnvironment processingEnv + ) + throws Exception { + Module moduleAnnotation = moduleElement.getAnnotation(Module.class); + if (moduleAnnotation == null) return; + // Module 信息 + String moduleName = moduleElement.getSimpleName().toString(); + String moduleAlias = moduleAnnotation.alias(); + + // 获取 Module Release Environment 数据 + Element environmentElement = _getModuleReleaseEnvironment(moduleElement, processingEnv); + if (environmentElement != null) { + // 创建 Environment 变量名 List + List environmentVarNameList = new ArrayList<>(); + sModuleNameMap.put(moduleName, environmentVarNameList); + + // 创建私有常量变量 + // private static final ModuleBean MODULE_XXX = new ModuleBean(); + FieldSpec moduleField = FieldSpec + .builder(ModuleBean.class, _getModuleVarName_UpperCase(moduleName)) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T($S, $S)", ModuleBean.class, moduleName, moduleAlias) + .addJavadoc(String.format("[ Module ] name: %s, alias: %s\n", moduleName, moduleAlias)) + .build(); + classBuilder.addField(moduleField); + + // 创建 Module Environment 数据 + builderModuleEnvironment_DATA(classBuilder, moduleElement, environmentElement); + } + } + + /** + * 创建 Module Environment 数据 + * @param classBuilder DevEnvironment 类构建对象 + * @param moduleElement 使用注解修饰的 Module Element + * @param environmentElement 使用注解修饰的 Environment Element + */ + public static void builderModuleEnvironment_DATA( + final TypeSpec.Builder classBuilder, + final Element moduleElement, + final Element environmentElement + ) { + // Module 信息 + String moduleName = moduleElement.getSimpleName().toString(); + // Environment 信息 + Environment environmentAnnotation = environmentElement.getAnnotation(Environment.class); + String environmentName = environmentElement.getSimpleName().toString(); + String environmentValue = environmentAnnotation.value(); + String environmentAlias = environmentAnnotation.alias(); + + // 创建私有常量变量 + // private static final EnvironmentBean ENVIRONMENT_MODULENAME_XXX = new EnvironmentBean(); + FieldSpec environmentField = FieldSpec + .builder(EnvironmentBean.class, _getEnvironmentVarName_UpperCase(moduleName, environmentName)) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .initializer("new $T($S, $S, $S, $N)", EnvironmentBean.class, environmentName, + environmentValue, environmentAlias, _getModuleVarName_UpperCase(moduleName) + ) + .addJavadoc(String.format("[ Environment ] name: %s, alias: %s, [ Module ] name: %s\n", + environmentName, environmentAlias, moduleName + )) + .build(); + classBuilder.addField(environmentField); + + // 记录 Environment 变量名 ( 传入的 environmentElement ) 属于 Release Environment Element 存储在 index 0 + sModuleNameMap.get(moduleName).add(_getEnvironmentVarName_UpperCase(moduleName, environmentName)); + } + + /** + * 构建 Reset 方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderResetMethod(final TypeSpec.Builder classBuilder) { + // 构建 reset 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("if ($N != null && $N($N)) {\n", VAR_CONTEXT, METHOD_DELETE_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add("}\n"); + + // public static final void reset() {} + MethodSpec resetMethod = MethodSpec + .methodBuilder(METHOD_RESET) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addCode(codeBlockBuilder.build()) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("重置操作\n") + .addJavadoc("

Reset Operating\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(resetMethod); + } + + /** + * 构建 isRelease 方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderIsReleaseMethod(final TypeSpec.Builder classBuilder) { + // public static final Boolean isRelease() {} + MethodSpec isReleaseMethod = MethodSpec + .methodBuilder(METHOD_IS_RELEASE) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return true") + .addJavadoc("是否使用 releaseAnnotationProcessor 构建\n") + .addJavadoc("

Whether Release Annotation Processor Compile\n") + .addJavadoc("@return {@code true} yes, {@code false} no\n") + .build(); + classBuilder.addMethod(isReleaseMethod); + } + + /** + * 构建 static{} 初始化代码 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderStaticInit(final TypeSpec.Builder classBuilder) { + // 创建 Module List 集合变量 + FieldSpec moduleListField = FieldSpec + .builder(_getListType(ModuleBean.class), VAR_MODULE_LIST, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) +// .initializer("new $T<$T>()", ArrayList.class, ModuleBean.class) + .addJavadoc("Module List\n") + .build(); + classBuilder.addField(moduleListField); + + if (!sModuleNameMap.isEmpty()) { + // 构建 static {} 初始化代码 + CodeBlock.Builder staticCodeBlockBuilder = CodeBlock.builder(); + // 创建 module List 集合 VAR_MODULELIST + staticCodeBlockBuilder.addStatement("List<$T> $N = new $T<>()", ModuleBean.class, + VAR_MODULELIST, ArrayList.class + ); + + Iterator>> iterator = sModuleNameMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + // Module 名 + String moduleName = entry.getKey(); + // Environment 变量名 ( 因为 release 只能有一个, 这里直接获取 0 ) + String environmentVarName = entry.getValue().get(0); + + // 添加 moduleVarName 到 VAR_MODULELIST 中 + staticCodeBlockBuilder.add("\n") + .addStatement("$N.add($N)", VAR_MODULELIST, _getModuleVarName_UpperCase(moduleName)); + // 添加 Environment 到对应 ModuleBean.getEnvironments() 中 + staticCodeBlockBuilder.addStatement("$N.$N().add($N)", _getModuleVarName_UpperCase(moduleName), + METHOD_GET_MODULE_ENVIRONMENTS_LIST, environmentVarName + ); + } + // 初始化 Module List 集合变量 VAR_MODULE_LIST + staticCodeBlockBuilder.add("\n") + .addStatement("$N = $T.unmodifiableList($N)", VAR_MODULE_LIST, Collections.class, VAR_MODULELIST); + // 创建代码 + classBuilder.addStaticBlock(staticCodeBlockBuilder.build()); + } + } + + /** + * 构建 getXxx 方法代码 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderGetMethod(final TypeSpec.Builder classBuilder) { + // public static final List getModuleList() {} + MethodSpec getModuleListMethod = MethodSpec + .methodBuilder(METHOD_GET_MODULE_LIST) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(_getListType(ModuleBean.class)) + .addStatement("return $N", VAR_MODULE_LIST) + .addJavadoc("获取全部 $N 配置列表\n", ModuleBean.class.getSimpleName()) + .addJavadoc("

Get All $N List\n", ModuleBean.class.getSimpleName()) + .addJavadoc("@return List<$N>\n", ModuleBean.class.getSimpleName()) + .build(); + classBuilder.addMethod(getModuleListMethod); + + // 创建 Module Environment get 方法 + Iterator>> iterator = sModuleNameMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry> entry = iterator.next(); + // Module 名 + String moduleName = entry.getKey(); + // Environment 变量名 ( 因为 release 只能有一个, 这里直接获取 0 ) + String environmentVarName = entry.getValue().get(0); + + // public static final ModuleBean getModule() {} + String getModuleMethodName = "get" + moduleName + STR_MODULE; + MethodSpec getModuleMethod = MethodSpec + .methodBuilder(getModuleMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(ModuleBean.class) + .addStatement("return $N", _getModuleVarName_UpperCase(moduleName)) + .addJavadoc("获取 $N [ Module ] Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Bean\n", moduleName) + .addJavadoc("@return $N [ Module ] Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleMethod); + + // = + + // public static final EnvironmentBean getModuleReleaseEnvironment() {} + String getModuleReleaseEnvironmentMethodName = "get" + moduleName + STR_RELEASE_ENVIRONMENT; + MethodSpec getModuleReleaseEnvironmentMethod = MethodSpec + .methodBuilder(getModuleReleaseEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(EnvironmentBean.class) + .addStatement("return $N", environmentVarName) + .addJavadoc("获取 $N [ Module ] Release Environment Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Release Environment Bean\n", moduleName) + .addJavadoc("@return $N [ Module ] Release Environment Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleReleaseEnvironmentMethod); + + // = + + // public static final EnvironmentBean getModuleEnvironment(final Context context) {} + String getModuleEnvironmentMethodName = "get" + moduleName + STR_ENVIRONMENT; + MethodSpec getModuleEnvironmentMethod = MethodSpec + .methodBuilder(getModuleEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(EnvironmentBean.class) + .addStatement("return $N()", getModuleReleaseEnvironmentMethodName) + .addJavadoc("获取 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Get $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return $N [ Module ] Selected Environment Bean\n", moduleName) + .build(); + classBuilder.addMethod(getModuleEnvironmentMethod); + + // = + + // public static final String getModuleEnvironmentValue(final Context context) {} + String getModuleEnvironmentValueMethodName = "get" + moduleName + STR_ENVIRONMENT_VALUE; + MethodSpec getModuleEnvironmentValueMethod = MethodSpec + .methodBuilder(getModuleEnvironmentValueMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(String.class) + .addStatement("return $N($N).$N()", getModuleEnvironmentMethodName, VAR_CONTEXT, METHOD_GET_ENVIRONMENTS_VALUE) + .addJavadoc("获取 $N [ Module ] Selected Environment Value\n", moduleName) + .addJavadoc("

Get $N [ Module ] Selected Environment Value\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return $N [ Module ] Selected Environment Value\n", moduleName) + .build(); + classBuilder.addMethod(getModuleEnvironmentValueMethod); + + // = + + // public static final Boolean setModuleEnvironment(final Context context, final EnvironmentBean newEnvironment) {} + String setModuleEnvironmentMethodName = "set" + moduleName + STR_ENVIRONMENT; + MethodSpec setModuleEnvironmentMethod = MethodSpec + .methodBuilder(setModuleEnvironmentMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .addParameter(EnvironmentBean.class, VAR_NEW_ENVIRONMENT, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("设置 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Set $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@param $N environment bean\n", VAR_NEW_ENVIRONMENT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(setModuleEnvironmentMethod); + + // = + + // public static final Boolean resetModule(final Context context) {} + String resetModuleMethodName = METHOD_RESET + moduleName; + MethodSpec resetModuleMethod = MethodSpec + .methodBuilder(resetModuleMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("重置 $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("

Reset $N [ Module ] Selected Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(resetModuleMethod); + + // = + + // public static final Boolean isModuleAnnotation(final Context context) {} + String isModuleAnnotationMethodName = String.format(METHOD_IS_ANNOTATION, moduleName); + MethodSpec isModuleAnnotationMethod = MethodSpec + .methodBuilder(isModuleAnnotationMethodName) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return true") + .addJavadoc("是否 $N [ Module ] Annotation Environment Bean\n", moduleName) + .addJavadoc("

Whether $N [ Module ] Annotation Environment Bean\n", moduleName) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(isModuleAnnotationMethod); + } + } + + /** + * 构建模块环境改变触发事件方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderEnvironmentChangeListener(final TypeSpec.Builder classBuilder) { + // public static final Boolean addOnEnvironmentChangeListener(final OnEnvironmentChangeListener listener) {} + MethodSpec addOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_ADD_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(OnEnvironmentChangeListener.class, VAR_LISTENER, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("添加模块环境改变触发事件\n") + .addJavadoc("

Add Environment Change Listener\n") + .addJavadoc("@param $N environment change listener\n", VAR_LISTENER) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(addOnEnvironmentChangeListenerMethod); + + // = + + // public static final Boolean removeOnEnvironmentChangeListener(final OnEnvironmentChangeListener listener) {} + MethodSpec removeOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_REMOVE_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .addParameter(OnEnvironmentChangeListener.class, VAR_LISTENER, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("移除模块环境改变触发事件\n") + .addJavadoc("

Remove Environment Change Listener\n") + .addJavadoc("@param $N environment change listener\n", VAR_LISTENER) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(removeOnEnvironmentChangeListenerMethod); + + // = + + // public static final Boolean clearOnEnvironmentChangeListener() {} + MethodSpec clearOnEnvironmentChangeListenerMethod = MethodSpec + .methodBuilder(METHOD_CLEAR_ONENVIRONMENT_CHANGE_LISTENER) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .returns(Boolean.class) + .addStatement("return false") + .addJavadoc("清空模块环境改变触发事件\n") + .addJavadoc("

Clear All Environment Change Listener\n") + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(clearOnEnvironmentChangeListenerMethod); + } + + /** + * 构建存储相关方法 + * @param classBuilder DevEnvironment 类构建对象 + */ + public static void builderStorageMethod(final TypeSpec.Builder classBuilder) { + // 构建 getStorageDir 实现代码 + CodeBlock.Builder codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File file = new File($N.getCacheDir(), $S);\n", VAR_CONTEXT, ENVIRONMENT_FILE_NAME); + codeBlockBuilder.add(" if (!file.exists()) file.mkdirs();\n"); + codeBlockBuilder.add(" return file;\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // private static final File getStorageDir(final Context context) {} + MethodSpec getStorageDirMethod = MethodSpec + .methodBuilder(METHOD_GET_STORAGE_DIR) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(File.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return null") + .addJavadoc("获取环境配置存储路径 - path /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .addJavadoc("

Get Environment Configure Storage Dir - path /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return /data/data/package/cache/$N\n", ENVIRONMENT_FILE_NAME) + .build(); + classBuilder.addMethod(getStorageDirMethod); + + // = + + // 构建 deleteStorageDir 实现代码 + codeBlockBuilder = CodeBlock.builder(); + codeBlockBuilder.add("try {\n"); + codeBlockBuilder.add(" File storage = $N($N);\n", METHOD_GET_STORAGE_DIR, VAR_CONTEXT); + codeBlockBuilder.add(" if (storage != null) {\n"); + codeBlockBuilder.add(" String[] strs = storage.list();\n"); + codeBlockBuilder.add(" for (String fileName : strs) {\n"); + codeBlockBuilder.add(" File file = new File(storage, fileName);\n"); + codeBlockBuilder.add(" if (!file.isDirectory()) file.delete();\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add(" return true;\n"); + codeBlockBuilder.add(" }\n"); + codeBlockBuilder.add("} catch (Exception e) {\n"); + codeBlockBuilder.add(" $T.printStackTrace(e);\n", LogUtils.class); + codeBlockBuilder.add("}\n"); + + // private static final Boolean deleteStorageDir(final Context context) {} + MethodSpec deleteStorageDirMethod = MethodSpec + .methodBuilder(METHOD_DELETE_STORAGE_DIR) + .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) + .addParameter(TYPE_NAME_CONTEXT, VAR_CONTEXT, Modifier.FINAL) + .returns(Boolean.class) + .addCode(codeBlockBuilder.build()) + .addStatement("return false") + .addJavadoc("删除环境存储配置文件\n") + .addJavadoc("

Delete Environment Storage Configure File\n") + .addJavadoc("@param $N {@link Context}\n", VAR_CONTEXT) + .addJavadoc("@return {@code true} success, {@code false} fail\n") + .build(); + classBuilder.addMethod(deleteStorageDirMethod); + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取 Module Release Environment 数据 + * @param moduleElement 使用注解修饰的 Module Element + * @param processingEnv {@link ProcessingEnvironment} + * @return Module Release Environment 数据 + */ + private static Element _getModuleReleaseEnvironment( + final Element moduleElement, + final ProcessingEnvironment processingEnv + ) + throws Exception { + Element environmentElement = null; + List allMembers = processingEnv.getElementUtils().getAllMembers((TypeElement) moduleElement); + for (Element member : allMembers) { + Environment environmentAnnotation = member.getAnnotation(Environment.class); + if (environmentAnnotation == null) continue; + + if (environmentAnnotation.isRelease()) { + if (environmentElement != null) { // 每个 Module 只能有一个 release 环境配置 + String moduleName = moduleElement.getSimpleName().toString(); + throw new Exception(moduleName + " module can be only one release environment configuration ( 每个 Module 只能有一个 release 环境配置 )"); + } + environmentElement = member; + } + } + if (environmentElement == null) { // 每个 Module 必须有一个 release 环境配置 + String moduleName = moduleElement.getSimpleName().toString(); + throw new Exception(moduleName + " module must have a release environment configuration ( 每个 Module 必须有一个 release 环境配置 )"); + } + return environmentElement; + } + + /** + * 获取 List Type + * @param type Bean.class + * @return List Type + */ + private static Type _getListType(final Class type) { + return new ParameterizedTypeImpl(new Type[]{type}, null, List.class); + } + + /** + * 获取 Module 拼接变量名 + * @param moduleName Module Name + * @return Module 拼接变量名 + */ + private static String _getModuleVarName_UpperCase(final String moduleName) { + return VAR_MODULE_PREFIX + moduleName.toUpperCase(); + } + + /** + * 获取 Environment 拼接变量名 + * @param moduleName Module Name + * @param environmentName Environment Name + * @return Environment 拼接变量名 + */ + private static String _getEnvironmentVarName_UpperCase( + final String moduleName, + final String environmentName + ) { + return VAR_ENVIRONMENT_PREFIX + moduleName.toUpperCase() + "_" + environmentName.toUpperCase(); + } +} \ No newline at end of file diff --git a/lib/Environment/README.md b/lib/Environment/README.md new file mode 100644 index 0000000000..927e87789b --- /dev/null +++ b/lib/Environment/README.md @@ -0,0 +1,345 @@ + + +## DevEnvironment + +DevEnvironment 是一个 Android 环境配置切换库,运用 Java 注解、APT、反射等原理实现一键切换环境的工具库 + + +### 该库解决什么问题 + +* App 在开发、测试、预生产、线上等阶段需要切换不同环境配置 + +* 同一个 App 中的不同模块,在同一阶段需要配置不同的环境 + +* 某些功能只能在指定环境下使用 + +* 需支持通过后台数据动态设置配置信息 + +* 环境配置在代码中写死,导致每次修改环境之后代码管理工具 ( Git、SVN ) 都会提示代码改动,一旦疏忽就提交了,这对于代码管理是不严谨的 + + +### DevEnvironment 库亮点 + +* 使用简单、且无需重新打包即可一键切换环境 + +* 支持模块化配置与切换环境 + +* 支持通过后台数据动态设置配置信息 + +* 支持添加切换环境配置回调 + +* 使用 Java 注解编译时生成 DevEnvironment 类,内部实现 `切换` `保存` `读取` 环境的逻辑代码 + + +### 为什么不用 Gradle + +| 比较内容 | DevEnvironment | Gradle Application Id 不同 | Gradle Application Id 相同 | +|:-:|:--:|:--:|:--:| +| 运行时切换环境 | :heavy_check_mark: | :x: | :x: | +| 切换环境回调 | :heavy_check_mark: | :x: | :x: | +| 切换环境逻辑 | 自动生成 | 需要自己实现 | 需要自己实现 | +| N 套环境打包数量 | 1 个 | N 个 | N 个 | +| 多套环境同时安装 | :heavy_check_mark: | :heavy_check_mark: | :x: | +| 支付等 SDK 包名校验 | :heavy_check_mark: | :x: | :heavy_check_mark: | +| 多模块环境配置 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| 测试环境不泄露 | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | +| …… | —— | —— | —— | + + +### 最新版本 + +module | DevEnvironment | DevEnvironmentCompiler | DevEnvironmentCompilerRelease +:---:|:---:|:---:|:---: +version | [![][maven_svg]][maven] | [![][maven_svg]][maven] | [![][maven_svg]][maven] + + +### Gradle + +```groovy +dependencies { + // Java + implementation 'io.github.afkt:DevEnvironment:1.1.2' + debugAnnotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.2' + releaseAnnotationProcessor 'io.github.afkt:DevEnvironmentCompilerRelease:1.1.2' + // 如果需要 Release 包,支持通过后台数据动态设置配置信息 则使用 debug compiler lib + // annotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.2' + + // Kotlin + implementation 'io.github.afkt:DevEnvironment:1.1.2' + kaptDebug 'io.github.afkt:DevEnvironmentCompiler:1.1.2' + kaptRelease 'io.github.afkt:DevEnvironmentCompilerRelease:1.1.2' + // 如果需要 Release 包,支持通过后台数据动态设置配置信息 则使用 debug compiler lib + // kapt 'io.github.afkt:DevEnvironmentCompiler:1.1.2' +} +``` + + +### 构建 DevEnvironment 类 + +> 点击菜单栏中的 “Build” -> “Rebuild Project”,等待编译完成 + +正常会在 `/build/generated/ap_generated_sources/debug/out/dev/environment` 内创建 DevEnvironment.java + + +-------- + + +### API + +* **环境配置工具类 DevEnvironment.java** + +| 方法 | 注释 | +| :- | :- | +| isRelease | 是否使用 releaseAnnotationProcessor 构建 | +| getModuleList | 获取全部 ModuleBean 配置列表 | +| addOnEnvironmentChangeListener | 添加模块环境改变触发事件 | +| removeOnEnvironmentChangeListener | 移除模块环境改变触发事件 | +| clearOnEnvironmentChangeListener | 清空模块环境改变触发事件 | +| reset | 重置操作 | +| getIMModule | 获取 IM [ Module ] Bean | +| getIMReleaseEnvironment | 获取 IM [ Module ] Release Environment Bean | +| getIMEnvironment | 获取 IM [ Module ] Selected Environment Bean | +| getIMEnvironmentValue | 获取 IM [ Module ] Selected Environment Value | +| setIMEnvironment | 设置 IM [ Module ] Selected Environment Bean | +| resetIM | 重置 IM [ Module ] Selected Environment Bean | +| isIMAnnotation | 是否 IM [ Module ] Annotation Environment Bean | + +```java +// 底部五个方法中 IM 属于 Module Name 例: +@Module(alias = "IM 模块") +private class IM { +} +``` + + +### 使用示例 + +```java +public final class Config { + + @Module(alias = "服务器请求地址") + private class Service { + + @Environment(value = "https://www.wanandroid.com/", isRelease = true, alias = "线上环境") + private String release; + + @Environment(value = "https://debug.com", alias = "测试环境") + private String debug; + + @Environment(value = "https://pre_release.com", alias = "预发布环境") + private String pre_release; + + @Environment(value = "https://development.com", alias = "开发环境") + private String development; + } + + @Module(alias = "开关") + private class Switch { + + @Environment(value = "true", isRelease = true) + private String open; + + @Environment(value = "false") + private String close; + } + + @Module(alias = "IM 模块") + private class IM { + + @Environment(value = "https://im.release.com/", isRelease = true, alias = "线上环境") + private String release; + + @Environment(value = "https://im.debug.com", alias = "测试环境") + private String debug; + } +} +``` + +* **@Module** + +被 `@Module` 修饰的类或接口表示一个模块,每个 `@Module` 有 n ( n > 0 ) 个被 `@Environment` 修饰的属性,表示该模块中有 n 种环境配置 + +* **@Environment** + +被 `@Environment` 修饰的属性表示一个环境,必须指定 **value** 属性值,此外还有两个可选属性:**isRelease** 和 **alias** + +`value`:该环境配置值 + +`isRelease`:是一个 Boolean 型的属性,默认为 false,当值为 true 时,它就是所在 `@Module` 的默认环境 + +> 每个 `@Module` **必须有且只有一个** `isRelease` 值为 true 的 `@Environment` + + +### 项目类结构 - [包目录][包目录] + +* Module 类([@Module][@Module]):模块 ( 注解标记类 ) + +* Environment 类([@Environment][@Environment]):环境配置 ( 注解标记类 ) + +* 模块环境改变接口([OnEnvironmentChangeListener][OnEnvironmentChangeListener]):模块环境发生变化时触发 + +> 每个 `@Module`、`@Environment` 都会生成对应的 **ModuleBean**、**EnvironmentBean** 实体类 + +* @Module 映射实体类([ModuleBean][ModuleBean]):@Module ( 注解标记类 ) 映射实体类 + +* @Environment 映射实体类([EnvironmentBean][EnvironmentBean]):@Environment ( 注解标记类 ) 映射实体类 + + +-------- + + +### DevEnvironmentCompiler、DevEnvironmentCompilerRelease 区别 + +* DevEnvironmentCompiler 属于 Debug ( 打包 / 编译 ) 注解处理器,使用该注解处理时生成的 DevEnvironment 允许设置选中的环境 ( `setXXEnvironment` 通过该方法设置,只有使用该注解处理才会实现该方法代码 ) + + 1. `getXXModule` 获取对应 Module 映射实体类 ModuleBean + + 2. `getXXReleaseEnvironment` 获取对应 Module isRelease 值为 true 的 Environment 映射实体类 EnvironmentBean + + 3. `getXXEnvironment` 获取对应 Module 选中的 Environment ( 默认选中 isRelease 值为 true 的 `@Environment` ) + + 4. `getXXEnvironmentValue` 获取对应 Module 选中的 Environment Value + + 5. `setXXEnvironment` 设置对应 Module 选中的 Environment + + 6. `resetXX` 用于删除对应 Module 选中的 Environment Config File + + 7. `isXXAnnotation` 用于判断对应 Module 选中的 Environment 是否属于注解环境配置 + +* DevEnvironmentCompilerRelease 属于 Release ( 打包 / 编译 ) 注解处理器,使用该注解处理时生成的 DevEnvironment 每个 Module 只会生成一个常量 Environment,并且无法进行修改设置 + + 1. `getXXModule` 获取对应 Module 映射实体类 ModuleBean + + 2. `getXXReleaseEnvironment` 获取对应 Module isRelease 值为 true 的 Environment 映射实体类 EnvironmentBean + + 3. `getXXEnvironment` 内部调用 `getXXReleaseEnvironment` + + 4. `getXXEnvironmentValue` 内部调用 `getXXEnvironment` 获取 Value + + 5. `setXXEnvironment` 内部不实现代码,直接返回 false ( 表示不支持设置 ) + + 6. `resetXX` 内部不实现代码,直接返回 false + + 7. `isXXAnnotation` 内部不实现代码,直接返回 true + +> DevEnvironmentCompilerRelease 编译生成的 DevEnvironment 类,全部属于 final 无法进行修改、设置,且部分方法内部不进行代码实现 + +> 而 DevEnvironmentCompiler 编译生成的 DevEnvironment 类,允许修改选中的 Environment 支持可视化切换、代码方式切换 +> +> 无特殊需求一般用于 debugAnnotationProcessor DevEnvironmentCompiler +> +> 如果需要 Release 下可切换环境则使用 annotationProcessor DevEnvironmentCompiler + + +-------- + + +### 切换环境方式 + +示例:[DevEnvironmentLibActivity][DevEnvironmentLibActivity] + +> 注:使用 DevEnvironmentCompilerRelease 注解编译生成不支持环境配置切换 + +1. 通过代码方式设置 setXXEnvironment + +```java +// 如果准备设置环境等于当前选中的环境,则会返回 false +EnvironmentBean custom = new EnvironmentBean("自定义配置", + "https://custom.com", "动态自定义", DevEnvironment.getServiceModule()); +boolean result = DevEnvironment.setServiceEnvironment(mContext, custom); +``` + +2. 通过可视化界面方式切换 + +```java +// 显示右上角重启按钮 +boolean result = DevEnvironmentActivity.start(mContext, new RestartCallback() { + @Override + public void onRestart() { + ActivityUtils.getManager().exitApplication(); + } +}); +// 不显示右上角重启按钮 +boolean result = DevEnvironmentActivity.start(mContext); +``` + + +### 环境切换监听事件 + +```java +// 添加环境改变监听事件 +DevEnvironment.addOnEnvironmentChangeListener(new OnEnvironmentChangeListener() { + + /** + * 模块环境发生变化时触发 + * @param module 环境发生变化的模块 + * @param oldEnvironment 该模块的旧环境 + * @param newEnvironment 该模块的最新环境 + */ + @Override + public void onEnvironmentChanged(ModuleBean module, EnvironmentBean oldEnvironment, + EnvironmentBean newEnvironment) { + } +}); +// 移除环境改变监听事件 +DevEnvironment.removeOnEnvironmentChangeListener(listener); +// 移除全部环境改变监听事件 +DevEnvironment.clearOnEnvironmentChangeListener(); +``` + + +### 获取 Module + +```java +// 有多少个 @Module 修饰,则会生成多少个 getXXModule 方法 +ModuleBean serviceModule = DevEnvironment.getServiceModule(); +ModuleBean switchModule = DevEnvironment.getSwitchModule(); +ModuleBean imModule = DevEnvironment.getIMModule(); +``` + + +### 获取 Module Environment + +```java +// getXXReleaseEnvironment 该方法返回 isRelease 值为 true 的 Environment ( 必须有且只有一个 ) +// 而 getXXEnvironment 获取的为当前 Module 选中的 Environment,可通过 setXXEnvironment 进行修改 + +EnvironmentBean serviceReleaseEnvironment = DevEnvironment.getServiceReleaseEnvironment(); +EnvironmentBean serviceEnvironment = DevEnvironment.getServiceEnvironment(mContext); + +EnvironmentBean switchReleaseEnvironment = DevEnvironment.getSwitchReleaseEnvironment(); +EnvironmentBean switchEnvironment = DevEnvironment.getSwitchEnvironment(mContext); + +EnvironmentBean imReleaseEnvironment = DevEnvironment.getIMReleaseEnvironment(); +EnvironmentBean imEnvironment = DevEnvironment.getIMEnvironment(mContext); +``` + + +### 实现原理 + +同 Butterknife、Greendao 等第三方库,通过编译时注解 ( APT 技术 ) 实现,具体可参考该库实现代码及 [link.mk][link.mk] 技术链接 + + +### 示例参考 + +DevEnvironment 文件生成配置:[HttpConstants][HttpConstants] + +DevEnvironment 使用:[DevEnvironmentLibActivity][DevEnvironmentLibActivity] + +> 点击菜单栏中的 “Build” -> “Rebuild Project”,等待编译完成 + + + + + +[maven_svg]: https://img.shields.io/badge/Maven-1.1.2-brightgreen.svg +[maven]: https://search.maven.org/search?q=io.github.afkt +[包目录]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment +[@Module]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Module.java +[@Environment]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/annotation/Environment.java +[OnEnvironmentChangeListener]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/listener/OnEnvironmentChangeListener.java +[ModuleBean]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/ModuleBean.java +[EnvironmentBean]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/DevEnvironmentBase/src/main/java/dev/environment/bean/EnvironmentBean.java +[DevEnvironmentLibActivity]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/feature/dev_environment/DevEnvironmentLibActivity.kt +[HttpConstants]: https://github.com/afkT/DevUtils/blob/master/application/DevUtilsApp/src/main/java/afkt/project/base/http/HttpConstants.kt +[link.mk]: https://github.com/afkT/DevUtils/blob/master/lib/Environment/link.md \ No newline at end of file diff --git a/lib/Environment/link.md b/lib/Environment/link.md new file mode 100644 index 0000000000..066cd1f72b --- /dev/null +++ b/lib/Environment/link.md @@ -0,0 +1,18 @@ +# Link + +* [Android 注解与 APT 技术](https://www.jianshu.com/p/7454a933dcaf) + +* [JavaPoet 初体验之手动实现依赖注入](https://blog.csdn.net/hq942845204/article/details/81185693) + +* [JavaPoet 简介](https://blog.csdn.net/Viiou/article/details/86388268) + +* [Java AbstractProcessor 实现自定义 ButterKnife](https://blog.csdn.net/kaifa1321/article/details/79683246) + +* [Android 打包之多版本、多环境、多渠道](https://www.jianshu.com/p/872dc6f89cb4) + +* [annotation processor 为啥没有被调用?](https://www.cnblogs.com/mengshu-lbq/p/11713397.html) + +* [Gradle Annotation Processor](https://docs.gradle.org/5.4.1/userguide/java_plugin.html#making_an_annotation_processor_incremental) + +* [自定义注解生成代码之 JavaPoet API 详解](https://blog.csdn.net/bencheng06/article/details/103220774) + diff --git a/lib/HttpCapture/CHANGELOG.md b/lib/HttpCapture/CHANGELOG.md new file mode 100644 index 0000000000..dbf3e0e205 --- /dev/null +++ b/lib/HttpCapture/CHANGELOG.md @@ -0,0 +1,66 @@ +Change Log +========== + +Version 1.1.3 *(2022-09-18)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +* `[Refactor]` 使用 Kotlin 重新编写该库代码 + +* `[Refactor]` 统一可视化页面 UI 样式 + +Version 1.1.2 *(2022-07-04)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.1.1 *(2022-05-13)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.1.0 *(2022-03-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.9 *(2022-02-13)* +---------------------------- + +* `[Update]` 新增抓包列表排序处理 + +Version 1.0.8 *(2022-01-23)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.7 *(2022-01-10)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.6 *(2021-12-30)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.5 *(2021-12-20)* +---------------------------- + +* `[Chore]` 依赖 DevApp、DevAssist 库同步升级 + +Version 1.0.4 *(2021-11-26)* +---------------------------- + +* `[Update]` 修改原本 Activity 管理代码使用 ActivityManagerAssist 进行管理控制 + +Version 1.0.3 *(2021-10-11)* +---------------------------- + +* `[Update]` 修改存储间隔以 10 分钟为单位 + +Version 1.0.2 *(2021-10-06)* +---------------------------- + +* Initial release diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/.gitignore b/lib/HttpCapture/DevHttpCaptureCompiler/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/build.gradle b/lib/HttpCapture/DevHttpCaptureCompiler/build.gradle new file mode 100644 index 0000000000..29880000cf --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/build.gradle @@ -0,0 +1,33 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) +apply from: rootProject.file(files.unified_use_view_binding_gradle) + +android { + defaultConfig { + versionCode versions.dev_http_capture_versionCode + versionName versions.dev_http_capture_compiler_version + } +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_assist + api deps.dev.dev_http_capture + } else { + // 编译时使用 + api project(':DevAssist') + api project(':DevHttpCapture') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/proguard-rules.pro b/lib/HttpCapture/DevHttpCaptureCompiler/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/project.properties b/lib/HttpCapture/DevHttpCaptureCompiler/project.properties new file mode 100644 index 0000000000..0d10e459fc --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevHttpCaptureCompiler +project.groupId=io.github.afkt +project.artifactId=DevHttpCaptureCompiler +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevHttpCaptureCompiler \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/AndroidManifest.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..fa9f7db34e --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/DevHttpCaptureCompiler.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/DevHttpCaptureCompiler.kt new file mode 100644 index 0000000000..a1b3d51d1c --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/DevHttpCaptureCompiler.kt @@ -0,0 +1,116 @@ +package dev + +import android.content.Context +import android.content.Intent +import dev.capture.UrlFunctionGet +import dev.capture.UtilsCompiler +import dev.capture.activity.DevHttpCaptureMainActivity +import dev.utils.DevFinal +import dev.utils.LogPrintUtils + +/** + * detail: OkHttp 抓包工具库 + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevHttpCaptureCompiler { + + // 日志 TAG + private val TAG = DevHttpCaptureCompiler::class.java.simpleName + + // ============= + // = 对外提供方法 = + // ============= + + /** + * 结束所有 Activity + */ + fun finishAllActivity() { + UtilsCompiler.finishAllActivity() + } + + // ========== + // = 跳转方法 = + // ========== + + /** + * 跳转抓包数据可视化 Activity + * @param context [Context] + * @return `true` success, `false` fail + */ + fun start(context: Context?): Boolean { + return start(context, "") + } + + /** + * 跳转抓包数据可视化 Activity + * @param context [Context] + * @param moduleName 模块名 ( 要求唯一性 ) + * @return `true` success, `false` fail + */ + fun start( + context: Context?, + moduleName: String + ): Boolean { + // 关闭全部页面 + finishAllActivity() + try { + val intent = Intent(context, DevHttpCaptureMainActivity::class.java) + intent.putExtra(DevFinal.STR.MODULE, moduleName) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context!!.startActivity(intent) + return true + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "start by module: %s", moduleName) + } + return false + } + + /** + * 添加接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param function 接口所属功能注释获取 + */ + fun putUrlFunction( + moduleName: String, + function: UrlFunctionGet + ) { + UtilsCompiler.putUrlFunction( + moduleName, function + ) + } + + /** + * 移除接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + */ + fun removeUrlFunction(moduleName: String) { + UtilsCompiler.removeUrlFunction(moduleName) + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UrlFunctionGet.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UrlFunctionGet.kt new file mode 100644 index 0000000000..f8ae1db7bd --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UrlFunctionGet.kt @@ -0,0 +1,25 @@ +package dev.capture + +/** + * detail: 接口所属功能注释获取 + * @author Ttt + */ +interface UrlFunctionGet { + + /** + * 接口所属功能注释获取 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param url 原始请求链接 + * @param method 请求方法 + * @param convertUrlKey url 匹配规则 ( 拆分 ? 前为 key 进行匹配 ) + * @param cacheFunction 缓存功能注释 + * @return 接口所属功能注释 + */ + fun toUrlFunction( + moduleName: String, + url: String, + method: String, + convertUrlKey: String?, + cacheFunction: String? + ): String? +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UtilsCompiler.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UtilsCompiler.kt new file mode 100644 index 0000000000..4eeff25c9c --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UtilsCompiler.kt @@ -0,0 +1,576 @@ +package dev.capture + +import android.app.Activity +import com.google.gson.GsonBuilder +import com.google.gson.JsonParser +import com.google.gson.stream.JsonReader +import dev.DevHttpCapture +import dev.callback.DevCallback +import dev.capture.compiler.R +import dev.capture.model.Items +import dev.utils.LogPrintUtils +import dev.utils.app.ClickUtils.ClickAssist +import dev.utils.app.HandlerUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.assist.ActivityManagerAssist +import dev.utils.common.CollectionUtils +import dev.utils.common.MapUtils +import dev.utils.common.StringUtils +import dev.utils.common.comparator.sort.WindowsExplorerStringSimpleComparator +import java.io.StringReader +import java.util.concurrent.CopyOnWriteArrayList + +internal object UtilsCompiler { + + // 日志 TAG + private val TAG = UtilsCompiler::class.java.simpleName + + // =================== + // = Activity 管理控制 = + // =================== + + // ActivityManagerAssist 实例 + private val mManager = ActivityManagerAssist() + + /** + * 添加 Activity + * @param activity [Activity] + */ + fun addActivity(activity: Activity?) { + mManager.addActivity(activity) + } + + /** + * 移除 Activity + * @param activity [Activity] + */ + fun removeActivity(activity: Activity?) { + mManager.removeActivity(activity) + } + + /** + * 结束所有 Activity + */ + fun finishAllActivity() { + mManager.finishAllActivity() + } + + // ======== + // = GSON = + // ======== + + // JSON 字符串转 T Object + private val FROM_GSON = createGson().create() + + // JSON 缩进 + private val INDENT_GSON = createGson().setPrettyPrinting().create() + + /** + * 创建 GsonBuilder + * @return [GsonBuilder] + */ + private fun createGson(): GsonBuilder { + return GsonBuilder().serializeNulls() + } + + /** + * JSON String 缩进处理 + * @param json JSON String + * @return JSON String + */ + private fun toJsonIndent(json: String?): String? { + try { + val reader = JsonReader(StringReader(json)) + reader.isLenient = true + val jsonElement = JsonParser.parseReader(reader) + return INDENT_GSON.toJson(jsonElement) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "toJsonIndent") + } + return null + } + + /** + * 将 JSON String 映射为指定类型对象 + * @param json JSON String + * @param classOfT [Class] T + * @param 泛型 + * @return instance of type + */ + private fun fromJson( + json: String?, + classOfT: Class? + ): T? { + try { + return FROM_GSON.fromJson(json, classOfT) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "fromJson") + } + return null + } + + // ============== + // = 接口所属功能 = + // ============== + + // key = moduleName, value = 接口所属功能注释获取 + private val URL_FUNCTION_MAP = linkedMapOf() + + /** + * 添加接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param function 接口所属功能注释获取 + */ + fun putUrlFunction( + moduleName: String, + function: UrlFunctionGet + ) { + if (StringUtils.isEmpty(moduleName)) return + URL_FUNCTION_MAP[moduleName] = function + } + + /** + * 移除接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + */ + fun removeUrlFunction(moduleName: String) { + if (StringUtils.isEmpty(moduleName)) return + URL_FUNCTION_MAP.remove(moduleName) + } + + /** + * 获取接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 接口所属功能注释获取 + */ + fun getUrlFunction(moduleName: String): UrlFunctionGet? { + if (StringUtils.isEmpty(moduleName)) return null + return URL_FUNCTION_MAP[moduleName] + } + + // ============ + // = Callback = + // ============ + + // 监听回调 + private val mCallbackLists = CopyOnWriteArrayList>() + + /** + * 移除所有回调 + */ + fun clearCallback() { + mCallbackLists.clear() + } + + /** + * 移除回调 ( 关闭页面调用 ) + * @param callback 回调事件 + */ + fun removeCallback(callback: DevCallback) { + mCallbackLists.remove(callback) + } + + /** + * 添加回调 + * @param callback 回调事件 + */ + fun addCallback(callback: DevCallback) { + if (mCallbackLists.contains(callback)) return + mCallbackLists.add(callback) + } + + /** + * 通知回调 + * @param isQuerying 是否查询中 + * @param size 数据数量 + */ + fun notifyCallback( + isQuerying: Boolean, + size: Int + ) { + for (callback in mCallbackLists) { + HandlerUtils.postRunnable { + try { + callback.callback(isQuerying, size) + } catch (ignored: Exception) { + } + } + } + } + + // ========== + // = 数据获取 = + // ========== + + // 是否查询中 + private var mQuerying = false + + // 数据源 + private val mDataMaps = linkedMapOf>() + + /** + * 查询数据 + * @param callback 回调事件 + * @param isRefresh 是否刷新操作 + */ + fun queryData( + callback: DevCallback, + isRefresh: Boolean + ) { + addCallback(callback) + val size = mDataMaps.size + if (mQuerying) { + notifyCallback(true, size) + return + } + // 如果存在数据且非刷新操作表示需要获取数据 + if (size != 0 && !isRefresh) { + notifyCallback(false, size) + return + } + mQuerying = true + // 触发通知表示查询中 + notifyCallback(true, size) + // 后台读取数据 + Thread { + val maps = DevHttpCapture.utils().getAllModule(false) + mDataMaps.clear() + mDataMaps.putAll(maps) + mQuerying = false + notifyCallback(false, mDataMaps.size) + clearCallback() + }.start() + } + + /** + * 移除所有数据 + */ + fun clearData() { + mDataMaps.clear() + } + + /** + * 是否查询中 + * @return `true` yes, `false` no + */ + fun isQuerying(): Boolean { + return mQuerying + } + + // ========== + // = 数据转换 = + // ========== + + // Windows 目录资源文件名排序比较器 + private val COMPARATOR = WindowsExplorerStringSimpleComparator() + + /** + * 获取首页数据源 + * @param moduleName 模块名 ( 要求唯一性 ) + * @return 首页数据源 + */ + fun getMainData(moduleName: String): List { + val lists = mutableListOf() + // 判断是否显示指定模块 + if (StringUtils.isEmpty(moduleName)) { + mDataMaps.forEach { entry -> + if (CollectionUtils.isNotEmpty(entry.value)) { + lists.add(Items.MainItem(entry.key, entry.value)) + } + } + } else { + mDataMaps[moduleName]?.let { + if (CollectionUtils.isNotEmpty(it)) { + lists.add(Items.MainItem(moduleName, it)) + } + } + } + // 进行排序 + lists.sortWith { o1, o2 -> COMPARATOR.compare(o1.moduleName, o2.moduleName) } + return lists + } + + /** + * 通过时间 ( yyyyMMdd ) 获取抓包存储 Item + * @param moduleName 模块名 ( 要求唯一性 ) + * @param date yyyyMMdd + * @return 抓包存储 Item + */ + private fun getCaptureItemByDate( + moduleName: String, + date: String + ): CaptureItem? { + if (StringUtils.isNotEmpty(date)) { + mDataMaps[moduleName]?.let { list -> + if (CollectionUtils.isNotEmpty(list)) { + for (item in list) { + if (date == item.yyyyMMdd) { + return item + } + } + } + } + } + return null + } + + /** + * 获取抓包文件数据 + * @param json 抓包文件 JSON 格式数据 + * @return 抓包文件数据 + */ + fun getFileData(json: String?): List { + val lists = mutableListOf() + val captureFile = fromJson(json, CaptureFile::class.java) + if (captureFile != null) { + captureFile.getCaptureInfo()?.let { captureInfo -> + // 接口所属功能 + val function = getUrlFunctionByInfo(captureFile, captureInfo) + if (StringUtils.isNotEmpty(function)) { + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_url_function), + function ?: "" + ) + ) + } + + // 请求链接 + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_request_url), + captureInfo.requestUrl + ) + ) + + // 请求方法 + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_request_method), + captureInfo.requestMethod + ) + ) + + // 请求头信息 + val requestHeader = mapToString(captureInfo.requestHeader).toString() + if (StringUtils.isNotEmpty(requestHeader)) { + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_request_header), + requestHeader + ) + ) + } + + // 请求数据 + val requestBody = mapToString(captureInfo.requestBody).toString() + if (StringUtils.isNotEmpty(requestBody)) { + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_request_body), + requestBody + ) + ) + } + + // 响应状态 + val responseStatus = mapToString(captureInfo.responseStatus).toString() + if (StringUtils.isNotEmpty(responseStatus)) { + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_response_status), + responseStatus + ) + ) + } + + // 响应头信息 + val responseHeader = mapToString(captureInfo.responseHeader).toString() + if (StringUtils.isNotEmpty(responseHeader)) { + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_response_header), + responseHeader + ) + ) + } + + // 响应数据 + lists.add( + Items.FileItem( + ResourceUtils.getString(R.string.dev_http_capture_response_body), + toJsonIndent(captureInfo.responseBody) ?: "" + ) + ) + } + } + return lists + } + + /** + * 获取对应时间 ( yyyyMMdd ) 指定筛选条件抓包列表数据 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param date yyyyMMdd + * @param dataType 数据来源类型 + * @param groupType 分组条件类型 + * @return 指定筛选条件抓包列表数据 + */ + fun getDateData( + moduleName: String, + date: String, + dataType: Items.DataType, + groupType: Items.GroupType + ): List { + val dataLists = mutableListOf() + // 通过时间 ( yyyyMMdd ) 获取抓包存储 Item + getCaptureItemByDate(moduleName, date)?.let { captureItem -> + val tempMaps = linkedMapOf>() + if (dataType === Items.DataType.T_ALL) { + // 以全部数据为展示 + tempMaps.putAll(captureItem.data) + } else { + // 以指定时间数据为展示 + captureItem.data.forEach { entry -> + if (CollectionUtils.isNotEmpty(entry.value)) { + if (Items.convertDataType(entry.key) === dataType) { + tempMaps[entry.key] = entry.value + } + } + } + } + // 以时间为分组 ( 展开条标题 ) + if (groupType == Items.GroupType.T_TIME) { + tempMaps.forEach { entry -> + dataLists.add(Items.GroupItem(entry.key, entry.value)) + } + } else { + // 以请求链接为分组 ( 展开条标题 ) + val urlMaps = linkedMapOf>() + tempMaps.values.forEach { lists -> + for (captureFile in lists) { + val urlKey = Items.convertUrlKey(captureFile.getUrl()) + MapUtils.putToList(urlMaps, urlKey, captureFile) + } + } + urlMaps.forEach { entry -> + val captureFile = entry.value[0] + val function = getUrlFunctionByFile(captureFile, entry.key) + val item = Items.GroupItem(entry.key, entry.value) + item.setFunction(function) + dataLists.add(item) + } + } + } + return dataLists + } + + // ============= + // = 接口所属功能 = + // ============= + + // 接口所属功能注释缓存 + private val mFunctionCacheMaps = mutableMapOf() + + /** + * 获取接口所属功能 + * @param captureFile 抓包存储文件 + * @param captureInfo 抓包数据 + * @return 接口所属功能 + */ + private fun getUrlFunctionByInfo( + captureFile: CaptureFile, + captureInfo: CaptureInfo + ): String? { + // 接口所属功能 + return getUrlFunction(captureFile.getModuleName())?.let { + val convertUrlKey = Items.convertUrlKey(captureInfo.requestUrl) + val cacheFunction = mFunctionCacheMaps[convertUrlKey] + val function = it.toUrlFunction( + captureFile.getModuleName(), captureFile.getUrl(), + captureFile.getMethod(), convertUrlKey, cacheFunction + ) + // 两个值不同才进行变更 + if (!StringUtils.equalsNotNull(cacheFunction, function)) { + if (convertUrlKey != null && function != null) { + mFunctionCacheMaps[convertUrlKey] = function + } + } + return function + } + } + + /** + * 获取接口所属功能 + * @param captureFile 抓包存储文件 + * @return 接口所属功能 + */ + fun getUrlFunctionByFile(captureFile: CaptureFile?): String? { + if (captureFile != null) { + val convertUrlKey = Items.convertUrlKey(captureFile.getUrl()) + return getUrlFunctionByFile(captureFile, convertUrlKey) + } + return null + } + + /** + * 获取接口所属功能 + * @param captureFile 抓包存储文件 + * @param convertUrlKey 拆分 Url 用于匹配接口所属功能注释 + * @return 接口所属功能 + */ + private fun getUrlFunctionByFile( + captureFile: CaptureFile?, + convertUrlKey: String? + ): String? { + if (captureFile != null) { + // 接口所属功能 + return getUrlFunction( + captureFile.getModuleName() + )?.let { + val cacheFunction = mFunctionCacheMaps[convertUrlKey] + val function = it.toUrlFunction( + captureFile.getModuleName(), captureFile.getUrl(), + captureFile.getMethod(), convertUrlKey, cacheFunction + ) + // 两个值不同才进行变更 + if (!StringUtils.equalsNotNull(cacheFunction, function)) { + if (convertUrlKey != null && function != null) { + mFunctionCacheMaps[convertUrlKey] = function + } + } + return function + } + } + return null + } + + // ========== + // = 刷新处理 = + // ========== + + // 刷新点击 ( 双击 ) 辅助类 + val REFRESH_CLICK = ClickAssist(10000L) + + /** + * 重置刷新点击处理 + */ + fun resetRefreshClick() { + REFRESH_CLICK.reset().setIntervalTime(10000L) + } + + // ============ + // = MapUtils = + // ============ + + /** + * 键值对拼接 + * @param map [Map] + * @return [StringBuilder] + */ + private fun mapToString(map: LinkedHashMap): StringBuilder { +// map.forEach { +// map[it.key] = toJsonIndent(it.value) ?: it.value +// } + return MapUtils.mapToString(map, ": ") + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureFileActivity.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureFileActivity.kt new file mode 100644 index 0000000000..3e18579420 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureFileActivity.kt @@ -0,0 +1,70 @@ +package dev.capture.activity + +import android.content.Intent +import android.os.Bundle +import dev.capture.UtilsCompiler +import dev.capture.adapter.AdapterCaptureFile +import dev.capture.base.BaseDevHttpActivity +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureFileActivityBinding +import dev.utils.DevFinal +import dev.utils.app.BarUtils +import dev.utils.app.ResourceUtils + +/** + * detail: DevHttpCapture 抓包数据详情页 + * @author Ttt + */ +class DevHttpCaptureFileActivity : BaseDevHttpActivity() { + + // 首页适配器 + private val mAdapter = AdapterCaptureFile() + + override fun createBinding(): DevHttpCaptureFileActivityBinding { + return DevHttpCaptureFileActivityBinding.inflate( + layoutInflater + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + // 设置状态栏颜色 + BarUtils.setStatusBarColor( + this, ResourceUtils.getColor(R.color.dev_http_capture_include_title_bg_color) + ) + // 初始化数据 + initValue(intent) + } + + override fun finishOperate() { + // 关闭当前页面 + finish() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化数据 + * @param intent Intent + */ + private fun initValue(intent: Intent) { + // 获取抓包文件 + val json = intent.getStringExtra(DevFinal.STR.JSON) + + // 设置点击事件 + binding.vidTitleInclude.vidBackIv.setOnClickListener { finishOperate() } + // 设置标题 + binding.vidTitleInclude.vidTitleTv.setText(R.string.dev_http_capture_details_title) + // 绑定适配器 + mAdapter.bindAdapter(binding.vidRv) + // 设置数据源 + binding.vidRv.post { + if (!isFinishing) { + mAdapter.setDataList(UtilsCompiler.getFileData(json)) + } + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureListActivity.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureListActivity.kt new file mode 100644 index 0000000000..c314eac007 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureListActivity.kt @@ -0,0 +1,264 @@ +package dev.capture.activity + +import android.content.Intent +import android.os.Bundle +import android.view.View +import dev.callback.DevCallback +import dev.capture.UtilsCompiler +import dev.capture.adapter.AdapterDateModuleList +import dev.capture.base.BaseDevHttpActivity +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureListActivityBinding +import dev.capture.model.Dialogs.DataTypeDialog +import dev.capture.model.Dialogs.GroupTypeDialog +import dev.capture.model.Items +import dev.capture.model.Items.GroupType +import dev.utils.DevFinal +import dev.utils.app.BarUtils +import dev.utils.app.ClickUtils.ClickAssist +import dev.utils.app.ClickUtils.OnDebouncingClickListener +import dev.utils.app.DialogUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.view.ViewHelper +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: DevHttpCapture 抓包数据列表 + * @author Ttt + */ +class DevHttpCaptureListActivity : BaseDevHttpActivity() { + + // 当前选中的 Module + private var mModule: String = "" + + // 当前选中的日期 + private var mDate: String = "" + + // 拼接筛选选项 + private var mOptions: String = "" + + // 数据来源类型 + private var mDataType = Items.DataType.T_ALL + + // 分组类型 + private var mGroupType = GroupType.T_TIME + + // 首页适配器 + private val mAdapter = AdapterDateModuleList() + + // 查询回调 + private val mCallback = object : DevCallback() { + override fun callback( + isQuerying: Boolean, + size: Int + ) { + if (!isFinishing) { + if (isQuerying) { + if (size == 0) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + } + return + } + // 如果和之前的选项不一样, 则清空历史多选数据 + if (getNewOptions() != mOptions) { + mOptions = getNewOptions() + mAdapter.setNotifyAdapter(false) + .clearSelectAll() + } + // 设置数据源 + mAdapter.setDataList( + UtilsCompiler.getDateData( + mModule, mDate, mDataType, mGroupType + ) + ) + // 判断是否存在数据 + ViewUtils.reverseVisibilitys( + mAdapter.isDataNotEmpty, + binding.vidRv, + binding.vidTipsInclude.vidTipsFl + ) + ToastTintUtils.success( + ResourceUtils.getString(R.string.dev_http_capture_query_complete) + ) + // 重置刷新点击处理 + UtilsCompiler.resetRefreshClick() + } + } + } + + override fun createBinding(): DevHttpCaptureListActivityBinding { + return DevHttpCaptureListActivityBinding.inflate( + layoutInflater + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + // 设置状态栏颜色 + BarUtils.setStatusBarColor( + this, ResourceUtils.getColor(R.color.dev_http_capture_include_title_bg_color) + ) + // 初始化数据 + initValue(intent) + } + + override fun finishOperate() { + // 移除回调 + UtilsCompiler.removeCallback(mCallback) + // 关闭当前页面 + finish() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化数据 + * @param intent Intent + */ + private fun initValue(intent: Intent) { + // 获取模块名 + mModule = intent.getStringExtra(DevFinal.STR.MODULE) ?: "" + // 获取时间 + mDate = intent.getStringExtra(DevFinal.STR.DATE) ?: "" + + // 设置点击事件 + binding.vidTitleInclude.vidBackIv.setOnClickListener { finishOperate() } + // 设置标题 + binding.vidTitleInclude.vidTitleTv.text = "$mDate - $mModule" + // 设置提示文案 + binding.vidTipsInclude.vidTipsTv.setText(R.string.dev_http_capture_query_no_data) + // 绑定适配器 + mAdapter.bindAdapter(binding.vidRv) + + // 刷新选项 View 文本 + refreshOptionsText() + // 设置新的拼接筛选选项 + mOptions = getNewOptions() + + // 初始化事件 + initListener() + + // ========== + // = 数据获取 = + // ========== + + queryData() + } + + /** + * 查询数据 + */ + private fun queryData() { + UtilsCompiler.queryData( + mCallback, false + ) + } + + /** + * 获取新的拼接筛选选项 + * @return 拼接筛选选项 + */ + private fun getNewOptions(): String { + return mDataType.type + "-" + mGroupType.type + } + + /** + * 刷新选项 View 文本 + */ + private fun refreshOptionsText() { + ViewHelper.get() + .setText(mDataType.getTitle(), binding.vidTabInclude.vidDataTypeTv) + .setText(mGroupType.getTitle(), binding.vidTabInclude.vidGroupTypeTv) + } + + // ========== + // = 事件处理 = + // ========== + + // 筛选选项点击 ( 双击 ) 辅助类 + private val mOptionsClick = ClickAssist(300L) + + /** + * 初始化事件 + */ + private fun initListener() { + initDialogs() + ViewHelper.get() + .setOnClick(object : OnDebouncingClickListener(mOptionsClick) { + override fun doClick(view: View) { + DialogUtils.closeDialog(mDataTypeDialog) + DialogUtils.showDialog(mDataTypeDialog) + } + }, binding.vidTabInclude.vidDataLl) + .setOnClick(object : OnDebouncingClickListener(mOptionsClick) { + override fun doClick(view: View) { + DialogUtils.closeDialog(mGroupTypeDialog) + DialogUtils.showDialog(mGroupTypeDialog) + } + }, binding.vidTabInclude.vidGroupLl) + .setOnClick(object : OnDebouncingClickListener(UtilsCompiler.REFRESH_CLICK) { + override fun doClick(view: View) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + if (!UtilsCompiler.isQuerying()) { + UtilsCompiler.queryData( + mCallback, true + ) + } + } + + override fun doInvalidClick(view: View) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + } + }, binding.vidRefreshFl) + } + + // ========== + // = 弹窗处理 = + // ========== + + // 数据来源选项 Dialog + private var mDataTypeDialog: DataTypeDialog? = null + + // 分组选项 Dialog + private var mGroupTypeDialog: GroupTypeDialog? = null + + /** + * 初始化 Dialog + */ + private fun initDialogs() { + mDataTypeDialog = DataTypeDialog( + this, object : DevCallback() { + override fun callback(value: Items.DataType) { + if (value !== mDataType) { + mDataType = value + // 刷新选项 View 文本 + refreshOptionsText() + // 查询数据 + queryData() + } + } + }) + mGroupTypeDialog = GroupTypeDialog( + this, object : DevCallback() { + override fun callback(value: GroupType) { + if (value !== mGroupType) { + mGroupType = value + // 刷新选项 View 文本 + refreshOptionsText() + // 查询数据 + queryData() + } + } + }) + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureMainActivity.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureMainActivity.kt new file mode 100644 index 0000000000..223f196271 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/activity/DevHttpCaptureMainActivity.kt @@ -0,0 +1,158 @@ +package dev.capture.activity + +import android.content.Intent +import android.os.Bundle +import android.view.View +import dev.DevHttpCapture +import dev.callback.DevCallback +import dev.capture.UtilsCompiler +import dev.capture.adapter.AdapterMainModule +import dev.capture.base.BaseDevHttpActivity +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureMainActivityBinding +import dev.capture.model.Items.MainItem +import dev.utils.DevFinal +import dev.utils.app.BarUtils +import dev.utils.app.ClickUtils.OnDebouncingClickListener +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.view.ViewHelper +import dev.utils.app.toast.ToastTintUtils +import dev.utils.common.StringUtils + +/** + * detail: DevHttpCapture 入口 + * @author Ttt + */ +class DevHttpCaptureMainActivity : BaseDevHttpActivity() { + + // 当前选中的 Module + private var mModule: String = "" + + // 首页适配器 + private val mAdapter = AdapterMainModule() + + // 查询回调 + private val mCallback = object : DevCallback() { + override fun callback( + isQuerying: Boolean, + size: Int + ) { + if (!isFinishing) { + if (isQuerying) { + if (size == 0) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + } + return + } + // 设置数据源 + mAdapter.setDataList(UtilsCompiler.getMainData(mModule)) + // 判断是否存在数据 + ViewUtils.reverseVisibilitys( + mAdapter.isDataNotEmpty, + binding.vidRv, + binding.vidTipsInclude.vidTipsFl + ) + ToastTintUtils.success( + ResourceUtils.getString(R.string.dev_http_capture_query_complete) + ) + // 重置刷新点击处理 + UtilsCompiler.resetRefreshClick() + } + } + } + + override fun createBinding(): DevHttpCaptureMainActivityBinding { + return DevHttpCaptureMainActivityBinding.inflate( + layoutInflater + ) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + // 设置状态栏颜色 + BarUtils.setStatusBarColor( + this, ResourceUtils.getColor(R.color.dev_http_capture_include_title_bg_color) + ) + // 初始化数据 + initValue(intent) + } + + override fun finishOperate() { + // 移除回调 + UtilsCompiler.clearCallback() + // 清空数据 + UtilsCompiler.clearData() + // 关闭所有页面 + UtilsCompiler.finishAllActivity() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 初始化数据 + * @param intent Intent + */ + private fun initValue(intent: Intent) { + // 移除回调 + UtilsCompiler.clearCallback() + // 清空数据 + UtilsCompiler.clearData() + + // 获取模块名 + mModule = intent.getStringExtra(DevFinal.STR.MODULE) ?: "" + + // 设置点击事件 + binding.vidTitleInclude.vidBackIv.setOnClickListener { finishOperate() } + // 设置标题 + binding.vidTitleInclude.vidTitleTv.text = DevHttpCapture.TAG + // 设置提示文案 + binding.vidTipsInclude.vidTipsTv.setText(R.string.dev_http_capture_query_no_data) + // 绑定适配器 + mAdapter.bindAdapter(binding.vidRv) + // 判断是否选择指定模块 + if (StringUtils.isNotEmpty(mModule)) { + // 默认展开该模块 + mAdapter.multiSelectMap.select( + mModule, MainItem(mModule, ArrayList()) + ) + } + + // ========== + // = 数据获取 = + // ========== + + UtilsCompiler.queryData( + mCallback, false + ) + + // ========== + // = 事件处理 = + // ========== + + ViewHelper.get() + .setOnClick(object : OnDebouncingClickListener(UtilsCompiler.REFRESH_CLICK) { + override fun doClick(view: View) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + if (!UtilsCompiler.isQuerying()) { + UtilsCompiler.queryData( + mCallback, true + ) + } + } + + override fun doInvalidClick(view: View) { + ToastTintUtils.normal( + ResourceUtils.getString(R.string.dev_http_capture_querying) + ) + } + }, binding.vidRefreshFl) + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterCaptureFile.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterCaptureFile.kt new file mode 100644 index 0000000000..a81675928d --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterCaptureFile.kt @@ -0,0 +1,48 @@ +package dev.capture.adapter + +import android.view.LayoutInflater +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt +import dev.capture.base.BaseDevHttpViewHolder +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureCaptureFileAdapterBinding +import dev.capture.model.Items +import dev.utils.app.ClipboardUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.helper.view.ViewHelper +import dev.utils.app.toast.ToastTintUtils + +/** + * detail: DevHttpCapture 抓包数据详情适配器 + * @author Ttt + */ +internal class AdapterCaptureFile : + DevDataAdapterExt>() { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseDevHttpViewHolder { + return BaseDevHttpViewHolder( + DevHttpCaptureCaptureFileAdapterBinding.inflate( + LayoutInflater.from(mContext), parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: BaseDevHttpViewHolder, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setText(item.title, holder.binding.vidTitleTv) + .setText(item.value, holder.binding.vidValueTv) + .setOnClick({ + ClipboardUtils.copyText(item.value) + ToastTintUtils.success( + ResourceUtils.getString(R.string.dev_http_capture_copy_success) + ) + }, holder.binding.vidCopyTv) + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleList.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleList.kt new file mode 100644 index 0000000000..8293a7a4cd --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleList.kt @@ -0,0 +1,78 @@ +package dev.capture.adapter + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt2 +import dev.capture.base.BaseDevHttpViewHolder +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureDateModuleListAdapterBinding +import dev.capture.model.Items.GroupItem +import dev.utils.app.ListViewUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.common.StringUtils + +/** + * detail: DevHttpCapture 对应模块具体日期抓包列表适配器 + * @author Ttt + */ +internal class AdapterDateModuleList : + DevDataAdapterExt2>() { + + // 延迟滑动时间 + private val mDelay = ResourceUtils.getInteger( + R.integer.dev_http_capture_query_item_scroll_delay + ).toLong().coerceAtLeast(30L) + + override fun getMultiSelectKey( + item: GroupItem, + position: Int + ): String { + return item.title + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseDevHttpViewHolder { + return BaseDevHttpViewHolder( + DevHttpCaptureDateModuleListAdapterBinding.inflate( + LayoutInflater.from(mContext), parent, false + ) + ) + } + + @SuppressLint("RecyclerView") + override fun onBindViewHolder( + holder: BaseDevHttpViewHolder, + position: Int + ) { + val item = getDataItem(position) + // 判断对应模块是否展开 + val unfold = mMultiSelectMap.isSelectKey(item.title) + if (ViewUtils.setVisibility(unfold, holder.binding.vidRv)) { + AdapterDateModuleListItem(item, holder.binding.vidRv) + } + QuickHelper.get(holder.binding.vidTitleTv) + .setText(item.title) + .setOnClick { view: View -> + // 反选展开状态 + mMultiSelectMap.toggle(item.title, item) + // 刷新适配器 + notifyDataChanged() + // 延时滑动到点击的索引 + view.postDelayed({ + ListViewUtils.scrollToPosition( + mRecyclerView, position + ) + }, mDelay) + } + // 接口所属功能 + QuickHelper.get(holder.binding.vidFunctionTv) + .setText(item.function) + .setVisibilitys(StringUtils.isNotEmpty(item.function)) + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleListItem.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleListItem.kt new file mode 100644 index 0000000000..d8b0b89862 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterDateModuleListItem.kt @@ -0,0 +1,95 @@ +package dev.capture.adapter + +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.adapter.DevDataAdapterExt +import dev.capture.CaptureFile +import dev.capture.UtilsCompiler +import dev.capture.activity.DevHttpCaptureFileActivity +import dev.capture.base.BaseDevHttpViewHolder +import dev.capture.compiler.databinding.DevHttpCaptureDateModuleListItemAdapterBinding +import dev.capture.model.Items.GroupItem +import dev.utils.DevFinal +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper +import dev.utils.app.helper.view.ViewHelper +import dev.utils.common.DateUtils +import dev.utils.common.StringUtils + +/** + * detail: DevHttpCapture 模块列表适配器 + * @author Ttt + */ +internal class AdapterDateModuleListItem( + private val groupItem: GroupItem, + recyclerView: RecyclerView +) : DevDataAdapterExt>() { + + init { + setDataList(groupItem.lists, false) + bindAdapter(recyclerView) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseDevHttpViewHolder { + return BaseDevHttpViewHolder( + DevHttpCaptureDateModuleListItemAdapterBinding.inflate( + LayoutInflater.from(mContext), parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: BaseDevHttpViewHolder, + position: Int + ) { + val item = getDataItem(position) + ViewHelper.get() + .setVisibilitys(isFirstPosition(position), holder.binding.vidLineView) + .setText(item.getMethod(), holder.binding.vidMethodTv) + .setText(item.getUrl(), holder.binding.vidUrlTv) + .setText(DateUtils.formatTime(item.getTime()), holder.binding.vidTimeTv) + .setOnClick({ + // 跳转 抓包数据详情 Activity + start(mContext, item) + }, holder.binding.root) + + // 判断分组筛选类型是否为请求链接类型 + if (ViewUtils.setVisibility( + StringUtils.isEmpty(groupItem.function), + holder.binding.vidFunctionTv + ) + ) { + val function = UtilsCompiler.getUrlFunctionByFile(item) + QuickHelper.get(holder.binding.vidFunctionTv) + .setText(function) + .setVisibilitys(StringUtils.isNotEmpty(function)) + } + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 跳转 抓包数据详情 Activity + * @param context [Context] + * @param captureFile 抓包存储文件 + */ + private fun start( + context: Context, + captureFile: CaptureFile + ) { + try { + val intent = Intent(context, DevHttpCaptureFileActivity::class.java) + intent.putExtra(DevFinal.STR.JSON, captureFile.toJson()) + context.startActivity(intent) + } catch (ignored: Exception) { + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModule.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModule.kt new file mode 100644 index 0000000000..ba7003084f --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModule.kt @@ -0,0 +1,71 @@ +package dev.capture.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import dev.adapter.DevDataAdapterExt2 +import dev.capture.base.BaseDevHttpViewHolder +import dev.capture.compiler.R +import dev.capture.compiler.databinding.DevHttpCaptureMainModuleAdapterBinding +import dev.capture.model.Items.MainItem +import dev.utils.app.ListViewUtils +import dev.utils.app.ResourceUtils +import dev.utils.app.ViewUtils +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: DevHttpCapture 模块适配器 + * @author Ttt + */ +internal class AdapterMainModule : + DevDataAdapterExt2>() { + + // 延迟滑动时间 + private val mDelay = ResourceUtils.getInteger( + R.integer.dev_http_capture_query_item_scroll_delay + ).toLong().coerceAtLeast(30L) + + override fun getMultiSelectKey( + item: MainItem, + position: Int + ): String { + return item.moduleName + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseDevHttpViewHolder { + return BaseDevHttpViewHolder( + DevHttpCaptureMainModuleAdapterBinding.inflate( + LayoutInflater.from(mContext), parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: BaseDevHttpViewHolder, + position: Int + ) { + val item = getDataItem(position) + // 判断对应模块是否展开 + val unfold = mMultiSelectMap.isSelectKey(item.moduleName) + if (ViewUtils.setVisibility(unfold, holder.binding.vidRv)) { + AdapterMainModuleList(item, holder.binding.vidRv) + } + QuickHelper.get(holder.binding.vidTitleTv) + .setText(item.moduleName) + .setOnClick { view: View -> + // 反选展开状态 + mMultiSelectMap.toggle(item.moduleName, item) + // 刷新适配器 + notifyDataChanged() + // 延时滑动到点击的索引 + view.postDelayed({ + ListViewUtils.scrollToPosition( + mRecyclerView, position + ) + }, mDelay) + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModuleList.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModuleList.kt new file mode 100644 index 0000000000..adfffca316 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/adapter/AdapterMainModuleList.kt @@ -0,0 +1,79 @@ +package dev.capture.adapter + +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.adapter.DevDataAdapterExt +import dev.capture.CaptureItem +import dev.capture.activity.DevHttpCaptureListActivity +import dev.capture.base.BaseDevHttpViewHolder +import dev.capture.compiler.databinding.DevHttpCaptureMainModuleListAdapterBinding +import dev.capture.model.Items.MainItem +import dev.utils.DevFinal +import dev.utils.app.helper.quick.QuickHelper + +/** + * detail: DevHttpCapture 模块列表适配器 + * @author Ttt + */ +internal class AdapterMainModuleList( + private val mainItem: MainItem, + recyclerView: RecyclerView +) : DevDataAdapterExt>() { + + init { + setDataList(mainItem.lists, false) + bindAdapter(recyclerView) + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): BaseDevHttpViewHolder { + return BaseDevHttpViewHolder( + DevHttpCaptureMainModuleListAdapterBinding.inflate( + LayoutInflater.from(mContext), parent, false + ) + ) + } + + override fun onBindViewHolder( + holder: BaseDevHttpViewHolder, + position: Int + ) { + val item = getDataItem(position) + QuickHelper.get(holder.binding.vidTitleTv) + .setText(item.yyyyMMdd) + .setOnClick { + // 跳转 抓包数据列表 Activity + start(mContext, mainItem.moduleName, item.yyyyMMdd) + } + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 跳转 抓包数据列表 Activity + * @param context [Context] + * @param moduleName 模块名 ( 要求唯一性 ) + * @param date yyyyMMdd + */ + private fun start( + context: Context, + moduleName: String, + date: String + ) { + try { + val intent = Intent(context, DevHttpCaptureListActivity::class.java) + intent.putExtra(DevFinal.STR.MODULE, moduleName) + intent.putExtra(DevFinal.STR.DATE, date) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) + } catch (ignored: Exception) { + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpActivity.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpActivity.kt new file mode 100644 index 0000000000..dea5585376 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpActivity.kt @@ -0,0 +1,40 @@ +package dev.capture.base + +import android.app.Activity +import android.os.Bundle +import androidx.viewbinding.ViewBinding +import dev.capture.UtilsCompiler + +/** + * detail: DevHttpCapture Base Activity + * @author Ttt + */ +abstract class BaseDevHttpActivity : Activity() { + + lateinit var binding: VB + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = createBinding() + // 添加 Activity + UtilsCompiler.addActivity(this) + } + + override fun onDestroy() { + super.onDestroy() + // 移除 Activity + UtilsCompiler.removeActivity(this) + } + + override fun onBackPressed() { + finishOperate() + } + + // ============ + // = abstract = + // ============ + + abstract fun createBinding(): VB + + abstract fun finishOperate() +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpViewHolder.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpViewHolder.kt new file mode 100644 index 0000000000..a6279f9d8a --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/base/BaseDevHttpViewHolder.kt @@ -0,0 +1,12 @@ +package dev.capture.base + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding + +/** + * detail: DevHttpCapture Base ViewHolder ViewBinding + * @author Ttt + */ +internal class BaseDevHttpViewHolder( + val binding: VB +) : RecyclerView.ViewHolder(binding.root) \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Dialogs.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Dialogs.kt new file mode 100644 index 0000000000..711a9dff37 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Dialogs.kt @@ -0,0 +1,75 @@ +package dev.capture.model + +import android.app.Dialog +import android.content.Context +import dev.callback.DevCallback +import dev.capture.R +import dev.capture.compiler.databinding.DevHttpCaptureDataTypeDialogBinding +import dev.capture.compiler.databinding.DevHttpCaptureGroupTypeDialogBinding +import dev.capture.model.Items.GroupType + +internal class Dialogs { + + /** + * 数据来源选项 Dialog + */ + class DataTypeDialog( + context: Context, + // 回调事件 + private val mCallback: DevCallback + ) : Dialog(context, R.style.DevDialogFullScreenTheme) { + + private val binding = DevHttpCaptureDataTypeDialogBinding.inflate( + layoutInflater + ) + + private fun callback(dataType: Items.DataType) { + dismiss() + mCallback.callback(dataType) + } + + init { + setContentView(binding.root) + + binding.apply { + vidAllTv.setOnClickListener { callback(Items.DataType.T_ALL) } + vid09Tv.setOnClickListener { callback(Items.DataType.T_0_9) } + vid1019Tv.setOnClickListener { callback(Items.DataType.T_10_19) } + vid2029Tv.setOnClickListener { callback(Items.DataType.T_20_29) } + vid3039Tv.setOnClickListener { callback(Items.DataType.T_30_39) } + vid4049Tv.setOnClickListener { callback(Items.DataType.T_40_49) } + vid5059Tv.setOnClickListener { callback(Items.DataType.T_50_59) } + vidCancelTv.setOnClickListener { dismiss() } + } + } + } + + /** + * 分组选项 Dialog + */ + class GroupTypeDialog( + context: Context, + // 回调事件 + private val mCallback: DevCallback + ) : Dialog(context, R.style.DevDialogFullScreenTheme) { + + private val binding = DevHttpCaptureGroupTypeDialogBinding.inflate( + layoutInflater + ) + + private fun callback(dataType: GroupType) { + dismiss() + mCallback.callback(dataType) + } + + init { + setContentView(binding.root) + + binding.apply { + vidTimeTv.setOnClickListener { callback(GroupType.T_TIME) } + vidUrlTv.setOnClickListener { callback(GroupType.T_URL) } + vidCancelTv.setOnClickListener { dismiss() } + } + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Items.kt b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Items.kt new file mode 100644 index 0000000000..48c76a7ba1 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/model/Items.kt @@ -0,0 +1,202 @@ +package dev.capture.model + +import dev.capture.CaptureFile +import dev.capture.CaptureItem +import dev.capture.compiler.R +import dev.utils.app.ResourceUtils +import dev.utils.common.ConvertUtils +import dev.utils.common.StringUtils + +internal class Items { + + /** + * 数据来源类型 + */ + enum class DataType( + val type: String + ) { + + // 全部 + T_ALL("1"), + + // 0-9 分钟 + T_0_9("2"), + + // 10-19 分钟 + T_10_19("3"), + + // 20-29 分钟 + T_20_29("4"), + + // 30-39 分钟 + T_30_39("5"), + + // 40-49 分钟 + T_40_49("6"), + + // 50-59 分钟 + T_50_59("7"), + + ; + + open fun getTitle(): String { + val res = when (this) { + T_0_9 -> R.string.dev_http_capture_data_type_0_9 + T_10_19 -> R.string.dev_http_capture_data_type_10_19 + T_20_29 -> R.string.dev_http_capture_data_type_20_29 + T_30_39 -> R.string.dev_http_capture_data_type_30_39 + T_40_49 -> R.string.dev_http_capture_data_type_40_49 + T_50_59 -> R.string.dev_http_capture_data_type_50_59 + else -> R.string.dev_http_capture_data_type_all + } + return ResourceUtils.getString(res) + } + } + + /** + * 分组条件类型 + */ + enum class GroupType( + val type: String + ) { + + // 抓包时间 + T_TIME("1"), + + // 请求链接, 以请求接口前缀 ( ? 号前 ) + T_URL("2"), + + ; + + open fun getTitle(): String { + val res = when (this) { + T_URL -> R.string.dev_http_capture_group_type_url + else -> R.string.dev_http_capture_group_type_time + } + return ResourceUtils.getString(res) + } + } + + /** + * 首页适配器数据包装类 + */ + class MainItem( + var moduleName: String, + var lists: List + ) + + /** + * 抓包列表数据包装类 + */ + class GroupItem( + var title: String, + var lists: List + ) { + var function: String? = null + + fun setFunction(function: String?): GroupItem { + this.function = function + return this + } + + init { + this.title = convertTitleByHHMM(title) + } + } + + /** + * 抓包文件数据包装类 + */ + class FileItem( + var title: String, + var value: String + ) + + // ============= + // = 内部转换方法 = + // ============= + + companion object { + + /** + * 根据时间转换数据类型 + * @param key HHMM + * @return 数据类型 + */ + fun convertDataType(key: String?): DataType? { + // 获取分钟 + val minute = StringUtils.substring( + key, 2, 4, false + ) + val mm = ConvertUtils.toInt(minute, -1) + if (mm == -1) return null + // 存储间隔以 10 分钟为单位 + return if (mm < 10) { // 00-09 + DataType.T_0_9 + } else if (mm < 20) { // 10-19 + DataType.T_10_19 + } else if (mm < 30) { // 20-29 + DataType.T_20_29 + } else if (mm < 40) { // 30-39 + DataType.T_30_39 + } else if (mm < 50) { // 40-49 + DataType.T_40_49 + } else { // 50-59 + DataType.T_50_59 + } + } + + /** + * 通过时间转换标题 + * @param title HHMM + * @return 转换后的标题 + * 1000 转换为 10 hour 00-09 minute + * 1010 转换为 10 hour 10-19 minute + * 1020 转换为 10 hour 20-29 minute + * 1030 转换为 10 hour 30-39 minute + * 1040 转换为 10 hour 40-49 minute + * 1050 转换为 10 hour 50-59 minute + */ + fun convertTitleByHHMM(title: String): String { + if (StringUtils.isLength(title, 4)) { + val builder = StringBuilder() + builder.append(title, 0, 2).append(" hour ") + // 获取分钟 + var minute = StringUtils.substring( + title, 2, 4, false + ) + val mm = ConvertUtils.toInt(minute) + // 存储间隔以 10 分钟为单位 + minute = if (mm < 10) { // 00-09 + "00" + } else if (mm < 20) { // 10-19 + "10" + } else if (mm < 30) { // 20-29 + "20" + } else if (mm < 40) { // 30-39 + "30" + } else if (mm < 50) { // 40-49 + "40" + } else { // 50-59 + "50" + } + builder.append(minute).append(" minute") + return builder.toString() + } + return title + } + + /** + * 拆分 Url 用于匹配接口所属功能注释 + * @param url 请求接口链接 + * @return 处理后的 Url + */ + fun convertUrlKey(url: String?): String? { + if (!StringUtils.isSpace(url)) { + val key = StringUtils.split(url, "\\?", 0) + return StringUtils.replaceEndsWith(key, "/", "") + } + return null + } + } +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_refresh.png b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_refresh.png new file mode 100644 index 0000000000..5a145cc37e Binary files /dev/null and b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_refresh.png differ diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_sort_arrow.png b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_sort_arrow.png new file mode 100644 index 0000000000..5be8363c4e Binary files /dev/null and b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_sort_arrow.png differ diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_tips.png b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_tips.png new file mode 100644 index 0000000000..8c8ff63055 Binary files /dev/null and b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable-xxhdpi/dev_http_capture_icon_tips.png differ diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_action_round_bg.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_action_round_bg.xml new file mode 100644 index 0000000000..fb7ee614ca --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_action_round_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_copy_bg.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_copy_bg.xml new file mode 100644 index 0000000000..d2c6810385 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_copy_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_item_bg.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_item_bg.xml new file mode 100644 index 0000000000..e19e7c329a --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_adapter_item_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_back.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_back.xml new file mode 100644 index 0000000000..6d6983dbaa --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_back.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_dialog_bg.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_dialog_bg.xml new file mode 100644 index 0000000000..f0ab5a23ca --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/drawable/dev_http_capture_dialog_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_capture_file_adapter.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_capture_file_adapter.xml new file mode 100644 index 0000000000..ad7ce5efdc --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_capture_file_adapter.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_data_type_dialog.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_data_type_dialog.xml new file mode 100644 index 0000000000..b5ba640f30 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_data_type_dialog.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_adapter.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_adapter.xml new file mode 100644 index 0000000000..70c9dc9c1d --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_adapter.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_item_adapter.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_item_adapter.xml new file mode 100644 index 0000000000..3fa142173b --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_date_module_list_item_adapter.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_file_activity.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_file_activity.xml new file mode 100644 index 0000000000..914c866ce3 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_file_activity.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_group_type_dialog.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_group_type_dialog.xml new file mode 100644 index 0000000000..ee9d944b95 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_group_type_dialog.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_list_options_tab.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_list_options_tab.xml new file mode 100644 index 0000000000..6858740f25 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_list_options_tab.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_tips.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_tips.xml new file mode 100644 index 0000000000..54c53609d0 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_tips.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_title.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_title.xml new file mode 100644 index 0000000000..b1cc110484 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_include_title.xml @@ -0,0 +1,32 @@ + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_list_activity.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_list_activity.xml new file mode 100644 index 0000000000..eeab72ea33 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_list_activity.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_activity.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_activity.xml new file mode 100644 index 0000000000..85fcfc4a0f --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_activity.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_adapter.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_adapter.xml new file mode 100644 index 0000000000..26b770bb73 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_adapter.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_list_adapter.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_list_adapter.xml new file mode 100644 index 0000000000..9525abc081 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/layout/dev_http_capture_main_module_list_adapter.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values-zh-rCN/strings.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000000..fffe50d03f --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,29 @@ + + + 抓包详情 + 正在加载中,请稍后… + 数据刷新完成 + 暂无抓包数据 + 复制 + 复制成功 + 取消 + 接口所属功能 + 请求方法 + 请求 Url + 请求 Header + 请求 Body + 响应状态 + 响应 Header + 响应 Body + 全部 + 00–09 分钟 + 10–19 分钟 + 20–29 分钟 + 30–39 分钟 + 40–49 分钟 + 50–59 分钟 + 抓包时间 + 请求链接 + 数据来源 + 分组类型 + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/strings.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/strings.xml new file mode 100644 index 0000000000..773a000ecc --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/strings.xml @@ -0,0 +1,32 @@ + + + DevHttpCapture + 30 + + Http Capture Details + Loading, Please Wait… + Data Refresh Complete + No Http Capture Data + Copy + Copy Success + cancel + Url Function + Request Method + Request Url + Request Header + Request Body + Response Status + Response Header + Response Body + all + 00–09 minute + 10–19 minute + 20–29 minute + 30–39 minute + 40–49 minute + 50–59 minute + capture time + request url + Data Sources + Group type + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/unified.xml b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/unified.xml new file mode 100644 index 0000000000..c6ffb797ff --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompiler/src/main/res/values/unified.xml @@ -0,0 +1,157 @@ + + + #e9e9e9 + + + + + + #ff4181 + + + + #0080ff + 4.0dp + + + + #ffffff + 6.0dp + + + + #ffffff + 4.0dp + + + + + + #ffffff + 20.0dp + 10.0dp + + #333333 + 17.0sp + + #333333 + 14.0sp + 7.0dp + + #ffffff + 14.0sp + 15.0dp + 15.0dp + 18.0dp + 3.0dp + + + + #66000000 + 280.0dp + #333333 + 15.0sp + 15.0dp + 12.0dp + #333333 + 17.0sp + 15.0sp + 12.0sp + 10.0sp + 12.0sp + #ff0000 + 14.0sp + 40.0sp + 10.0sp + 40.0sp + 15.0sp + + + + #0080ff + 10.0dp + 14.0dp + #ffffff + 0.1dp + #ffffff + 19.0sp + #ffffff + 15.0sp + 5.0sp + + + + #e9e9e9 + 10.0dp + 10.0dp + 10.0dp + 10.0dp + 10.0dp + 10.0sp + + #ff0000 + 18.0sp + #333333 + 16.0sp + #333333 + 16.0sp + #333333 + 14.0sp + + + + #3f51b5 + 3.0sp + 3.0sp + 3.0sp + 11.0sp + #ffffff + 12.0sp + #ffffff + 16.0sp + 7.0dp + 2.0dp + + + + #3f51b5 + 17.0sp + 20.0dp + 50.0dp + 180.0dp + + + + #3f51b5 + 56.0dp + 56.0dp + 5.0dp + + #ffffff + 18.0sp + + + + 50.0dp + 10.0dp + 15.0dp + 35.0dp + + + + #0080ff + 10.0dp + #ffffff + 0.1dp + 50.0dp + #ffffff + 19.0sp + + + + #d0d0d0 + 10.0dp + 45.0dp + #777777 + 17.0sp + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/.gitignore b/lib/HttpCapture/DevHttpCaptureCompilerRelease/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/build.gradle b/lib/HttpCapture/DevHttpCaptureCompilerRelease/build.gradle new file mode 100644 index 0000000000..2f4376f29e --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/build.gradle @@ -0,0 +1,30 @@ +apply from: rootProject.file(files.lib_app_kotlin_gradle) + +android.defaultConfig { + versionCode versions.dev_http_capture_versionCode + versionName versions.dev_http_capture_compiler_release_version +} + +// 是否发布版本 +def isPublishing = false + +dependencies { + if (isPublishing) { + // 打包时使用 + api deps.dev.dev_assist + api deps.dev.dev_http_capture + } else { + // 编译时使用 + api project(':DevAssist') + api project(':DevHttpCapture') + } +} + +// gradlew clean +// gradlew install +// gradlew bintrayUpload +//apply from: rootProject.file(files.bintray_upload_android) +//apply from: rootProject.file(files.sonatype_upload_android) +if (isPublishing) { + apply from: rootProject.file(files.sonatype_upload_android) +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/proguard-rules.pro b/lib/HttpCapture/DevHttpCaptureCompilerRelease/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/project.properties b/lib/HttpCapture/DevHttpCaptureCompilerRelease/project.properties new file mode 100644 index 0000000000..42940b8e17 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/project.properties @@ -0,0 +1,10 @@ +#project +project.name=DevHttpCaptureCompilerRelease +project.groupId=io.github.afkt +project.artifactId=DevHttpCaptureCompilerRelease +project.packaging=aar +project.siteUrl=https://github.com/afkT/DevUtils +project.gitUrl=https://github.com/afkT/DevUtils.git + +#javadoc +javadoc.name=DevHttpCaptureCompilerRelease \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/AndroidManifest.xml b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..f24063acb5 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/DevHttpCaptureCompiler.kt b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/DevHttpCaptureCompiler.kt new file mode 100644 index 0000000000..11525f4592 --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/DevHttpCaptureCompiler.kt @@ -0,0 +1,90 @@ +package dev + +import android.content.Context +import dev.capture.UrlFunctionGet + +/** + * detail: OkHttp 抓包工具库 + * @author Ttt + *

+ * GitHub + * @see https://github.com/afkT/DevUtils + * DevApp Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevApp/README.md + * DevAssist Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevAssist/README.md + * DevBase README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBase/README.md + * DevBaseMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevBaseMVVM/README.md + * DevMVVM README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevMVVM/README.md + * DevEngine README + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevEngine/README.md + * DevHttpCapture Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpCapture/README.md + * DevHttpManager Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevHttpManager/README.md + * DevRetrofit Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevRetrofit/README.md + * DevWidget Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevWidget/README.md + * DevEnvironment Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/Environment + * DevJava Api + * @see https://github.com/afkT/DevUtils/blob/master/lib/DevJava/README.md + */ +object DevHttpCaptureCompiler { + + // ============= + // = 对外提供方法 = + // ============= + + /** + * 结束所有 Activity + */ + fun finishAllActivity() {} + + // ========== + // = 跳转方法 = + // ========== + + /** + * 跳转抓包数据可视化 Activity + * @param context [Context] + * @return `true` success, `false` fail + */ + fun start(context: Context?): Boolean { + return false + } + + /** + * 跳转抓包数据可视化 Activity + * @param context [Context] + * @param moduleName 模块名 ( 要求唯一性 ) + * @return `true` success, `false` fail + */ + fun start( + context: Context?, + moduleName: String + ): Boolean { + return false + } + + /** + * 添加接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param function 接口所属功能注释获取 + */ + fun putUrlFunction( + moduleName: String, + function: UrlFunctionGet + ) { + } + + /** + * 移除接口所属功能注释 + * @param moduleName 模块名 ( 要求唯一性 ) + */ + fun removeUrlFunction(moduleName: String) {} +} \ No newline at end of file diff --git a/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/capture/UrlFunctionGet.kt b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/capture/UrlFunctionGet.kt new file mode 100644 index 0000000000..f8ae1db7bd --- /dev/null +++ b/lib/HttpCapture/DevHttpCaptureCompilerRelease/src/main/java/dev/capture/UrlFunctionGet.kt @@ -0,0 +1,25 @@ +package dev.capture + +/** + * detail: 接口所属功能注释获取 + * @author Ttt + */ +interface UrlFunctionGet { + + /** + * 接口所属功能注释获取 + * @param moduleName 模块名 ( 要求唯一性 ) + * @param url 原始请求链接 + * @param method 请求方法 + * @param convertUrlKey url 匹配规则 ( 拆分 ? 前为 key 进行匹配 ) + * @param cacheFunction 缓存功能注释 + * @return 接口所属功能注释 + */ + fun toUrlFunction( + moduleName: String, + url: String, + method: String, + convertUrlKey: String?, + cacheFunction: String? + ): String? +} \ No newline at end of file diff --git a/lib/HttpCapture/README.md b/lib/HttpCapture/README.md new file mode 100644 index 0000000000..e8750471c5 --- /dev/null +++ b/lib/HttpCapture/README.md @@ -0,0 +1,94 @@ + +## DevHttpCapture + +> 该库主要对使用 OkHttp 网络请求库的项目,提供 Http 抓包功能,并支持抓包数据加密存储。 +> +> **并且是以 Module ( ModuleName Key ) 为基础,支持组件化不同 Module 各自的抓包功能**,支持实时开关抓包功能、可控 Http 拦截过滤器。 +> +> 内置两个 Http 抓包拦截器,CallbackInterceptor ( 无存储逻辑,进行回调通知 )、HttpCaptureInterceptor ( 存在存储抓包数据逻辑 ) +> +> `DevHttpCaptureCompiler` 提供对 `DevHttpCapture` 抓包库可视化功能 + + +### 最新版本 + +module | DevHttpCapture | DevHttpCaptureCompiler | DevHttpCaptureCompilerRelease +:---:|:---:|:---:|:---: +version | [![][maven_svg]][maven] | [![][maven_svg]][maven] | [![][maven_svg]][maven] + + +### Gradle + +```groovy +dependencies { + debugImplementation 'io.github.afkt:DevHttpCaptureCompiler:1.1.3' + releaseImplementation 'io.github.afkt:DevHttpCaptureCompilerRelease:1.1.3' +} +``` + + +### 使用示例 + +```kotlin +// 显示所有 Module 抓包数据 +DevHttpCaptureCompiler.start(context) +// 显示指定 Module 抓包数据 +DevHttpCaptureCompiler.start(context, moduleName) + +// ======= +// = 可选 = +// ======= + +// 添加接口所属功能注释 +DevHttpCaptureCompiler.putUrlFunction(moduleName, UrlFunctionGet) +// 移除接口所属功能注释 +DevHttpCaptureCompiler.removeUrlFunction(moduleName) +``` + +### 目录结构 + +``` +- dev | 根目录 + - capture | Http 抓包实现代码 + - activity | 可视化页面 + - adapter | 适配器 + - base | 基础相关 + - model | 数据模型 +``` + + +### API + + +- dev | 根目录 + - [capture](#devcapture) | Http 抓包实现代码 + + +## **`dev`** + + +* **OkHttp 抓包工具库 ->** [DevHttpCaptureCompiler.kt](https://github.com/afkT/DevUtils/blob/master/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/DevHttpCaptureCompiler.kt) + +| 方法 | 注释 | +| :- | :- | +| finishAllActivity | 结束所有 Activity | +| start | 跳转抓包数据可视化 Activity | +| putUrlFunction | 添加接口所属功能注释 | +| removeUrlFunction | 移除接口所属功能注释 | + + +## **`dev.capture`** + + +* **接口所属功能注释获取 ->** [UrlFunctionGet.kt](https://github.com/afkT/DevUtils/blob/master/lib/HttpCapture/DevHttpCaptureCompiler/src/main/java/dev/capture/UrlFunctionGet.kt) + +| 方法 | 注释 | +| :- | :- | +| toUrlFunction | 接口所属功能注释获取 | + + + + + +[maven_svg]: https://img.shields.io/badge/Maven-1.1.3-brightgreen.svg +[maven]: https://search.maven.org/search?q=io.github.afkt \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/.gitignore b/lib/LocalModules/DevBaseView/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/LocalModules/DevBaseView/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/build.gradle b/lib/LocalModules/DevBaseView/build.gradle new file mode 100644 index 0000000000..79675d7b19 --- /dev/null +++ b/lib/LocalModules/DevBaseView/build.gradle @@ -0,0 +1,13 @@ +apply from: rootProject.file(files.build_app_kotlin_gradle) + +dependencies { + + // ========================== + // = 其他第三方库 - 小功能、简约 = + // ========================== + + // 下拉刷新框架 https://github.com/scwang90/SmartRefreshLayout + implementation deps.widget.smartrefreshlayout + implementation deps.widget.smartrefresh_header_classics + implementation deps.widget.smartrefresh_footer_classics +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/proguard-rules.pro b/lib/LocalModules/DevBaseView/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/lib/LocalModules/DevBaseView/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/AndroidManifest.xml b/lib/LocalModules/DevBaseView/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..8a95c1a67f --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/utils/assist/DevBaseRefreshAssist.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/utils/assist/DevBaseRefreshAssist.kt new file mode 100644 index 0000000000..b45a36621c --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/utils/assist/DevBaseRefreshAssist.kt @@ -0,0 +1,206 @@ +package dev.base.utils.assist + +import androidx.recyclerview.widget.RecyclerView +import com.scwang.smart.refresh.layout.SmartRefreshLayout +import com.scwang.smart.refresh.layout.api.RefreshFooter +import com.scwang.smart.refresh.layout.api.RefreshHeader +import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener +import com.scwang.smart.refresh.layout.listener.OnRefreshListener +import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener +import dev.assist.PageAssist + +/** + * detail: DevBase RefreshLayout 辅助类 + * @author Ttt + */ +class DevBaseRefreshAssist { + + // 请求页数辅助类 + private var mPageAssist = PageAssist() + + // Refresh Layout + private var mRefreshLayout: SmartRefreshLayout? = null + + // RecyclerView + private var mRecyclerView: RecyclerView? = null + + // Adapter + private var mAdapter: RecyclerView.Adapter<*>? = null + + // =========== + // = get/set = + // =========== + + fun getPageAssist(): PageAssist { + return mPageAssist + } + + fun setPageAssist(pageAssist: PageAssist): DevBaseRefreshAssist { + mPageAssist = pageAssist + return this + } + + fun getRefreshLayout(): SmartRefreshLayout? { + return mRefreshLayout + } + + fun setRefreshLayout(refreshLayout: SmartRefreshLayout?): DevBaseRefreshAssist { + mRefreshLayout = refreshLayout + return this + } + + fun getRecyclerView(): RecyclerView? { + return mRecyclerView + } + + fun setRecyclerView(recyclerView: RecyclerView?): DevBaseRefreshAssist { + mRecyclerView = recyclerView + return this + } + + fun > getAdapter(): T? { + mAdapter?.let { return mAdapter as? T } + return null + } + + fun setAdapter(adapter: RecyclerView.Adapter<*>?): DevBaseRefreshAssist { + adapter?.let { + mAdapter = it + mRecyclerView?.adapter = it + } + return this + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 设置 LayoutManager + * @param layoutManager RecyclerView LayoutManager + * @return [DevBaseRefreshAssist] + */ + fun setLayoutManager(layoutManager: RecyclerView.LayoutManager): DevBaseRefreshAssist { + mRecyclerView?.layoutManager = layoutManager + return this + } + + /** + * 是否启用下拉刷新 + * @param enabled 是否启用 + * @return [DevBaseRefreshAssist] + */ + fun setEnableRefresh(enabled: Boolean): DevBaseRefreshAssist { + mRefreshLayout?.setEnableRefresh(enabled) + return this + } + + /** + * 是否启用上拉加载更多 + * @param enabled 是否启用 + * @return [DevBaseRefreshAssist] + */ + fun setEnableLoadMore(enabled: Boolean): DevBaseRefreshAssist { + mRefreshLayout?.setEnableLoadMore(enabled) + return this + } + + /** + * 设置数据全部加载完成 ( 将不能再次触发加载功能 ) + * @param noMoreData 是否有更多数据 `true` 无数据, `false` 还有数据 + * @return [DevBaseRefreshAssist] + */ + fun setNoMoreData(noMoreData: Boolean): DevBaseRefreshAssist { + mRefreshLayout?.setNoMoreData(noMoreData) + return this + } + + /** + * 设置刷新监听器 + * @param listener 刷新监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnRefreshListener(listener: OnRefreshListener?): DevBaseRefreshAssist { + mRefreshLayout?.setOnRefreshListener(listener) + return this + } + + /** + * 设置加载监听器 + * @param listener 加载监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnLoadMoreListener(listener: OnLoadMoreListener?): DevBaseRefreshAssist { + mRefreshLayout?.setOnLoadMoreListener(listener) + return this + } + + /** + * 设置刷新和加载监听器 + * @param listener 刷新、加载监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnRefreshLoadMoreListener(listener: OnRefreshLoadMoreListener?): DevBaseRefreshAssist { + mRefreshLayout?.setOnRefreshLoadMoreListener(listener) + return this + } + + // = + + /** + * 完成刷新 + * @return [DevBaseRefreshAssist] + */ + fun finishRefresh(): DevBaseRefreshAssist { + mRefreshLayout?.let { if (it.isRefreshing) it.finishRefresh() } + return this + } + + /** + * 完成加载 + * @return [DevBaseRefreshAssist] + */ + fun finishLoadMore(): DevBaseRefreshAssist { + mRefreshLayout?.finishLoadMore() + return this + } + + /** + * 完成刷新、加载 + * @return [DevBaseRefreshAssist] + */ + fun finishRefreshAndLoad(): DevBaseRefreshAssist { + return finishRefresh().finishLoadMore() + } + + /** + * 完成刷新或加载 + * @param refresh 是否刷新 + * @return [DevBaseRefreshAssist] + */ + fun finishRefreshOrLoad(refresh: Boolean): DevBaseRefreshAssist { + return if (refresh) finishRefresh() else finishLoadMore() + } + + // = + + /** + * 设置指定刷新头 + * @param header RefreshHeader 刷新头 + * @return [DevBaseRefreshAssist] + */ + fun setRefreshHeader(header: RefreshHeader): DevBaseRefreshAssist { + mRefreshLayout?.setRefreshHeader(header) + return this + } + + /** + * 设置指定刷新尾巴 + * @param footer RefreshFooter 刷新尾巴 + * @return [DevBaseRefreshAssist] + */ + fun setRefreshFooter(footer: RefreshFooter): DevBaseRefreshAssist { + mRefreshLayout?.setRefreshFooter(footer) + return this + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseButton.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseButton.kt new file mode 100644 index 0000000000..684cee4870 --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseButton.kt @@ -0,0 +1,26 @@ +package dev.base.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatButton + +/** + * detail: Base Button + * @author Ttt + * 便于全局控制、替换字体、样式等 + */ +class BaseButton : AppCompatButton { + + constructor(context: Context) : super(context) + + constructor( + context: Context, + attrs: AttributeSet? + ) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseEditText.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseEditText.kt new file mode 100644 index 0000000000..df8e19c22d --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseEditText.kt @@ -0,0 +1,26 @@ +package dev.base.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatEditText + +/** + * detail: Base EditText + * @author Ttt + * 便于全局控制、替换字体、样式等 + */ +class BaseEditText : AppCompatEditText { + + constructor(context: Context) : super(context) + + constructor( + context: Context, + attrs: AttributeSet? + ) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseImageView.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseImageView.kt new file mode 100644 index 0000000000..42a7cc2c68 --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseImageView.kt @@ -0,0 +1,26 @@ +package dev.base.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView + +/** + * detail: Base ImageView + * @author Ttt + * 便于全局控制、替换字体、样式等 + */ +class BaseImageView : AppCompatImageView { + + constructor(context: Context) : super(context) + + constructor( + context: Context, + attrs: AttributeSet? + ) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseRefreshView.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseRefreshView.kt new file mode 100644 index 0000000000..c9c6762042 --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseRefreshView.kt @@ -0,0 +1,285 @@ +package dev.base.widget + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.annotation.RequiresApi +import androidx.recyclerview.widget.RecyclerView +import com.scwang.smart.refresh.footer.ClassicsFooter +import com.scwang.smart.refresh.header.ClassicsHeader +import com.scwang.smart.refresh.layout.SmartRefreshLayout +import com.scwang.smart.refresh.layout.api.RefreshFooter +import com.scwang.smart.refresh.layout.api.RefreshHeader +import com.scwang.smart.refresh.layout.listener.OnLoadMoreListener +import com.scwang.smart.refresh.layout.listener.OnRefreshListener +import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener +import dev.assist.PageAssist +import dev.base.utils.assist.DevBaseRefreshAssist +import dev.base_view.databinding.BaseRefreshViewBinding + +/** + * detail: Base Refresh、Load View + * @author Ttt + * 通用 下拉刷新、上拉加载 封装 View + */ +class BaseRefreshView : LinearLayout { + + constructor(context: Context) : super(context) { + initialize() + } + + constructor( + context: Context, + attrs: AttributeSet? + ) : super(context, attrs) { + initialize() + } + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) { + initialize() + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + initialize() + } + + // = + + // 最外层 View + private var mBody: FrameLayout? = null + + // DevBase RefreshLayout 辅助类 + private var mAssist = DevBaseRefreshAssist() + + /** + * 默认初始化操作 + */ + private fun initialize() { + orientation = VERTICAL + val context = context + + // 初始化 View + val binding = BaseRefreshViewBinding.inflate(LayoutInflater.from(context)) + mBody = binding.vidFl + // 初始化 Refresh 数据 + mAssist + .setRecyclerView(binding.vidRv) + .setRefreshLayout(binding.vidRefresh) + .setRefreshHeader(ClassicsHeader(context)) // 刷新头 + .setRefreshFooter(ClassicsFooter(context)) // 刷新尾巴 + .setEnableRefresh(true) // 开启下拉刷新 + .setEnableLoadMore(true) // 开启上拉加载 + // 不需要阻尼效果 + mAssist.getRefreshLayout()?.setEnableOverScrollDrag(false) + + // 添加 View + addView( + binding.root, + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + } + + // ======================== + // = DevBaseRefreshAssist2 = + // ======================== + + fun getAssist(): DevBaseRefreshAssist { + return mAssist + } + + // =========== + // = get/set = + // =========== + + fun getBody(): FrameLayout? { + return mBody + } + + fun getPageAssist(): PageAssist { + return mAssist.getPageAssist() + } + + fun setPageAssist(pageAssist: PageAssist): BaseRefreshView { + mAssist.setPageAssist(pageAssist) + return this + } + + fun getRefreshLayout(): SmartRefreshLayout? { + return mAssist.getRefreshLayout() + } + + fun setRefreshLayout(refreshLayout: SmartRefreshLayout?): BaseRefreshView { + mAssist.setRefreshLayout(refreshLayout) + return this + } + + fun getRecyclerView(): RecyclerView? { + return mAssist.getRecyclerView() + } + + fun setRecyclerView(recyclerView: RecyclerView?): BaseRefreshView { + mAssist.setRecyclerView(recyclerView) + return this + } + + fun > getAdapter(): T? { + return mAssist.getAdapter() + } + + fun setAdapter(adapter: RecyclerView.Adapter<*>?): BaseRefreshView { + mAssist.setAdapter(adapter) + return this + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 设置 LayoutManager + * @param layoutManager [RecyclerView.LayoutManager] + * @return [DevBaseRefreshAssist] + */ + fun setLayoutManager(layoutManager: RecyclerView.LayoutManager): BaseRefreshView { + mAssist.setLayoutManager(layoutManager) + return this + } + + /** + * 是否启用下拉刷新 + * @param enabled 是否启用 + * @return [DevBaseRefreshAssist] + */ + fun setEnableRefresh(enabled: Boolean): BaseRefreshView { + mAssist.setEnableRefresh(enabled) + return this + } + + /** + * 是否启用上拉加载更多 + * @param enabled 是否启用 + * @return [DevBaseRefreshAssist] + */ + fun setEnableLoadMore(enabled: Boolean): BaseRefreshView { + mAssist.setEnableLoadMore(enabled) + return this + } + + /** + * 设置数据全部加载完成 ( 将不能再次触发加载功能 ) + * @param noMoreData 是否有更多数据 `true` 无数据, `false` 还有数据 + * @return [DevBaseRefreshAssist] + */ + fun setNoMoreData(noMoreData: Boolean): BaseRefreshView { + mAssist.setNoMoreData(noMoreData) + return this + } + + /** + * 设置刷新监听器 + * @param listener 刷新监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnRefreshListener(listener: OnRefreshListener?): BaseRefreshView { + mAssist.setOnRefreshListener(listener) + return this + } + + /** + * 设置加载监听器 + * @param listener 加载监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnLoadMoreListener(listener: OnLoadMoreListener?): BaseRefreshView { + mAssist.setOnLoadMoreListener(listener) + return this + } + + /** + * 设置刷新和加载监听器 + * @param listener 刷新、加载监听器 + * @return [DevBaseRefreshAssist] + */ + fun setOnRefreshLoadMoreListener(listener: OnRefreshLoadMoreListener?): BaseRefreshView { + mAssist.setOnRefreshLoadMoreListener(listener) + return this + } + + // = + + /** + * 完成刷新 + * @return [DevBaseRefreshAssist] + */ + fun finishRefresh(): BaseRefreshView { + mAssist.finishRefresh() + return this + } + + /** + * 完成加载 + * @return [DevBaseRefreshAssist] + */ + fun finishLoadMore(): BaseRefreshView { + mAssist.finishLoadMore() + return this + } + + /** + * 完成刷新、加载 + * @return [DevBaseRefreshAssist] + */ + fun finishRefreshAndLoad(): BaseRefreshView { + mAssist.finishRefreshAndLoad() + return this + } + + /** + * 完成刷新或加载 + * @param refresh 是否刷新 + * @return [DevBaseRefreshAssist] + */ + fun finishRefreshOrLoad(refresh: Boolean): BaseRefreshView { + mAssist.finishRefreshOrLoad(refresh) + return this + } + + // = + + /** + * 设置指定刷新头 + * @param header RefreshHeader 刷新头 + * @return [DevBaseRefreshAssist] + */ + fun setRefreshHeader(header: RefreshHeader): BaseRefreshView { + mAssist.setRefreshHeader(header) + return this + } + + /** + * 设置指定刷新尾巴 + * @param footer RefreshFooter 刷新尾巴 + * @return [DevBaseRefreshAssist] + */ + fun setRefreshFooter(footer: RefreshFooter): BaseRefreshView { + mAssist.setRefreshFooter(footer) + return this + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseTextView.kt b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseTextView.kt new file mode 100644 index 0000000000..edb83b00d0 --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/java/dev/base/widget/BaseTextView.kt @@ -0,0 +1,26 @@ +package dev.base.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView + +/** + * detail: Base TextView + * @author Ttt + * 便于全局控制、替换字体、样式等 + */ +class BaseTextView : AppCompatTextView { + + constructor(context: Context) : super(context) + + constructor( + context: Context, + attrs: AttributeSet? + ) : super(context, attrs) + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) +} \ No newline at end of file diff --git a/lib/LocalModules/DevBaseView/src/main/res/layout/base_refresh_view.xml b/lib/LocalModules/DevBaseView/src/main/res/layout/base_refresh_view.xml new file mode 100644 index 0000000000..bbd9159335 --- /dev/null +++ b/lib/LocalModules/DevBaseView/src/main/res/layout/base_refresh_view.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/LocalModules/DevOther/.gitignore b/lib/LocalModules/DevOther/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/LocalModules/DevOther/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/LocalModules/DevOther/build.gradle b/lib/LocalModules/DevOther/build.gradle new file mode 100644 index 0000000000..6dc7356613 --- /dev/null +++ b/lib/LocalModules/DevOther/build.gradle @@ -0,0 +1,2 @@ +apply from: rootProject.file(files.build_app_kotlin_gradle) +apply from: rootProject.file(files.deps_other_lib) \ No newline at end of file diff --git a/lib/LocalModules/DevOther/proguard-rules.pro b/lib/LocalModules/DevOther/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/LocalModules/DevOther/src/main/AndroidManifest.xml b/lib/LocalModules/DevOther/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..47e1eaedcc --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/AndroidManifest.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/assist/WebViewAssist.kt b/lib/LocalModules/DevOther/src/main/java/dev/assist/WebViewAssist.kt new file mode 100644 index 0000000000..e2fcc09efe --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/assist/WebViewAssist.kt @@ -0,0 +1,1680 @@ +package dev.assist + +import android.graphics.Paint +import android.os.Build +import android.view.KeyEvent +import android.view.View +import android.webkit.* +import android.webkit.WebSettings.LayoutAlgorithm +import android.webkit.WebSettings.RenderPriority +import android.webkit.WebView.HitTestResult +import dev.DevUtils +import dev.assist.WebViewAssist.Builder.OnApplyListener +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.app.ViewUtils + +/** + * detail: WebView 辅助类 + * @author Ttt + * WebView 截图使用 [dev.utils.app.CapturePictureUtils.snapshotByWebView] + * WebView 全面解析 + * @see https://www.jianshu.com/p/3e0136c9e748 + * Android WebView 常见问题及解决方案汇总 + * @see https://www.cnblogs.com/Free-Thinker/p/6179016.html + * WebView 与 JavaScript 的交互总结 + * @see https://www.jianshu.com/p/5cc2eae14e07 + * WebView 使用漏洞 + * @see https://blog.csdn.net/carson_ho/article/details/64904635 + * Android Webview H5 交互之 LocalStorage + * @see https://www.jianshu.com/p/379a0681ce25 + */ +class WebViewAssist @JvmOverloads constructor(listener: Boolean = true) { + + // WebView + private var mWebView: WebView? = null + + // WebView 常用配置构建类 + private var mBuilder: Builder? = null + + init { + /** + * listener 是否复用监听事件 + * 使用全局配置, 需要手动调用 [apply] 方法 + */ + mBuilder = sGlobalBuilder?.clone(listener) + } + + // ========== + // = 静态方法 = + // ========== + + companion object { + + // 日志 TAG + private val TAG = WebViewAssist::class.java.simpleName + + // 全局配置 + private var sGlobalBuilder: Builder? = null + + /** + * 设置全局 WebView 常用配置构建类 + * @param builder [Builder] + */ + fun setGlobalBuilder(builder: Builder) { + sGlobalBuilder = builder + } + + /** + * 获取全局 WebView 常用配置构建类 + * @return [Builder] + */ + fun getGlobalBuilder(): Builder? { + return sGlobalBuilder + } + + // ========== + // = Cookie = + // ========== + + /** + * 将 Cookie 设置到 WebView + * @param url 加载的 Url + * @param cookie 同步的 cookie + * @return `true` success, `false` fail + * 若两次设置相同, 则覆盖上一次的 + * 同步 Cookie 要在 WebView 加载 url 之前, 否则 WebView 无法获得相应的 Cookie, 也就无法通过验证 + * Cookie 应该被及时更新, 否则很可能导致 WebView 拿的是旧的 session id 和服务器进行通信 + */ + fun setCookie( + url: String?, + cookie: String? + ): Boolean { + try { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + CookieSyncManager.createInstance(DevUtils.getContext()) + } + val cookieManager = CookieManager.getInstance() + // 如果没有特殊需求, 这里只需要将 session id 以 "key=value" 形式作为 Cookie 即可 + cookieManager.setCookie(url, cookie) + return true + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "setCookie") + } + return false + } + + /** + * 获取指定 Url 的 Cookie + * @param url Url + * @return Cookie + */ + fun getCookie(url: String?): String? { + try { + val cookieManager = CookieManager.getInstance() + return cookieManager.getCookie(url) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "getCookie") + } + return null + } + + /** + * 移除 Cookie + * @param callback 移除回调 + */ + fun removeCookie(callback: ValueCallback?) { + removeSessionCookie(callback) + removeAllCookie(callback) + } + + /** + * 移除 Session Cookie + * @param callback 移除回调 + * @return `true` success, `false` fail + */ + fun removeSessionCookie(callback: ValueCallback?): Boolean { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeSessionCookies(callback) + } else { + CookieManager.getInstance().removeSessionCookie() + } + return true + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "removeSessionCookie") + } + return false + } + + /** + * 移除所有的 Cookie + * @param callback 移除回调 + * @return `true` success, `false` fail + */ + fun removeAllCookie(callback: ValueCallback?): Boolean { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().removeAllCookies(callback) + } else { + CookieManager.getInstance().removeAllCookie() + } + return true + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "removeAllCookie") + } + return false + } + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 设置 WebView + * @param webView [WebView] + * @return [WebViewAssist] + * 如果在 [setWebView] 前调用了 [setBuilder] 则需要手动调用 [apply] + */ + fun setWebView(webView: WebView?): WebViewAssist { + mWebView = webView + return this + } + + /** + * 获取 WebView + * @return [WebView] + */ + fun getWebView(): WebView? { + return mWebView + } + + /** + * WebView 是否不为 null + * @return `true` yes, `false` no + */ + fun isWebViewNotEmpty(): Boolean { + return mWebView != null + } + + /** + * 设置 WebView 常用配置构建类 + * @param builder [Builder] + * @return [WebViewAssist] + */ + fun setBuilder(builder: Builder?): WebViewAssist { + return setBuilder(builder, true) + } + + /** + * 设置 WebView 常用配置构建类 + * @param builder [Builder] + * @param apply 是否应用配置 + * @return [WebViewAssist] + */ + fun setBuilder( + builder: Builder?, + apply: Boolean + ): WebViewAssist { + mBuilder = builder + mBuilder?.let { + it.setWebViewAssist(this) + if (apply) { + it.apply() + } + } + return this + } + + /** + * 获取 WebView 常用配置构建类 + * @return [Builder] + */ + fun getBuilder(): Builder? { + return mBuilder + } + + /** + * 应用 ( 设置 ) 配置 + * @return [Builder] + */ + fun apply(): WebViewAssist { + return setBuilder(mBuilder) + } + + // ========== + // = 加载方法 = + // ========== + + /** + * 加载网页 + * @param url 资源地址 + * @return [WebViewAssist] + * 加载一个网页 + * loadUrl("http://www.baidu.com") + * 加载应用资源文件内的网页 + * loadUrl("file:///android_asset/test.html") + */ + fun loadUrl(url: String): WebViewAssist { + mWebView?.loadUrl(url) + return this + } + + /** + * 加载网页 + * @param url 资源地址 + * @param additionalHttpHeaders Http 请求头信息 + * @return [WebViewAssist] + */ + fun loadUrl( + url: String, + additionalHttpHeaders: Map + ): WebViewAssist { + mWebView?.loadUrl(url, additionalHttpHeaders) + return this + } + + /** + * 加载 Html 代码 + * @param data Html 数据 + * @param mimeType 加载网页的类型 + * @param encoding 编码格式 + * @return [WebViewAssist] + */ + @Deprecated("推荐使用 loadDataWithBaseURL") + fun loadData( + data: String, + mimeType: String?, + encoding: String? + ): WebViewAssist { + mWebView?.loadData(data, mimeType, encoding) + return this + } + + /** + * 加载 Html 代码 + * @param baseUrl 基础链接 + * @param data Html 数据 + * @param mimeType 加载网页的类型 + * @param encoding 编码格式 + * @param historyUrl 可用历史记录 + * @return [WebViewAssist] + */ + fun loadDataWithBaseURL( + baseUrl: String?, + data: String, + mimeType: String?, + encoding: String?, + historyUrl: String? + ): WebViewAssist { + mWebView?.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl) + return this + } + + /** + * 使用 POST 方法将带有 postData 的 url 加载到 WebView 中 + * @param url 资源地址 + * @param postData post 数据 ( 注意 UrlEncode ) + * @return [WebViewAssist] + * 如果 url 不是网络 url [loadUrl] 加载 + */ + fun postUrl( + url: String, + postData: ByteArray + ): WebViewAssist { + mWebView?.postUrl(url, postData) + return this + } + + // = + + /** + * 加载 Html 代码 + * @param data Html 数据 + * @return [WebViewAssist] + */ + fun loadDataWithBaseURL(data: String): WebViewAssist { + return loadDataWithBaseURL(null, data, null) + } + + /** + * 加载 Html 代码 + * @param baseUrl 基础链接 + * @param data Html 数据 + * @param historyUrl 可用历史记录 + * @return [WebViewAssist] + */ + fun loadDataWithBaseURL( + baseUrl: String?, + data: String, + historyUrl: String? + ): WebViewAssist { + return loadDataWithBaseURL(baseUrl, data, "text/html", DevFinal.ENCODE.UTF_8, historyUrl) + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 获取 WebView 配置 + * @return [WebSettings] + */ + fun getSettings(): WebSettings? { + return mWebView?.settings + } + + /** + * 获取浏览器标识 UA + * @return 浏览器标识 UA + */ + fun getUserAgentString(): String? { + val webSettings = getSettings() + return webSettings?.userAgentString + } + + /** + * 设置浏览器标识 + * @param ua 浏览器标识 + * @return [WebViewAssist] + */ + fun setUserAgentString(ua: String?): WebViewAssist { + val webSettings = getSettings() + webSettings?.userAgentString = ua + return this + } + + // = + + /** + * 添加 JS 交互注入对象 + * @param obj JavaScript 交互方法注入对象 + * @param interfaceName 在 JavaScript 中公开对象的名称 + * @return [WebViewAssist] + */ + fun addJavascriptInterface( + obj: Any, + interfaceName: String + ): WebViewAssist { + mWebView?.addJavascriptInterface(obj, interfaceName) + return this + } + + /** + * 移除 JS 交互注入对象 + * @param interfaceName 在 JavaScript 中公开对象的名称 + * @return [WebViewAssist] + */ + fun removeJavascriptInterface(interfaceName: String): WebViewAssist { + mWebView?.removeJavascriptInterface(interfaceName) + return this + } + + /** + * 执行 JS 方法 + * @param script JS 内容 + * @param callback 执行回调结果 ( 返回值 ) + * @return [WebViewAssist] + */ + fun evaluateJavascript( + script: String, + callback: ValueCallback? + ): WebViewAssist { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mWebView?.let { + try { + it.evaluateJavascript(script, callback) + } catch (e: Exception) { + LogPrintUtils.eTag(TAG, e, "evaluateJavascript") + } + } + } + return this + } + + // = + + /** + * 设置处理各种通知和请求事件对象 + * @param client [WebViewClient] + * @return [WebViewAssist] + * 常用方法 + * onPageStarted()onPageFinished() 页面加载时和页面加载完毕时调用 + * shouldOverrideKeyEvent() 重写此方法才能处理浏览器中的按键事件 + * shouldInterceptRequest() 页面每一次请求资源之前都会调用这个方法 ( 非 UI 线程调用 ) + * onLoadResource() 页面加载资源时调用, 每加载一个资源 ( 比如图片 ) 就调用一次 + * onReceivedError() 加载页面的服务器出现错误 ( 比如 404 ) 时回调 + * onReceivedSslError() 重写此方法可以让 WebView 处理 https 请求 + * doUpdateVisitedHistory() 更新历史记录 + * onFormResubmission() 应用程序重新请求网页数据 + * onReceivedHttpAuthRequest() 获取返回信息授权请求 + * onScaleChanged() WebView 发生缩放改变时调用 + * onUnhandledKeyEvent() Key 事件未被加载时调用 + */ + fun setWebViewClient(client: WebViewClient): WebViewAssist { + mWebView?.webViewClient = client + return this + } + + /** + * 获取处理各种通知和请求事件对象 + * @return [WebViewClient] + */ + fun getWebViewClient(): WebViewClient? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return mWebView?.webViewClient + } + return null + } + + // = + + /** + * 设置辅助 WebView 处理 Javascript 对话框、标题等对象 + * @param client [WebChromeClient] + * @return [WebViewAssist] + * 常用方法 + * onProgressChanged() 获得网页的加载进度并显示 + * onReceivedTitle() 获得网页的标题时回调 + * onReceivedIcon() 获得网页的图标时回调 + * onCreateWindow() 打开新窗口时回调 + * onCloseWindow() 关闭窗口时回调 + * onJsAlert() 网页弹出提示框时触发此方法 + * onJsConfirm() 网页弹出确认框时触发此方法 + * onJsPrompt() 网页弹出输入框时触发此方法 + */ + fun setWebChromeClient(client: WebChromeClient?): WebViewAssist { + mWebView?.webChromeClient = client + return this + } + + /** + * 获取辅助 WebView 处理 Javascript 对话框、标题等对象 + * @return [WebChromeClient] + */ + fun getWebChromeClient(): WebChromeClient? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + return mWebView?.webChromeClient + } + return null + } + + // = + + /** + * 销毁处理 + * @return [WebViewAssist] + * 避免 WebView 引起的内存泄漏 + * 可通过 WebView 所在的 Activity 新启一个进程结束时 System.exit(0) 退出当前进程 + * Activity onDestroy use + */ + fun destroy(): WebViewAssist { + mWebView?.let { + it.clearHistory() + ViewUtils.removeSelfFromParent(mWebView) + it.loadUrl("about:blank") + it.stopLoading() + it.webChromeClient = null + it.destroy() + mWebView = null + mBuilder?.setWebViewAssist(null) + } + return this + } + + /** + * WebView 是否可以后退 + * @return `true` yes, `false` no + */ + fun canGoBack(): Boolean { + return mWebView?.canGoBack() ?: false + } + + /** + * WebView 后退 + * @return [WebViewAssist] + */ + fun goBack(): WebViewAssist { + mWebView?.goBack() + return this + } + + // = + + /** + * WebView 是否可以前进 + * @return `true` yes, `false` no + */ + fun canGoForward(): Boolean { + return mWebView?.canGoForward() ?: false + } + + /** + * WebView 前进 + * @return [WebViewAssist] + */ + fun goForward(): WebViewAssist { + mWebView?.goForward() + return this + } + + // = + + /** + * WebView 是否可以跳转到当前起始点相距的历史记录 + * @param steps 相距索引 + * @return `true` yes, `false` no + */ + fun canGoBackOrForward(steps: Int): Boolean { + return mWebView?.canGoBackOrForward(steps) ?: false + } + + /** + * WebView 跳转到当前起始点相距的历史记录 + * @param steps 相距索引 + * @return [WebViewAssist] + * 以当前的 index 为起始点前进或者后退到历史记录中指定的 steps + * 如果 steps 为负数则为后退, 正数则为前进 + */ + fun goBackOrForward(steps: Int): WebViewAssist { + mWebView?.goBackOrForward(steps) + return this + } + + // = + + /** + * 刷新页面 ( 当前页面的所有资源都会重新加载 ) + * @return [WebViewAssist] + */ + fun reload(): WebViewAssist { + mWebView?.reload() + return this + } + + /** + * 停止加载 + * @return [WebViewAssist] + */ + fun stopLoading(): WebViewAssist { + mWebView?.stopLoading() + return this + } + + /** + * 清除资源缓存 + * @param includeDiskFiles 是否清空本地缓存 ( false 则只会清空内存缓存, true 全部清空 ) + * @return [WebViewAssist] + * 缓存是针对每个应用程序的, 因此这将清除所有使用的 WebView 的缓存 + */ + fun clearCache(includeDiskFiles: Boolean): WebViewAssist { + mWebView?.clearCache(includeDiskFiles) + return this + } + + /** + * 清除当前 WebView 访问的历史记录 + * @return [WebViewAssist] + */ + fun clearHistory(): WebViewAssist { + mWebView?.clearHistory() + return this + } + + /** + * 清除自动完成填充的表单数据 + * @return [WebViewAssist] + * 并不会清除 WebView 存储到本地的数据 + */ + fun clearFormData(): WebViewAssist { + mWebView?.clearFormData() + return this + } + + /** + * 获取缩放比例 + * @return 缩放比例 + */ + fun getScale(): Float { + return mWebView?.scale ?: 1.0F + } + + /** + * 获取当前可见区域的顶端距整个页面顶端的距离 ( 当前内容滚动的距离 ) + * @return 当前内容滚动的距离 + */ + fun getScrollY(): Int { + return mWebView?.scrollY ?: 0 + } + + /** + * 获取当前内容横向滚动距离 + * @return 当前内容横向滚动距离 + */ + fun getScrollX(): Int { + return mWebView?.scrollX ?: 0 + } + + /** + * 获取 HTML 的高度 ( 原始高度, 不包括缩放后的高度 ) + * @return HTML 的高度 ( 原始高度, 不包括缩放后的高度 ) + * 可通过 setWebViewClient onScaleChanged(WebView view, float oldScale, float newScale) 获取缩放比例 + * 或者通过 webView.getScale() 获取 ( 该方法已抛弃 ) + */ + fun getContentHeight(): Int { + return mWebView?.contentHeight ?: 0 + } + + /** + * 获取缩放高度 + * @return 缩放高度 + */ + fun getScaleHeight(): Int { + return getScaleHeight(getScale()) + } + + /** + * 获取缩放高度 + * @param scale 缩放比例 + * @return 缩放高度 + */ + fun getScaleHeight(scale: Float): Int { + return (getContentHeight() * scale).toInt() + } + + /** + * 获取 WebView 控件高度 + * @return WebView 控件高度 + */ + fun getHeight(): Int { + return mWebView?.height ?: 0 + } + + /** + * 将视图内容向下滚动一半页面大小 + * @param bottom 是否滑动到底部 + * @return [WebViewAssist] + */ + fun pageDown(bottom: Boolean): WebViewAssist { + mWebView?.pageDown(bottom) + return this + } + + /** + * 将视图内容向上滚动一半页面大小 + * @param top 是否滑动到顶部 + * @return [WebViewAssist] + */ + fun pageUp(top: Boolean): WebViewAssist { + mWebView?.pageUp(top) + return this + } + + /** + * 处理按键 ( 是否回退 ) + * @param keyCode 按键类型 + * @param event 按键事件 + * @return `true` 拦截事件, `false` 不拦截接着处理 + * Activity use + * @Override + * public boolean onKeyDown(int keyCode, KeyEvent event) { + * if (webViewAssist.handlerKeyDown(keyCode, event)) return true + * return super.onKeyDown(keyCode, event) + * } + */ + fun handlerKeyDown( + keyCode: Int, + event: KeyEvent? + ): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK && canGoBack()) { + goBack() + return true + } + return false + } + + /** + * 关闭 WebView 硬件加速功能 + * @return [WebViewAssist] + * 解决 WebView 闪烁问题 + */ + fun setLayerTypeSoftware(): WebViewAssist { + return setLayerType(View.LAYER_TYPE_SOFTWARE, null) + } + + /** + * 设置 WebView 硬件加速类型 + * @param layerType 硬件加速类型 + * @param paint [Paint] + * @return [WebViewAssist] + */ + fun setLayerType( + layerType: Int, + paint: Paint? + ): WebViewAssist { + mWebView?.setLayerType(layerType, paint) + return this + } + + /** + * 获取当前 Url + * @return 当前 Url + */ + fun getUrl(): String? { + return mWebView?.url + } + + /** + * 获取最初请求 Url + * @return 最初请求 Url + */ + fun getOriginalUrl(): String? { + return mWebView?.originalUrl + } + + /** + * 获取长按事件类型 + * @return 长按事件类型 + * WebView.HitTestResult.UNKNOWN_TYPE // 未知类型 + * WebView.HitTestResult.PHONE_TYPE // 电话类型 + * WebView.HitTestResult.EMAIL_TYPE // 电子邮件类型 + * WebView.HitTestResult.GEO_TYPE // 地图类型 + * WebView.HitTestResult.SRC_ANCHOR_TYPE // 超链接类型 + * WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE // 带有链接的图片类型 + * WebView.HitTestResult.IMAGE_TYPE // 单纯的图片类型 + * WebView.HitTestResult.EDIT_TEXT_TYPE // 选中的文字类型 + *

+ * mWebView.setOnLongClickListener(new View.OnLongClickListener() { + * @Override + * public boolean onLongClick(View view) { + * WebView.HitTestResult result = webViewAssist.getHitTestResult() + * if(result != null) { + * switch (result.getType()) { + * case WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE: + * String imgUrl = result.getExtra() + * return true + * } + * } + * return false + * } + * }) + *

+ * HitTestResult.getType() 获取所选中目标的类型, 可以是图片、超链接、邮件、电话等等 + * HitTestResult.getExtra() 获取额外的信息 + */ + fun getHitTestResult(): HitTestResult? { + return mWebView?.hitTestResult + } + + // ========== + // = 配置相关 = + // ========== + + /** + * detail: WebView 常用配置构建类 + * @author Ttt + * 有特殊配置可在 [OnApplyListener.onApply] 回调中进行增加配置设置 + */ + open class Builder @JvmOverloads constructor(listener: OnApplyListener? = null) { + + // WebView 辅助类 + private var mWebViewAssist: WebViewAssist? = null + + // 应用配置监听事件 + private var mApplyListener: OnApplyListener? = null + + init { + /** + * listener 应用配置监听事件 + */ + setOnApplyListener(listener) + } + + // = + + /** + * 设置 WebView 辅助类 + * @param webViewAssist WebView 辅助类 + * @return [Builder] + */ + internal fun setWebViewAssist(webViewAssist: WebViewAssist?): Builder { + mWebViewAssist = webViewAssist + return this + } + + /** + * 应用 ( 设置 ) 配置 + * @return [Builder] + */ + fun apply(): Builder { + return applyPri() + } + + // ======= + // = 事件 = + // ======= + + /** + * 设置应用配置监听事件 + * @param listener [OnApplyListener] + * @return [Builder] + */ + fun setOnApplyListener(listener: OnApplyListener?): Builder { + mApplyListener = listener + return this + } + + /** + * 获取应用配置监听事件 + * @return [OnApplyListener] + */ + fun getApplyListener(): OnApplyListener? { + return mApplyListener + } + + /** + * detail: 应用配置监听事件 + * @author Ttt + */ + interface OnApplyListener { + + /** + * 应用配置通知方法 + * @param webViewAssist WebView 辅助类 + * @param builder WebView 常用配置构建类 + */ + fun onApply( + webViewAssist: WebViewAssist?, + builder: Builder + ) + } + + // ========== + // = 克隆方法 = + // ========== + + /** + * 克隆方法 ( 用于全局配置克隆操作 ) + * @param listener 是否复用监听事件 + * @return [Builder] + */ + fun clone(listener: Boolean): Builder { + val builder = Builder() + if (listener) { // 复用监听事件 + builder.setOnApplyListener(mApplyListener) + } + builder.mJavaScriptEnabled = mJavaScriptEnabled + builder.mRenderPriority = mRenderPriority + builder.mUseWideViewPort = mUseWideViewPort + builder.mLoadWithOverviewMode = mLoadWithOverviewMode + builder.mLayoutAlgorithm = mLayoutAlgorithm + builder.mSupportZoom = mSupportZoom + builder.mBuiltInZoomControls = mBuiltInZoomControls + builder.mDisplayZoomControls = mDisplayZoomControls + builder.mTextZoom = mTextZoom + builder.mStandardFontFamily = mStandardFontFamily + builder.mDefaultFontSize = mDefaultFontSize + builder.mMinimumFontSize = mMinimumFontSize + builder.mMixedContentMode = mMixedContentMode + builder.mLoadsImagesAutomatically = mLoadsImagesAutomatically + builder.mJavaScriptCanOpenWindowsAutomatically = mJavaScriptCanOpenWindowsAutomatically + builder.mDefaultTextEncodingName = mDefaultTextEncodingName + builder.mGeolocationEnabled = mGeolocationEnabled + builder.mUserAgentString = mUserAgentString + builder.mAllowFileAccess = mAllowFileAccess + builder.mAllowFileAccessFromFileURLs = mAllowFileAccessFromFileURLs + builder.mAllowUniversalAccessFromFileURLs = mAllowUniversalAccessFromFileURLs + builder.mCacheMode = mCacheMode + builder.mDomStorageEnabled = mDomStorageEnabled + builder.mAppCacheEnabled = mAppCacheEnabled + builder.mAppCachePath = mAppCachePath + builder.mAppCacheMaxSize = mAppCacheMaxSize + builder.mDatabaseEnabled = mDatabaseEnabled + builder.mDatabasePath = mDatabasePath + return builder + } + + /** + * 重置方法 + * @return [Builder] + */ + fun reset(): Builder { + this.mJavaScriptEnabled = true + this.mRenderPriority = null + this.mUseWideViewPort = false + this.mLoadWithOverviewMode = true + this.mLayoutAlgorithm = null + this.mSupportZoom = true + this.mBuiltInZoomControls = false + this.mDisplayZoomControls = false + this.mTextZoom = 0 + this.mStandardFontFamily = null + this.mDefaultFontSize = 0 + this.mMinimumFontSize = 0 + this.mMixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + this.mLoadsImagesAutomatically = true + this.mJavaScriptCanOpenWindowsAutomatically = true + this.mDefaultTextEncodingName = DevFinal.ENCODE.UTF_8 + this.mGeolocationEnabled = true + this.mUserAgentString = null + this.mAllowFileAccess = true + this.mAllowFileAccessFromFileURLs = false + this.mAllowUniversalAccessFromFileURLs = false + this.mCacheMode = WebSettings.LOAD_NO_CACHE + this.mDomStorageEnabled = true + this.mAppCacheEnabled = true + this.mAppCachePath = null + this.mAppCacheMaxSize = 5 * 1024 * 1024 + this.mDatabaseEnabled = true + this.mDatabasePath = null + return this + } + + // ========== + // = 配置方法 = + // ========== + + // 是否支持 JavaScript + private var mJavaScriptEnabled = true + + // 渲染优先级 + private var mRenderPriority: RenderPriority? = null + + // 是否使用宽视图 ( 是否支持 html viewport 设置了会导致字体变小 ) + private var mUseWideViewPort = false + + // 是否按宽度缩小内容以适合屏幕 + private var mLoadWithOverviewMode = true + + // 基础布局算法, 大于 4.4 使用 TEXT_AUTOSIZING 解决前端 REM 适配方案有误差情况 + private var mLayoutAlgorithm: LayoutAlgorithm? = null + + // 是否支持缩放 + private var mSupportZoom = true + + // 是否显示内置缩放工具 + private var mBuiltInZoomControls = false + + // 是否显示缩放工具 + private var mDisplayZoomControls = false + + // 文本缩放倍数 + private var mTextZoom = 0 + + // WebView 字体, 默认字体 "sans-serif" + private var mStandardFontFamily: String? = null + + // WebView 字体大小 + private var mDefaultFontSize = 0 + + // WebView 支持最小字体大小 + private var mMinimumFontSize = 0 + + // 混合内容模式 + private var mMixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + + // 是否支持自动加载图片 + private var mLoadsImagesAutomatically = true + + // 是否支持通过 JS 打开新窗口 + private var mJavaScriptCanOpenWindowsAutomatically = true + + // 编码格式 + private var mDefaultTextEncodingName: String? = DevFinal.ENCODE.UTF_8 + + // 是否允许网页执行定位操作 + private var mGeolocationEnabled = true + + // 浏览器标识 UA + private var mUserAgentString: String? = null + + // 是否可以访问文件 ( false 不影响 assets 和 resources 资源的加载 ) + private var mAllowFileAccess = true + + // 是否允许通过 file url 加载的 JS 代码读取其他的本地文件 + private var mAllowFileAccessFromFileURLs = false + + // 是否允许通过 file url 加载的 JS 可以访问其他的源 ( 包括 http、https 等源 ) + private var mAllowUniversalAccessFromFileURLs = false + + // WebView 缓存模式 + private var mCacheMode = WebSettings.LOAD_NO_CACHE + + // 是否支持 DOM Storage + private var mDomStorageEnabled = true + + // 是否开启 Application Caches 功能 + private var mAppCacheEnabled = true + + // Application Caches 地址 + private var mAppCachePath: String? = null + + // Application Caches 大小 + private var mAppCacheMaxSize = (5 * 1024 * 1024).toLong() + + // 是否支持数据库缓存 + private var mDatabaseEnabled = true + + // 数据库缓存路径 + private var mDatabasePath: String? = null + + /** + * 应用 ( 设置 ) 配置 + * @return [Builder] + */ + private fun applyPri(): Builder { + mWebViewAssist?.let { assist -> + assist.getSettings()?.let { webSettings -> + // 如果访问的页面中要与 JavaScript 交互, 则 WebView 必须设置支持 JavaScript + webSettings.javaScriptEnabled = mJavaScriptEnabled + if (mRenderPriority != null) { + webSettings.setRenderPriority(mRenderPriority) // 设置渲染优先级 + } + + // 设置自适应屏幕两者合用 + webSettings.useWideViewPort = mUseWideViewPort // 是否使用宽视图 + webSettings.loadWithOverviewMode = mLoadWithOverviewMode // 是否按宽度缩小内容以适合屏幕 + if (mLayoutAlgorithm != null) { // WebSettings.LayoutAlgorithm.SINGLE_COLUMN 4.4 抛弃了 + // 大于 4.4 使用 TEXT_AUTOSIZING 解决前端 REM 适配方案有误差情况 + webSettings.layoutAlgorithm = mLayoutAlgorithm + } + + // 缩放操作 + webSettings.setSupportZoom(mSupportZoom) // 是否支持缩放 + webSettings.builtInZoomControls = + mBuiltInZoomControls // 是否显示内置缩放工具, 若为 false 则该 WebView 不可缩放 + webSettings.displayZoomControls = mDisplayZoomControls // 是否显示缩放工具 + if (mTextZoom > 0) { + webSettings.textZoom = mTextZoom // 文本缩放倍数 + } + if (mStandardFontFamily != null) { + webSettings.standardFontFamily = mStandardFontFamily // 设置 WebView 字体 + } + if (mDefaultFontSize > 0) { + webSettings.defaultFontSize = mDefaultFontSize // 设置 WebView 字体大小 + } + if (mMinimumFontSize > 0) { + webSettings.minimumFontSize = mMinimumFontSize // 设置 WebView 支持最小字体大小 + } + + // 5.0 以上默认禁止了 https 和 http 混用以下方式是开启 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + webSettings.mixedContentMode = mMixedContentMode + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webSettings.loadsImagesAutomatically = + mLoadsImagesAutomatically // 是否支持自动加载图片 + } + + // 是否支持通过 JS 打开新窗口 + webSettings.javaScriptCanOpenWindowsAutomatically = + mJavaScriptCanOpenWindowsAutomatically + // 设置编码格式 + if (mDefaultTextEncodingName != null) { + webSettings.defaultTextEncodingName = mDefaultTextEncodingName + } + // 是否允许网页执行定位操作 + webSettings.setGeolocationEnabled(mGeolocationEnabled) + // 设置浏览器标识 UA + if (mUserAgentString != null) { + webSettings.userAgentString = mUserAgentString + } + + // 是否可以访问文件 + webSettings.allowFileAccess = mAllowFileAccess + // 是否允许通过 file url 加载的 JS 代码读取其他的本地文件 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + webSettings.allowFileAccessFromFileURLs = mAllowFileAccessFromFileURLs + } + // 是否允许通过 file url 加载的 JS 可以访问其他的源 ( 包括 http、https 等源 ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + webSettings.allowUniversalAccessFromFileURLs = + mAllowUniversalAccessFromFileURLs + } + // 设置 WebView 缓存模式 + if (mCacheMode > 0) { + // LOAD_CACHE_ONLY 不使用网络, 只读取本地缓存数据 + // LOAD_DEFAULT ( 默认 ) 根据 cache-control 决定是否从网络上取数据 + // LOAD_NO_CACHE 不使用缓存, 只从网络获取数据. + // LOAD_CACHE_ELSE_NETWORK 只要本地有, 无论是否过期或者 no-cache 都使用缓存中的数据 + webSettings.cacheMode = mCacheMode + } + + // 是否支持 DOM Storage + webSettings.domStorageEnabled = mDomStorageEnabled + // 是否开启 Application Caches 功能 + webSettings.setAppCacheEnabled(mAppCacheEnabled) + if (mAppCacheEnabled) { + // Application Caches 地址 + if (mAppCachePath != null) { + webSettings.setAppCachePath(mAppCachePath) + } + // Application Caches 大小 + webSettings.setAppCacheMaxSize(mAppCacheMaxSize) + } + + // 是否支持数据库缓存 + webSettings.databaseEnabled = mDatabaseEnabled + if (mDatabaseEnabled) { + // 数据库缓存路径 + if (mDatabasePath != null) { + webSettings.databasePath = mDatabasePath + } + } + } + } + mApplyListener?.onApply(mWebViewAssist, this) + return this + } + + // =========== + // = get/set = + // =========== + + /** + * 是否支持 JavaScript + * @return `true` yes, `false` no + */ + fun isJavaScriptEnabled(): Boolean { + return mJavaScriptEnabled + } + + /** + * 设置是否支持 JavaScript + * @param javaScriptEnabled `true` yes, `false` no + * @return [Builder] + */ + fun setJavaScriptEnabled(javaScriptEnabled: Boolean): Builder { + mJavaScriptEnabled = javaScriptEnabled + return this + } + + /** + * 获取渲染优先级 + * @return 渲染优先级 + */ + fun getRenderPriority(): RenderPriority? { + return mRenderPriority + } + + /** + * 设置渲染优先级 + * @param renderPriority 渲染优先级 + * @return [Builder] + */ + fun setRenderPriority(renderPriority: RenderPriority?): Builder { + mRenderPriority = renderPriority + return this + } + + /** + * 是否使用宽视图 + * @return `true` yes, `false` no + */ + fun isUseWideViewPort(): Boolean { + return mUseWideViewPort + } + + /** + * 设置是否使用宽视图 + * @param useWideViewPort `true` yes, `false` no + * @return [Builder] + * 是否支持 html viewport 设置了会导致字体变小 + */ + fun setUseWideViewPort(useWideViewPort: Boolean): Builder { + mUseWideViewPort = useWideViewPort + return this + } + + /** + * 是否按宽度缩小内容以适合屏幕 + * @return `true` yes, `false` no + */ + fun isLoadWithOverviewMode(): Boolean { + return mLoadWithOverviewMode + } + + /** + * 设置是否按宽度缩小内容以适合屏幕 + * @param loadWithOverviewMode `true` yes, `false` no + * @return [Builder] + */ + fun setLoadWithOverviewMode(loadWithOverviewMode: Boolean): Builder { + mLoadWithOverviewMode = loadWithOverviewMode + return this + } + + /** + * 获取基础布局算法 + * @return 基础布局算法 + */ + fun getLayoutAlgorithm(): LayoutAlgorithm? { + return mLayoutAlgorithm + } + + /** + * 设置基础布局算法 + * @param layoutAlgorithm 基础布局算法 + * @return [Builder] + */ + fun setLayoutAlgorithm(layoutAlgorithm: LayoutAlgorithm?): Builder { + mLayoutAlgorithm = layoutAlgorithm + return this + } + + /** + * 是否支持缩放 + * @return `true` yes, `false` no + */ + fun isSupportZoom(): Boolean { + return mSupportZoom + } + + /** + * 设置是否支持缩放 + * @param supportZoom `true` yes, `false` no + * @return [Builder] + */ + fun setSupportZoom(supportZoom: Boolean): Builder { + mSupportZoom = supportZoom + return this + } + + /** + * 是否显示内置缩放工具 + * @return `true` yes, `false` no + */ + fun isBuiltInZoomControls(): Boolean { + return mBuiltInZoomControls + } + + /** + * 设置是否显示内置缩放工具 + * @param builtInZoomControls `true` yes, `false` no + * @return [Builder] + */ + fun setBuiltInZoomControls(builtInZoomControls: Boolean): Builder { + mBuiltInZoomControls = builtInZoomControls + return this + } + + /** + * 是否显示缩放工具 + * @return `true` yes, `false` no + */ + fun isDisplayZoomControls(): Boolean { + return mDisplayZoomControls + } + + /** + * 设置是否显示缩放工具 + * @param displayZoomControls `true` yes, `false` no + * @return [Builder] + */ + fun setDisplayZoomControls(displayZoomControls: Boolean): Builder { + mDisplayZoomControls = displayZoomControls + return this + } + + /** + * 获取文本缩放倍数 + * @return 文本缩放倍数 + */ + fun getTextZoom(): Int { + return mTextZoom + } + + /** + * 设置文本缩放倍数 + * @param textZoom 文本缩放倍数 + * @return [Builder] + */ + fun setTextZoom(textZoom: Int): Builder { + mTextZoom = textZoom + return this + } + + /** + * 获取 WebView 字体 + * @return WebView 字体 + */ + fun getStandardFontFamily(): String? { + return mStandardFontFamily + } + + /** + * 设置 WebView 字体 + * @param standardFontFamily WebView 字体 + * @return [Builder] + */ + fun setStandardFontFamily(standardFontFamily: String?): Builder { + mStandardFontFamily = standardFontFamily + return this + } + + /** + * 获取 WebView 字体大小 + * @return WebView 字体大小 + */ + fun getDefaultFontSize(): Int { + return mDefaultFontSize + } + + /** + * 设置 WebView 字体大小 + * @param defaultFontSize WebView 字体大小 + * @return [Builder] + */ + fun setDefaultFontSize(defaultFontSize: Int): Builder { + mDefaultFontSize = defaultFontSize + return this + } + + /** + * 获取 WebView 支持最小字体大小 + * @return WebView 支持最小字体大小 + */ + fun getMinimumFontSize(): Int { + return mMinimumFontSize + } + + /** + * 设置 WebView 支持最小字体大小 + * @param minimumFontSize WebView 支持最小字体大小 + * @return [Builder] + */ + fun setMinimumFontSize(minimumFontSize: Int): Builder { + mMinimumFontSize = minimumFontSize + return this + } + + /** + * 获取混合内容模式 + * @return 混合内容模式 + */ + fun getMixedContentMode(): Int { + return mMixedContentMode + } + + /** + * 设置混合内容模式 + * @param mixedContentMode 混合内容模式 + * @return [Builder] + */ + fun setMixedContentMode(mixedContentMode: Int): Builder { + mMixedContentMode = mixedContentMode + return this + } + + /** + * 是否支持自动加载图片 + * @return `true` yes, `false` no + */ + fun isLoadsImagesAutomatically(): Boolean { + return mLoadsImagesAutomatically + } + + /** + * 设置是否支持自动加载图片 + * @param loadsImagesAutomatically `true` yes, `false` no + * @return [Builder] + */ + fun setLoadsImagesAutomatically(loadsImagesAutomatically: Boolean): Builder { + mLoadsImagesAutomatically = loadsImagesAutomatically + return this + } + + /** + * 是否支持通过 JS 打开新窗口 + * @return `true` yes, `false` no + */ + fun isJavaScriptCanOpenWindowsAutomatically(): Boolean { + return mJavaScriptCanOpenWindowsAutomatically + } + + /** + * 设置是否支持通过 JS 打开新窗口 + * @param javaScriptCanOpenWindowsAutomatically `true` yes, `false` no + * @return [Builder] + */ + fun setJavaScriptCanOpenWindowsAutomatically(javaScriptCanOpenWindowsAutomatically: Boolean): Builder { + mJavaScriptCanOpenWindowsAutomatically = javaScriptCanOpenWindowsAutomatically + return this + } + + /** + * 获取编码格式 + * @return 编码格式 + */ + fun getDefaultTextEncodingName(): String? { + return mDefaultTextEncodingName + } + + /** + * 设置编码格式 + * @param defaultTextEncodingName 编码格式 + * @return [Builder] + */ + fun setDefaultTextEncodingName(defaultTextEncodingName: String?): Builder { + mDefaultTextEncodingName = defaultTextEncodingName + return this + } + + /** + * 是否允许网页执行定位操作 + * @return `true` yes, `false` no + */ + fun isGeolocationEnabled(): Boolean { + return mGeolocationEnabled + } + + /** + * 设置是否允许网页执行定位操作 + * @param geolocationEnabled `true` yes, `false` no + * @return [Builder] + */ + fun setGeolocationEnabled(geolocationEnabled: Boolean): Builder { + mGeolocationEnabled = geolocationEnabled + return this + } + + /** + * 获取浏览器标识 UA + * @return 浏览器标识 UA + */ + fun getUserAgentString(): String? { + return mUserAgentString + } + + /** + * 设置浏览器标识 UA + * @param userAgentString 浏览器标识 UA + * @return [Builder] + */ + fun setUserAgentString(userAgentString: String?): Builder { + mUserAgentString = userAgentString + return this + } + + /** + * 是否可以访问文件 ( false 不影响 assets 和 resources 资源的加载 ) + * @return `true` yes, `false` no + */ + fun isAllowFileAccess(): Boolean { + return mAllowFileAccess + } + + /** + * 设置是否可以访问文件 ( false 不影响 assets 和 resources 资源的加载 ) + * @param allowFileAccess `true` yes, `false` no + * @return [Builder] + */ + fun setAllowFileAccess(allowFileAccess: Boolean): Builder { + mAllowFileAccess = allowFileAccess + return this + } + + /** + * 是否允许通过 file url 加载的 JS 代码读取其他的本地文件 + * @return `true` yes, `false` no + */ + fun isAllowFileAccessFromFileURLs(): Boolean { + return mAllowFileAccessFromFileURLs + } + + /** + * 设置是否允许通过 file url 加载的 JS 代码读取其他的本地文件 + * @param allowFileAccessFromFileURLs `true` yes, `false` no + * @return [Builder] + */ + fun setAllowFileAccessFromFileURLs(allowFileAccessFromFileURLs: Boolean): Builder { + mAllowFileAccessFromFileURLs = allowFileAccessFromFileURLs + return this + } + + /** + * 是否允许通过 file url 加载的 JS 可以访问其他的源 ( 包括 http、https 等源 ) + * @return `true` yes, `false` no + */ + fun isAllowUniversalAccessFromFileURLs(): Boolean { + return mAllowUniversalAccessFromFileURLs + } + + /** + * 设置是否允许通过 file url 加载的 JS 可以访问其他的源 ( 包括 http、https 等源 ) + * @param allowUniversalAccessFromFileURLs `true` yes, `false` no + * @return [Builder] + */ + fun setAllowUniversalAccessFromFileURLs(allowUniversalAccessFromFileURLs: Boolean): Builder { + mAllowUniversalAccessFromFileURLs = allowUniversalAccessFromFileURLs + return this + } + + /** + * 获取 WebView 缓存模式 + * @return WebView 缓存模式 + */ + fun getCacheMode(): Int { + return mCacheMode + } + + /** + * 设置 WebView 缓存模式 + * @param cacheMode WebView 缓存模式 + * @return [Builder] + */ + fun setCacheMode(cacheMode: Int): Builder { + mCacheMode = cacheMode + return this + } + + /** + * 是否支持 DOM Storage + * @return `true` yes, `false` no + */ + fun isDomStorageEnabled(): Boolean { + return mDomStorageEnabled + } + + /** + * 设置是否支持 DOM Storage + * @param domStorageEnabled `true` yes, `false` no + * @return [Builder] + */ + fun setDomStorageEnabled(domStorageEnabled: Boolean): Builder { + mDomStorageEnabled = domStorageEnabled + return this + } + + /** + * 是否开启 Application Caches 功能 + * @return `true` yes, `false` no + */ + fun isAppCacheEnabled(): Boolean { + return mAppCacheEnabled + } + + /** + * 设置是否开启 Application Caches 功能 + * @param appCacheEnabled `true` yes, `false` no + * @return [Builder] + */ + fun setAppCacheEnabled(appCacheEnabled: Boolean): Builder { + mAppCacheEnabled = appCacheEnabled + return this + } + + /** + * 获取 Application Caches 地址 + * @return Application Caches 地址 + */ + fun getAppCachePath(): String? { + return mAppCachePath + } + + /** + * 设置 Application Caches 地址 + * @param appCachePath Application Caches 地址 + * @return [Builder] + */ + fun setAppCachePath(appCachePath: String?): Builder { + mAppCachePath = appCachePath + return this + } + + /** + * 获取 Application Caches 大小 + * @return Application Caches 大小 + */ + fun getAppCacheMaxSize(): Long { + return mAppCacheMaxSize + } + + /** + * 设置 Application Caches 大小 + * @param appCacheMaxSize Application Caches 大小 + * @return [Builder] + */ + fun setAppCacheMaxSize(appCacheMaxSize: Long): Builder { + mAppCacheMaxSize = appCacheMaxSize + return this + } + + /** + * 是否支持数据库缓存 + * @return `true` yes, `false` no + */ + fun isDatabaseEnabled(): Boolean { + return mDatabaseEnabled + } + + /** + * 设置是否支持数据库缓存 + * @param databaseEnabled `true` yes, `false` no + * @return [Builder] + */ + fun setDatabaseEnabled(databaseEnabled: Boolean): Builder { + mDatabaseEnabled = databaseEnabled + return this + } + + /** + * 获取数据库缓存路径 + * @return 数据库缓存路径 + */ + fun getDatabasePath(): String? { + return mDatabasePath + } + + /** + * 设置数据库缓存路径 + * @param databasePath 数据库缓存路径 + * @return [Builder] + */ + fun setDatabasePath(databasePath: String?): Builder { + mDatabasePath = databasePath + return this + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/other/DataStoreUtils.kt b/lib/LocalModules/DevOther/src/main/java/dev/other/DataStoreUtils.kt new file mode 100644 index 0000000000..f1bd4cc3dc --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/other/DataStoreUtils.kt @@ -0,0 +1,700 @@ +package dev.other + +import android.content.Context +import androidx.datastore.core.DataMigration +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.SharedPreferencesMigration +import androidx.datastore.preferences.core.* +import androidx.datastore.preferences.core.Preferences.Key +import androidx.datastore.preferences.preferencesDataStoreFile +import dev.DevUtils +import dev.utils.DevFinal +import dev.utils.LogPrintUtils +import dev.utils.common.StringUtils +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import java.io.IOException + +/** + * detail: DataStore 工具类 + * @author Ttt + * Google 再见 SharedPreferences 拥抱 Jetpack DataStore + * @see https://juejin.im/post/6881442312560803853 + * Google 再见 SharedPreferences 拥抱 Jetpack DataStore ( 二 ) + * @see https://juejin.im/post/6888847647802097672 + *

+ * DataStore 文件存储目录: /data/data/<包名>/files/datastore + * 仅支持 Int、String、Boolean、Float、Long、Double + * 具体查看 [androidx.datastore.preferences.core.PreferencesKeys] + *

+ * 注意事项: 限制同一个 name 只能创建一次 DataStore 并存储对象进行存储复用 + * 多次创建 SingleProcessDataStore 会抛出 + * There are multiple DataStores active for the same file: $file. + * You should either maintain your DataStore as a singleton + * or confirm that there is no two DataStore's active on the same file + * (by confirming that the scope is cancelled) + */ +object DataStoreUtils { + + // 日志 TAG + private val TAG = DataStoreUtils::class.java.simpleName + + // Map + private val cacheMap = HashMap() + + // 默认值 + const val INT_VALUE: Int = DevFinal.DEFAULT.INT + const val LONG_VALUE: Long = DevFinal.DEFAULT.LONG + const val FLOAT_VALUE: Float = DevFinal.DEFAULT.FLOAT + const val DOUBLE_VALUE: Double = DevFinal.DEFAULT.DOUBLE + const val BOOLEAN_VALUE: Boolean = DevFinal.DEFAULT.BOOLEAN + const val STRING_VALUE: String = DevFinal.DEFAULT.STRING + + /** + * 获取 DataStore 操作类 + * @param storeName DataStore Name + * @return [InnerDataStore] + */ + fun get(storeName: String?): InnerDataStore { + val key = if (StringUtils.isEmpty(storeName)) TAG else storeName!! + var value = cacheMap[key] + if (value != null) return value + value = InnerDataStore(key) + cacheMap[key] = value + return value + } + + /** + * 获取 DataStore 操作类 + * @param dataStore [DataStore] + * @return [InnerDataStore] + */ + fun get(dataStore: DataStore?): InnerDataStore { + return InnerDataStore(dataStore) + } + + /** + * SharedPreferences 迁移到 DataStore + * @param storeName DataStore Name + * @param spNames SharedPreferences Name Array + * @return [InnerDataStore] + *

+ * 进行迁移前, 不能使用该 storeName 进行创建 DataStore + * 具体看顶部注意事项 + */ + @Throws(Exception::class) + fun migrationSPToDataStore( + storeName: String, + vararg spNames: String + ): InnerDataStore { + if (spNames.isEmpty()) throw Exception("spNames size is zero") + + val context = getContext() + val lists = mutableListOf>() + for (name in spNames) { + if (!StringUtils.isEmpty(name)) { + lists.add(SharedPreferencesMigration(context, name)) + } + } + // 传入 migrations 参数, 构建一个 DataStore 之后 + // 需要执行一次读或写, DataStore 才会自动合并 SharedPreference 文件内容 + val dataStore = PreferenceDataStoreFactory.create( + migrations = lists + ) { + getContext().preferencesDataStoreFile(storeName) + } + val value = InnerDataStore(dataStore) + cacheMap[storeName] = value + return value + } + + /** + * 移除 InnerDataStore 缓存 + * @param key storeName + * @return `true` success, `false` fail + */ + fun removeCache(key: String?): Boolean { + if (cacheMap.containsKey(key)) { + cacheMap.remove(key) + return true + } + return false + } + + /** + * 清空 InnerDataStore 缓存 + */ + fun clearCache() { + cacheMap.clear() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取全局 Context + * @return Context + */ + private fun getContext(): Context { + return DevUtils.getContext() + } + + // ========= + // = 内部类 = + // ========= + + /** + * detail: DataStore 内部操作类 + * @author Ttt + */ + class InnerDataStore private constructor() { + + private var dataStore: DataStore? = null + + constructor(storeName: String) : this() { + this.dataStore = PreferenceDataStoreFactory.create { + getContext().preferencesDataStoreFile(storeName) + } + } + + constructor(dataStore: DataStore?) : this() { + this.dataStore = dataStore + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + private suspend fun innerPut( + key: Key, + value: T + ) { + dataStore?.edit { mutablePreferences -> + mutablePreferences[key] = value + } + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 不存在 key 返回默认 Value + * @return [Flow] + */ + private fun innerGetFlow( + key: Key, + defaultValue: T + ): Flow? { + return dataStore?.data?.catch { + LogPrintUtils.eTag(TAG, it, key.name) + // 当读取数据遇到错误时, 如果是 IOException 异常, 发送一个 emptyPreferences, 来重新使用 + // 但是如果是其他的异常, 最好将它抛出去, 不要隐藏问题 + if (it is IOException) { + emit(emptyPreferences()) + } else { + throw it + } + }?.map { preferences -> preferences[key] ?: defaultValue } + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 不存在 key 返回默认 Value + * @return Value + */ + private suspend fun innerGetValue( + key: Key, + defaultValue: T + ): T { + return innerGetFlow(key, defaultValue)?.first()!! + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 获取 DataStore + * @return [DataStore] + */ + fun getDataStore(): DataStore? { + return dataStore + } + + // ======= + // = 存储 = + // ======= + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: Int + ) { + innerPut(key = intPreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: Int + ) { + innerPut(key = key, value = value) + } + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: String + ) { + innerPut(key = stringPreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: String + ) { + innerPut(key = key, value = value) + } + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: Boolean + ) { + innerPut(key = booleanPreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: Boolean + ) { + innerPut(key = key, value = value) + } + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: Float + ) { + innerPut(key = floatPreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: Float + ) { + innerPut(key = key, value = value) + } + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: Long + ) { + innerPut(key = longPreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: Long + ) { + innerPut(key = key, value = value) + } + + /** + * 保存数据 + * @param key Key + * @param value Value + */ + suspend fun put( + key: String, + value: Double + ) { + innerPut(key = doublePreferencesKey(key), value = value) + } + + /** + * 保存数据 + * @param key [Preferences.Key] + * @param value Value + */ + suspend fun put( + key: Key, + value: Double + ) { + innerPut(key = key, value = value) + } + + // ======== + // = Flow = + // ======== + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getIntFlow( + key: String, + defaultValue: Int = INT_VALUE + ): Flow? { + return innerGetFlow(key = intPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getIntFlow( + key: Key, + defaultValue: Int = INT_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getStringFlow( + key: String, + defaultValue: String = STRING_VALUE + ): Flow? { + return innerGetFlow(key = stringPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getStringFlow( + key: Key, + defaultValue: String = STRING_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getBooleanFlow( + key: String, + defaultValue: Boolean = BOOLEAN_VALUE + ): Flow? { + return innerGetFlow(key = booleanPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getBooleanFlow( + key: Key, + defaultValue: Boolean = BOOLEAN_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getFloatFlow( + key: String, + defaultValue: Float = FLOAT_VALUE + ): Flow? { + return innerGetFlow(key = floatPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getFloatFlow( + key: Key, + defaultValue: Float = FLOAT_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getLongFlow( + key: String, + defaultValue: Long = LONG_VALUE + ): Flow? { + return innerGetFlow(key = longPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getLongFlow( + key: Key, + defaultValue: Long = LONG_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key Key + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getDoubleFlow( + key: String, + defaultValue: Double = DOUBLE_VALUE + ): Flow? { + return innerGetFlow(key = doublePreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取数据 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return [Flow] + */ + fun getDoubleFlow( + key: Key, + defaultValue: Double = DOUBLE_VALUE + ): Flow? { + return innerGetFlow(key = key, defaultValue = defaultValue) + } + + // ========= + // = Value = + // ========= + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getInt( + key: String, + defaultValue: Int = INT_VALUE + ): Int { + return innerGetValue(key = intPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getInt( + key: Key, + defaultValue: Int = INT_VALUE + ): Int { + return innerGetValue(key = key, defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getString( + key: String, + defaultValue: String = STRING_VALUE + ): String { + return innerGetValue(key = stringPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getString( + key: Key, + defaultValue: String = STRING_VALUE + ): String { + return innerGetValue(key = key, defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getBoolean( + key: String, + defaultValue: Boolean = BOOLEAN_VALUE + ): Boolean { + return innerGetValue(key = booleanPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getBoolean( + key: Key, + defaultValue: Boolean = BOOLEAN_VALUE + ): Boolean { + return innerGetValue(key = key, defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getFloat( + key: String, + defaultValue: Float = FLOAT_VALUE + ): Float { + return innerGetValue(key = floatPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getFloat( + key: Key, + defaultValue: Float = FLOAT_VALUE + ): Float { + return innerGetValue(key = key, defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getLong( + key: String, + defaultValue: Long = LONG_VALUE + ): Long { + return innerGetValue(key = longPreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getLong( + key: Key, + defaultValue: Long = LONG_VALUE + ): Long { + return innerGetValue(key = key, defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key Key + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getDouble( + key: String, + defaultValue: Double = DOUBLE_VALUE + ): Double { + return innerGetValue(key = doublePreferencesKey(key), defaultValue = defaultValue) + } + + /** + * 获取值 + * @param key [Preferences.Key] + * @param defaultValue 默认 Value + * @return Value + */ + suspend fun getDouble( + key: Key, + defaultValue: Double = DOUBLE_VALUE + ): Double { + return innerGetValue(key = key, defaultValue = defaultValue) + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/AppStateReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/AppStateReceiver.kt new file mode 100644 index 0000000000..2cef57438f --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/AppStateReceiver.kt @@ -0,0 +1,105 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils + +/** + * detail: 应用状态监听广播 ( 安装、更新、卸载 ) + * @author Ttt + */ +class AppStateReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = AppStateReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 被操作应用包名 + val packageName = intent.data?.encodedSchemeSpecificPart + // 判断类型 + when (action) { + // 应用安装 + Intent.ACTION_PACKAGE_ADDED -> sListener?.onAdded(packageName) + // 应用更新 + Intent.ACTION_PACKAGE_REPLACED -> sListener?.onReplaced(packageName) + // 应用卸载 + Intent.ACTION_PACKAGE_REMOVED -> sListener?.onRemoved(packageName) + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 应用安装 + * @param packageName 应用包名 + */ + fun onAdded(packageName: String?) + + /** + * 应用更新 + * @param packageName 应用包名 + */ + fun onReplaced(packageName: String?) + + /** + * 应用卸载 + * @param packageName 应用包名 + */ + fun onRemoved(packageName: String?) + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = AppStateReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) // 安装 + addAction(Intent.ACTION_PACKAGE_REPLACED) // 更新 + addAction(Intent.ACTION_PACKAGE_REMOVED) // 卸载 + addDataScheme("package") + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/BatteryReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/BatteryReceiver.kt new file mode 100644 index 0000000000..af5d37f9e7 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/BatteryReceiver.kt @@ -0,0 +1,135 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils + +/** + * detail: 电量监听广播 + * @author Ttt + */ +class BatteryReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = BatteryReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 获取当前电量, 范围是 0-100 + val level = intent.getIntExtra("level", 0) + // 判断类型 + when (action) { + // 电量状态发送改变 + Intent.ACTION_BATTERY_CHANGED -> sListener?.onBatteryChanged(level) + // 电量低 + Intent.ACTION_BATTERY_LOW -> sListener?.onBatteryLow(level) + // 电量从低变回高 + Intent.ACTION_BATTERY_OKAY -> sListener?.onBatteryOkay(level) + // 连接充电器 + Intent.ACTION_POWER_CONNECTED -> sListener?.onPowerConnected(level, true) + // 断开充电器 + Intent.ACTION_POWER_DISCONNECTED -> sListener?.onPowerConnected(level, false) + // 电力使用情况总结 + Intent.ACTION_POWER_USAGE_SUMMARY -> sListener?.onPowerUsageSummary(level) + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 电量改变通知 + * @param level 电量百分比 + */ + fun onBatteryChanged(level: Int) + + /** + * 电量低通知 + * @param level 电量百分比 + */ + fun onBatteryLow(level: Int) + + /** + * 电量从低变回高通知 + * @param level 电量百分比 + */ + fun onBatteryOkay(level: Int) + + /** + * 充电状态改变通知 + * @param level 电量百分比 + * @param isConnected 是否充电连接中 + */ + fun onPowerConnected( + level: Int, + isConnected: Boolean + ) + + /** + * 电力使用情况总结 + * @param level 电量百分比 + */ + fun onPowerUsageSummary(level: Int) + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = BatteryReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 电量状态发送改变 + addAction(Intent.ACTION_BATTERY_CHANGED) + // 电量低 + addAction(Intent.ACTION_BATTERY_LOW) + // 电量从低变回高 + addAction(Intent.ACTION_BATTERY_OKAY) + // 连接充电器 + addAction(Intent.ACTION_POWER_CONNECTED) + // 断开充电器 + addAction(Intent.ACTION_POWER_DISCONNECTED) + // 电力使用情况总结 + addAction(Intent.ACTION_POWER_USAGE_SUMMARY) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/NetWorkReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/NetWorkReceiver.kt new file mode 100644 index 0000000000..c2e6a3727c --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/NetWorkReceiver.kt @@ -0,0 +1,132 @@ +package dev.receiver + +import android.Manifest +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import androidx.annotation.RequiresPermission +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils +import dev.utils.app.NetWorkUtils + +/** + * detail: 网络监听广播 + * @author Ttt + */ +class NetWorkReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = NetWorkReceiver::class.java.simpleName + + @SuppressLint("MissingPermission") + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 网络连接状态改变时通知 + if (ConnectivityManager.CONNECTIVITY_ACTION == action) { + try { + // 设置连接类型 + mConnectState = getConnectType() + // 触发事件 + sListener?.onNetworkState(mConnectState) + } catch (ignored: Exception) { + } + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 网络连接状态改变时通知 + * @param type 通知类型 + */ + fun onNetworkState(type: Int) + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = NetWorkReceiver() + + private var sListener: Listener? = null + + private const val BASE = 202030 + + // Wifi + const val NET_WIFI = BASE + 1 + + // 移动网络 + const val NET_MOBILE = BASE + 2 + + // ( 无网络 / 未知 ) 状态 + const val NO_NETWORK = BASE + 3 + + // 当前连接的状态 + private var mConnectState = NO_NETWORK + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 网络连接状态改变时通知 + addAction(ConnectivityManager.CONNECTIVITY_ACTION) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + + /** + * 是否连接网络 + * @return `true` yes, `false` no + */ + fun isConnectNetWork(): Boolean { + return mConnectState == NET_WIFI || mConnectState == NET_MOBILE + } + + /** + * 获取连接的网络类型 + * @return 连接的网络类型 + */ + @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE) + fun getConnectType(): Int { + // -1 = 等于未知, 1 = Wifi, 2 = 移动网络 + return when (NetWorkUtils.getConnectType()) { + 1 -> NET_WIFI + 2 -> NET_MOBILE + else -> NO_NETWORK + } + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/PhoneReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/PhoneReceiver.kt new file mode 100644 index 0000000000..d107bec7e8 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/PhoneReceiver.kt @@ -0,0 +1,168 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils + +/** + * detail: 手机监听广播 + * @author Ttt + * 所需权限 + * + * + * + */ +class PhoneReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = PhoneReceiver::class.java.simpleName + + // 是否拨号打出 + private var mDialOut = false + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 通话号码 + val number: String? + // 判断类型 + when (action) { + // 拨出电话意图 + NEW_OUTGOING_CALL -> { + // 表示属于拨号 + mDialOut = true + // 拨出号码 + number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) + // 触发事件 + sListener?.onPhoneStateChanged(CallState.OUTGOING, number) + } + // 电话状态监听意图 + PHONE_STATE -> { + // 通话号码 + number = intent.getStringExtra("incoming_number") + // 通话状态 + intent.getStringExtra("state")?.let { state -> + // 判断状态 + when (state.uppercase()) { + RINGING -> { // 未接 + mDialOut = false + // 接入电话铃响 + sListener?.onPhoneStateChanged(CallState.INCOMING_RING, number) + } + OFFHOOK -> { // 已接 + if (!mDialOut) { + // 接入通话中 + sListener?.onPhoneStateChanged(CallState.INCOMING, number) + } else { + } + } + IDLE -> { // 挂断 + if (mDialOut) { + // 播出电话结束 + sListener?.onPhoneStateChanged(CallState.OUTGOING_END, number) + } else { + // 接入通话完毕 + sListener?.onPhoneStateChanged(CallState.INCOMING_END, number) + } + } + else -> { + } + } + } + } + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 电话状态改变通知 + * @param state 通话状态 + * @param number 通话号码 + */ + fun onPhoneStateChanged( + state: CallState, + number: String? + ) + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = PhoneReceiver() + + private var sListener: Listener? = null + + // 电话状态监听意图 + private const val PHONE_STATE = "android.intent.action.PHONE_STATE" + + // 拨出电话意图 + private const val NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL" + + // 未接 + private const val RINGING = "RINGING" + + // 已接 + private const val OFFHOOK = "OFFHOOK" + + // 挂断 + private const val IDLE = "IDLE" + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 电话状态监听意图 + addAction(PHONE_STATE) + // 拨出电话意图 + addAction(NEW_OUTGOING_CALL) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + } + + /** + * detail: 通话状态 + * @author Ttt + */ + enum class CallState { + OUTGOING, // 播出电话 + OUTGOING_END, // 播出电话结束 + INCOMING_RING, // 接入电话铃响 + INCOMING, // 接入通话中 + INCOMING_END // 接入通话完毕 + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/SMSReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/SMSReceiver.kt new file mode 100644 index 0000000000..deab8c9397 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/SMSReceiver.kt @@ -0,0 +1,138 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.telephony.SmsMessage +import dev.utils.app.AppUtils + +/** + * detail: 短信监听广播 + * @author Ttt + * 所需权限 + * + */ +class SMSReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = SMSReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + (intent.extras?.get("pdus") as? Array<*>)?.let { pdus -> + var originatingAddress: String? = null + var serviceCenterAddress: String? = null + // 消息内容 + val builder = StringBuilder() + // 循环拼接内容 + for (it in pdus) { + val sms = SmsMessage.createFromPdu(it as? ByteArray) + builder.append(sms.messageBody) // 消息内容 ( 多条消息合并成一条 ) + originatingAddress = sms.originatingAddress + serviceCenterAddress = sms.serviceCenterAddress + // 收到消息提醒 ( 超过长度的消息变成两条会触发多次 ) + sListener?.onMessage(sms) + } + // 最终触发通知 ( 超过长度的短信消息, 最终合并成一条内容体 ) + sListener?.onMessage( + builder.toString(), originatingAddress, serviceCenterAddress + ) + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 最终触发通知 ( 超过长度的短信消息, 最终合并成一条内容体 ) + * @param message 短信内容 + * @param originatingAddress 短信的原始地址 ( 发件人 ) + * @param serviceCenterAddress 短信服务中心地址 + */ + fun onMessage( + message: String?, + originatingAddress: String?, + serviceCenterAddress: String? + ) + + /** + * 收到消息提醒 ( 超过长度的消息变成两条会触发多次 ) + * @param message [SmsMessage] + */ + fun onMessage(message: SmsMessage?) {} + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = SMSReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 短信获取监听 + addAction("android.provider.Telephony.SMS_RECEIVED") + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + + // ========== + // = 其他方法 = + // ========== + + /** + * 获取消息数据 + * @param message [SmsMessage] + * @return 消息数据 + */ + fun getMessageData(message: SmsMessage?): String { + val builder = StringBuilder() + message?.let { it -> + builder.append("\ngetDisplayMessageBody: ").append(it.displayMessageBody) + builder.append("\ngetDisplayOriginatingAddress: ") + .append(it.displayOriginatingAddress) + builder.append("\ngetEmailBody: ").append(it.emailBody) + builder.append("\ngetEmailFrom: ").append(it.emailFrom) + builder.append("\ngetMessageBody: ").append(it.messageBody) + builder.append("\ngetOriginatingAddress: ").append(it.originatingAddress) + builder.append("\ngetPseudoSubject: ").append(it.pseudoSubject) + builder.append("\ngetServiceCenterAddress: ").append(it.serviceCenterAddress) + builder.append("\ngetIndexOnIcc: ").append(it.indexOnIcc) + builder.append("\ngetMessageClass: ").append(it.messageClass) + builder.append("\ngetUserData: ").append(String(it.userData)) + } + return builder.toString() + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/ScreenReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/ScreenReceiver.kt new file mode 100644 index 0000000000..19b2714659 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/ScreenReceiver.kt @@ -0,0 +1,102 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils + +/** + * detail: 屏幕监听广播 ( 锁屏 / 解锁 / 亮屏 ) + * @author Ttt + */ +class ScreenReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = ScreenReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 判断类型 + when (action) { + // 用户打开屏幕 ( 屏幕变亮 ) + Intent.ACTION_SCREEN_ON -> sListener?.screenOn() + // 锁屏触发 + Intent.ACTION_SCREEN_OFF -> sListener?.screenOff() + // 用户解锁触发 + Intent.ACTION_USER_PRESENT -> sListener?.userPresent() + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 用户打开屏幕 ( 屏幕变亮 ) + */ + fun screenOn() + + /** + * 锁屏触发 + */ + fun screenOff() + + /** + * 用户解锁触发 + */ + fun userPresent() + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = ScreenReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 用户打开屏幕 ( 屏幕变亮 ) + addAction(Intent.ACTION_SCREEN_ON) + // 锁屏触发 + addAction(Intent.ACTION_SCREEN_OFF) + // 用户解锁触发 + addAction(Intent.ACTION_USER_PRESENT) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/TimeReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/TimeReceiver.kt new file mode 100644 index 0000000000..54f0192159 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/TimeReceiver.kt @@ -0,0 +1,102 @@ +package dev.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils + +/** + * detail: 时间监听广播 + * @author Ttt + */ +class TimeReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = TimeReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 判断类型 + when (action) { + // 每分钟调用 + Intent.ACTION_TIME_TICK -> sListener?.onTimeTick() + // 设置时间 + Intent.ACTION_TIME_CHANGED -> sListener?.onTimeChanged() + // 时区改变 + Intent.ACTION_TIMEZONE_CHANGED -> sListener?.onTimeZoneChanged() + } + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 每分钟调用 + */ + fun onTimeTick() + + /** + * 设置时间 + */ + fun onTimeChanged() + + /** + * 时区改变 + */ + fun onTimeZoneChanged() + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = TimeReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 每分钟调用 + addAction(Intent.ACTION_TIME_TICK) + // 设置时间 + addAction(Intent.ACTION_TIME_CHANGED) + // 时区改变 + addAction(Intent.ACTION_TIMEZONE_CHANGED) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/receiver/WifiReceiver.kt b/lib/LocalModules/DevOther/src/main/java/dev/receiver/WifiReceiver.kt new file mode 100644 index 0000000000..77aa9e7464 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/receiver/WifiReceiver.kt @@ -0,0 +1,273 @@ +package dev.receiver + +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.net.NetworkInfo +import android.net.wifi.WifiManager +import android.os.Message +import android.os.Parcelable +import dev.utils.LogPrintUtils +import dev.utils.app.AppUtils +import dev.utils.app.wifi.WifiUtils + +/** + * detail: Wifi 监听广播 + * @author Ttt + * 所需权限 + * + * + */ +class WifiReceiver private constructor() : BroadcastReceiver() { + + // 日志 TAG + private val TAG = WifiReceiver::class.java.simpleName + + override fun onReceive( + context: Context, + intent: Intent + ) { + val action = intent.action + // 打印触发的广播 + LogPrintUtils.dTag(TAG, "onReceive Action: %s", action) + // 触发回调通知 ( 每次进入都通知 ) + sListener?.onIntoTrigger() + // 内部处理 + innerReceive(context, intent) + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 触发回调通知 ( 每次进入都通知 ) + */ + fun onIntoTrigger() {} + + /** + * 触发回调通知 + * @param what 触发类型 + */ + fun onTrigger(what: Int) + + /** + * 触发回调通知 ( Wifi 连接过程的状态 ) + * @param what 触发类型 + * @param msg 触发信息 + */ + fun onTrigger( + what: Int, + msg: Message? + ) + + /** + * Wifi 开关状态 + * @param isOpenWifi 是否打开 Wifi + */ + fun onWifiSwitch(isOpenWifi: Boolean) + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + + private val sReceiver = WifiReceiver() + + private var sListener: Listener? = null + + /** + * 注册广播监听 + */ + fun register() { + IntentFilter().apply { + // 当调用 WifiManager 的 startScan() 方法, 扫描结束后, 系统会发出改 Action 广播 + addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) + // 当前连接的 Wifi 强度发生变化触发 + addAction(WifiManager.RSSI_CHANGED_ACTION) + // Wifi 在连接过程的状态返回 + addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION) + // 监听 Wifi 的打开与关闭等状态, 与 Wifi 的连接无关 + addAction(WifiManager.WIFI_STATE_CHANGED_ACTION) + // 发送 Wifi 连接的过程信息, 如果出错 ERROR 信息才会收到, 连接 Wifi 时触发, 触发多次 + addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION) + // 判断是否 Wifi 打开了, 变化触发一次 + addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION) + // 注册广播 + AppUtils.registerReceiver(sReceiver, this) + } + } + + /** + * 取消广播监听 + */ + fun unregister() = AppUtils.unregisterReceiver(sReceiver) + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + + // ======= + // = 常量 = + // ======= + + private const val BASE = 302030 + + // startScan() 扫描附近 Wifi 结束触发 + const val WIFI_SCAN_FINISH = BASE + 1 + + // 已连接的 Wifi 强度发生变化 + const val WIFI_RSSI_CHANGED = BASE + 2 + + // Wifi 认证错误 ( 密码错误等 ) + const val WIFI_ERROR_AUTHENTICATING = BASE + 3 + + // 连接错误 ( 其他错误 ) + const val WIFI_ERROR_UNKNOWN = BASE + 4 + + // Wifi 已打开 + const val WIFI_STATE_ENABLED = BASE + 5 + + // Wifi 正在打开 + const val WIFI_STATE_ENABLING = BASE + 6 + + // Wifi 已关闭 + const val WIFI_STATE_DISABLED = BASE + 7 + + // Wifi 正在关闭 + const val WIFI_STATE_DISABLING = BASE + 8 + + // Wifi 状态未知 + const val WIFI_STATE_UNKNOWN = BASE + 9 + + // Wifi 连接成功 + const val CONNECTED = BASE + 10 + + // Wifi 连接中 + const val CONNECTING = BASE + 11 + + // Wifi 连接失败、断开 + const val DISCONNECTED = BASE + 12 + + // Wifi 暂停、延迟 + const val SUSPENDED = BASE + 13 + + // Wifi 未知 + const val UNKNOWN = BASE + 14 + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 收到广播时处理 + * @param context Context + * @param intent Intent + */ + @SuppressLint("MissingPermission") + private fun innerReceive( + context: Context, + intent: Intent + ) { + // 判断类型 + when (intent.action) { + // 当调用 WifiManager 的 startScan() 方法, 扫描结束后, 系统会发出改 Action 广播 + WifiManager.SCAN_RESULTS_AVAILABLE_ACTION -> { + sListener?.onTrigger(WIFI_SCAN_FINISH) + } + // 当前连接的 Wifi 强度发生变化触发 + WifiManager.RSSI_CHANGED_ACTION -> { + sListener?.onTrigger(WIFI_RSSI_CHANGED) + } + // 发送 Wifi 连接的过程信息, 如果出错 ERROR 信息才会收到, 连接 Wifi 时触发, 触发多次 + WifiManager.SUPPLICANT_STATE_CHANGED_ACTION -> { + // 出现错误状态, 则获取错误状态 + val wifiErrorCode = intent.getIntExtra( + WifiManager.EXTRA_SUPPLICANT_ERROR, 0 + ) + // 判断错误状态 + when (wifiErrorCode) { + // 认证错误, 如密码错误等 + WifiManager.ERROR_AUTHENTICATING -> { + sListener?.onTrigger(WIFI_ERROR_AUTHENTICATING) + } + // 连接错误 ( 其他错误 ) + else -> { + sListener?.onTrigger(WIFI_ERROR_UNKNOWN) + } + } + } + // 监听 Wifi 的打开与关闭等状态, 与 Wifi 的连接无关 + WifiManager.WIFI_STATE_CHANGED_ACTION -> { + // 获取 Wifi 状态 + val wifiState = intent.getIntExtra( + WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN + ) + when (wifiState) { + // 已打开 + WifiManager.WIFI_STATE_ENABLED -> sListener?.onTrigger(WIFI_STATE_ENABLED) + // 正在打开 + WifiManager.WIFI_STATE_ENABLING -> sListener?.onTrigger(WIFI_STATE_ENABLING) + // 已关闭 + WifiManager.WIFI_STATE_DISABLED -> sListener?.onTrigger(WIFI_STATE_DISABLED) + // 正在关闭 + WifiManager.WIFI_STATE_DISABLING -> sListener?.onTrigger(WIFI_STATE_DISABLING) + // 未知 + WifiManager.WIFI_STATE_UNKNOWN -> sListener?.onTrigger(WIFI_STATE_UNKNOWN) + } + } + // Wifi 在连接过程的状态返回 + WifiManager.NETWORK_STATE_CHANGED_ACTION -> { + (intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO) as? NetworkInfo)?.let { networkInfo -> + // 获取连接的状态 + val state = networkInfo.state + // 通知消息 + val msg = Message() + // 当前连接的 SSID + msg.obj = WifiUtils.getSSID() + msg.what = UNKNOWN + // 判断连接状态 + when (state) { + // 连接成功 + NetworkInfo.State.CONNECTED -> msg.what = CONNECTED + // 连接中 + NetworkInfo.State.CONNECTING -> msg.what = CONNECTING + // 连接失败、断开 + NetworkInfo.State.DISCONNECTED -> msg.what = DISCONNECTED + // 暂停、延迟 + NetworkInfo.State.SUSPENDED -> msg.what = SUSPENDED + // 未知 + NetworkInfo.State.UNKNOWN -> msg.what = UNKNOWN + else -> msg.what = UNKNOWN + } + // 触发回调 + sListener?.onTrigger(msg.what, msg) + } + } + // 判断是否 Wifi 打开了, 变化触发一次 + WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION -> { + // 判断是否打开 Wifi + val isOpenWifi = intent.getBooleanExtra( + WifiManager.EXTRA_SUPPLICANT_CONNECTED, false + ) + // 触发回调 + sListener?.onWifiSwitch(isOpenWifi) + } + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/service/AccessibilityListenerService.kt b/lib/LocalModules/DevOther/src/main/java/dev/service/AccessibilityListenerService.kt new file mode 100644 index 0000000000..0409d5670f --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/service/AccessibilityListenerService.kt @@ -0,0 +1,182 @@ +package dev.service + +import android.accessibilityservice.AccessibilityService +import android.view.accessibility.AccessibilityEvent +import dev.utils.LogPrintUtils +import dev.utils.app.AccessibilityUtils +import dev.utils.app.ServiceUtils + +/** + * detail: 无障碍功能监听服务 + * @author Ttt + * 所需权限 + * + * 具体配合 [AccessibilityUtils] 使用 + * AccessibilityService 在 API < 18 的时候使用 AccessibilityService + * 动态配置方式, 只能用来配置动态属性: eventTypes、feedbackType、flags、notificationTimeout、packageNames + * var serviceInfo = AccessibilityServiceInfo() + * serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK + * serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC + * serviceInfo.packageNames = new String[]{ "afkt.project" } + * serviceInfo.notificationTimeout=100 + * setServiceInfo(serviceInfo) + */ +class AccessibilityListenerService : AccessibilityService() { + + /** + * 通过这个函数可以接收系统发送来的 AccessibilityEvent + * @param event [AccessibilityEvent] + * 接收来的 AccessibilityEvent 是经过过滤的, 过滤是在配置工作时设置的 + */ + override fun onAccessibilityEvent(event: AccessibilityEvent) { + AccessibilityUtils.setService(this) + sListener?.onAccessibilityEvent(event, this) + } + + /** + * 系统想要中断 AccessibilityService 返给的响应时会调用 + */ + override fun onInterrupt() { + LogPrintUtils.dTag(TAG, "onInterrupt") + sListener?.onInterrupt() + } + + /** + * 系统成功绑定该服务时被触发, 也就是当你在设置中开启相应的服务 + * 系统成功的绑定了该服务时会触发, 通常我们可以在这里做一些初始化操作 + */ + override fun onServiceConnected() { + super.onServiceConnected() + LogPrintUtils.dTag(TAG, "onServiceConnected") + } + + // ========== + // = 生命周期 = + // ========== + + override fun onCreate() { + super.onCreate() + LogPrintUtils.dTag(TAG, "onCreate") + sListener?.onServiceCreated(this) + self = this + } + + override fun onDestroy() { + super.onDestroy() + LogPrintUtils.dTag(TAG, "onDestroy") + sListener?.onServiceDestroy() + sListener = null + self = null + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 通过这个函数可以接收系统发送来的 AccessibilityEvent + * @param event [AccessibilityEvent] + * @param service [AccessibilityListenerService] + * 接收来的 AccessibilityEvent 是经过过滤的, 过滤是在配置工作时设置的 + */ + fun onAccessibilityEvent( + event: AccessibilityEvent?, + service: AccessibilityListenerService? + ) + + /** + * 系统想要中断 AccessibilityService 返给的响应时会调用 + */ + fun onInterrupt() {} + + /** + * 服务创建通知 + * @param service [AccessibilityListenerService] + */ + fun onServiceCreated(service: AccessibilityListenerService?) {} + + /** + * 服务销毁通知 + */ + fun onServiceDestroy() {} + } + + // ============= + // = 对外公开方法 = + // ============= + + companion object { + // 日志 TAG + private val TAG = AccessibilityService::class.java.simpleName + + /** + * 获取当前服务所持对象 + * @return [AccessibilityListenerService] + */ + // 当前服务所持对象 + var self: AccessibilityListenerService? = null + private set + + /** + * 启动服务 + */ + fun startService() { + ServiceUtils.startService(AccessibilityListenerService::class.java) + } + + /** + * 停止服务 + */ + fun stopService() { + ServiceUtils.stopService(AccessibilityListenerService::class.java) + } + + // = + + private var sListener: Listener? = null + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + + // = + + /** + * 检查是否开启无障碍功能 + * @return `true` open, `false` close + * 未开启则跳转至辅助功能设置页面 + */ + fun checkAccessibility(): Boolean { + return AccessibilityUtils.checkAccessibility() + } + + /** + * 检查是否开启无障碍功能 + * @param packageName 应用包名 + * @return `true` open, `false` close + * 未开启则跳转至辅助功能设置页面 + */ + fun checkAccessibility(packageName: String?): Boolean { + return AccessibilityUtils.checkAccessibility(packageName) + } + + /** + * 判断是否开启无障碍功能 + * @param packageName 应用包名 + * @return `true` open, `false` close + */ + fun isAccessibilitySettingsOn(packageName: String?): Boolean { + return AccessibilityUtils.isAccessibilitySettingsOn(packageName) + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/java/dev/service/NotificationService.kt b/lib/LocalModules/DevOther/src/main/java/dev/service/NotificationService.kt new file mode 100644 index 0000000000..87e0395d4e --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/java/dev/service/NotificationService.kt @@ -0,0 +1,218 @@ +package dev.service + +import android.annotation.TargetApi +import android.content.Intent +import android.os.Build +import android.service.notification.NotificationListenerService +import android.service.notification.StatusBarNotification +import dev.utils.LogPrintUtils +import dev.utils.app.NotificationUtils +import dev.utils.app.ServiceUtils + +/** + * detail: 通知栏监听服务 + * @author Ttt + * @see https://www.jianshu.com/p/981e7de2c7be + * cancelAllNotifications() 删除系统中所有可被清除的通知 + * getActiveNotifications() 返回当前系统所有通知到 StatusBarNotification[] + * 所需权限 + * + */ +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +class NotificationService : NotificationListenerService() { + + // ========== + // = 通知回调 = + // ========== + + /** + * 当系统收到新的通知后触发回调 + * @param sbn [StatusBarNotification] + */ + override fun onNotificationPosted(sbn: StatusBarNotification) { + if (self != null) sListener?.apply { onNotificationPosted(sbn) } + } + + /** + * 当系统通知被删掉后触发回调 + * @param sbn [StatusBarNotification] + */ + override fun onNotificationRemoved(sbn: StatusBarNotification) { + if (self != null) sListener?.apply { onNotificationRemoved(sbn) } + } + + // ========== + // = 生命周期 = + // ========== + + override fun onCreate() { + super.onCreate() + LogPrintUtils.dTag(TAG, "onCreate") + sListener?.onServiceCreated(this) + self = this + } + + override fun onDestroy() { + super.onDestroy() + LogPrintUtils.dTag(TAG, "onDestroy") + sListener?.onServiceDestroy() + sListener = null + self = null + } + + override fun onStartCommand( + intent: Intent, + flags: Int, + startId: Int + ): Int { + LogPrintUtils.dTag(TAG, "onStartCommand") + sListener?.let { + return it.onStartCommand(this, intent, flags, startId) + } + return START_STICKY + } + + // ======= + // = 接口 = + // ======= + + /** + * detail: 监听回调事件 + * @author Ttt + */ + interface Listener { + + /** + * 服务创建通知 + * @param service [NotificationService] + */ + fun onServiceCreated(service: NotificationService?) + + /** + * 服务销毁通知 + */ + fun onServiceDestroy() + + /** + * 触发服务指令 + * @param service [NotificationService] + * @param intent [Intent] + * @param flags Additional data about this start request. + * @param startId A unique integer representing this specific request to start. Use with [stopSelfResult]. + * @return The return value indicates what semantics the system should + * use for the service's current started state. It may be one of the + * constants associated with the [START_CONTINUATION_MASK] bits. + */ + fun onStartCommand( + service: NotificationService?, + intent: Intent?, + flags: Int, + startId: Int + ): Int + + /** + * 当系统收到新的通知后触发回调 + * @param sbn [StatusBarNotification] + * 当 API > 18 时, 利用 Notification.extras 来获取通知内容, extras 是在 API 19 时被加入的 + * 当 API = 18 时, 利用反射获取 Notification 中的内容 + */ + fun onNotificationPosted(sbn: StatusBarNotification?) + + /** + * 当系统通知被删掉后触发回调 + * @param sbn [StatusBarNotification] + */ + fun onNotificationRemoved(sbn: StatusBarNotification?) + } + + // ============= + // = 对外公开方法 = + // ============= + + /** + * 取消通知方法 + * @param sbn [StatusBarNotification] + * cancelNotification(String key) 是 API >= 21 才可以使用的, 利用 StatusBarNotification 的 getKey() 方法来获取 key 并取消通知 + * cancelNotification(String pkg, String tag, int id) 在 API < 21 时可以使用, 在 API >= 21 时使用此方法来取消通知将无效 ( 被废弃 ) + */ + fun cancelNotification(sbn: StatusBarNotification) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + cancelNotification(sbn.key) + } else { + cancelNotification(sbn.packageName, sbn.tag, sbn.id) + } + } + + companion object { + // 日志 TAG + private val TAG = NotificationService::class.java.simpleName + + /** + * 获取当前服务所持对象 + * @return [NotificationService] + */ + // 当前服务所持对象 + var self: NotificationService? = null + private set + + /** + * 启动服务 + */ + fun startService() { + ServiceUtils.startService(NotificationService::class.java) + } + + /** + * 停止服务 + */ + fun stopService() { + ServiceUtils.stopService(NotificationService::class.java) + } + + // = + + private var sListener: Listener? = null + + /** + * 设置监听回调事件 + * @param listener [Listener] + */ + fun setListener(listener: Listener?) { + sListener = listener + } + + // = + + /** + * 检查是否有获取通知栏信息权限并跳转设置页面 + * @return `true` yes, `false` no + */ + fun checkAndIntentSetting(): Boolean { + return NotificationUtils.checkAndIntentSetting() + } + + /** + * 判断是否有获取通知栏信息权限 + * @return `true` yes, `false` no + */ + fun isNotificationListenerEnabled(): Boolean { + return NotificationUtils.isNotificationListenerEnabled() + } + + /** + * 判断是否有获取通知栏信息权限 + * @param packageName 应用包名 + * @return `true` yes, `false` no + */ + fun isNotificationListenerEnabled(packageName: String?): Boolean { + return NotificationUtils.isNotificationListenerEnabled(packageName) + } + + /** + * 跳转到设置页面, 开启获取通知栏信息权限 + */ + fun startNotificationListenSettings() { + NotificationUtils.startNotificationListenSettings() + } + } +} \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/res/values/strings.xml b/lib/LocalModules/DevOther/src/main/res/values/strings.xml new file mode 100644 index 0000000000..dad72025f3 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + 无障碍监听 + \ No newline at end of file diff --git a/lib/LocalModules/DevOther/src/main/res/xml/accessibility_config.xml b/lib/LocalModules/DevOther/src/main/res/xml/accessibility_config.xml new file mode 100644 index 0000000000..238bd1e6c7 --- /dev/null +++ b/lib/LocalModules/DevOther/src/main/res/xml/accessibility_config.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/lib/LocalModules/DevSKU/.gitignore b/lib/LocalModules/DevSKU/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/lib/LocalModules/DevSKU/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/lib/LocalModules/DevSKU/build.gradle b/lib/LocalModules/DevSKU/build.gradle new file mode 100644 index 0000000000..480535c4f6 --- /dev/null +++ b/lib/LocalModules/DevSKU/build.gradle @@ -0,0 +1 @@ +apply from: rootProject.file(files.build_app_kotlin_gradle) \ No newline at end of file diff --git a/lib/LocalModules/DevSKU/proguard-rules.pro b/lib/LocalModules/DevSKU/proguard-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/LocalModules/DevSKU/src/main/AndroidManifest.xml b/lib/LocalModules/DevSKU/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..307e3687a1 --- /dev/null +++ b/lib/LocalModules/DevSKU/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/LocalModules/DevSKU/src/main/java/dev/sku/SKU.kt b/lib/LocalModules/DevSKU/src/main/java/dev/sku/SKU.kt new file mode 100644 index 0000000000..54d07434d6 --- /dev/null +++ b/lib/LocalModules/DevSKU/src/main/java/dev/sku/SKU.kt @@ -0,0 +1,605 @@ +package dev.sku + +import android.text.TextUtils +import dev.base.multiselect.DevMultiSelectMap +import dev.utils.DevFinal + +/** + * detail: SKU 封装类 + * @author Ttt + */ +class SKU { + + /** + * 数据集基本模型 + * @property stock 库存 + * @property price 价格 + * @property value 原始数据 + */ + class Model( + val stock: Int, + val price: Double, + val value: T? = null + ) { + // 库存集合 ( 所有组合库存集, 可进行排序获取最大最小库存 ) + val stockList = mutableListOf() + + // 价格集合 ( 所有组合价格集, 可进行排序获取最高最低价格 ) + val priceList = mutableListOf() + } + + /** + * 属性 + * @property id id + * @property name 属性名 + * @property attrList 属性值列表 + */ + class Attr( + val id: Int, + val name: String?, + val attrList: List + ) + + /** + * 属性值 + * @property id id + * @property value 属性值 + * @property state 属性值状态 + */ + class AttrValue( + val id: Int, + val value: String?, + private var state: State = State.NOT_OPTIONAL + ) { + + /** + * 是否可选 ( 可点击 ) + * @return `true` yes, `false` no + */ + fun isOptional(): Boolean { + return state == State.OPTIONAL + } + + /** + * 是否不可选 ( 不可点击 ) + * @return `true` yes, `false` no + */ + fun isNotOptional(): Boolean { + return state == State.NOT_OPTIONAL + } + + // = + + /** + * 设置可选 ( 可点击 ) + * @return `true` yes, `false` no + */ + fun setOptional(): AttrValue { + state = State.OPTIONAL + return this + } + + /** + * 设置不可选 ( 不可点击 ) + * @return `true` yes, `false` no + */ + fun setNotOptional(): AttrValue { + state = State.NOT_OPTIONAL + return this + } + } + + /** + * 属性值状态 + * @property desc 状态描述 + */ + enum class State( + val desc: String + ) { + + OPTIONAL("可选 ( 可点击 )"), + + NOT_OPTIONAL("不可选 ( 不可点击 )"), + } +} + +/** + * detail: SKU 数据处理包装 ( 对外公开快捷使用 ) + * @author Ttt + */ +class SKUData { + + // SKU 控制器 ( 内部数据处理 ) + private val mSKUController = SKUController() + + // 选中的属性值 ( key = SKU.Attr.id, value = SKU.AttrValue ) + private val mSelect = DevMultiSelectMap() + + /** + * 初始化方法 + * @param attrs 属性集 + * @param skuModels SKU 数据集 + * @return SKUData + */ + fun initialize( + attrs: List, + skuModels: Map, SKU.Model> + ): SKUData { + mSelect.clearSelects() + mSKUController.initialize(attrs, skuModels) + return this + } + + /** + * 自动选中属性 + * @param attrIds 待选中属性值 id + * @return SKUData + */ + fun autoSelectAttr(attrIds: List): SKUData { + mSelect.clearSelects() + mSKUController.mOriginal.forEach { attr -> + attr.attrList.forEach VALUE_FOR@{ attrValue -> + // 如果集合中包含对应属性值 id 则表示选中该属性 + if (attrIds.contains(attrValue.id)) { + mSelect.select(attr.id, attrValue) + return@VALUE_FOR + } + } + } + return this + } + + // ========== + // = 快捷方法 = + // ========== + + /** + * 是否选择属性 + * @return `true` yes, `false` no + */ + fun isSelectAttr(): Boolean { + return mSelect.isSelect + } + + /** + * 是否全选每个属性 ( 每个属性的属性值集合都选中 ) + * @return `true` yes, `false` no + */ + fun isAllSelectAttr(): Boolean { + val size = mSelect.selectSize + return size != 0 && size == mSKUController.mOriginal.size + } + + // ========== + // = 多选相关 = + // ========== + + /** + * 获取属性值多选辅助类 + * @return DevMultiSelectMap + */ + fun getSelect(): DevMultiSelectMap { + return mSelect + } + + /** + * 设置非选中操作 + * @param attrId SKU.Attr.id + * @return SKUData + */ + fun unselect(attrId: Int): SKUData { + mSelect.unselect(attrId) + return this + } + + /** + * 设置选中操作 + * @param attrId SKU.Attr.id + * @param attrValue SKU.AttrValue + * @return SKUData + */ + fun select( + attrId: Int, + attrValue: SKU.AttrValue + ): SKUData { + mSelect.select(attrId, attrValue) + return this + } + + /** + * 是否选中对应属性 + * @param attrId SKU.Attr.id + * @param attrValue SKU.AttrValue + * @return `true` yes, `false` no + */ + fun isSelect( + attrId: Int, + attrValue: SKU.AttrValue + ): Boolean { + return mSelect.getSelectValue(attrId)?.id == attrValue.id + } + + // ============= + // = 属性状态相关 = + // ============= + + /** + * 刷新状态数据 + * @return List + */ + fun refreshStateData(): List { + val adapterData = mutableListOf() + adapterData.addAll(mSKUController.mOriginal) + + val hasSelectAttr = isSelectAttr() + // 循环所有属性集 + adapterData.forEach { attr -> + attr.attrList.forEach { attrValue -> + attrValue.setNotOptional() + mSKUController.mSKUModelAll["${attrValue.id}"]?.apply { + if (stock > 0) { + attrValue.setOptional() + } + } + + // ============= + // = 校验已选属性 = + // ============= + + if (hasSelectAttr) { + val cacheSelect = DevMultiSelectMap() + cacheSelect.putSelects(mSelect.selects) + cacheSelect.unselect(attr.id) + cacheSelect.select(attr.id, attrValue) + + // 获取选中属性对应的数据集基本模型 + getModel(cacheSelect.selectValues)?.apply { + if (stock > 0) { + attrValue.setOptional() + } else { + attrValue.setNotOptional() + } + } + } + } + } + // 重置数据 + return mSKUController.mStateData.apply { + clear() + addAll(adapterData) + } + } + + /** + * 获取选中属性对应的数据集基本模型 + * @return SKU.Model + */ + fun getModel(): SKU.Model? { + return getModel(mSelect.selectValues) + } + + /** + * 获取选中属性对应的数据集基本模型 ( 内部复用 ) + * @param value List + * @return SKU.Model + */ + private fun getModel(value: List): SKU.Model? { + val attrIds = toAttrIds(value) + val key = SKUUtils.joinKeySort(attrIds) + return mSKUController.mSKUModelAll[key] + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 转换属性值 id List + * @param value List + * @return List + */ + private fun toAttrIds(value: List): List { + val list = mutableListOf() + value.forEach { + list.add(it.id) + } + return list + } +} + +/** + * detail: SKU 控制器 ( 内部数据处理 ) + * @author Ttt + */ +internal class SKUController { + + // 动态状态数据 ( 直接用于 Adapter 显示 ) + val mStateData: MutableList = mutableListOf() + + // 原始属性集 ( 初始化传入 ) + val mOriginal: MutableList = mutableListOf() + + // SKU 数据集基本模型 ( key = List.join, value = SKU.Model ) + val mSKUModel: MutableMap> = mutableMapOf() + + // SKU 数据集基本模型全部组合数 ( key = List.join, value = SKU.Model ) + val mSKUModelAll: MutableMap> = mutableMapOf() + + /** + * 初始化方法 + * @param attrs 属性集 + * @param skuModels SKU 数据集 + * @return SKUController + */ + fun initialize( + attrs: List, + skuModels: Map, SKU.Model> + ): SKUController { + mOriginal.apply { + clear() + addAll(attrs) + } + mSKUModel.apply { + clear() + skuModels.iterator().forEach { + // 为防止 Key Ids 顺序不同先进行排序 + val key = SKUUtils.joinKeySort(it.key) + put(key, it.value) + } + } + mSKUModelAll.apply { + clear() + // 补全 SKU 数据集基本模型 ( 防止返回非完全的属性集合 ) + putAll(complementSKUModel(mOriginal, mSKUModel)) + // 组合 SKU 数据集基本模型 ( 全部组合数添加 ) + putAll(combineSKUModel()) + // 可以考虑接着循环所有库存为 0 的 key, 判断接着的子节点是否存在库存, 存在的话则进行重置库存 + } + return this + } + + // ========================== + // = SKU 数据集基本模型补全处理 = + // ========================== + + /** + * 补全 SKU 数据集基本模型 ( 防止返回非完全的属性集合 ) + * @param attrs 原始属性集 ( 初始化传入 ) + * @param skuModels SKU 数据集基本模型 + * @return LinkedHashMap> + */ + private fun complementSKUModel( + attrs: List, + skuModels: Map> + ): MutableMap> { + // SKU 数据集基本模型全部组合数 ( key = List.join, value = SKU.Model ) + val skuModelAll = mutableMapOf>() + val count = attrs.size + attrs.firstOrNull()?.attrList?.forEach { + forSKUAttr(1, count, mutableListOf(it.id), attrs, skuModels, skuModelAll) + } + return skuModelAll + } + + /** + * 循环 SKU 属性 ( 递归方法 ) + * @param index 属性层级 + * @param count 属性总层数 + * @param attrIds 每层累加属性值 id + * @param attrs 原始属性集 ( 初始化传入 ) + * @param skuModels SKU 数据集基本模型 + * @param skuModelAll SKU 数据集基本模型全部组合数 + */ + private fun forSKUAttr( + index: Int, + count: Int, + attrIds: List, + attrs: List, + skuModels: Map>, + skuModelAll: MutableMap> + ) { + if (index >= count) { + val key = SKUUtils.joinKeySort(attrIds) + val value = skuModels[key] + if (value != null) { + skuModelAll[key] = value + } else { + skuModelAll[key] = SKU.Model(0, 0.0) + } + } else { + attrs[index].attrList.forEach { + forSKUAttr( + index + 1, count, + attrIds.plus(it.id), + attrs, skuModels, skuModelAll + ) + } + } + } + + // ========== + // = 组合处理 = + // ========== + + /** + * 组合 SKU 数据集基本模型 ( 全部组合数添加 ) + */ + private fun combineSKUModel(): MutableMap> { + // SKU 数据集基本模型全部组合数 + val maps = mSKUModelAll.toMutableMap() + mSKUModelAll.keys.forEach { key -> + mSKUModelAll[key]?.let { value -> + // 拆分 SKU.AttrValue.id + val attrIds = SKUUtils.splitKey(key) + // 获取所有组合 + val combineIds = SKUUtils.arrayCombine(attrIds) + combineIds.forEach { ids -> + add2SKUResult(ids, value, maps) + } + } + } + return maps + } + + /** + * 添加到数据集合 + * @param keys id 组合 + * @param model SKU 数据集基本模型 + * @param maps SKU 数据集基本模型全部组合数 + */ + private fun add2SKUResult( + keys: ArrayList, + model: SKU.Model, + maps: MutableMap> + ) { + val key = SKUUtils.joinKeyByString(keys) + val value = maps[key] + if (value != null) { + value.stockList.add(model.stock) + value.priceList.add(model.price) + } else { + maps[key] = model + } + } +} + +/** + * detail: SKU 快捷工具类 + * @author Ttt + * 电商平台商品 SKU 组合查询算法实现 + * @see https://hooray.github.io/posts/8b2bd6f8 + * SKU 算法探讨 ( Android 版 ) + * @see https://www.jianshu.com/p/45c7d9dfe5fc + * SKU 选择解决方案 + * @see https://juejin.cn/post/6915321356540198926 + * Java 计算组合数以及生成组合排列 + * @see https://blog.csdn.net/haiyoushui123456/article/details/84338494 + */ +internal object SKUUtils { + + /** + * 拼接 Key + * @param attrIds List + * @return String + */ + private fun joinKey(attrIds: List): String { + return TextUtils.join(DevFinal.SYMBOL.SEMICOLON, attrIds) + } + + /** + * 拼接 Key ( 进行排序 ) + * @param attrIds List + * @return String + */ + fun joinKeySort(attrIds: List): String { + return joinKey(attrIds.sorted()) + } + + /** + * 拼接 Key + * @param attrIds List + * @return String + */ + fun joinKeyByString(attrIds: List): String { + return TextUtils.join(DevFinal.SYMBOL.SEMICOLON, attrIds) + } + + /** + * 拆分 Key + * @param key 待拆分 Key + * @return Array + */ + fun splitKey(key: String): Array { + return key.split(DevFinal.SYMBOL.SEMICOLON.toRegex()).toTypedArray() + } + + // ========== + // = 内部方法 = + // ========== + + /** + * 获取所有的组合放到 ArrayList 里面 + * @param attrIds List + * @return ArrayList + */ + fun arrayCombine(attrIds: Array?): ArrayList> { + attrIds?.let { + val len = it.size + val result = ArrayList>() + for (n in 1 until len) { + val flags = getCombineFlags(len, n) + for (i in flags.indices) { + val flag = flags[i] + val combine = ArrayList() + for (j in flag.indices) { + if (flag[j] == 1) { + combine.add(it[j]) + } + } + result.add(combine) + } + } + return result + } + return ArrayList() + } + + /** + * 从 len 中取 number 的所有组合 + * @param len 元素总数 + * @param number 需要组合数 + * @return ArrayList + */ + private fun getCombineFlags( + len: Int, + number: Int + ): ArrayList> { + if (number <= 0) return ArrayList() + val result = ArrayList>() + val flag = arrayOfNulls(len) + var hasNext = true + var cnt: Int // 计数 + // 初始化 + for (i in 0 until len) { + flag[i] = if (i < number) 1 else 0 + } + result.cloneAdd(flag.clone()) + while (hasNext) { + cnt = 0 + for (i in 0 until len - 1) { + if (flag[i] == 1 && flag[i + 1] == 0) { + for (j in 0 until i) { + flag[j] = if (j < cnt) 1 else 0 + } + flag[i] = 0 + flag[i + 1] = 1 + val temp = flag.clone() + result.cloneAdd(temp) + if ( + !TextUtils.join("", temp) + .substring(len - number) + .contains("0") + ) { + hasNext = false + } + break + } + if (flag[i] == 1) { + cnt++ + } + } + } + return result + } + + /** + * 添加克隆数组 + * @param element Array + */ + private fun ArrayList>.cloneAdd(element: Array) { + try { + add(element.requireNoNulls()) + } catch (ignored: Exception) { + } + } +} \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000000..db4c93899f --- /dev/null +++ b/lib/README.md @@ -0,0 +1,82 @@ +# About + +该目录主要存储 DevUtils project 全部 lib module,封装快捷使用的工具类及 API 方法调用,隐藏内部逻辑判断对外提供便捷辅助类、统一回调类、Bean、Event 以及 +Engine 兼容框架等 + +## 目录结构 + +``` +- lib | 根目录 + - DevApp | Android 工具类库 + - DevAssist | 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 + - DevBase | Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 + - DevBaseMVVM | MVVM ( ViewDataBinding + ViewModel ) 基类库 + - DevEngine | 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 + - DevHttpCapture | OkHttp 抓包工具库 + - DevHttpManager | OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + - DevJava | Java 工具类库 ( 不依赖 android api ) + - DevMVVM | DataBinding 工具类库 + - DevRetrofit | Retrofit + Kotlin Coroutines 封装 + - DevWidget | 自定义 View UI 库 + - Environment | Android 环境配置切换库 + - DevEnvironment | 环境切换可视化 UI 操作 + - DevEnvironmentBase | 注解类、实体类、监听事件等通用基础 + - DevEnvironmentCompiler | Debug ( 打包 / 编译 ) 生成实现代码 + - DevEnvironmentCompilerRelease | Release ( 打包 / 编译 ) 生成实现代码 + - HttpCapture | OkHttp 抓包工具库 ( 可视化功能 ) + - DevHttpCaptureCompiler | Debug ( 打包 / 编译 ) 实现代码 + - DevHttpCaptureCompilerRelease | Release ( 打包 / 编译 ) 实现代码 + - LocalModules | 本地 Module lib ( 非发布库 ) + - DevBaseView | 通用基础 View 封装 ( 非基类库 ) + - DevOther | 功能、工具类二次封装, 直接 copy 使用【 大部分迁移至 DevUtils-repo 】 + - DevSKU | 商品 SKU 组合封装实现 +``` + + +## Dev 系列开发库全部 Lib Gradle + +```gradle + +// DevApp - Android 工具类库 +implementation 'io.github.afkt:DevAppX:2.4.1' + +// DevAssist - 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 +implementation 'io.github.afkt:DevAssist:1.3.6' + +// DevBase - Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 +implementation 'io.github.afkt:DevBase:1.1.3' + +// DevBaseMVVM - MVVM ( ViewDataBinding + ViewModel ) 基类库 +implementation 'io.github.afkt:DevBaseMVVM:1.1.1' + +// DevEngine - 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 +implementation 'io.github.afkt:DevEngine:1.0.8' + +// DevHttpCapture - OkHttp 抓包工具库 +implementation 'io.github.afkt:DevHttpCapture:1.1.2' + +// DevHttpCaptureCompiler - OkHttp 抓包工具库 ( 可视化功能 ) +debugImplementation 'io.github.afkt:DevHttpCaptureCompiler:1.1.2' +releaseImplementation 'io.github.afkt:DevHttpCaptureCompilerRelease:1.1.2' + +// DevHttpManager - OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) +implementation 'io.github.afkt:DevHttpManager:1.0.2' + +// DevRetrofit - Retrofit + Kotlin Coroutines 封装 +implementation 'io.github.afkt:DevRetrofit:1.0.1' + +// DevWidget - 自定义 View UI 库 +implementation 'io.github.afkt:DevWidgetX:1.1.9' + +// DevEnvironment - Android 环境配置切换库 +implementation 'io.github.afkt:DevEnvironment:1.1.1' +debugAnnotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.1' // kaptDebug +releaseAnnotationProcessor 'io.github.afkt:DevEnvironmentCompilerRelease:1.1.1' // kaptRelease +//annotationProcessor 'io.github.afkt:DevEnvironmentCompiler:1.1.1' // kapt + +// DevJava - Java 工具类库 ( 不依赖 android api ) +implementation 'io.github.afkt:DevJava:1.4.7' // 用于纯 Java 开发,如果依赖了 DevApp 则不需要依赖 DevJava +``` + + + diff --git a/lib/publish.md b/lib/publish.md new file mode 100644 index 0000000000..11d6e9f3e4 --- /dev/null +++ b/lib/publish.md @@ -0,0 +1,23 @@ + +## 发布顺序及依赖情况 + +``` +- DevApp | Android 工具类库 + - DevAssist | 封装逻辑代码, 实现多个快捷功能辅助类、以及 Engine 兼容框架等 + - DevEngine | 第三方框架解耦、一键替换第三方库、同类库多 Engine 组件化混合使用 + - DevMVVM | DataBinding 工具类库 + - DevBase | Base ( Activity、Fragment )、MVP、ViewBinding、ContentLayout 基类库 + - DevBaseMVVM | MVVM ( ViewDataBinding + ViewModel ) 基类库 + - DevHttpCapture | OkHttp 抓包工具库 + - DevHttpCaptureCompiler | Debug ( 打包 / 编译 ) 实现代码 + - DevHttpCaptureCompilerRelease | Release ( 打包 / 编译 ) 实现代码 + - DevHttpManager | OkHttp 管理库 ( Retrofit 多 BaseUrl 管理、Progress 监听 ) + - DevWidget | 自定义 View UI 库 +- DevRetrofit | Retrofit + Kotlin Coroutines 封装 +- DevEnvironmentBase | 注解类、实体类、监听事件等通用基础 + - DevEnvironment | 环境切换可视化 UI 操作 + - DevEnvironmentCompiler | Debug ( 打包 / 编译 ) 生成实现代码 + - DevEnvironmentCompilerRelease | Release ( 打包 / 编译 ) 生成实现代码 +- DevJava | Java 工具类库 ( 不依赖 android api ) +``` + diff --git a/settings.gradle b/settings.gradle index 2d6df85eb9..70f8e45393 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,93 @@ -include ':app', ':DevLibUtils' +// ======= +// = Lib = +// ======= + +def libs = [ + "DevApp", + "DevAssist", + "DevBase", + "DevBaseMVVM", + "DevMVVM", + "DevEngine", + "DevHttpCapture", + "DevHttpManager", + "DevRetrofit", + "DevWidget", + "DevJava", +] + +libs.forEach { + include(":$it") + project(":$it").projectDir = new File(rootDir, "lib/$it") +} + +def libs_compiler = [ + "DevHttpCaptureCompiler", + "DevHttpCaptureCompilerRelease", +] + +libs_compiler.forEach { + include(":$it") + if (it.startsWith("DevHttpCapture")) { + project(":$it").projectDir = new File(rootDir, "lib/HttpCapture/$it") + } +} + +def environment_libs = [ + "DevEnvironment", + "DevEnvironmentBase", + "DevEnvironmentCompiler", + "DevEnvironmentCompilerRelease", +] + +environment_libs.forEach { + include(":$it") + project(":$it").projectDir = new File(rootDir, "lib/Environment/$it") +} + +// ================= +// = Local Modules = +// ================= + +def local_modules = [ + "DevBaseView", + "DevOther", + "DevSKU", +] + +local_modules.forEach { + def itName = it + + if (itName.indexOf("/") != -1) { + itName = itName.substring( + itName.lastIndexOf("/") + 1, + itName.length() + ) + } + include(":$itName") + project(":$itName").projectDir = new File(rootDir, "lib/LocalModules/$it") +} + +// =============== +// = Application = +// =============== + +def applications = [ + + // ======================== + // = DevUtils Demo 演示应用 = + // ======================== + + "DevUtilsApp", + + // ========== + // = 其他项目 = + // ========== + + "DevBaseDemo", +] + +applications.forEach { + include(":$it") + project(":$it").projectDir = new File(rootDir, "application/$it") +} \ No newline at end of file diff --git a/sonatype.properties b/sonatype.properties new file mode 100644 index 0000000000..e7475a995b --- /dev/null +++ b/sonatype.properties @@ -0,0 +1,11 @@ +#sonatype +signing.keyId= +signing.password= +signing.secretKeyRingFile=../../File/SonaType/devuitls/devutils_android.gpg +ossrhUsername=jtongttt +ossrhPassword= + +#developer +developer.id=jtongttt +developer.name=ChenJiaTong +developer.email=jtongttt@gmail.com \ No newline at end of file
+ *     Android 动画机制详细解读
+ *     @see 
+ *     @see 
+ *     Android 一共有多少种动画, 准确告诉你
+ *     @see 
+ *     Android 中弹簧动画的那些事 - SpringAnimation
+ *     @see 
+ *     Android 弹性动画的三种实现方式
+ *     @see 
+ *     Android 动画
+ *     @see 
+ *     Android 动效开篇
+ *     @see 
+ *     插值器 ( Interpolator ) 的使用说明
+ *     @see 
+ *     

+ * setInterpolator: + * {@link AccelerateInterpolator} 先减速后加速 + * {@link AnticipateInterpolator} 动画开始之前有回弹效果 + * {@link BounceInterpolator} 结束回弹效果 + * {@link CycleInterpolator} 跳一跳效果 + * {@link OvershootInterpolator} 动画结束时向前弹一定距离再回到原来位置 + * {@link AccelerateDecelerateInterpolator} 系统默认的动画效果, 先加速后减速 + * {@link AnticipateOvershootInterpolator} 开始之前向前甩, 结束的时候向后甩 + * {@link DecelerateInterpolator} 开始加速再减速 + *

+ * 设置无限重复次数 + * {@link Animation#setRepeatCount} param {@link Animation#INFINITE} + *